From 4cd9907e94be47ba35fbd389bbaf28b93639de5c Mon Sep 17 00:00:00 2001 From: Dexter <5452298+divmgl@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:29:20 -0500 Subject: [PATCH] 3.0.0 --- .eslintrc | 17 - .prettierrc | 2 +- .vscode/settings.json | 8 + README.md | 94 ++-- build.ts | 21 + bun.lockb | Bin 0 -> 90414 bytes dist/cjs/index.js | 564 ++++++++++++++++++++++ dist/esm/index.js | 527 +++++++++++++++++++++ dist/index.d.ts | 57 --- dist/index.d.ts.map | 1 - dist/index.js | 410 ---------------- dist/index.js.map | 1 - dist/types.d.ts | 37 -- dist/types.d.ts.map | 1 - dist/types.js | 7 - dist/types.js.map | 1 - dist/types/index.d.ts | 95 ++++ eslint.config.mjs | 33 ++ package.json | 34 +- pnpm-lock.yaml | 1050 ----------------------------------------- src/index.test.ts | 298 ++++++++++++ src/index.ts | 484 +++++++++++-------- src/types.ts | 43 -- test/index.test.js | 276 ----------- tsconfig.build.json | 16 + tsconfig.json | 29 +- 26 files changed, 1937 insertions(+), 2169 deletions(-) delete mode 100644 .eslintrc create mode 100644 .vscode/settings.json create mode 100644 build.ts create mode 100755 bun.lockb create mode 100644 dist/cjs/index.js create mode 100644 dist/esm/index.js delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.d.ts.map delete mode 100644 dist/index.js delete mode 100644 dist/index.js.map delete mode 100644 dist/types.d.ts delete mode 100644 dist/types.d.ts.map delete mode 100644 dist/types.js delete mode 100644 dist/types.js.map create mode 100644 dist/types/index.d.ts create mode 100644 eslint.config.mjs delete mode 100644 pnpm-lock.yaml create mode 100644 src/index.test.ts delete mode 100644 src/types.ts delete mode 100644 test/index.test.js create mode 100644 tsconfig.build.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index bc009d4..0000000 --- a/.eslintrc +++ /dev/null @@ -1,17 +0,0 @@ -{ // Basically only need this file for Mocha - "extends": ["eslint:recommended"], - "parserOptions": { - "ecmaVersion": 2017 - }, - "rules": { - "semi": ["error", "never"], - "no-console": ["off"], - "no-unused-vars": 1 - }, - "env": { - "node": true, - "es6": true, - "mocha": true - }, - "ignorePatterns": ["dist"] -} diff --git a/.prettierrc b/.prettierrc index a78c710..8fe0345 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "printWidth": 80, - "singleQuote": true, + "singleQuote": false, "trailingComma": "none", "bracketSpacing": true, "jsxBracketSameLine": false, diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5b2fb0c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + }, + "eslint.enable": true +} diff --git a/README.md b/README.md index e9ace1b..7a26a04 100644 --- a/README.md +++ b/README.md @@ -2,51 +2,55 @@ ![Tests](https://github.com/getjackd/jackd/actions/workflows/test.yml/badge.svg) +Modern beanstalkd client for Node.js. + ```js -// Connecting -const Jackd = require('jackd') +const Jackd = require("jackd") const beanstalkd = new Jackd() await beanstalkd.connect() -// Producing -await beanstalkd.put('Hello!') +// Publishing a job +await beanstalkd.put("Hello!") -// Consuming +// Consuming a job const job = await beanstalkd.reserve() // => { id: '1', payload: 'Hello!' } -// ...process the job... then: +// Process the job, then delete it await beanstalkd.delete(job.id) ``` ## Installation +```bash +npm install jackd +yarn add jackd +pnpm add jackd +bun add jackd ``` -$ npm i jackd -``` - -### Version 2.x fixes a critical bug - -> :warning: If you're using `jackd` in production, you should upgrade `jackd` to version 2.x. [Read more here.](https://github.com/getjackd/jackd/releases/tag/v2.0.1) ## Why -Most `beanstalkd` clients don't support promises (fivebeans, nodestalker) and the ones that do have too many dependencies (node-beanstalkd-client). I wanted to make a package that has: +Beanstalkd is a simple and blazing fast work queue. It's a great tool for building background jobs, queuing up tasks, and more. It deserves a modern Node.js client! + +That's where Jackd comes in. Jackd has: - A concise and easy to use API - Native promise support - No dependencies - Protocol accuracy/completeness -## Overview +These features put Jackd ahead of other Beanstalkd clients. -`beanstalkd` is a simple and blazing fast work queue. Producers connected through TCP sockets (by default on port `11300`) send in jobs to be processed at a later time by a consumer. +If you don't have experience using Beanstalkd, [it's a good idea to read the Beanstalkd protocol before using this library.](https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt) -If you don't have experience using `beanstalkd`, [it's a good idea to read the `beanstalkd` protocol before using this library.](https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt) +## Overview + +Producers connected through TCP sockets (by default on port `11300`) send in jobs to be processed at a later time by a consumer. ### Connecting and disconnecting ```js -const Jackd = require('jackd') +const Jackd = require("jackd") const beanstalkd = new Jackd() await beanstalkd.connect() // Connects to localhost:11300 @@ -61,25 +65,27 @@ await beanstalkd.disconnect() // You can also use beanstalkd.quit; it's an alias You can add jobs to a tube by using the `put` command, which accepts a payload and returns a job ID. -`beanstalkd` job payloads are byte arrays. Passing in a `Buffer` will send the payload as-is. +Beanstalkd job payloads are byte arrays. Passing in a `Uint8Array` will send the payload as-is. ```js // This is a byte array of a UTF-8 encoded string -const jobId = await beanstalkd.put(Buffer.from('my long running job')) +const jobId = await beanstalkd.put( + new TextEncoder().encode("my long running job") +) ``` You can also pass in a `String` or an `Object` and `jackd` will automatically convert these values into byte arrays. ```js -const jobId = await beanstalkd.put('my long running job') // Buffer.from(string) -const jobId = await beanstalkd.put({ foo: 'bar' }) // Buffer.from(JSON.stringify(object)) +const jobId = await beanstalkd.put("my long running job") // TextEncoder.encode(string) +const jobId = await beanstalkd.put({ foo: "bar" }) // TextEncoder.encode(JSON.stringify(object)) ``` All jobs sent to beanstalkd have a priority, a delay, and TTR (time-to-run) specification. By default, all jobs are published with `0` priority, `0` delay, and `60` TTR, which means consumers will have 60 seconds to finish the job after reservation. You can override these defaults: ```js await beanstalkd.put( - { foo: 'bar' }, + { foo: "bar" }, { delay: 2, // Two second delay priority: 10, @@ -95,8 +101,8 @@ Jobs with lower priorities are handled first. Refer to [the protocol specs](http All jobs by default are added to the `default` tube. You can change where you produce jobs with the `use` command. ```js -const tubeName = await beanstalkd.use('awesome-tube') // => 'awesome-tube' -await beanstalkd.put({ foo: 'bar' }) +const tubeName = await beanstalkd.use("awesome-tube") // => 'awesome-tube' +await beanstalkd.put({ foo: "bar" }) ``` ### Consumers @@ -106,22 +112,34 @@ await beanstalkd.put({ foo: 'bar' }) Consumers reserve jobs from tubes. Using `await` on a `reserve` command is a blocking operation and execution will stop until a job has been reserved. ```js +// Get a job with string payload const { id, payload } = await beanstalkd.reserve() // wait until job incoming -console.log({ id, payload }) // => { id: '1', payload: Buffer } +console.log({ id, payload }) // => { id: '1', payload: 'Hello!' } + +// Get a job with raw Uint8Array payload +const { id, payload } = await beanstalkd.reserveRaw() // wait until job incoming +console.log({ id, payload }) // => { id: '1', payload: Uint8Array } ``` -`jackd` will return the payload as-is. This means you'll have to handle the encoding yourself. For instance, if you sent in an `Object`, you'll need to first convert the `Buffer` to a `String` and then parse the JSON. +When using `reserve()`, the payload will be automatically decoded as a string. This is convenient for text-based payloads. + +When using `reserveRaw()`, you'll get the raw `Uint8Array` payload. This is useful when dealing with binary data or when you need to control the decoding yourself: ```js +// Using reserve() for string payloads (automatically decoded) const { id, payload } = await beanstalkd.reserve() -const object = JSON.parse(payload.toString()) +console.log(payload) // Already a string + +// Using reserveRaw() for raw payloads +const { id, payload } = await beanstalkd.reserveRaw() +const message = new TextDecoder().decode(payload) // Manual decoding ``` -If you passed in a `String`, you'll need to convert the incoming `Buffer` to a string. +If you passed in an object when putting the job, you'll need to parse the JSON string: ```js const { id, payload } = await beanstalkd.reserve() -const message = payload.toString() +const object = JSON.parse(payload) ``` #### Performing job operations (delete/bury/touch/release) @@ -153,13 +171,13 @@ await beanstalkd.bury(id) await beanstalkd.kickJob(id) ``` -You'll notice that the kick operation is suffixed by `Job`. This is because there is a `kick` command in `beanstalkd` which will kick a certain number of jobs back into the tube. +You'll notice that the kick operation is suffixed by `Job`. This is because there is a `kick` command in Beanstalkd which will kick a certain number of jobs back into the tube. ```js await beanstalkd.kick(10) // 10 buried jobs will be moved to a ready state ``` -Consumers will sometimes need additional time to run jobs. You can `touch` those jobs to let `beanstalkd` know you're still processing them. +Consumers will sometimes need additional time to run jobs. You can `touch` those jobs to let Beanstalkd know you're still processing them. ```js await beanstalkd.touch(id) @@ -170,14 +188,14 @@ await beanstalkd.touch(id) By default, all consumers will watch the `default` tube only. Consumers can elect what tubes they want to watch. ```js -const numberOfTubesWatched = await beanstalkd.watch('my-special-tube') +const numberOfTubesWatched = await beanstalkd.watch("my-special-tube") // => 2 ``` Consumers can also ignore tubes. ```js -const numberOfTubesWatched = await beanstalkd.ignore('default') +const numberOfTubesWatched = await beanstalkd.ignore("default") // => 1 ``` @@ -185,7 +203,7 @@ Be aware that attempting to ignore the only tube being watched will return an er ### Executing YAML commands -`beanstalkd` has a number of commands that return YAML payloads. These commands mostly return statistics regarding the current `beanstalkd` instance. `jackd`, on purpose, does not ship with a YAML parser. This is to: +Beanstalkd has a number of commands that return YAML payloads. These commands mostly return statistics regarding the current Beanstalkd instance. `jackd`, on purpose, does not ship with a YAML parser. This is to: - Avoid dependencies - Stay close to the protocol spec @@ -208,9 +226,9 @@ current-jobs-buried: 0 You can then pipe this result through a YAML parser to get the actual contents of the YAML file. ```js -const YAML = require('yaml') -const stats = await beanstalkd.executeMultiPartCommand('stats\r\n') -const { 'total-jobs': totalJobs } = YAML.parse(stats) +const YAML = require("yaml") +const stats = await beanstalkd.executeMultiPartCommand("stats\r\n") +const { "total-jobs": totalJobs } = YAML.parse(stats) console.log(totalJobs) // => 0 ``` @@ -221,7 +239,7 @@ You may be looking to design a process that does nothing else but consume jobs. ```js /* consumer.js */ -const Jackd = require('jackd') +const Jackd = require("jackd") const beanstalkd = new Jackd() start() diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..2ed3bb7 --- /dev/null +++ b/build.ts @@ -0,0 +1,21 @@ +import esbuild from "esbuild" + +// Then build bundles +await Promise.all([ + esbuild.build({ + entryPoints: ["src/index.ts"], + bundle: true, + format: "esm", + outfile: "dist/esm/index.js", + target: "node18", + platform: "node" + }), + esbuild.build({ + entryPoints: ["src/index.ts"], + bundle: true, + format: "cjs", + outfile: "dist/cjs/index.js", + target: "node18", + platform: "node" + }) +]) diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..5f9e98cbb3f70335946c46c14c43a8a6bf6aff79 GIT binary patch literal 90414 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 ikZ^ARg?EAU&Xjvb8(_WgZvq { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + CommandExecution: () => CommandExecution, + InvalidResponseError: () => InvalidResponseError, + JackdClient: () => JackdClient, + default: () => index_default +}); +module.exports = __toCommonJS(index_exports); +var import_net = require("net"); +var import_assert = __toESM(require("assert"), 1); +var import_events = __toESM(require("events"), 1); +var DELIMITER = "\r\n"; +var CommandExecution = class { + handlers = []; + emitter = new import_events.default(); +}; +var JackdClient = class { + socket = new import_net.Socket(); + connected = false; + buffer = new Uint8Array(); + chunkLength = 0; + // beanstalkd executes all commands serially. Because Node.js is single-threaded, + // this allows us to queue up all of the messages and commands as they're invokved + // without needing to explicitly wait for promises. + messages = []; + executions = []; + constructor() { + this.socket.on("ready", () => { + this.connected = true; + }); + this.socket.on("close", () => { + this.connected = false; + }); + this.socket.on("data", async (incoming) => { + const newBuffer = new Uint8Array(this.buffer.length + incoming.length); + newBuffer.set(this.buffer); + newBuffer.set(new Uint8Array(incoming), this.buffer.length); + this.buffer = newBuffer; + await this.processChunk(this.buffer); + }); + } + async processChunk(head) { + let index = -1; + if (this.chunkLength > 0) { + const remainingBytes = this.chunkLength - head.length; + if (remainingBytes > -DELIMITER.length) { + return; + } + index = head.length - DELIMITER.length; + this.chunkLength = 0; + } else { + const delimiterBytes = new TextEncoder().encode(DELIMITER); + index = findIndex(head, delimiterBytes); + } + if (index > -1) { + this.messages.push(head.slice(0, index)); + await this.flushExecutions(); + const tail = head.slice(index + DELIMITER.length); + this.buffer = tail; + await this.processChunk(tail); + } + } + async flushExecutions() { + for (let i = 0; i < this.executions.length; i++) { + if (this.messages.length === 0) { + return; + } + const execution = this.executions[0]; + const { handlers, emitter } = execution; + try { + while (handlers.length && this.messages.length) { + const handler = handlers.shift(); + const result = await handler(this.messages.shift()); + if (handlers.length === 0) { + emitter.emit("resolve", result); + this.executions.shift(); + i--; + break; + } + } + } catch (err) { + emitter.emit("reject", err); + this.executions.shift(); + i--; + } + } + } + /** + * For environments where network partitioning is common. + * @returns {Boolean} + */ + isConnected() { + return this.connected; + } + async connect(opts) { + let host = "localhost"; + let port = 11300; + if (opts && opts.host) { + host = opts.host; + } + if (opts && opts.port) { + port = opts.port; + } + await new Promise((resolve, reject) => { + this.socket.once("error", (error) => { + if (error.code === "EISCONN") { + return resolve(); + } + reject(error); + }); + this.socket.connect(port, host, resolve); + }); + return this; + } + write(buffer) { + (0, import_assert.default)(buffer); + return new Promise((resolve, reject) => { + this.socket.write(buffer, (err) => err ? reject(err) : resolve()); + }); + } + quit = async () => { + if (!this.connected) return; + const waitForClose = new Promise((resolve, reject) => { + this.socket.once("close", resolve); + this.socket.once("error", reject); + }); + this.socket.end(new TextEncoder().encode("quit\r\n")); + await waitForClose; + }; + close = this.quit; + disconnect = this.quit; + put = this.createCommandHandler( + (payload, { priority, delay, ttr } = {}) => { + (0, import_assert.default)(payload); + let body; + if (typeof payload === "object") { + const string = JSON.stringify(payload); + body = new TextEncoder().encode(string); + } else { + body = new TextEncoder().encode(payload); + } + const command = new TextEncoder().encode( + `put ${priority || 0} ${delay || 0} ${ttr || 60} ${body.length}\r +` + ); + const delimiter = new TextEncoder().encode(DELIMITER); + const result = new Uint8Array( + command.length + body.length + delimiter.length + ); + result.set(command); + result.set(body, command.length); + result.set(delimiter, command.length + body.length); + return result; + }, + [ + (buffer) => { + const ascii = validate(buffer, [ + BURIED, + EXPECTED_CRLF, + JOB_TOO_BIG, + DRAINING + ]); + if (ascii.startsWith(INSERTED)) { + const [, id] = ascii.split(" "); + return parseInt(id); + } + invalidResponse(ascii); + } + ] + ); + use = this.createCommandHandler( + (tube) => { + (0, import_assert.default)(tube); + return new TextEncoder().encode(`use ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(USING)) { + const [, tube] = ascii.split(" "); + return tube; + } + invalidResponse(ascii); + } + ] + ); + createReserveHandlers(additionalResponses = [], decodePayload = true) { + let id; + return [ + (buffer) => { + const ascii = validate(buffer, [ + DEADLINE_SOON, + TIMED_OUT, + ...additionalResponses + ]); + if (ascii.startsWith(RESERVED)) { + const [, incomingId, bytes] = ascii.split(" "); + id = parseInt(incomingId); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return { + id, + payload: decodePayload ? new TextDecoder().decode(payload) : payload + }; + } + ]; + } + reserve = this.createCommandHandler( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], true) + ); + reserveRaw = this.createCommandHandler( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], false) + ); + reserveWithTimeout = this.createCommandHandler( + (seconds) => new TextEncoder().encode(`reserve-with-timeout ${seconds}\r +`), + this.createReserveHandlers([], true) + ); + reserveJob = this.createCommandHandler( + (id) => new TextEncoder().encode(`reserve-job ${id}\r +`), + this.createReserveHandlers([NOT_FOUND], true) + ); + delete = this.createCommandHandler( + (id) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`delete ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === DELETED) return; + invalidResponse(ascii); + } + ] + ); + release = this.createCommandHandler( + (id, { priority, delay } = {}) => { + (0, import_assert.default)(id); + return new TextEncoder().encode( + `release ${id} ${priority || 0} ${delay || 0}\r +` + ); + }, + [ + (buffer) => { + const ascii = validate(buffer, [BURIED, NOT_FOUND]); + if (ascii === RELEASED) return; + invalidResponse(ascii); + } + ] + ); + bury = this.createCommandHandler( + (id, priority) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`bury ${id} ${priority || 0}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === BURIED) return; + invalidResponse(ascii); + } + ] + ); + touch = this.createCommandHandler( + (id) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`touch ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === TOUCHED) return; + invalidResponse(ascii); + } + ] + ); + watch = this.createCommandHandler( + (tube) => { + (0, import_assert.default)(tube); + return new TextEncoder().encode(`watch ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(WATCHING)) { + const [, count] = ascii.split(" "); + return parseInt(count); + } + invalidResponse(ascii); + } + ] + ); + ignore = this.createCommandHandler( + (tube) => { + (0, import_assert.default)(tube); + return new TextEncoder().encode(`ignore ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_IGNORED]); + if (ascii.startsWith(WATCHING)) { + const [, count] = ascii.split(" "); + return parseInt(count); + } + invalidResponse(ascii); + } + ] + ); + pauseTube = this.createCommandHandler( + (tube, { delay } = {}) => new TextEncoder().encode(`pause-tube ${tube} ${delay || 0}`), + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === PAUSED) return; + invalidResponse(ascii); + } + ] + ); + /* Other commands */ + peek = this.createCommandHandler((id) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`peek ${id}\r +`); + }, this.createPeekHandlers()); + createPeekHandlers() { + let id; + return [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(FOUND)) { + const [, peekId, bytes] = ascii.split(" "); + id = parseInt(peekId); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return { + id, + payload: new TextDecoder().decode(payload) + }; + } + ]; + } + peekReady = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-ready\r +`); + }, this.createPeekHandlers()); + peekDelayed = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-delayed\r +`); + }, this.createPeekHandlers()); + peekBuried = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-buried\r +`); + }, this.createPeekHandlers()); + kick = this.createCommandHandler( + (bound) => { + (0, import_assert.default)(bound); + return new TextEncoder().encode(`kick ${bound}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(KICKED)) { + const [, kicked] = ascii.split(" "); + return parseInt(kicked); + } + invalidResponse(ascii); + } + ] + ); + kickJob = this.createCommandHandler( + (id) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`kick-job ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(KICKED)) return; + invalidResponse(ascii); + } + ] + ); + statsJob = this.createCommandHandler((id) => { + (0, import_assert.default)(id); + return new TextEncoder().encode(`stats-job ${id}\r +`); + }, this.createYamlCommandHandlers()); + statsTube = this.createCommandHandler((tube) => { + (0, import_assert.default)(tube); + return new TextEncoder().encode(`stats-tube ${tube}\r +`); + }, this.createYamlCommandHandlers()); + stats = this.createCommandHandler( + () => new TextEncoder().encode(`stats\r +`), + this.createYamlCommandHandlers() + ); + listTubes = this.createCommandHandler( + () => new TextEncoder().encode(`list-tubes\r +`), + this.createYamlCommandHandlers() + ); + listTubesWatched = this.createCommandHandler( + () => new TextEncoder().encode(`list-tubes-watched\r +`), + this.createYamlCommandHandlers() + ); + createYamlCommandHandlers() { + return [ + (buffer) => { + const ascii = validate(buffer, [DEADLINE_SOON, TIMED_OUT]); + if (ascii.startsWith(OK)) { + const [, bytes] = ascii.split(" "); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return new TextDecoder().decode(payload); + } + ]; + } + getCurrentTube = this.createCommandHandler( + () => new TextEncoder().encode(`list-tube-used\r +`), + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(USING)) { + const [, tube] = ascii.split(" "); + return tube; + } + invalidResponse(ascii); + } + ] + ); + listTubeUsed = this.getCurrentTube; + createCommandHandler(commandStringFunction, handlers) { + return async (...args) => { + const commandString = commandStringFunction.apply(this, args); + await this.write(commandString); + const emitter = new import_events.default(); + this.executions.push({ + handlers: handlers.concat(), + emitter + }); + return await new Promise((resolve, reject) => { + emitter.once("resolve", resolve); + emitter.once("reject", reject); + }); + }; + } +}; +var index_default = JackdClient; +function validate(buffer, additionalResponses = []) { + const ascii = new TextDecoder().decode(buffer); + const errors = [OUT_OF_MEMORY, INTERNAL_ERROR, BAD_FORMAT, UNKNOWN_COMMAND]; + if (errors.concat(additionalResponses).some((error) => ascii.startsWith(error))) { + throw new Error(ascii); + } + return ascii; +} +var InvalidResponseError = class extends Error { + response = "internal error"; +}; +function invalidResponse(ascii) { + console.log(ascii); + const error = new InvalidResponseError(`Unexpected response: ${ascii}`); + error.response = ascii; + throw error; +} +function findIndex(array, subarray) { + for (let i = 0; i <= array.length - subarray.length; i++) { + let found = true; + for (let j = 0; j < subarray.length; j++) { + if (array[i + j] !== subarray[j]) { + found = false; + break; + } + } + if (found) return i; + } + return -1; +} +var RESERVED = "RESERVED"; +var INSERTED = "INSERTED"; +var USING = "USING"; +var TOUCHED = "TOUCHED"; +var DELETED = "DELETED"; +var BURIED = "BURIED"; +var RELEASED = "RELEASED"; +var NOT_FOUND = "NOT_FOUND"; +var OUT_OF_MEMORY = "OUT_OF_MEMORY"; +var INTERNAL_ERROR = "INTERNAL_ERROR"; +var BAD_FORMAT = "BAD_FORMAT"; +var UNKNOWN_COMMAND = "UNKNOWN_COMMAND"; +var EXPECTED_CRLF = "EXPECTED_CRLF"; +var JOB_TOO_BIG = "JOB_TOO_BIG"; +var DRAINING = "DRAINING"; +var TIMED_OUT = "TIMED_OUT"; +var DEADLINE_SOON = "DEADLINE_SOON"; +var FOUND = "FOUND"; +var WATCHING = "WATCHING"; +var NOT_IGNORED = "NOT_IGNORED"; +var KICKED = "KICKED"; +var PAUSED = "PAUSED"; +var OK = "OK"; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + CommandExecution, + InvalidResponseError, + JackdClient +}); diff --git a/dist/esm/index.js b/dist/esm/index.js new file mode 100644 index 0000000..f47a23a --- /dev/null +++ b/dist/esm/index.js @@ -0,0 +1,527 @@ +// src/index.ts +import { Socket } from "net"; +import assert from "assert"; +import EventEmitter from "events"; +var DELIMITER = "\r\n"; +var CommandExecution = class { + handlers = []; + emitter = new EventEmitter(); +}; +var JackdClient = class { + socket = new Socket(); + connected = false; + buffer = new Uint8Array(); + chunkLength = 0; + // beanstalkd executes all commands serially. Because Node.js is single-threaded, + // this allows us to queue up all of the messages and commands as they're invokved + // without needing to explicitly wait for promises. + messages = []; + executions = []; + constructor() { + this.socket.on("ready", () => { + this.connected = true; + }); + this.socket.on("close", () => { + this.connected = false; + }); + this.socket.on("data", async (incoming) => { + const newBuffer = new Uint8Array(this.buffer.length + incoming.length); + newBuffer.set(this.buffer); + newBuffer.set(new Uint8Array(incoming), this.buffer.length); + this.buffer = newBuffer; + await this.processChunk(this.buffer); + }); + } + async processChunk(head) { + let index = -1; + if (this.chunkLength > 0) { + const remainingBytes = this.chunkLength - head.length; + if (remainingBytes > -DELIMITER.length) { + return; + } + index = head.length - DELIMITER.length; + this.chunkLength = 0; + } else { + const delimiterBytes = new TextEncoder().encode(DELIMITER); + index = findIndex(head, delimiterBytes); + } + if (index > -1) { + this.messages.push(head.slice(0, index)); + await this.flushExecutions(); + const tail = head.slice(index + DELIMITER.length); + this.buffer = tail; + await this.processChunk(tail); + } + } + async flushExecutions() { + for (let i = 0; i < this.executions.length; i++) { + if (this.messages.length === 0) { + return; + } + const execution = this.executions[0]; + const { handlers, emitter } = execution; + try { + while (handlers.length && this.messages.length) { + const handler = handlers.shift(); + const result = await handler(this.messages.shift()); + if (handlers.length === 0) { + emitter.emit("resolve", result); + this.executions.shift(); + i--; + break; + } + } + } catch (err) { + emitter.emit("reject", err); + this.executions.shift(); + i--; + } + } + } + /** + * For environments where network partitioning is common. + * @returns {Boolean} + */ + isConnected() { + return this.connected; + } + async connect(opts) { + let host = "localhost"; + let port = 11300; + if (opts && opts.host) { + host = opts.host; + } + if (opts && opts.port) { + port = opts.port; + } + await new Promise((resolve, reject) => { + this.socket.once("error", (error) => { + if (error.code === "EISCONN") { + return resolve(); + } + reject(error); + }); + this.socket.connect(port, host, resolve); + }); + return this; + } + write(buffer) { + assert(buffer); + return new Promise((resolve, reject) => { + this.socket.write(buffer, (err) => err ? reject(err) : resolve()); + }); + } + quit = async () => { + if (!this.connected) return; + const waitForClose = new Promise((resolve, reject) => { + this.socket.once("close", resolve); + this.socket.once("error", reject); + }); + this.socket.end(new TextEncoder().encode("quit\r\n")); + await waitForClose; + }; + close = this.quit; + disconnect = this.quit; + put = this.createCommandHandler( + (payload, { priority, delay, ttr } = {}) => { + assert(payload); + let body; + if (typeof payload === "object") { + const string = JSON.stringify(payload); + body = new TextEncoder().encode(string); + } else { + body = new TextEncoder().encode(payload); + } + const command = new TextEncoder().encode( + `put ${priority || 0} ${delay || 0} ${ttr || 60} ${body.length}\r +` + ); + const delimiter = new TextEncoder().encode(DELIMITER); + const result = new Uint8Array( + command.length + body.length + delimiter.length + ); + result.set(command); + result.set(body, command.length); + result.set(delimiter, command.length + body.length); + return result; + }, + [ + (buffer) => { + const ascii = validate(buffer, [ + BURIED, + EXPECTED_CRLF, + JOB_TOO_BIG, + DRAINING + ]); + if (ascii.startsWith(INSERTED)) { + const [, id] = ascii.split(" "); + return parseInt(id); + } + invalidResponse(ascii); + } + ] + ); + use = this.createCommandHandler( + (tube) => { + assert(tube); + return new TextEncoder().encode(`use ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(USING)) { + const [, tube] = ascii.split(" "); + return tube; + } + invalidResponse(ascii); + } + ] + ); + createReserveHandlers(additionalResponses = [], decodePayload = true) { + let id; + return [ + (buffer) => { + const ascii = validate(buffer, [ + DEADLINE_SOON, + TIMED_OUT, + ...additionalResponses + ]); + if (ascii.startsWith(RESERVED)) { + const [, incomingId, bytes] = ascii.split(" "); + id = parseInt(incomingId); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return { + id, + payload: decodePayload ? new TextDecoder().decode(payload) : payload + }; + } + ]; + } + reserve = this.createCommandHandler( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], true) + ); + reserveRaw = this.createCommandHandler( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], false) + ); + reserveWithTimeout = this.createCommandHandler( + (seconds) => new TextEncoder().encode(`reserve-with-timeout ${seconds}\r +`), + this.createReserveHandlers([], true) + ); + reserveJob = this.createCommandHandler( + (id) => new TextEncoder().encode(`reserve-job ${id}\r +`), + this.createReserveHandlers([NOT_FOUND], true) + ); + delete = this.createCommandHandler( + (id) => { + assert(id); + return new TextEncoder().encode(`delete ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === DELETED) return; + invalidResponse(ascii); + } + ] + ); + release = this.createCommandHandler( + (id, { priority, delay } = {}) => { + assert(id); + return new TextEncoder().encode( + `release ${id} ${priority || 0} ${delay || 0}\r +` + ); + }, + [ + (buffer) => { + const ascii = validate(buffer, [BURIED, NOT_FOUND]); + if (ascii === RELEASED) return; + invalidResponse(ascii); + } + ] + ); + bury = this.createCommandHandler( + (id, priority) => { + assert(id); + return new TextEncoder().encode(`bury ${id} ${priority || 0}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === BURIED) return; + invalidResponse(ascii); + } + ] + ); + touch = this.createCommandHandler( + (id) => { + assert(id); + return new TextEncoder().encode(`touch ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === TOUCHED) return; + invalidResponse(ascii); + } + ] + ); + watch = this.createCommandHandler( + (tube) => { + assert(tube); + return new TextEncoder().encode(`watch ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(WATCHING)) { + const [, count] = ascii.split(" "); + return parseInt(count); + } + invalidResponse(ascii); + } + ] + ); + ignore = this.createCommandHandler( + (tube) => { + assert(tube); + return new TextEncoder().encode(`ignore ${tube}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_IGNORED]); + if (ascii.startsWith(WATCHING)) { + const [, count] = ascii.split(" "); + return parseInt(count); + } + invalidResponse(ascii); + } + ] + ); + pauseTube = this.createCommandHandler( + (tube, { delay } = {}) => new TextEncoder().encode(`pause-tube ${tube} ${delay || 0}`), + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii === PAUSED) return; + invalidResponse(ascii); + } + ] + ); + /* Other commands */ + peek = this.createCommandHandler((id) => { + assert(id); + return new TextEncoder().encode(`peek ${id}\r +`); + }, this.createPeekHandlers()); + createPeekHandlers() { + let id; + return [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(FOUND)) { + const [, peekId, bytes] = ascii.split(" "); + id = parseInt(peekId); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return { + id, + payload: new TextDecoder().decode(payload) + }; + } + ]; + } + peekReady = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-ready\r +`); + }, this.createPeekHandlers()); + peekDelayed = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-delayed\r +`); + }, this.createPeekHandlers()); + peekBuried = this.createCommandHandler(() => { + return new TextEncoder().encode(`peek-buried\r +`); + }, this.createPeekHandlers()); + kick = this.createCommandHandler( + (bound) => { + assert(bound); + return new TextEncoder().encode(`kick ${bound}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer); + if (ascii.startsWith(KICKED)) { + const [, kicked] = ascii.split(" "); + return parseInt(kicked); + } + invalidResponse(ascii); + } + ] + ); + kickJob = this.createCommandHandler( + (id) => { + assert(id); + return new TextEncoder().encode(`kick-job ${id}\r +`); + }, + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(KICKED)) return; + invalidResponse(ascii); + } + ] + ); + statsJob = this.createCommandHandler((id) => { + assert(id); + return new TextEncoder().encode(`stats-job ${id}\r +`); + }, this.createYamlCommandHandlers()); + statsTube = this.createCommandHandler((tube) => { + assert(tube); + return new TextEncoder().encode(`stats-tube ${tube}\r +`); + }, this.createYamlCommandHandlers()); + stats = this.createCommandHandler( + () => new TextEncoder().encode(`stats\r +`), + this.createYamlCommandHandlers() + ); + listTubes = this.createCommandHandler( + () => new TextEncoder().encode(`list-tubes\r +`), + this.createYamlCommandHandlers() + ); + listTubesWatched = this.createCommandHandler( + () => new TextEncoder().encode(`list-tubes-watched\r +`), + this.createYamlCommandHandlers() + ); + createYamlCommandHandlers() { + return [ + (buffer) => { + const ascii = validate(buffer, [DEADLINE_SOON, TIMED_OUT]); + if (ascii.startsWith(OK)) { + const [, bytes] = ascii.split(" "); + this.chunkLength = parseInt(bytes); + return; + } + invalidResponse(ascii); + }, + (payload) => { + return new TextDecoder().decode(payload); + } + ]; + } + getCurrentTube = this.createCommandHandler( + () => new TextEncoder().encode(`list-tube-used\r +`), + [ + (buffer) => { + const ascii = validate(buffer, [NOT_FOUND]); + if (ascii.startsWith(USING)) { + const [, tube] = ascii.split(" "); + return tube; + } + invalidResponse(ascii); + } + ] + ); + listTubeUsed = this.getCurrentTube; + createCommandHandler(commandStringFunction, handlers) { + return async (...args) => { + const commandString = commandStringFunction.apply(this, args); + await this.write(commandString); + const emitter = new EventEmitter(); + this.executions.push({ + handlers: handlers.concat(), + emitter + }); + return await new Promise((resolve, reject) => { + emitter.once("resolve", resolve); + emitter.once("reject", reject); + }); + }; + } +}; +var index_default = JackdClient; +function validate(buffer, additionalResponses = []) { + const ascii = new TextDecoder().decode(buffer); + const errors = [OUT_OF_MEMORY, INTERNAL_ERROR, BAD_FORMAT, UNKNOWN_COMMAND]; + if (errors.concat(additionalResponses).some((error) => ascii.startsWith(error))) { + throw new Error(ascii); + } + return ascii; +} +var InvalidResponseError = class extends Error { + response = "internal error"; +}; +function invalidResponse(ascii) { + console.log(ascii); + const error = new InvalidResponseError(`Unexpected response: ${ascii}`); + error.response = ascii; + throw error; +} +function findIndex(array, subarray) { + for (let i = 0; i <= array.length - subarray.length; i++) { + let found = true; + for (let j = 0; j < subarray.length; j++) { + if (array[i + j] !== subarray[j]) { + found = false; + break; + } + } + if (found) return i; + } + return -1; +} +var RESERVED = "RESERVED"; +var INSERTED = "INSERTED"; +var USING = "USING"; +var TOUCHED = "TOUCHED"; +var DELETED = "DELETED"; +var BURIED = "BURIED"; +var RELEASED = "RELEASED"; +var NOT_FOUND = "NOT_FOUND"; +var OUT_OF_MEMORY = "OUT_OF_MEMORY"; +var INTERNAL_ERROR = "INTERNAL_ERROR"; +var BAD_FORMAT = "BAD_FORMAT"; +var UNKNOWN_COMMAND = "UNKNOWN_COMMAND"; +var EXPECTED_CRLF = "EXPECTED_CRLF"; +var JOB_TOO_BIG = "JOB_TOO_BIG"; +var DRAINING = "DRAINING"; +var TIMED_OUT = "TIMED_OUT"; +var DEADLINE_SOON = "DEADLINE_SOON"; +var FOUND = "FOUND"; +var WATCHING = "WATCHING"; +var NOT_IGNORED = "NOT_IGNORED"; +var KICKED = "KICKED"; +var PAUSED = "PAUSED"; +var OK = "OK"; +export { + CommandExecution, + InvalidResponseError, + JackdClient, + index_default as default +}; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 61f6639..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -/// -/// -import { Socket } from 'net'; -import { CommandExecution, CommandHandler, ConnectOpts, Job, CtorOpts, PutOpts } from './types'; -export declare class JackdClient { - socket: Socket; - connected: Boolean; - buffer: Buffer; - chunkLength: number; - useLegacyStringPayloads: boolean; - messages: Buffer[]; - executions: CommandExecution[]; - constructor(opts?: CtorOpts); - processChunk(head: Buffer): Promise; - flushExecutions(): Promise; - isConnected(): Boolean; - connect(opts?: ConnectOpts): Promise; - write(buffer: Buffer): Promise; - quit(): Promise; - close: () => Promise; - disconnect: () => Promise; - put: (payload: string | object | Buffer, options?: PutOpts) => Promise; - use: (tubeId: string) => Promise; - createReserveHandlers(additionalResponses?: Array): [CommandHandler, CommandHandler]; - decodeAsciiWhenLegacy: (payload: Buffer) => Buffer | string; - reserve: () => Promise; - reserveWithTimeout: (args_0: number) => Promise; - reserveJob: (args_0: number) => Promise; - delete: (jobId: string) => Promise; - release: (jobId: string, options?: import("./types").ReleaseOpts) => Promise; - bury: (jobId: string, priority: number) => Promise; - touch: (jobId: string) => Promise; - watch: (tubeId: string) => Promise; - ignore: (tubeId: string) => Promise; - pauseTube: (tubeId: string, options?: import("./types").PauseTubeOpts) => Promise; - peek: (jobId: string) => Promise; - createPeekHandlers(): [CommandHandler, CommandHandler]; - peekReady: () => Promise; - peekDelayed: () => Promise; - peekBuried: () => Promise; - kick: (jobsCount: number) => Promise; - kickJob: (jobId: string) => Promise; - statsJob: (jobId: string) => Promise; - statsTube: (jobId: string) => Promise; - stats: (jobId: string) => Promise; - listTubes: (jobId: string) => Promise; - listTubesWatched: (jobId: string) => Promise; - createYamlCommandHandlers(): [CommandHandler, CommandHandler]; - getCurrentTube: () => Promise; - listTubeUsed: () => Promise; - createCommandHandler(commandStringFunction: (...args: any[]) => Buffer, handlers: CommandHandler[]): (...args: TArgs) => Promise; -} -export default JackdClient; -export declare class InvalidResponseError extends Error { - response: string; -} -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map deleted file mode 100644 index 149f153..0000000 --- a/dist/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAI5B,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,GAAG,EAMH,QAAQ,EACR,OAAO,EACR,MAAM,SAAS,CAAA;AAIhB,qBAAa,WAAW;IACtB,MAAM,EAAE,MAAM,CAAe;IAC7B,SAAS,EAAE,OAAO,CAAQ;IAC1B,MAAM,EAAE,MAAM,CAAkB;IAChC,WAAW,EAAE,MAAM,CAAI;IACvB,uBAAuB,EAAE,OAAO,CAAQ;IAKxC,QAAQ,EAAE,MAAM,EAAE,CAAK;IACvB,UAAU,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAK;gBAE5B,IAAI,CAAC,EAAE,QAAQ;IAsBrB,YAAY,CAAC,IAAI,EAAE,MAAM;IAsCzB,eAAe;IA0CrB,WAAW,IAAI,OAAO;IAIhB,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BhD,KAAK,CAAC,MAAM,EAAE,MAAM;IAQd,IAAI;IAIV,KAAK,sBAAY;IACjB,UAAU,sBAAY;IAEtB,GAAG,4EA0CF;IAED,GAAG,sCAiBF;IAED,qBAAqB,CACnB,mBAAmB,GAAE,KAAK,CAAC,MAAM,CAAM,GACtC,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IA4B9C,qBAAqB,YAAa,MAAM,KAAG,MAAM,GAAG,MAAM,CACU;IAEpE,OAAO,qBAGN;IAED,kBAAkB,mCAGjB;IAED,UAAU,mCAGT;IAED,MAAM,mCAaL;IAED,OAAO,4EAeN;IAED,IAAI,qDAYH;IAED,KAAK,mCAYJ;IAED,KAAK,sCAiBJ;IAED,MAAM,sCAgBL;IAED,SAAS,+EAWR;IAID,IAAI,kCAGyB;IAE7B,kBAAkB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAsBjE,SAAS,qBAEoB;IAE7B,WAAW,qBAEkB;IAE7B,UAAU,qBAEmB;IAE7B,IAAI,yCAgBH;IAED,OAAO,mCAYN;IAED,QAAQ,qCAG4B;IAEpC,SAAS,qCAG2B;IAEpC,KAAK,qCAGJ;IAED,SAAS,qCAGR;IAED,gBAAgB,qCAGf;IAED,yBAAyB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IAuB3E,cAAc,wBAYb;IAED,YAAY,wBAAsB;IAElC,oBAAoB,CAAC,KAAK,SAAS,GAAG,EAAE,EAAE,OAAO,EAC/C,qBAAqB,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,EACjD,QAAQ,EAAE,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,GACzC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC;CAoBxC;AAID,eAAe,WAAW,CAAA;AAa1B,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,EAAE,MAAM,CAAA;CACjB"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index d14760f..0000000 --- a/dist/index.js +++ /dev/null @@ -1,410 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.InvalidResponseError = exports.JackdClient = void 0; -const net_1 = require("net"); -const assert = require("assert"); -const EventEmitter = require("events"); -const DELIMITER = '\r\n'; -class JackdClient { - constructor(opts) { - this.socket = new net_1.Socket(); - this.connected = false; - this.buffer = Buffer.from([]); - this.chunkLength = 0; - this.useLegacyStringPayloads = false; - this.messages = []; - this.executions = []; - this.close = this.quit; - this.disconnect = this.quit; - this.put = this.createCommandHandler((payload, { priority, delay, ttr } = {}) => { - assert(payload); - let body = payload; - if (typeof body === 'object') { - body = JSON.stringify(payload); - } - if (typeof body === 'string') { - body = Buffer.from(body); - } - let command = Buffer.from(`put ${priority || 0} ${delay || 0} ${ttr || 60} ${body.length}\r\n`, 'ascii'); - return Buffer.concat([command, body, Buffer.from(DELIMITER, 'ascii')]); - }, [ - async (buffer) => { - const ascii = validate(buffer, [ - BURIED, - EXPECTED_CRLF, - JOB_TOO_BIG, - DRAINING - ]); - if (ascii.startsWith(INSERTED)) { - const [, id] = ascii.split(' '); - return id; - } - invalidResponse(ascii); - } - ]); - this.use = this.createCommandHandler(tube => { - assert(tube); - return Buffer.from(`use ${tube}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer); - if (ascii.startsWith(USING)) { - const [, tube] = ascii.split(' '); - return tube; - } - invalidResponse(ascii); - } - ]); - this.decodeAsciiWhenLegacy = (payload) => this.useLegacyStringPayloads ? payload.toString('ascii') : payload; - this.reserve = this.createCommandHandler(() => Buffer.from('reserve\r\n', 'ascii'), this.createReserveHandlers()); - this.reserveWithTimeout = this.createCommandHandler(seconds => Buffer.from(`reserve-with-timeout ${seconds}\r\n`, 'ascii'), this.createReserveHandlers()); - this.reserveJob = this.createCommandHandler(id => Buffer.from(`reserve-job ${id}\r\n`, 'ascii'), this.createReserveHandlers([NOT_FOUND])); - this.delete = this.createCommandHandler(id => { - assert(id); - return Buffer.from(`delete ${id}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii === DELETED) - return; - invalidResponse(ascii); - } - ]); - this.release = this.createCommandHandler((id, { priority, delay } = {}) => { - assert(id); - return Buffer.from(`release ${id} ${priority || 0} ${delay || 0}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [BURIED, NOT_FOUND]); - if (ascii === RELEASED) - return; - invalidResponse(ascii); - } - ]); - this.bury = this.createCommandHandler((id, priority) => { - assert(id); - return Buffer.from(`bury ${id} ${priority || 0}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii === BURIED) - return; - invalidResponse(ascii); - } - ]); - this.touch = this.createCommandHandler(id => { - assert(id); - return Buffer.from(`touch ${id}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii === TOUCHED) - return; - invalidResponse(ascii); - } - ]); - this.watch = this.createCommandHandler(tube => { - assert(tube); - return Buffer.from(`watch ${tube}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer); - if (ascii.startsWith(WATCHING)) { - const [, count] = ascii.split(' '); - return parseInt(count); - } - invalidResponse(ascii); - } - ]); - this.ignore = this.createCommandHandler(tube => { - assert(tube); - return Buffer.from(`ignore ${tube}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [NOT_IGNORED]); - if (ascii.startsWith(WATCHING)) { - const [, count] = ascii.split(' '); - return parseInt(count); - } - invalidResponse(ascii); - } - ]); - this.pauseTube = this.createCommandHandler((tube, { delay } = {}) => Buffer.from(`pause-tube ${tube} ${delay || 0}`, 'ascii'), [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii === PAUSED) - return; - invalidResponse(ascii); - } - ]); - this.peek = this.createCommandHandler(id => { - assert(id); - return Buffer.from(`peek ${id}\r\n`, 'ascii'); - }, this.createPeekHandlers()); - this.peekReady = this.createCommandHandler(() => { - return Buffer.from(`peek-ready\r\n`, 'ascii'); - }, this.createPeekHandlers()); - this.peekDelayed = this.createCommandHandler(() => { - return Buffer.from(`peek-delayed\r\n`, 'ascii'); - }, this.createPeekHandlers()); - this.peekBuried = this.createCommandHandler(() => { - return Buffer.from(`peek-buried\r\n`, 'ascii'); - }, this.createPeekHandlers()); - this.kick = this.createCommandHandler(bound => { - assert(bound); - return Buffer.from(`kick ${bound}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer); - if (ascii.startsWith(KICKED)) { - const [, kicked] = ascii.split(' '); - return parseInt(kicked); - } - invalidResponse(ascii); - } - ]); - this.kickJob = this.createCommandHandler(id => { - assert(id); - return Buffer.from(`kick-job ${id}\r\n`, 'ascii'); - }, [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii.startsWith(KICKED)) - return; - invalidResponse(ascii); - } - ]); - this.statsJob = this.createCommandHandler(id => { - assert(id); - return Buffer.from(`stats-job ${id}\r\n`, 'ascii'); - }, this.createYamlCommandHandlers()); - this.statsTube = this.createCommandHandler(tube => { - assert(tube); - return Buffer.from(`stats-tube ${tube}\r\n`, 'ascii'); - }, this.createYamlCommandHandlers()); - this.stats = this.createCommandHandler(() => Buffer.from(`stats\r\n`, 'ascii'), this.createYamlCommandHandlers()); - this.listTubes = this.createCommandHandler(() => Buffer.from(`list-tubes\r\n`, 'ascii'), this.createYamlCommandHandlers()); - this.listTubesWatched = this.createCommandHandler(() => Buffer.from(`list-tubes-watched\r\n`, 'ascii'), this.createYamlCommandHandlers()); - this.getCurrentTube = this.createCommandHandler(() => Buffer.from(`list-tube-used\r\n`, 'ascii'), [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii.startsWith(USING)) { - const [, tube] = ascii.split(' '); - return tube; - } - invalidResponse(ascii); - } - ]); - this.listTubeUsed = this.getCurrentTube; - if (opts && opts.useLegacyStringPayloads) { - this.useLegacyStringPayloads = true; - } - this.socket.on('ready', () => { - this.connected = true; - }); - this.socket.on('close', () => { - this.connected = false; - }); - this.socket.on('data', async (incoming) => { - this.buffer = Buffer.concat([this.buffer, incoming]); - await this.processChunk(this.buffer); - }); - } - async processChunk(head) { - let index = -1; - if (this.chunkLength > 0) { - const remainingBytes = this.chunkLength - head.length; - if (remainingBytes > -DELIMITER.length) { - return; - } - index = head.length - DELIMITER.length; - this.chunkLength = 0; - } - else { - index = head.indexOf(DELIMITER); - } - if (index > -1) { - this.messages.push(head.subarray(0, index)); - await this.flushExecutions(); - const tail = head.subarray(index + DELIMITER.length, head.length); - this.buffer = tail; - await this.processChunk(tail); - } - } - async flushExecutions() { - for (let i = 0; i < this.executions.length; i++) { - if (this.messages.length === 0) { - return; - } - const execution = this.executions[0]; - const { handlers, emitter } = execution; - try { - while (handlers.length && this.messages.length) { - const handler = handlers.shift(); - const result = await handler(this.messages.shift()); - if (handlers.length === 0) { - emitter.emit('resolve', result); - this.executions.shift(); - i--; - break; - } - } - } - catch (err) { - emitter.emit('reject', err); - this.executions.shift(); - i--; - } - } - } - isConnected() { - return this.connected; - } - async connect(opts) { - let host = undefined; - let port = 11300; - if (opts && opts.host) { - host = opts.host; - } - if (opts && opts.port) { - port = opts.port; - } - await new Promise((resolve, reject) => { - this.socket.once('error', (error) => { - if (error.code === 'EISCONN') { - return resolve(); - } - reject(error); - }); - this.socket.connect(port, host, resolve); - }); - return this; - } - write(buffer) { - assert(buffer); - return new Promise((resolve, reject) => { - this.socket.write(buffer, err => (err ? reject(err) : resolve())); - }); - } - async quit() { - this.socket.end(Buffer.from('quit\r\n', 'ascii')); - } - createReserveHandlers(additionalResponses = []) { - const self = this; - let id; - return [ - async (buffer) => { - const ascii = validate(buffer, [ - DEADLINE_SOON, - TIMED_OUT, - ...additionalResponses - ]); - if (ascii.startsWith(RESERVED)) { - const [, incomingId, bytes] = ascii.split(' '); - id = incomingId; - self.chunkLength = parseInt(bytes); - return; - } - invalidResponse(ascii); - }, - async (payload) => { - return { id, payload: this.decodeAsciiWhenLegacy(payload) }; - } - ]; - } - createPeekHandlers() { - let self = this; - let id; - return [ - async (buffer) => { - const ascii = validate(buffer, [NOT_FOUND]); - if (ascii.startsWith(FOUND)) { - const [, peekId, bytes] = ascii.split(' '); - id = peekId; - self.chunkLength = parseInt(bytes); - return; - } - invalidResponse(ascii); - }, - async (payload) => { - return { id, payload: this.decodeAsciiWhenLegacy(payload) }; - } - ]; - } - createYamlCommandHandlers() { - const self = this; - return [ - async (buffer) => { - const ascii = validate(buffer, [DEADLINE_SOON, TIMED_OUT]); - if (ascii.startsWith(OK)) { - const [, bytes] = ascii.split(' '); - self.chunkLength = parseInt(bytes); - return; - } - invalidResponse(ascii); - }, - async (payload) => { - return payload.toString('ascii'); - } - ]; - } - createCommandHandler(commandStringFunction, handlers) { - const self = this; - return async function command() { - const commandString = commandStringFunction.apply(this, arguments); - await self.write(commandString); - const emitter = new EventEmitter(); - self.executions.push({ - handlers: handlers.concat(), - emitter - }); - return await new Promise((resolve, reject) => { - emitter.once('resolve', resolve); - emitter.once('reject', reject); - }); - }; - } -} -exports.JackdClient = JackdClient; -module.exports = JackdClient; -exports.default = JackdClient; -function validate(buffer, additionalResponses = []) { - const ascii = buffer.toString('ascii'); - const errors = [OUT_OF_MEMORY, INTERNAL_ERROR, BAD_FORMAT, UNKNOWN_COMMAND]; - if (errors.concat(additionalResponses).some(error => ascii.startsWith(error))) { - throw new Error(ascii); - } - return ascii; -} -class InvalidResponseError extends Error { -} -exports.InvalidResponseError = InvalidResponseError; -function invalidResponse(ascii) { - const error = new InvalidResponseError(`Unexpected response: ${ascii}`); - error.response = ascii; - throw error; -} -const RESERVED = 'RESERVED'; -const INSERTED = 'INSERTED'; -const USING = 'USING'; -const TOUCHED = 'TOUCHED'; -const DELETED = 'DELETED'; -const BURIED = 'BURIED'; -const RELEASED = 'RELEASED'; -const NOT_FOUND = 'NOT_FOUND'; -const OUT_OF_MEMORY = 'OUT_OF_MEMORY'; -const INTERNAL_ERROR = 'INTERNAL_ERROR'; -const BAD_FORMAT = 'BAD_FORMAT'; -const UNKNOWN_COMMAND = 'UNKNOWN_COMMAND'; -const EXPECTED_CRLF = 'EXPECTED_CRLF'; -const JOB_TOO_BIG = 'JOB_TOO_BIG'; -const DRAINING = 'DRAINING'; -const TIMED_OUT = 'TIMED_OUT'; -const DEADLINE_SOON = 'DEADLINE_SOON'; -const FOUND = 'FOUND'; -const WATCHING = 'WATCHING'; -const NOT_IGNORED = 'NOT_IGNORED'; -const KICKED = 'KICKED'; -const PAUSED = 'PAUSED'; -const OK = 'OK'; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index cf1370f..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types.d.ts b/dist/types.d.ts deleted file mode 100644 index 31f1cd9..0000000 --- a/dist/types.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// -/// -import EventEmitter = require('events'); -export type CommandHandler = (chunk: Buffer) => Promise; -export declare class CommandExecution { - handlers: CommandHandler[]; - emitter: EventEmitter; -} -export interface CtorOpts { - useLegacyStringPayloads: boolean; -} -export interface ConnectOpts { - host: string; - port?: number; -} -export interface PutOpts { - delay?: number; - priority?: number; - ttr?: number; -} -export interface Job { - id: string; - payload: Buffer | string; -} -export interface ReleaseOpts { - priority?: number; - delay?: number; -} -export interface PauseTubeOpts { - delay?: number; -} -export type PutArgs = [payload: Buffer | string | object, options?: PutOpts]; -export type ReleaseArgs = [jobId: string, options?: ReleaseOpts]; -export type PauseTubeArgs = [tubeId: string, options?: PauseTubeOpts]; -export type JobArgs = [jobId: string]; -export type TubeArgs = [tubeId: string]; -//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map deleted file mode 100644 index eba0f7d..0000000 --- a/dist/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;AAAA,OAAO,YAAY,GAAG,QAAQ,QAAQ,CAAC,CAAA;AAEvC,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAE7D,qBAAa,gBAAgB,CAAC,CAAC;IAC7B,QAAQ,EAAE,cAAc,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAA;IACpC,OAAO,EAAE,YAAY,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,uBAAuB,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,CAAC,CAAA;AAChE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,CAAA;AACrE,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;AACrC,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js deleted file mode 100644 index 57525c1..0000000 --- a/dist/types.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CommandExecution = void 0; -class CommandExecution { -} -exports.CommandExecution = CommandExecution; -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/types.js.map b/dist/types.js.map deleted file mode 100644 index e4f477a..0000000 --- a/dist/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAIA,MAAa,gBAAgB;CAG5B;AAHD,4CAGC"} \ No newline at end of file diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts new file mode 100644 index 0000000..47687c9 --- /dev/null +++ b/dist/types/index.d.ts @@ -0,0 +1,95 @@ +import { Socket } from "net"; +import EventEmitter from "events"; +export type CommandHandler = (chunk: Uint8Array) => T | Promise; +export declare class CommandExecution { + handlers: CommandHandler[]; + emitter: EventEmitter; +} +export interface JackdConnectOpts { + host: string; + port?: number; +} +export interface JackdPutOpts { + delay?: number; + priority?: number; + ttr?: number; +} +export interface JackdJobRaw { + id: number; + payload: Uint8Array; +} +export interface JackdJob { + id: number; + payload: string; +} +interface JackdReleaseOpts { + priority?: number; + delay?: number; +} +interface JackdPauseTubeOpts { + delay?: number; +} +type JackdPutArgs = [ + payload: Uint8Array | string | object, + options?: JackdPutOpts +]; +type JackdReleaseArgs = [jobId: number, options?: JackdReleaseOpts]; +type JackdPauseTubeArgs = [tubeId: string, options?: JackdPauseTubeOpts]; +type JackdJobArgs = [jobId: number]; +type JackdTubeArgs = [tubeId: string]; +type JackdArgs = JackdPutArgs | JackdReleaseArgs | JackdPauseTubeArgs | JackdJobArgs | JackdTubeArgs | never[] | number[] | [jobId: number, priority?: number]; +export declare class JackdClient { + socket: Socket; + connected: boolean; + buffer: Uint8Array; + chunkLength: number; + messages: Uint8Array[]; + executions: CommandExecution[]; + constructor(); + processChunk(head: Uint8Array): Promise; + flushExecutions(): Promise; + /** + * For environments where network partitioning is common. + * @returns {Boolean} + */ + isConnected(): boolean; + connect(opts?: JackdConnectOpts): Promise; + write(buffer: Uint8Array): Promise; + quit: () => Promise; + close: () => Promise; + disconnect: () => Promise; + put: (payload: string | object | Uint8Array, options?: JackdPutOpts | undefined) => Promise; + use: (tubeId: string) => Promise; + createReserveHandlers(additionalResponses?: Array, decodePayload?: boolean): [CommandHandler, CommandHandler]; + reserve: () => Promise; + reserveRaw: () => Promise; + reserveWithTimeout: (args_0: number) => Promise; + reserveJob: (args_0: number) => Promise; + delete: (jobId: number) => Promise; + release: (jobId: number, options?: JackdReleaseOpts | undefined) => Promise; + bury: (jobId: number, priority?: number | undefined) => Promise; + touch: (jobId: number) => Promise; + watch: (tubeId: string) => Promise; + ignore: (tubeId: string) => Promise; + pauseTube: (tubeId: string, options?: JackdPauseTubeOpts | undefined) => Promise; + peek: (jobId: number) => Promise; + createPeekHandlers(): [CommandHandler, CommandHandler]; + peekReady: () => Promise; + peekDelayed: () => Promise; + peekBuried: () => Promise; + kick: (jobsCount: number) => Promise; + kickJob: (jobId: number) => Promise; + statsJob: (jobId: number) => Promise; + statsTube: (tubeId: string) => Promise; + stats: () => Promise; + listTubes: () => Promise; + listTubesWatched: () => Promise; + createYamlCommandHandlers(): [CommandHandler, CommandHandler]; + getCurrentTube: () => Promise; + listTubeUsed: () => Promise; + createCommandHandler(commandStringFunction: (...args: TArgs) => Uint8Array, handlers: CommandHandler[]): (...args: TArgs) => Promise; +} +export default JackdClient; +export declare class InvalidResponseError extends Error { + response: string; +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..7d0aa73 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,33 @@ +import tseslint from "typescript-eslint" +import js from "@eslint/js" +import unusedImports from "eslint-plugin-unused-imports" + +const config = [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname + } + } + }, + { + plugins: { + "unused-imports": unusedImports + }, + rules: { + "unused-imports/no-unused-imports": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + prefer: "type-imports", + fixStyle: "separate-type-imports" + } + ] + } + } +] + +export default config diff --git a/package.json b/package.json index 1e2433b..e31f30c 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,21 @@ { "name": "jackd", - "version": "2.2.2", + "version": "3.0.0", "description": "Modern beanstalkd client for Node.js", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "type": "module", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { - "build": "tsc", + "build": "tsc --project tsconfig.build.json && bun build.ts", "dev": "tsc --watch", - "lint": "eslint src", - "test": "mocha --bail" + "lint": "eslint src" }, "files": [ "dist/" @@ -27,11 +34,14 @@ }, "homepage": "https://github.com/getjackd/jackd#readme", "devDependencies": { - "@types/node": "^20.6.0", - "chai": "^4.3.8", - "eslint": "^8.49.0", - "mocha": "^10.2.0", - "typescript": "^5.2.2", - "yaml": "^2.3.2" + "@eslint/js": "^9.18.0", + "@types/node": "22.10.7", + "esbuild": "0.24.2", + "eslint": "9.18.0", + "eslint-plugin-unused-imports": "^4.1.4", + "typescript": "5.7.3", + "typescript-eslint": "^8.20.0", + "vitest": "3.0.2", + "yaml": "2.7.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 05acae4..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,1050 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -devDependencies: - '@types/node': - specifier: ^20.6.0 - version: 20.6.0 - chai: - specifier: ^4.3.8 - version: 4.3.8 - eslint: - specifier: ^8.49.0 - version: 8.49.0 - mocha: - specifier: ^10.2.0 - version: 10.2.0 - typescript: - specifier: ^5.2.2 - version: 5.2.2 - yaml: - specifier: ^2.3.2 - version: 2.3.2 - -packages: - - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.49.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.49.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.8.0: - resolution: {integrity: sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) - espree: 9.6.1 - globals: 13.21.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.49.0: - resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@humanwhocodes/config-array@0.11.11: - resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4(supports-color@8.1.1) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - - /@types/node@20.6.0: - resolution: {integrity: sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==} - dev: true - - /acorn-jsx@5.3.2(acorn@8.10.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.10.0 - dev: true - - /acorn@8.10.0: - resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: true - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - dev: true - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - dev: true - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true - - /chai@4.3.8: - resolution: {integrity: sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.2 - deep-eql: 4.1.3 - get-func-name: 2.0.0 - loupe: 2.3.6 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /check-error@1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true - - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /debug@4.3.4(supports-color@8.1.1): - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 - dev: true - - /decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - dev: true - - /deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - dependencies: - type-detect: 4.0.8 - dev: true - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} - engines: {node: '>=0.3.1'} - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: true - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.49.0: - resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) - '@eslint-community/regexpp': 4.8.0 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.49.0 - '@humanwhocodes/config-array': 0.11.11 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.21.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.3 - dev: true - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.1.0 - dev: true - - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache@3.1.0: - resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} - engines: {node: '>=12.0.0'} - dependencies: - flatted: 3.2.7 - keyv: 4.5.3 - rimraf: 3.0.2 - dev: true - - /flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - dev: true - - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true - - /get-func-name@2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - dev: true - - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /globals@13.21.0: - resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: true - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - dev: true - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - dev: true - - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /keyv@4.5.3: - resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} - dependencies: - json-buffer: 3.0.1 - dev: true - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true - - /loupe@2.3.6: - resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} - dependencies: - get-func-name: 2.0.0 - dev: true - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /minimatch@5.0.1: - resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} - engines: {node: '>=10'} - dependencies: - brace-expansion: 2.0.1 - dev: true - - /mocha@10.2.0: - resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} - engines: {node: '>= 14.0.0'} - hasBin: true - dependencies: - ansi-colors: 4.1.1 - browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) - diff: 5.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 7.2.0 - he: 1.2.0 - js-yaml: 4.1.0 - log-symbols: 4.1.0 - minimatch: 5.0.1 - ms: 2.1.3 - nanoid: 3.3.3 - serialize-javascript: 6.0.0 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - workerpool: 6.2.1 - yargs: 16.2.0 - yargs-parser: 20.2.4 - yargs-unparser: 2.0.0 - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /nanoid@3.3.3: - resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} - dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true - - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - dependencies: - safe-buffer: 5.2.1 - dev: true - - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true - - /serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} - dependencies: - randombytes: 2.1.0 - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - dependencies: - has-flag: 4.0.0 - dev: true - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /workerpool@6.2.1: - resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} - dev: true - - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true - - /yaml@2.3.2: - resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} - engines: {node: '>= 14'} - dev: true - - /yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - dev: true - - /yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - dev: true - - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.4 - dev: true - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..e803fe9 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,298 @@ +import Jackd from "." +import YAML from "yaml" +import crypto from "crypto" + +import { describe, it, expect, beforeEach, afterEach } from "vitest" + +describe("jackd", () => { + let client: Jackd + + it("can connect to and disconnect from beanstalkd", async () => { + const c = new Jackd() + await c.connect() + await c.close() + }) + + describe("connectivity", () => { + setupTestSuiteLifecycleWithClient() + + it("connected", () => { + expect(client.connected).toBeTruthy() + }) + + it("disconnected", async () => { + await client.disconnect() + expect(client.connected).toBeFalsy() + }) + }) + + describe("producers", () => { + setupTestSuiteLifecycleWithClient() + + it("can insert jobs", async () => { + let id + + try { + id = await client.put("some random job") + expect(id).toBeDefined() + } finally { + if (id) await client.delete(id) + } + }) + + it("can insert jobs with objects", async () => { + let id: number | undefined + + try { + id = await client.put({ foo: "bar" }) + expect(id).toBeDefined() + + const job = await client.reserve() + expect(job.payload).toEqual('{"foo":"bar"}') + } finally { + if (id !== undefined) await client.delete(id) + } + }) + + it("can insert jobs with priority", async () => { + let id + + try { + id = await client.put({ foo: "bar" }, { priority: 12342342 }) + expect(id).toBeDefined() + + const job = await client.reserve() + expect(job.payload).toEqual('{"foo":"bar"}') + } finally { + if (id) await client.delete(id) + } + }) + }) + + describe("consumers", () => { + setupTestSuiteLifecycleWithClient() + + it("can reserve jobs", async () => { + let id: number | undefined + + try { + id = await client.put("some random job") + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual("some random job") + } finally { + if (id !== undefined) await client.delete(id) + } + }) + + it("can reserve jobs with raw payload", async () => { + let id: number | undefined + + try { + const testString = "some random job" + id = await client.put(testString) + const job = await client.reserveRaw() + + expect(job.id).toEqual(id) + expect(new TextDecoder().decode(job.payload)).toEqual(testString) + } finally { + if (id !== undefined) await client.delete(id) + } + }) + + it("can reserve delayed jobs", async () => { + let id + + try { + id = await client.put("some random job", { + delay: 1 + }) + + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual("some random job") + } finally { + if (id) await client.delete(id) + } + }) + + it("can reserve jobs by id", async () => { + let id: number | undefined + + try { + id = await client.put("some random job", { + delay: 1 + }) + + const job = await client.reserveJob(id) + expect(job.payload).toEqual("some random job") + } finally { + if (id !== undefined) await client.delete(id) + } + }) + + it("handles not found", async () => { + try { + await client.reserveJob(4) + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect((err as Error).message).toEqual("NOT_FOUND") + } + }) + + it("can insert and process jobs on a different tube", async () => { + let id + try { + await client.use("some-other-tube") + id = await client.put("some random job on another tube") + + await client.watch("some-other-tube") + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual("some random job on another tube") + } finally { + if (id) await client.delete(id) + } + }) + + it("will ignore jobs from default", async () => { + let id, defaultId + try { + defaultId = await client.put("job on default") + await client.use("some-other-tube") + id = await client.put("some random job on another tube") + + await client.watch("some-other-tube") + await client.ignore("default") + + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual("some random job on another tube") + } finally { + if (id) await client.delete(id) + if (defaultId) await client.delete(defaultId) + } + }) + + it("handles multiple promises fired at once", async () => { + let id1, id2 + + try { + await client.use("some-tube") + const firstJobPromise = client.put("some-job") + await client.watch("some-random-tube") + await client.use("some-another-tube") + const secondJobPromise = client.put("some-job") + + id1 = await firstJobPromise + id2 = await secondJobPromise + } finally { + if (id1) await client.delete(id1) + if (id2) await client.delete(id2) + } + }) + + it("can receive huge jobs", async () => { + let id + + try { + // job larger than a socket data frame + const hugeText = + crypto.randomBytes(15000).toString("hex") + + "\r\n" + + crypto.randomBytes(15000).toString("hex") + + id = await client.put(hugeText) + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual(hugeText) + } finally { + if (id) await client.delete(id) + } + }) + + it("can peek buried jobs", async () => { + let id: number | undefined + + try { + await client.use("some-tube") + + id = await client.put("some-job") + + await client.watch("some-tube") + await client.reserve() + await client.bury(id) + + const job = await client.peekBuried() + + expect(job.id).toEqual(id) + } finally { + if (id) await client.delete(id) + } + }) + }) + + describe("stats", () => { + setupTestSuiteLifecycleWithClient() + + it("brings back stats", async () => { + const stats = await client.stats() + YAML.parse(stats) + }) + }) + + describe("bugfixes", () => { + setupTestSuiteLifecycleWithClient() + + it("can receive jobs with new lines jobs", async () => { + let id + + try { + // job larger than a socket data frame + const payload = "this job should not fail!\r\n" + + id = await client.put(payload) + const job = await client.reserve() + + expect(job.id).toEqual(id) + expect(job.payload).toEqual(payload) + } finally { + if (id) await client.delete(id) + } + }) + + it("can continue execution after bad command", async () => { + let id + + try { + // Bad command + // @ts-expect-error We're testing the error handling + await client.delete("nonexistent job") + } catch (err) { + expect(err).toBeInstanceOf(Error) + } + + try { + id = await client.put("my awesome job") + } finally { + if (id) await client.delete(id) + } + }) + }) + + function setupTestSuiteLifecycleWithClient() { + beforeEach(async () => { + client = new Jackd() + await client.connect() + }) + + afterEach(async () => { + await client.close() + }) + } +}) diff --git a/src/index.ts b/src/index.ts index 3a2f843..7b5e7bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,59 +1,102 @@ -import { Socket } from 'net' -import assert = require('assert') -import EventEmitter = require('events') - -import { - CommandExecution, - CommandHandler, - ConnectOpts, - Job, - PauseTubeArgs, - PutArgs, - ReleaseArgs, - JobArgs, - TubeArgs, - CtorOpts, - PutOpts -} from './types' - -const DELIMITER = '\r\n' +import { Socket } from "net" +import assert from "assert" +import EventEmitter from "events" + +const DELIMITER = "\r\n" + +type JackdPayload = Uint8Array | string | object + +export type CommandHandler = (chunk: Uint8Array) => T | Promise + +export class CommandExecution { + handlers: CommandHandler[] = [] + emitter: EventEmitter = new EventEmitter() +} + +export interface JackdConnectOpts { + host: string + port?: number +} + +export interface JackdPutOpts { + delay?: number + priority?: number + ttr?: number +} + +export interface JackdJobRaw { + id: number + payload: Uint8Array +} + +export interface JackdJob { + id: number + payload: string +} + +interface JackdReleaseOpts { + priority?: number + delay?: number +} + +interface JackdPauseTubeOpts { + delay?: number +} + +type JackdPutArgs = [ + payload: Uint8Array | string | object, + options?: JackdPutOpts +] +type JackdReleaseArgs = [jobId: number, options?: JackdReleaseOpts] +type JackdPauseTubeArgs = [tubeId: string, options?: JackdPauseTubeOpts] +type JackdJobArgs = [jobId: number] +type JackdTubeArgs = [tubeId: string] +type JackdBuryArgs = [jobId: number, priority?: number] + +type JackdArgs = + | JackdPutArgs + | JackdReleaseArgs + | JackdPauseTubeArgs + | JackdJobArgs + | JackdTubeArgs + | never[] + | number[] + | [jobId: number, priority?: number] export class JackdClient { socket: Socket = new Socket() - connected: Boolean = false - buffer: Buffer = Buffer.from([]) + connected: boolean = false + buffer: Uint8Array = new Uint8Array() chunkLength: number = 0 - useLegacyStringPayloads: boolean = false // beanstalkd executes all commands serially. Because Node.js is single-threaded, // this allows us to queue up all of the messages and commands as they're invokved // without needing to explicitly wait for promises. - messages: Buffer[] = [] - executions: CommandExecution[] = [] - - constructor(opts?: CtorOpts) { - if (opts && opts.useLegacyStringPayloads) { - this.useLegacyStringPayloads = true - } + messages: Uint8Array[] = [] + executions: CommandExecution[] = [] - this.socket.on('ready', () => { + constructor() { + this.socket.on("ready", () => { this.connected = true }) - this.socket.on('close', () => { + this.socket.on("close", () => { this.connected = false }) // When we receive data from the socket, let's process it and put it in our // messages. - this.socket.on('data', async incoming => { + this.socket.on("data", async incoming => { // Write the incoming data onto the buffer - this.buffer = Buffer.concat([this.buffer, incoming]) + const newBuffer = new Uint8Array(this.buffer.length + incoming.length) + newBuffer.set(this.buffer) + newBuffer.set(new Uint8Array(incoming), this.buffer.length) + this.buffer = newBuffer await this.processChunk(this.buffer) }) } - async processChunk(head: Buffer) { + async processChunk(head: Uint8Array) { let index = -1 // If we're waiting on some bytes from a command... @@ -74,18 +117,19 @@ export class JackdClient { index = head.length - DELIMITER.length this.chunkLength = 0 } else { - index = head.indexOf(DELIMITER) + const delimiterBytes = new TextEncoder().encode(DELIMITER) + index = findIndex(head, delimiterBytes) } if (index > -1) { - this.messages.push(head.subarray(0, index)) + this.messages.push(head.slice(0, index)) // We have to start flushing executions as soon as we push messages. This is to avoid // instances where job payloads might contain line breaks. We let the downstream handlers // set the incoming bytes almost immediately. await this.flushExecutions() - const tail = head.subarray(index + DELIMITER.length, head.length) + const tail = head.slice(index + DELIMITER.length) this.buffer = tail await this.processChunk(tail) } @@ -107,10 +151,10 @@ export class JackdClient { while (handlers.length && this.messages.length) { const handler = handlers.shift() - const result = await handler(this.messages.shift()) + const result = await handler!(this.messages.shift()!) if (handlers.length === 0) { - emitter.emit('resolve', result) + emitter.emit("resolve", result) // We modified the executions array by removing an element. Decrement the loop. this.executions.shift() @@ -120,7 +164,7 @@ export class JackdClient { } } } catch (err) { - emitter.emit('reject', err) + emitter.emit("reject", err) // This execution is botched, don't hang the entire queue this.executions.shift() @@ -133,12 +177,12 @@ export class JackdClient { * For environments where network partitioning is common. * @returns {Boolean} */ - isConnected(): Boolean { + isConnected(): boolean { return this.connected } - async connect(opts?: ConnectOpts): Promise { - let host: string = undefined + async connect(opts?: JackdConnectOpts): Promise { + let host: string = "localhost" let port = 11300 if (opts && opts.host) { @@ -150,8 +194,8 @@ export class JackdClient { } await new Promise((resolve, reject) => { - this.socket.once('error', (error: NodeJS.ErrnoException) => { - if (error.code === 'EISCONN') { + this.socket.once("error", (error: NodeJS.ErrnoException) => { + if (error.code === "EISCONN") { return resolve() } @@ -164,7 +208,7 @@ export class JackdClient { return this } - write(buffer: Buffer) { + write(buffer: Uint8Array) { assert(buffer) return new Promise((resolve, reject) => { @@ -172,40 +216,50 @@ export class JackdClient { }) } - async quit() { - this.socket.end(Buffer.from('quit\r\n', 'ascii')) + quit = async () => { + if (!this.connected) return + + const waitForClose = new Promise((resolve, reject) => { + this.socket.once("close", resolve) + this.socket.once("error", reject) + }) + + this.socket.end(new TextEncoder().encode("quit\r\n")) + await waitForClose } close = this.quit disconnect = this.quit - put = this.createCommandHandler( - ( - payload: Buffer | string | object, - { priority, delay, ttr }: PutOpts = {} - ) => { + put = this.createCommandHandler( + (payload: JackdPayload, { priority, delay, ttr }: JackdPutOpts = {}) => { assert(payload) - let body: any = payload - - // If the caller passed in an object, convert it to a string - if (typeof body === 'object') { - body = JSON.stringify(payload) - } - - // If the body is a string, convert it to a UTF-8 Buffer - if (typeof body === 'string') { - body = Buffer.from(body) + let body: Uint8Array + + // If the caller passed in an object, convert it to a valid Uint8Array from a JSON string + if (typeof payload === "object") { + const string = JSON.stringify(payload) + body = new TextEncoder().encode(string) + } else { + // Anything else, just capture the Uint8Array + body = new TextEncoder().encode(payload) } - let command = Buffer.from( - `put ${priority || 0} ${delay || 0} ${ttr || 60} ${body.length}\r\n`, - 'ascii' + const command = new TextEncoder().encode( + `put ${priority || 0} ${delay || 0} ${ttr || 60} ${body.length}\r\n` ) - return Buffer.concat([command, body, Buffer.from(DELIMITER, 'ascii')]) + const delimiter = new TextEncoder().encode(DELIMITER) + const result = new Uint8Array( + command.length + body.length + delimiter.length + ) + result.set(command) + result.set(body, command.length) + result.set(delimiter, command.length + body.length) + return result }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [ BURIED, EXPECTED_CRLF, @@ -214,8 +268,8 @@ export class JackdClient { ]) if (ascii.startsWith(INSERTED)) { - const [, id] = ascii.split(' ') - return id + const [, id] = ascii.split(" ") + return parseInt(id) } invalidResponse(ascii) @@ -223,17 +277,17 @@ export class JackdClient { ] ) - use = this.createCommandHandler( + use = this.createCommandHandler( tube => { assert(tube) - return Buffer.from(`use ${tube}\r\n`, 'ascii') + return new TextEncoder().encode(`use ${tube}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer) if (ascii.startsWith(USING)) { - const [, tube] = ascii.split(' ') + const [, tube] = ascii.split(" ") return tube } @@ -242,14 +296,14 @@ export class JackdClient { ] ) - createReserveHandlers( - additionalResponses: Array = [] - ): [CommandHandler, CommandHandler] { - const self = this - let id: string + createReserveHandlers( + additionalResponses: Array = [], + decodePayload: boolean = true + ): [CommandHandler, CommandHandler] { + let id: number return [ - async buffer => { + (buffer: Uint8Array) => { const ascii = validate(buffer, [ DEADLINE_SOON, TIMED_OUT, @@ -257,46 +311,50 @@ export class JackdClient { ]) if (ascii.startsWith(RESERVED)) { - const [, incomingId, bytes] = ascii.split(' ') - id = incomingId - self.chunkLength = parseInt(bytes) - + const [, incomingId, bytes] = ascii.split(" ") + id = parseInt(incomingId) + this.chunkLength = parseInt(bytes) return } invalidResponse(ascii) }, - async (payload: Buffer) => { - return { id, payload: this.decodeAsciiWhenLegacy(payload) } + (payload: Uint8Array) => { + return { + id, + payload: decodePayload ? new TextDecoder().decode(payload) : payload + } as T } ] } - decodeAsciiWhenLegacy = (payload: Buffer): Buffer | string => - this.useLegacyStringPayloads ? payload.toString('ascii') : payload + reserve = this.createCommandHandler<[], JackdJob>( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], true) + ) - reserve = this.createCommandHandler<[], Job>( - () => Buffer.from('reserve\r\n', 'ascii'), - this.createReserveHandlers() + reserveRaw = this.createCommandHandler<[], JackdJobRaw>( + () => new TextEncoder().encode("reserve\r\n"), + this.createReserveHandlers([], false) ) - reserveWithTimeout = this.createCommandHandler<[number], Job>( - seconds => Buffer.from(`reserve-with-timeout ${seconds}\r\n`, 'ascii'), - this.createReserveHandlers() + reserveWithTimeout = this.createCommandHandler<[number], JackdJob>( + seconds => new TextEncoder().encode(`reserve-with-timeout ${seconds}\r\n`), + this.createReserveHandlers([], true) ) - reserveJob = this.createCommandHandler<[number], Job>( - id => Buffer.from(`reserve-job ${id}\r\n`, 'ascii'), - this.createReserveHandlers([NOT_FOUND]) + reserveJob = this.createCommandHandler<[number], JackdJob>( + id => new TextEncoder().encode(`reserve-job ${id}\r\n`), + this.createReserveHandlers([NOT_FOUND], true) ) - delete = this.createCommandHandler( + delete = this.createCommandHandler( id => { assert(id) - return Buffer.from(`delete ${id}\r\n`, 'ascii') + return new TextEncoder().encode(`delete ${id}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii === DELETED) return @@ -305,16 +363,15 @@ export class JackdClient { ] ) - release = this.createCommandHandler( + release = this.createCommandHandler( (id, { priority, delay } = {}) => { assert(id) - return Buffer.from( - `release ${id} ${priority || 0} ${delay || 0}\r\n`, - 'ascii' + return new TextEncoder().encode( + `release ${id} ${priority || 0} ${delay || 0}\r\n` ) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [BURIED, NOT_FOUND]) if (ascii === RELEASED) return invalidResponse(ascii) @@ -322,13 +379,13 @@ export class JackdClient { ] ) - bury = this.createCommandHandler<[jobId: string, priority: number], void>( + bury = this.createCommandHandler( (id, priority) => { assert(id) - return Buffer.from(`bury ${id} ${priority || 0}\r\n`, 'ascii') + return new TextEncoder().encode(`bury ${id} ${priority || 0}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii === BURIED) return invalidResponse(ascii) @@ -336,13 +393,13 @@ export class JackdClient { ] ) - touch = this.createCommandHandler( + touch = this.createCommandHandler( id => { assert(id) - return Buffer.from(`touch ${id}\r\n`, 'ascii') + return new TextEncoder().encode(`touch ${id}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii === TOUCHED) return invalidResponse(ascii) @@ -350,17 +407,17 @@ export class JackdClient { ] ) - watch = this.createCommandHandler( + watch = this.createCommandHandler( tube => { assert(tube) - return Buffer.from(`watch ${tube}\r\n`, 'ascii') + return new TextEncoder().encode(`watch ${tube}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer) if (ascii.startsWith(WATCHING)) { - const [, count] = ascii.split(' ') + const [, count] = ascii.split(" ") return parseInt(count) } @@ -369,17 +426,17 @@ export class JackdClient { ] ) - ignore = this.createCommandHandler( + ignore = this.createCommandHandler( tube => { assert(tube) - return Buffer.from(`ignore ${tube}\r\n`, 'ascii') + return new TextEncoder().encode(`ignore ${tube}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_IGNORED]) if (ascii.startsWith(WATCHING)) { - const [, count] = ascii.split(' ') + const [, count] = ascii.split(" ") return parseInt(count) } invalidResponse(ascii) @@ -387,12 +444,12 @@ export class JackdClient { ] ) - pauseTube = this.createCommandHandler( + pauseTube = this.createCommandHandler( (tube, { delay } = {}) => - Buffer.from(`pause-tube ${tube} ${delay || 0}`, 'ascii'), + new TextEncoder().encode(`pause-tube ${tube} ${delay || 0}`), [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii === PAUSED) return invalidResponse(ascii) @@ -402,55 +459,56 @@ export class JackdClient { /* Other commands */ - peek = this.createCommandHandler(id => { + peek = this.createCommandHandler(id => { assert(id) - return Buffer.from(`peek ${id}\r\n`, 'ascii') + return new TextEncoder().encode(`peek ${id}\r\n`) }, this.createPeekHandlers()) - createPeekHandlers(): [CommandHandler, CommandHandler] { - let self = this - let id: string + createPeekHandlers(): [CommandHandler, CommandHandler] { + let id: number return [ - async (buffer: Buffer) => { + (buffer: Uint8Array) => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii.startsWith(FOUND)) { - const [, peekId, bytes] = ascii.split(' ') - id = peekId - self.chunkLength = parseInt(bytes) - + const [, peekId, bytes] = ascii.split(" ") + id = parseInt(peekId) + this.chunkLength = parseInt(bytes) return } invalidResponse(ascii) }, - async (payload: Buffer) => { - return { id, payload: this.decodeAsciiWhenLegacy(payload) } + (payload: Uint8Array) => { + return { + id, + payload: new TextDecoder().decode(payload) + } } ] } - peekReady = this.createCommandHandler<[], Job>(() => { - return Buffer.from(`peek-ready\r\n`, 'ascii') + peekReady = this.createCommandHandler<[], JackdJob>(() => { + return new TextEncoder().encode(`peek-ready\r\n`) }, this.createPeekHandlers()) - peekDelayed = this.createCommandHandler<[], Job>(() => { - return Buffer.from(`peek-delayed\r\n`, 'ascii') + peekDelayed = this.createCommandHandler<[], JackdJob>(() => { + return new TextEncoder().encode(`peek-delayed\r\n`) }, this.createPeekHandlers()) - peekBuried = this.createCommandHandler<[], Job>(() => { - return Buffer.from(`peek-buried\r\n`, 'ascii') + peekBuried = this.createCommandHandler<[], JackdJob>(() => { + return new TextEncoder().encode(`peek-buried\r\n`) }, this.createPeekHandlers()) kick = this.createCommandHandler<[jobsCount: number], number>( bound => { assert(bound) - return Buffer.from(`kick ${bound}\r\n`, 'ascii') + return new TextEncoder().encode(`kick ${bound}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer) if (ascii.startsWith(KICKED)) { - const [, kicked] = ascii.split(' ') + const [, kicked] = ascii.split(" ") return parseInt(kicked) } @@ -459,13 +517,13 @@ export class JackdClient { ] ) - kickJob = this.createCommandHandler( + kickJob = this.createCommandHandler( id => { assert(id) - return Buffer.from(`kick-job ${id}\r\n`, 'ascii') + return new TextEncoder().encode(`kick-job ${id}\r\n`) }, [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii.startsWith(KICKED)) return invalidResponse(ascii) @@ -473,61 +531,58 @@ export class JackdClient { ] ) - statsJob = this.createCommandHandler(id => { + statsJob = this.createCommandHandler(id => { assert(id) - return Buffer.from(`stats-job ${id}\r\n`, 'ascii') + return new TextEncoder().encode(`stats-job ${id}\r\n`) }, this.createYamlCommandHandlers()) - statsTube = this.createCommandHandler(tube => { + statsTube = this.createCommandHandler(tube => { assert(tube) - return Buffer.from(`stats-tube ${tube}\r\n`, 'ascii') + return new TextEncoder().encode(`stats-tube ${tube}\r\n`) }, this.createYamlCommandHandlers()) - stats = this.createCommandHandler( - () => Buffer.from(`stats\r\n`, 'ascii'), + stats = this.createCommandHandler<[], string>( + () => new TextEncoder().encode(`stats\r\n`), this.createYamlCommandHandlers() ) - listTubes = this.createCommandHandler( - () => Buffer.from(`list-tubes\r\n`, 'ascii'), + listTubes = this.createCommandHandler<[], string>( + () => new TextEncoder().encode(`list-tubes\r\n`), this.createYamlCommandHandlers() ) - listTubesWatched = this.createCommandHandler( - () => Buffer.from(`list-tubes-watched\r\n`, 'ascii'), + listTubesWatched = this.createCommandHandler<[], string>( + () => new TextEncoder().encode(`list-tubes-watched\r\n`), this.createYamlCommandHandlers() ) createYamlCommandHandlers(): [CommandHandler, CommandHandler] { - const self = this - return [ - async buffer => { + (buffer: Uint8Array) => { const ascii = validate(buffer, [DEADLINE_SOON, TIMED_OUT]) if (ascii.startsWith(OK)) { - const [, bytes] = ascii.split(' ') - self.chunkLength = parseInt(bytes) - + const [, bytes] = ascii.split(" ") + this.chunkLength = parseInt(bytes) return } invalidResponse(ascii) }, - async (payload: Buffer) => { + (payload: Uint8Array) => { // Payloads for internal beanstalkd commands are always returned in ASCII - return payload.toString('ascii') + return new TextDecoder().decode(payload) } ] } getCurrentTube = this.createCommandHandler<[], string>( - () => Buffer.from(`list-tube-used\r\n`, 'ascii'), + () => new TextEncoder().encode(`list-tube-used\r\n`), [ - async buffer => { + buffer => { const ascii = validate(buffer, [NOT_FOUND]) if (ascii.startsWith(USING)) { - const [, tube] = ascii.split(' ') + const [, tube] = ascii.split(" ") return tube } invalidResponse(ascii) @@ -537,40 +592,41 @@ export class JackdClient { listTubeUsed = this.getCurrentTube - createCommandHandler( - commandStringFunction: (...args: any[]) => Buffer, + createCommandHandler( + commandStringFunction: (...args: TArgs) => Uint8Array, handlers: CommandHandler[] ): (...args: TArgs) => Promise { - const self = this - - return async function command() { - const commandString: Buffer = commandStringFunction.apply(this, arguments) - await self.write(commandString) + return async (...args) => { + const commandString: Uint8Array = commandStringFunction.apply(this, args) + await this.write(commandString) const emitter = new EventEmitter() - self.executions.push({ + this.executions.push({ handlers: handlers.concat(), emitter }) return await new Promise((resolve, reject) => { - emitter.once('resolve', resolve) - emitter.once('reject', reject) + emitter.once("resolve", resolve) + emitter.once("reject", reject) }) } } } -module.exports = JackdClient - export default JackdClient -function validate(buffer: Buffer, additionalResponses: string[] = []): string { - const ascii = buffer.toString('ascii') +function validate( + buffer: Uint8Array, + additionalResponses: string[] = [] +): string { + const ascii = new TextDecoder().decode(buffer) const errors = [OUT_OF_MEMORY, INTERNAL_ERROR, BAD_FORMAT, UNKNOWN_COMMAND] - if (errors.concat(additionalResponses).some(error => ascii.startsWith(error))) { + if ( + errors.concat(additionalResponses).some(error => ascii.startsWith(error)) + ) { throw new Error(ascii) } @@ -578,35 +634,51 @@ function validate(buffer: Buffer, additionalResponses: string[] = []): string { } export class InvalidResponseError extends Error { - response: string + response: string = "internal error" } function invalidResponse(ascii: string) { + console.log(ascii) const error = new InvalidResponseError(`Unexpected response: ${ascii}`) error.response = ascii throw error } -const RESERVED = 'RESERVED' -const INSERTED = 'INSERTED' -const USING = 'USING' -const TOUCHED = 'TOUCHED' -const DELETED = 'DELETED' -const BURIED = 'BURIED' -const RELEASED = 'RELEASED' -const NOT_FOUND = 'NOT_FOUND' -const OUT_OF_MEMORY = 'OUT_OF_MEMORY' -const INTERNAL_ERROR = 'INTERNAL_ERROR' -const BAD_FORMAT = 'BAD_FORMAT' -const UNKNOWN_COMMAND = 'UNKNOWN_COMMAND' -const EXPECTED_CRLF = 'EXPECTED_CRLF' -const JOB_TOO_BIG = 'JOB_TOO_BIG' -const DRAINING = 'DRAINING' -const TIMED_OUT = 'TIMED_OUT' -const DEADLINE_SOON = 'DEADLINE_SOON' -const FOUND = 'FOUND' -const WATCHING = 'WATCHING' -const NOT_IGNORED = 'NOT_IGNORED' -const KICKED = 'KICKED' -const PAUSED = 'PAUSED' -const OK = 'OK' +// Helper function to find index of subarray +function findIndex(array: Uint8Array, subarray: Uint8Array): number { + for (let i = 0; i <= array.length - subarray.length; i++) { + let found = true + for (let j = 0; j < subarray.length; j++) { + if (array[i + j] !== subarray[j]) { + found = false + break + } + } + if (found) return i + } + return -1 +} + +const RESERVED = "RESERVED" +const INSERTED = "INSERTED" +const USING = "USING" +const TOUCHED = "TOUCHED" +const DELETED = "DELETED" +const BURIED = "BURIED" +const RELEASED = "RELEASED" +const NOT_FOUND = "NOT_FOUND" +const OUT_OF_MEMORY = "OUT_OF_MEMORY" +const INTERNAL_ERROR = "INTERNAL_ERROR" +const BAD_FORMAT = "BAD_FORMAT" +const UNKNOWN_COMMAND = "UNKNOWN_COMMAND" +const EXPECTED_CRLF = "EXPECTED_CRLF" +const JOB_TOO_BIG = "JOB_TOO_BIG" +const DRAINING = "DRAINING" +const TIMED_OUT = "TIMED_OUT" +const DEADLINE_SOON = "DEADLINE_SOON" +const FOUND = "FOUND" +const WATCHING = "WATCHING" +const NOT_IGNORED = "NOT_IGNORED" +const KICKED = "KICKED" +const PAUSED = "PAUSED" +const OK = "OK" diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 32b0015..0000000 --- a/src/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import EventEmitter = require('events') - -export type CommandHandler = (chunk: Buffer) => Promise - -export class CommandExecution { - handlers: CommandHandler[] - emitter: EventEmitter -} - -export interface CtorOpts { - useLegacyStringPayloads: boolean -} - -export interface ConnectOpts { - host: string - port?: number -} - -export interface PutOpts { - delay?: number - priority?: number - ttr?: number -} - -export interface Job { - id: string - payload: Buffer | string -} - -export interface ReleaseOpts { - priority?: number - delay?: number -} - -export interface PauseTubeOpts { - delay?: number -} - -export type PutArgs = [payload: Buffer | string | object, options?: PutOpts] -export type ReleaseArgs = [jobId: string, options?: ReleaseOpts] -export type PauseTubeArgs = [tubeId: string, options?: PauseTubeOpts] -export type JobArgs = [jobId: string] -export type TubeArgs = [tubeId: string] diff --git a/test/index.test.js b/test/index.test.js deleted file mode 100644 index c0dbeca..0000000 --- a/test/index.test.js +++ /dev/null @@ -1,276 +0,0 @@ -const { expect } = require('chai') -const Jackd = require('../dist') -const YAML = require('yaml') -const crypto = require('crypto') - -describe('jackd', function () { - it('can connect to and disconnect from beanstalkd', async function () { - const c = new Jackd({ useLegacyStringPayloads: true }) - await c.connect() - await c.close() - }) - - describe('connectivity', function () { - setupTestSuiteLifecycleWithClient() - - it('connected', function () { - expect(this.client.connected).to.be.ok - }) - - it('disconnected', async function () { - await this.client.disconnect() - expect(this.client.ocnnected).to.not.be.ok - }) - }) - - describe('producers', function () { - setupTestSuiteLifecycleWithClient() - - it('can insert jobs', async function () { - let id - - try { - id = await this.client.put('some random job') - expect(id).to.be.ok - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can insert jobs with objects', async function () { - let id - - try { - id = await this.client.put({ foo: 'bar' }) - expect(id).to.be.ok - - const job = await this.client.reserve() - expect(String(job.payload)).to.equal('{"foo":"bar"}') - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can insert jobs with priority', async function () { - let id - - try { - id = await this.client.put({ foo: 'bar' }, { priority: 12342342 }) - expect(id).to.be.ok - - const job = await this.client.reserve() - expect(String(job.payload)).to.equal('{"foo":"bar"}') - } finally { - if (id) await this.client.delete(id) - } - }) - }) - - describe('consumers', function () { - setupTestSuiteLifecycleWithClient() - - it('can reserve jobs', async function () { - let id - try { - id = await this.client.put('some random job') - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal('some random job') - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can reserve delayed jobs', async function () { - let id - - try { - id = await this.client.put('some random job', { - delay: 1 - }) - - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal('some random job') - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can reserve jobs by id', async function () { - let id - - try { - id = await this.client.put('some random job', { - delay: 1 - }) - - const job = await this.client.reserveJob(id) - expect(String(job.payload)).to.equal('some random job') - } finally { - if (id) await this.client.delete(id) - } - }) - - it('handles not found', async function () { - try { - await this.client.reserveJob(4) - } catch (err) { - expect(err.message).to.equal('NOT_FOUND') - } - }) - - it('can insert and process jobs on a different tube', async function () { - let id - try { - await this.client.use('some-other-tube') - id = await this.client.put('some random job on another tube') - - await this.client.watch('some-other-tube') - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal('some random job on another tube') - } finally { - if (id) await this.client.delete(id) - } - }) - - it('will ignore jobs from default', async function () { - let id, defaultId - try { - defaultId = await this.client.put('job on default') - await this.client.use('some-other-tube') - id = await this.client.put('some random job on another tube') - - await this.client.watch('some-other-tube') - await this.client.ignore('default') - - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal('some random job on another tube') - } finally { - if (id) await this.client.delete(id) - if (defaultId) await this.client.delete(defaultId) - } - }) - - it('handles multiple promises fired at once', async function () { - let id1, id2 - - try { - this.client.use('some-tube') - const firstJobPromise = this.client.put('some-job') - this.client.watch('some-random-tube') - this.client.use('some-another-tube') - const secondJobPromise = this.client.put('some-job') - - id1 = await firstJobPromise - id2 = await secondJobPromise - } finally { - if (id1) await this.client.delete(id1) - if (id2) await this.client.delete(id2) - } - }) - - it('can receive huge jobs', async function () { - let id - - try { - // job larger than a socket data frame - const hugeText = - crypto.randomBytes(15000).toString('hex') + - '\r\n' + - crypto.randomBytes(15000).toString('hex') - - id = await this.client.put(hugeText) - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal(hugeText) - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can peek buried jobs', async function () { - let id - try { - await this.client.use('some-tube') - - id = await this.client.put('some-job') - - await this.client.watch('some-tube') - await this.client.reserve() - await this.client.bury(id) - - const job = await this.client.peekBuried() - - expect(job.id).to.equal(id) - } finally { - if (id) await this.client.delete(id) - } - }) - }) - - describe('stats', function () { - setupTestSuiteLifecycleWithClient() - - it('brings back stats', async function () { - const stats = await this.client.stats() - YAML.parse(stats) - }) - }) - - describe('bugfixes', function () { - setupTestSuiteLifecycleWithClient() - - it('can receive jobs with new lines jobs', async function () { - let id - - try { - // job larger than a socket data frame - const payload = 'this job should not fail!\r\n' - - id = await this.client.put(payload) - const job = await this.client.reserve() - - expect(job.id).to.equal(id) - expect(String(job.payload)).to.equal(payload) - } finally { - if (id) await this.client.delete(id) - } - }) - - it('can continue execution after bad command', async function () { - let id - - try { - // Bad command - await this.client.delete('nonexistent job') - } catch (err) { - expect(err).to.be.an('error') - } - - try { - id = await this.client.put('my awesome job') - } finally { - if (id) await this.client.delete(id) - } - }) - }) -}) - -function setupTestSuiteLifecycleWithClient() { - beforeEach(async function () { - this.client = new Jackd() - await this.client.connect() - }) - - afterEach(async function () { - await this.client.close() - }) -} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..1d4f889 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist/types" + }, + "include": ["src/index.ts"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.spec.ts", + "build.ts", + "eslint.config.mjs" + ] +} diff --git a/tsconfig.json b/tsconfig.json index d5908fb..cac818e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,22 @@ { "compilerOptions": { - "module": "commonjs", - "noImplicitAny": true, - "removeComments": true, - "preserveConstEnums": true, - "sourceMap": true, - "target": "ES2017", - "outDir": "./dist", - "declaration": true, - "declarationMap": true + "lib": ["ES2022"], + "target": "ES2022", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "noEmit": true, + + "types": ["node"], + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.spec.ts"] + "include": ["src/**/*", "build.ts", "eslint.config.mjs"] }