From 20068c994858b6b59b42472690113435532dc442 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Wed, 18 Feb 2026 15:52:39 -0500 Subject: [PATCH] convert to ts, maybe --- .gitignore | 1 + CLAUDE.md | 63 + Cakefile | 59 - lib/client.js | 103 -- lib/debug.js | 224 ---- lib/dispatch.js | 410 ------- lib/errors.js | 16 - lib/iced.js | 5 - lib/list.js | 57 - lib/listener.js | 316 ----- lib/lock.js | 5 - lib/log.js | 130 --- lib/main.js | 61 - lib/pack.js | 112 -- lib/packetizer.js | 136 --- lib/ring.js | 72 -- lib/server.js | 149 --- lib/timer.js | 50 - lib/transport.js | 802 ------------- lib/version.js | 5 - package-lock.json | 1685 +++++++++++++++++++++++++-- package.json | 21 +- src/client.iced | 31 - src/client.ts | 23 + src/debug.iced | 190 --- src/debug.ts | 231 ++++ src/dispatch.iced | 267 ----- src/dispatch.ts | 331 ++++++ src/errors.iced | 11 - src/errors.ts | 26 + src/iced.iced | 2 - src/list.iced | 45 - src/list.ts | 40 + src/listener.iced | 182 --- src/listener.ts | 190 +++ src/lock.iced | 2 - src/lock.ts | 23 + src/log.iced | 69 -- src/log.ts | 93 ++ src/main.iced | 39 - src/main.ts | 39 + src/pack.iced | 69 -- src/pack.ts | 103 ++ src/packetizer.iced | 158 --- src/packetizer.ts | 144 +++ src/ring.iced | 70 -- src/ring.ts | 69 ++ src/server.iced | 131 --- src/server.ts | 142 +++ src/timer.iced | 28 - src/timer.ts | 43 + src/transport.iced | 492 -------- src/transport.ts | 509 ++++++++ src/version.iced | 5 - src/version.ts | 1 + test/all.iced | 236 ---- test/helpers.ts | 43 + test/support/jenky_server.js | 3 - test/support/jenky_server_main.iced | 24 - test/test1.iced | 42 - test/test1.test.ts | 60 + test/test10.iced | 58 - test/test10.test.ts | 65 ++ test/test11.iced | 127 -- test/test11.test.ts | 132 +++ test/test12.iced | 60 - test/test12.test.ts | 58 + test/test2.iced | 39 - test/test2.test.ts | 57 + test/test3.iced | 46 - test/test3.test.ts | 68 ++ test/test4.iced | 98 -- test/test4.test.ts | 100 ++ test/test5.iced | 42 - test/test6.iced | 64 - test/test6.test.ts | 58 + test/test7.iced | 98 -- test/test7.test.ts | 89 ++ test/test8.iced | 73 -- test/test8.test.ts | 77 ++ test/test9.iced | 59 - test/test9.test.ts | 73 ++ test/test_pack.iced | 14 - test/test_pack.test.ts | 20 + tsconfig.json | 17 + vitest.config.ts | 17 + 86 files changed, 4609 insertions(+), 5688 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 Cakefile delete mode 100644 lib/client.js delete mode 100644 lib/debug.js delete mode 100644 lib/dispatch.js delete mode 100644 lib/errors.js delete mode 100644 lib/iced.js delete mode 100644 lib/list.js delete mode 100644 lib/listener.js delete mode 100644 lib/lock.js delete mode 100644 lib/log.js delete mode 100644 lib/main.js delete mode 100644 lib/pack.js delete mode 100644 lib/packetizer.js delete mode 100644 lib/ring.js delete mode 100644 lib/server.js delete mode 100644 lib/timer.js delete mode 100644 lib/transport.js delete mode 100644 lib/version.js delete mode 100644 src/client.iced create mode 100644 src/client.ts delete mode 100644 src/debug.iced create mode 100644 src/debug.ts delete mode 100644 src/dispatch.iced create mode 100644 src/dispatch.ts delete mode 100644 src/errors.iced create mode 100644 src/errors.ts delete mode 100644 src/iced.iced delete mode 100644 src/list.iced create mode 100644 src/list.ts delete mode 100644 src/listener.iced create mode 100644 src/listener.ts delete mode 100644 src/lock.iced create mode 100644 src/lock.ts delete mode 100644 src/log.iced create mode 100644 src/log.ts delete mode 100644 src/main.iced create mode 100644 src/main.ts delete mode 100644 src/pack.iced create mode 100644 src/pack.ts delete mode 100644 src/packetizer.iced create mode 100644 src/packetizer.ts delete mode 100644 src/ring.iced create mode 100644 src/ring.ts delete mode 100644 src/server.iced create mode 100644 src/server.ts delete mode 100644 src/timer.iced create mode 100644 src/timer.ts delete mode 100644 src/transport.iced create mode 100644 src/transport.ts delete mode 100644 src/version.iced create mode 100644 src/version.ts delete mode 100644 test/all.iced create mode 100644 test/helpers.ts delete mode 100644 test/support/jenky_server.js delete mode 100644 test/support/jenky_server_main.iced delete mode 100644 test/test1.iced create mode 100644 test/test1.test.ts delete mode 100644 test/test10.iced create mode 100644 test/test10.test.ts delete mode 100644 test/test11.iced create mode 100644 test/test11.test.ts delete mode 100644 test/test12.iced create mode 100644 test/test12.test.ts delete mode 100644 test/test2.iced create mode 100644 test/test2.test.ts delete mode 100644 test/test3.iced create mode 100644 test/test3.test.ts delete mode 100644 test/test4.iced create mode 100644 test/test4.test.ts delete mode 100644 test/test5.iced delete mode 100644 test/test6.iced create mode 100644 test/test6.test.ts delete mode 100644 test/test7.iced create mode 100644 test/test7.test.ts delete mode 100644 test/test8.iced create mode 100644 test/test8.test.ts delete mode 100644 test/test9.iced create mode 100644 test/test9.test.ts delete mode 100644 test/test_pack.iced create mode 100644 test/test_pack.test.ts create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 83a7897..f7423e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *~ node_modules/ npm-debug.log +lib/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e9ac149 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +## Build & Test Commands + +- **Build:** `npm run build` — compiles TypeScript (`src/*.ts`) to JavaScript (`lib/*.js`) with declarations +- **Test:** `npm test` — runs the full test suite via vitest +- **Type check:** `npx tsc --noEmit` — type check without emitting + +Source language is TypeScript (`.ts` files) with async/await for async control flow. + +## Architecture + +Framed msgpack-RPC library for Node.js used by Keybase. Messages are msgpack-encoded and length-prefixed (framed). + +### Class Hierarchy + +``` +Packetizer — frame-level read/write (length-prefixed msgpack) + └─ Dispatch — RPC message routing (invoke/response/notify), handler registry + └─ Transport — TCP/TLS/Unix socket connection management + └─ RobustTransport — auto-reconnect with call queuing +``` + +### Key Modules + +| File | Role | +|------|------| +| `transport.ts` | `Transport` (single connection) and `RobustTransport` (auto-reconnect + queue) | +| `dispatch.ts` | `Dispatch` — RPC invoke/response/notify routing; `Response` helper | +| `packetizer.ts` | Framing: length-prefix encoding/decoding over a byte ring buffer | +| `client.ts` | `Client` — thin wrapper; binds a program name to a transport for `invoke`/`notify` | +| `server.ts` | `Server`, `SimpleServer`, `ContextualServer`, `Handler` — listener-based servers | +| `listener.ts` | `Listener` — TCP/TLS server socket accepting connections, spawns Transports | +| `pack.ts` | Msgpack encode/decode abstraction (supports `@msgpack/msgpack`, `purepack`, `msgpack`) | +| `ring.ts` | `Ring` — circular byte buffer for the packetizer | +| `errors.ts` | Error types: `EofError`, `UnknownMethodError` | +| `log.ts` | Logging infrastructure | +| `debug.ts` | Debug message tracing | +| `lock.ts` | Promise-based `Lock` with `acquire()`/`release()` | + +### RPC Protocol (msgpack arrays) + +- **Invoke:** `[0, seqid, method, param]` +- **Response:** `[1, seqid, error, result]` +- **Notify:** `[2, method, param]` (fire-and-forget) +- **Invoke Compressed:** `[4, seqid, ctype, method, param]` (gzip via pako) + +### Entry Point + +`src/main.ts` → `lib/main.js` — re-exports all public classes and the version. + +### Async API + +All async operations use Promises/async-await: +- `client.invoke(method, args)` → `Promise<{error, result}>` +- `client.invoke_compressed(method, ctype, args)` → `Promise<{error, result}>` +- `transport.connect()` → `Promise` +- `listener.listen()` → `Promise` +- `listener.close()` → `Promise` + +### Tests + +Tests use vitest with sequential execution (tests share ports). Test files are in `test/*.test.ts`. diff --git a/Cakefile b/Cakefile deleted file mode 100644 index 7213b09..0000000 --- a/Cakefile +++ /dev/null @@ -1,59 +0,0 @@ -{spawn, exec} = require 'child_process' -fs = require 'fs' -path = require 'path' - -LIB = "lib/" - -task 'build', 'build the whole jam', (cb) -> - console.log "Building" - files = fs.readdirSync 'src' - files = ('src/' + file for file in files when file.match(/\.iced$/)) - await clearLibJs defer() - await runIced [ '-I', 'node', '-c', '-o', LIB ].concat(files), defer() - await writeVersion defer() - console.log "Done building." - cb() if typeof cb is 'function' - -runIced = (args, cb) -> - proc = spawn 'node_modules/.bin/iced', args - proc.stderr.on 'data', (buffer) -> console.log buffer.toString() - proc.stdout.on 'data', (buffer) -> console.log buffer.toString().trim() - await proc.on 'exit', defer status - process.exit(1) if status != 0 - cb() - -clearLibJs = (cb) -> - files = fs.readdirSync 'lib' - files = ("lib/#{file}" for file in files when file.match(/\.js$/)) - fs.unlinkSync f for f in files - cb() - -task 'test', "run the test suite", (cb) -> - await runIced [ "test/all.iced"], defer() - cb() if typeof cb is 'function' - -task 'vtest', "run the test suite, w/ verbosity", (cb) -> - await runIced [ "test/all.iced", '-d'], defer() - cb() if typeof cb is 'function' - -writeVersion = (cb) -> - infile = "package.json" - stem = "version.js" - outfile = path.join LIB, stem - await fs.readFile infile, defer err, data - ok = false - if err - console.log "Error reading #{infile}: #{err}" - else - try - obj = JSON.parse data - v = obj.version - code = "exports.version = \"#{v}\";" - await fs.writeFile outfile, code, defer err - if err - console.log "Error writing #{outfile}: #{err}" - else - ok = true - catch e - console.log "JSON parse error: #{e}" - cb ok diff --git a/lib/client.js b/lib/client.js deleted file mode 100644 index dc6168d..0000000 --- a/lib/client.js +++ /dev/null @@ -1,103 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Client, iced, __iced_k, __iced_k_noop; - - iced = require('iced-runtime'); - __iced_k = __iced_k_noop = function() {}; - - iced = require('./iced').runtime; - - exports.Client = Client = (function() { - function Client(transport, program) { - this.transport = transport; - this.program = program != null ? program : null; - } - - Client.prototype.invoke = function(method, args, cb) { - var arg, err, res, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - arg = { - program: this.program, - method: method, - args: args, - notify: false - }; - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/client.iced", - funcname: "Client.invoke" - }); - _this.transport.invoke(arg, __iced_deferrals.defer({ - assign_fn: (function() { - return function() { - err = arguments[0]; - return res = arguments[1]; - }; - })(), - lineno: 15 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - return cb(err, res); - }; - })(this)); - }; - - Client.prototype.invoke_compressed = function(method, ctype, args, cb) { - var arg, err, res, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - arg = { - program: this.program, - method: method, - ctype: ctype, - args: args, - notify: false - }; - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/client.iced", - funcname: "Client.invoke_compressed" - }); - _this.transport.invoke(arg, __iced_deferrals.defer({ - assign_fn: (function() { - return function() { - err = arguments[0]; - return res = arguments[1]; - }; - })(), - lineno: 20 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - return cb(err, res); - }; - })(this)); - }; - - Client.prototype.notify = function(method, args) { - var program; - method = this.make_method(method); - program = this._program; - return this.transport.invoke({ - program: this.program, - method: method, - args: args, - notify: true - }); - }; - - return Client; - - })(); - -}).call(this); diff --git a/lib/debug.js b/lib/debug.js deleted file mode 100644 index 6d3a52d..0000000 --- a/lib/debug.js +++ /dev/null @@ -1,224 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Debugger, F, F2S, Message, SF, dir, flip_dir, log, sflags_to_flags, type; - - log = require("./log"); - - F = { - NONE: 0, - METHOD: 0x1, - REMOTE: 0x2, - SEQID: 0x4, - TIMESTAMP: 0x8, - ERR: 0x10, - ARG: 0x20, - RES: 0x40, - TYPE: 0x80, - COMPRESSION_TYPE: 0x90, - DIR: 0x100, - PORT: 0x200, - VERBOSE: 0x400, - ALL: 0xfffffff - }; - - F.LEVEL_0 = F.NONE; - - F.LEVEL_1 = F.METHOD | F.TYPE | F.DIR | F.TYPE | F.COMPRESSION_TYPE; - - F.LEVEL_2 = F.LEVEL_1 | F.SEQID | F.TIMESTAMP | F.REMOTE | F.PORT; - - F.LEVEL_3 = F.LEVEL_2 | F.ERR; - - F.LEVEL_4 = F.LEVEL_3 | F.RES | F.ARG; - - SF = { - m: F.METHOD, - a: F.REMOTE, - s: F.SEQID, - t: F.TIMESTAMP, - p: F.ARG, - r: F.RES, - e: F.ERR, - c: F.TYPE, - C: F.COMPRESSION_TYPE, - d: F.DIRECTION, - v: F.VERBOSE, - P: F.PORT, - A: F.ALL, - 0: F.LEVEL_0, - 1: F.LEVEL_1, - 2: F.LEVEL_2, - 3: F.LEVEL_3, - 4: F.LEVEL_4 - }; - - dir = { - INCOMING: 1, - OUTGOING: 2 - }; - - flip_dir = function(d) { - if (d === dir.INCOMING) { - return dir.OUTGOING; - } else { - return dir.INCOMING; - } - }; - - type = { - SERVER: 1, - CLIENT_NOTIFY: 2, - CLIENT_INVOKE: 3, - CLIENT_INVOKE_COMPRESSED: 4 - }; - - F2S = {}; - - F2S[F.DIR] = {}; - - F2S[F.DIR][dir.INCOMING] = "in"; - - F2S[F.DIR][dir.OUTGOING] = "out"; - - F2S[F.TYPE] = {}; - - F2S[F.TYPE][type.SERVER] = "server"; - - F2S[F.TYPE][type.CLIENT_NOTIFY] = "cli.notify"; - - F2S[F.TYPE][type.CLIENT_INVOKE] = "cli.invoke"; - - F2S[F.TYPE][type.CLIENT_INVOKE_COMPRESSED] = "cli.invokeCompresed"; - - exports.constants = { - type: type, - dir: dir, - flags: F, - sflags: SF, - field_to_string: F2S - }; - - exports.sflags_to_flags = sflags_to_flags = function(s) { - var c, i, res, _i, _ref; - s = "" + s; - res = 0; - for (i = _i = 0, _ref = s.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - c = s.charAt(i); - res |= SF[c]; - } - return res; - }; - - exports.Debugger = Debugger = (function() { - function Debugger(flags, log_obj, log_obj_mthd) { - this.log_obj = log_obj; - this.log_obj_mthd = log_obj_mthd; - this.flags = typeof flags === 'string' ? sflags_to_flags(flags) : flags; - if (!this.log_obj) { - this.log_obj = log.new_default_logger(); - this.log_obj_mthd = this.log_obj.info; - } - } - - Debugger.prototype.new_message = function(dict) { - return new Message(dict, this); - }; - - Debugger.prototype._output = function(json_msg) { - return this.log_obj_mthd.call(this.log_obj, JSON.stringify(json_msg)); - }; - - Debugger.prototype._skip_flag = function(f) { - return f & (F.REMOTE | F.PORT); - }; - - Debugger.prototype.call = function(msg) { - var V, do_copy, f2s, flag, key, new_json_msg, uck, val, _ref; - new_json_msg = {}; - V = this.flags & F.VERBOSE; - if (this.flags & F.TIMESTAMP) { - new_json_msg.timestamp = (new Date()).getTime() / 1000.0; - } - _ref = msg.to_json_object(); - for (key in _ref) { - val = _ref[key]; - uck = key.toUpperCase(); - flag = F[uck]; - do_copy = this._skip_flag(flag) ? false : (this.flags & flag) === 0 ? false : key === "res" ? msg.show_res(V) : key === "arg" ? msg.show_arg(V) : true; - if (do_copy) { - if ((f2s = F2S[flag]) != null) { - val = f2s[val]; - } - new_json_msg[key] = val; - } - } - return this._output(new_json_msg); - }; - - return Debugger; - - })(); - - exports.Message = Message = (function() { - "A debug message --- a wrapper around a dictionary object, with\na few additional methods."; - function Message(_msg, _debugger) { - this._msg = _msg; - this._debugger = _debugger != null ? _debugger : null; - } - - Message.prototype.response = function(error, result) { - this._msg.err = error; - this._msg.res = result; - this._msg.dir = flip_dir(this._msg.dir); - return this; - }; - - Message.prototype.to_json_object = function() { - return this._msg; - }; - - Message.prototype.call = function() { - return this._debugger.call(this); - }; - - Message.prototype.set = function(k, v) { - return this.msg[k] = v; - }; - - Message.prototype.is_server = function() { - return this._msg.type === type.SERVER; - }; - - Message.prototype.is_client = function() { - return !this.is_server(); - }; - - Message.prototype.is_incoming = function() { - return this._msg.dir === dir.INCOMING; - }; - - Message.prototype.is_outgoing = function() { - return !this.is_incoming(); - }; - - Message.prototype.show_arg = function(V) { - return V || (this.is_server() && this.is_incoming()) || (this.is_client() && this.is_outgoing()); - }; - - Message.prototype.show_res = function(V) { - return V || (this.is_server() && this.is_outgoing()) || (this.is_client() && this.is_incoming()); - }; - - return Message; - - })(); - - exports.make_debugger = function(d, lo) { - if (d === 0) { - return null; - } else { - return new Debugger(d, lo, lo.debug); - } - }; - -}).call(this); diff --git a/lib/dispatch.js b/lib/dispatch.js deleted file mode 100644 index 3a4276f..0000000 --- a/lib/dispatch.js +++ /dev/null @@ -1,410 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_NONE, Dispatch, E, Packetizer, Response, compress, dbg, iced, pack, pako, uncompress, unpack, __iced_k, __iced_k_noop, _ref, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - - iced = require('iced-runtime'); - __iced_k = __iced_k_noop = function() {}; - - Packetizer = require('./packetizer').Packetizer; - - _ref = require('./pack'), unpack = _ref.unpack, pack = _ref.pack; - - dbg = require('./debug'); - - E = require('./errors'); - - pako = require('pako'); - - iced = require('./iced').runtime; - - exports.COMPRESSION_TYPE_NONE = COMPRESSION_TYPE_NONE = 0; - - exports.COMPRESSION_TYPE_GZIP = COMPRESSION_TYPE_GZIP = 1; - - compress = function(ctype, data) { - if (ctype == null) { - return data; - } - switch (ctype) { - case COMPRESSION_TYPE_NONE: - return data; - case COMPRESSION_TYPE_GZIP: - data = pack(data); - data = pako.deflate(data, { - windowBits: 31 - }); - return data; - default: - throw new Error("Compress: unknown compression type " + ctype); - } - }; - - uncompress = function(ctype, data) { - var err, _ref1; - if (ctype == null) { - return data; - } - switch (ctype) { - case COMPRESSION_TYPE_NONE: - return data; - case COMPRESSION_TYPE_GZIP: - data = pako.inflate(data); - _ref1 = unpack(data), err = _ref1[0], data = _ref1[1]; - if (err == null) { - return data; - } - throw err; - break; - default: - throw new Error("Uncompress: unknown compression type " + ctype); - } - }; - - exports.Response = Response = (function() { - function Response(dispatch, seqid, ctype) { - this.dispatch = dispatch; - this.seqid = seqid; - this.ctype = ctype; - this.debug_msg = null; - } - - Response.prototype.result = function(res) { - res = compress(this.ctype, res); - if (this.debug_msg) { - this.debug_msg.response(null, res).call(); - } - return this.dispatch.respond(this.seqid, null, res); - }; - - Response.prototype.error = function(err) { - if (this.debug_msg) { - this.debug_msg.response(err, null).call(); - } - return this.dispatch.respond(this.seqid, err, null); - }; - - return Response; - - })(); - - exports.Dispatch = Dispatch = (function(_super) { - __extends(Dispatch, _super); - - Dispatch.prototype.INVOKE = 0; - - Dispatch.prototype.RESPONSE = 1; - - Dispatch.prototype.NOTIFY = 2; - - Dispatch.prototype.INVOKE_COMPRESSED = 4; - - function Dispatch() { - this._invocations = {}; - this._handlers = {}; - this._seqid = 1; - this._dbgr = null; - this._generic_handler = null; - Dispatch.__super__.constructor.apply(this, arguments); - } - - Dispatch.prototype.set_debugger = function(d) { - return this._dbgr = d; - }; - - Dispatch.prototype.set_generic_handler = function(h) { - return this._generic_handler = h; - }; - - Dispatch.prototype._dispatch = function(msg) { - var ctype, error, method, param, response, result, seqid, type; - if (!(msg instanceof Array) || msg.length < 2) { - return this._warn("Bad input packet in dispatch"); - } else { - switch ((type = msg.shift())) { - case this.INVOKE: - seqid = msg[0], method = msg[1], param = msg[2]; - response = new Response(this, seqid); - return this._serve({ - method: method, - param: param, - response: response - }); - case this.NOTIFY: - method = msg[0], param = msg[1]; - return this._serve({ - method: method, - param: param - }); - case this.RESPONSE: - seqid = msg[0], error = msg[1], result = msg[2]; - return this._dispatch_handle_response({ - seqid: seqid, - error: error, - result: result - }); - case this.INVOKE_COMPRESSED: - seqid = msg[0], ctype = msg[1], method = msg[2], param = msg[3]; - param = uncompress(ctype, param); - response = new Response(this, seqid, ctype); - return this._serve({ - method: method, - param: param, - response: response - }); - default: - return this._warn("Unknown message type: " + type); - } - } - }; - - Dispatch.prototype._dispatch_handle_response = function(_arg) { - var error, result, seqid; - seqid = _arg.seqid, error = _arg.error, result = _arg.result; - return this._call_cb({ - seqid: seqid, - error: error, - result: result - }); - }; - - Dispatch.prototype._call_cb = function(_arg) { - var cb, error, result, seqid; - seqid = _arg.seqid, error = _arg.error, result = _arg.result; - cb = this._invocations[seqid]; - if (cb) { - delete this._invocations[seqid]; - error = this.unwrap_incoming_error(error); - return cb(error, result); - } - }; - - Dispatch.prototype.cancel = function(seqid) { - return this._call_cb({ - seqid: seqid, - error: "cancelled", - result: null - }); - }; - - Dispatch.prototype._next_seqid = function() { - var ret; - ret = this._seqid; - this._seqid++; - return ret; - }; - - Dispatch.prototype.make_method = function(prog, meth) { - if (prog) { - return [prog, meth].join("."); - } else { - return meth; - } - }; - - Dispatch.prototype.respond = function(seqid, error, result) { - var msg; - msg = [this.RESPONSE, seqid, error, result]; - return this.send(msg); - }; - - Dispatch.prototype.invoke = function(_arg, cb, out) { - var args, ctype, debug_msg, dtype, error, method, msg, notify, program, result, seqid, type, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - program = _arg.program, ctype = _arg.ctype, method = _arg.method, args = _arg.args, notify = _arg.notify; - method = this.make_method(program, method); - seqid = this._next_seqid(); - if (notify) { - type = this.NOTIFY; - dtype = dbg.constants.type.CLIENT_NOTIFY; - } else if (ctype != null) { - type = this.INVOKE_COMPRESSED; - dtype = dbg.constants.type.CLIENT_INVOKE_COMPRESSED; - } else { - type = this.INVOKE; - dtype = dbg.constants.type.CLIENT_INVOKE; - } - if (ctype != null) { - args = compress(ctype, args); - msg = [type, seqid, ctype, method, args]; - } else { - msg = [type, seqid, method, args]; - } - if (this._dbgr) { - debug_msg = this._dbgr.new_message({ - method: method, - seqid: seqid, - ctype: ctype, - arg: args, - dir: dbg.constants.dir.OUTGOING, - remote: this.remote_address(), - port: this.remote_port(), - type: dtype - }); - debug_msg.call(); - } - this.send(msg); - (function(_this) { - return (function(__iced_k) { - if ((cb != null) || !notify) { - if (out != null) { - out.cancel = function() { - return _this.cancel(seqid); - }; - } - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/dispatch.iced", - funcname: "Dispatch.invoke" - }); - _this._invocations[seqid] = __iced_deferrals.defer({ - assign_fn: (function() { - return function() { - error = arguments[0]; - return result = arguments[1]; - }; - })(), - lineno: 190 - }); - __iced_deferrals._fulfill(); - })(function() { - if ((ctype != null) && !error) { - result = uncompress(ctype, result); - } - return __iced_k(debug_msg ? debug_msg.response(error, result).call() : void 0); - }); - } else { - return __iced_k(); - } - }); - })(this)((function(_this) { - return function() { - if (cb) { - return cb(error, result); - } - }; - })(this)); - }; - - Dispatch.prototype._dispatch_reset = function() { - var cb, inv, key, _results; - inv = this._invocations; - this._invocations = {}; - _results = []; - for (key in inv) { - cb = inv[key]; - _results.push(cb(new E.EofError(), {})); - } - return _results; - }; - - Dispatch.prototype._serve = function(_arg) { - var debug_msg, err, hw, method, pair, param, response; - method = _arg.method, param = _arg.param, response = _arg.response; - pair = this.get_handler_pair(method); - if (this._dbgr) { - debug_msg = this._dbgr.new_message({ - method: method, - seqid: response.seqid, - arg: param, - dir: dbg.constants.dir.INCOMING, - remote: this.remote_address(), - port: this.remote_port(), - type: dbg.constants.type.SERVER, - error: pair ? null : "unknown method" - }); - if (response) { - response.debug_msg = debug_msg; - } - debug_msg.call(); - } - if (this._generic_handler != null) { - return this._generic_handler({ - method: method, - param: param, - response: response, - dispatch: this - }); - } else if ((pair != null) && ((hw = this.get_hook_wrapper()) != null)) { - return hw({ - method: pair[1], - thisobj: pair[0], - param: param, - response: response, - dispatch: this - }); - } else if (pair) { - return pair[1].call(pair[0], param, response, this); - } else if (response != null) { - err = new E.UnknownMethodError(method); - err.method = method; - return response.error(this.wrap_outgoing_error(err)); - } - }; - - Dispatch.prototype.get_handler_this = function(m) { - return this; - }; - - Dispatch.prototype.get_hook_wrapper = function() { - return null; - }; - - Dispatch.prototype.wrap_outgoing_error = function(s) { - return s.toString(); - }; - - Dispatch.prototype.unwrap_incoming_error = function(s) { - if (typeof s === 'string') { - return new Error(s); - } else { - return s; - } - }; - - Dispatch.prototype.get_handler_pair = function(m) { - var h; - h = this._handlers[m]; - if (h) { - return [this.get_handler_this(m), h]; - } else { - return null; - } - }; - - Dispatch.prototype.add_handler = function(method, hook, program) { - if (program == null) { - program = null; - } - method = this.make_method(program, method); - return this._handlers[method] = hook; - }; - - Dispatch.prototype.add_program = function(program, hooks) { - var hook, method, _results; - _results = []; - for (method in hooks) { - hook = hooks[method]; - _results.push(this.add_handler(method, hook, program)); - } - return _results; - }; - - Dispatch.prototype.add_programs = function(programs) { - var hooks, program, _results; - _results = []; - for (program in programs) { - hooks = programs[program]; - _results.push(this.add_program(program, hooks)); - } - return _results; - }; - - return Dispatch; - - })(Packetizer); - -}).call(this); diff --git a/lib/errors.js b/lib/errors.js deleted file mode 100644 index 1eb0630..0000000 --- a/lib/errors.js +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var E, ie; - - ie = require('iced-error'); - - module.exports = E = ie.make_errors({ - UNKNOWN_METHOD: "No method available", - EOF: "EOF from server" - }); - - E.UnknownMethodError.prototype.toString = function() { - return "unknown method: " + this.method; - }; - -}).call(this); diff --git a/lib/iced.js b/lib/iced.js deleted file mode 100644 index 4ae2751..0000000 --- a/lib/iced.js +++ /dev/null @@ -1,5 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - exports.runtime = require('iced-runtime'); - -}).call(this); diff --git a/lib/list.js b/lib/list.js deleted file mode 100644 index 152e01d..0000000 --- a/lib/list.js +++ /dev/null @@ -1,57 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var List; - - exports.List = List = (function() { - function List() { - this._head = null; - this._tail = null; - } - - List.prototype.push = function(o) { - o.__list_prev = this._tail; - o.__list_next = null; - if (this._tail) { - this._tail.__list_next = o; - } - this._tail = o; - if (!this._head) { - return this._head = o; - } - }; - - List.prototype.walk = function(fn) { - var next, p, _results; - p = this._head; - _results = []; - while (p) { - next = p.__list_next; - fn(p); - _results.push(p = next); - } - return _results; - }; - - List.prototype.remove = function(w) { - var next, prev; - next = w.__list_next; - prev = w.__list_prev; - if (prev) { - prev.__list_next = next; - } else { - this._head = next; - } - if (next) { - next.__list_prev = prev; - } else { - this._tail = prev; - } - w.__list_next = null; - return w.__list_prev = null; - }; - - return List; - - })(); - -}).call(this); diff --git a/lib/listener.js b/lib/listener.js deleted file mode 100644 index 53e00c6..0000000 --- a/lib/listener.js +++ /dev/null @@ -1,316 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var List, Listener, Transport, dbg, iced, log, net, tls, __iced_k, __iced_k_noop; - - iced = require('iced-runtime'); - __iced_k = __iced_k_noop = function() {}; - - net = require('net'); - - tls = require('tls'); - - Transport = require('./transport').Transport; - - List = require('./list').List; - - log = require('./log'); - - dbg = require('./debug'); - - iced = require('./iced').runtime; - - exports.Listener = Listener = (function() { - function Listener(_arg) { - var log_obj; - this.port = _arg.port, this.host = _arg.host, this.path = _arg.path, this.TransportClass = _arg.TransportClass, log_obj = _arg.log_obj, this.tls_opts = _arg.tls_opts; - if (!this.TransportClass) { - this.TransportClass = Transport; - } - this.set_logger(log_obj); - this._children = new List; - this._dbgr = null; - } - - Listener.prototype._default_logger = function() { - var h, l; - l = log.new_default_logger(); - l.set_prefix("RPC-Server"); - h = this.host || "0.0.0.0"; - if (this.port != null) { - l.set_remote("" + h + ":" + this.port); - } else if (this.path != null) { - l.set_remote(this.path); - } - return l; - }; - - Listener.prototype.set_debugger = function(d) { - return this._dbgr = d; - }; - - Listener.prototype.set_debug_flags = function(f, apply_to_children) { - this.set_debugger(dbg.make_debugger(f, this.log_obj)); - if (apply_to_children) { - return this.walk_children((function(_this) { - return function(c) { - return c.set_debug_flags(f); - }; - })(this)); - } - }; - - Listener.prototype.set_logger = function(o) { - if (o == null) { - o = this._default_logger(); - } - return this.log_obj = o; - }; - - Listener.prototype.make_new_transport = function(c) { - var x; - if (!this.do_tcp_delay) { - c.setNoDelay(true); - } - x = new this.TransportClass({ - net_stream: c, - host: c.remoteAddress, - port: c.remotePort, - parent: this, - log_obj: this.make_new_log_object(c), - dbgr: this._dbgr - }); - this._children.push(x); - return x; - }; - - Listener.prototype.make_new_log_object = function(c) { - var a, r; - a = c.address(); - r = [c.address, c.port].join(":"); - return this.log_obj.make_child({ - prefix: "RPC", - remote: r - }); - }; - - Listener.prototype.walk_children = function(fn) { - return this._children.walk(fn); - }; - - Listener.prototype.close_child = function(c) { - return this._children.remove(c); - }; - - Listener.prototype.set_port = function(p) { - return this.port = p; - }; - - Listener.prototype._got_new_connection = function(c) { - var x; - x = this.make_new_transport(c); - return this.got_new_connection(x); - }; - - Listener.prototype.got_new_connection = function(x) { - throw new Error("@got_new_connection() is pure virtual; please implement!"); - }; - - Listener.prototype._make_server = function() { - if (this.tls_opts != null) { - return this._net_server = tls.createServer(this.tls_opts, (function(_this) { - return function(c) { - return _this._got_new_connection(c); - }; - })(this)); - } else { - return this._net_server = net.createServer((function(_this) { - return function(c) { - return _this._got_new_connection(c); - }; - })(this)); - } - }; - - Listener.prototype.close = function(cb) { - var ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - (function(_this) { - return (function(__iced_k) { - if (_this._net_server) { - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/listener.iced", - funcname: "Listener.close" - }); - _this._net_server.close(__iced_deferrals.defer({ - lineno: 120 - })); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - return __iced_k(); - } - }); - })(this)((function(_this) { - return function() { - _this._net_server = null; - return cb(); - }; - })(this)); - }; - - Listener.prototype.handle_close = function() { - return this.log_obj.info("listener closing down"); - }; - - Listener.prototype.handle_error = function(err) { - this._net_server = null; - return this.log_obj.error("error in listener: " + err); - }; - - Listener.prototype._set_hooks = function() { - this._net_server.on('error', (function(_this) { - return function(err) { - return _this.handle_error(err); - }; - })(this)); - return this._net_server.on('close', (function(_this) { - return function(err) { - return _this.handle_close(); - }; - })(this)); - }; - - Listener.prototype.listen = function(cb) { - var ERR, OK, err, rv, which, x, ___iced_passed_deferral, __iced_deferrals, __iced_k, _ref; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - this._make_server(); - _ref = [0, 1], OK = _ref[0], ERR = _ref[1]; - rv = new iced.Rendezvous; - x = this._net_server; - if (this.port != null) { - x.listen(this.port, this.host); - } else { - x.listen(this.path); - } - x.on('error', rv.id(ERR).defer({ - assign_fn: (function(_this) { - return function() { - return function() { - return err = arguments[0]; - }; - }; - })(this)(), - lineno: 153, - context: __iced_deferrals - })); - x.on('listening', rv.id(OK).defer({ - lineno: 154, - context: __iced_deferrals - })); - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/listener.iced", - funcname: "Listener.listen" - }); - rv.wait(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return which = arguments[0]; - }; - })(), - lineno: 156 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - if (which === OK) { - err = null; - _this._set_hooks(); - } else { - _this.log_obj.error(err); - _this._net_server = null; - } - return cb(err); - }; - })(this)); - }; - - Listener.prototype.listen_retry = function(delay, cb) { - var err, go, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - go = true; - err = null; - (function(_this) { - return (function(__iced_k) { - var _while; - _while = function(__iced_k) { - var _break, _continue, _next; - _break = __iced_k; - _continue = function() { - return iced.trampoline(function() { - return _while(__iced_k); - }); - }; - _next = _continue; - if (!go) { - return _break(); - } else { - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/listener.iced", - funcname: "Listener.listen_retry" - }); - _this.listen(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return err = arguments[0]; - }; - })(), - lineno: 174 - })); - __iced_deferrals._fulfill(); - })(function() { - (function(__iced_k) { - if ((err != null ? err.code : void 0) === 'EADDRINUSE') { - _this.log_obj.warn(err); - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/listener.iced", - funcname: "Listener.listen_retry" - }); - setTimeout(__iced_deferrals.defer({ - lineno: 177 - }), delay * 1000); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - return __iced_k(go = false); - } - })(_next); - }); - } - }; - _while(__iced_k); - }); - })(this)((function(_this) { - return function() { - return cb(err); - }; - })(this)); - }; - - return Listener; - - })(); - -}).call(this); diff --git a/lib/lock.js b/lib/lock.js deleted file mode 100644 index f6d782e..0000000 --- a/lib/lock.js +++ /dev/null @@ -1,5 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - module.exports = require('iced-lock'); - -}).call(this); diff --git a/lib/log.js b/lib/log.js deleted file mode 100644 index f733971..0000000 --- a/lib/log.js +++ /dev/null @@ -1,130 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var L, Logger, default_level, default_logger_class, stringify; - - exports.levels = L = { - NONE: 0, - DEBUG: 1, - INFO: 2, - WARN: 3, - ERROR: 4, - FATAL: 5, - TOP: 6 - }; - - default_level = L.INFO; - - stringify = function(o) { - if (o == null) { - return ""; - } else if (o instanceof Uint8Array) { - return new TextDecoder().decode(o); - } else if (Error.isError(o)) { - return o.toString(); - } else { - return "" + o; - } - }; - - exports.Logger = Logger = (function() { - function Logger(_arg) { - this.prefix = _arg.prefix, this.remote = _arg.remote, this.level = _arg.level; - if (!this.prefix) { - this.prefix = "RPC"; - } - if (!this.remote) { - this.remote = "-"; - } - this.output_hook = function(m) { - return console.log(m); - }; - this.level = this.level != null ? this.level : default_level; - } - - Logger.prototype.set_level = function(l) { - return this.level = l; - }; - - Logger.prototype.set_remote = function(r) { - return this.remote = r; - }; - - Logger.prototype.set_prefix = function(p) { - return this.prefix = p; - }; - - Logger.prototype.debug = function(m) { - if (this.level <= L.DEBUG) { - return this._log(m, "D", null, L.DEBUG); - } - }; - - Logger.prototype.info = function(m) { - if (this.level <= L.INFO) { - return this._log(m, "I", null, L.INFO); - } - }; - - Logger.prototype.warn = function(m) { - if (this.level <= L.WARN) { - return this._log(m, "W", null, L.WARN); - } - }; - - Logger.prototype.error = function(m) { - if (this.level <= L.ERROR) { - return this._log(m, "E", null, L.ERROR); - } - }; - - Logger.prototype.fatal = function(m) { - if (this.level <= L.FATAL) { - return this._log(m, "F", null, L.FATAL); - } - }; - - Logger.prototype._log = function(m, l, ohook, level) { - var parts; - parts = []; - if (this.prefix != null) { - parts.push(this.prefix); - } - if (l) { - parts.push("[" + l + "]"); - } - if (this.remote) { - parts.push(this.remote); - } - parts.push(stringify(m)); - if (!ohook) { - ohook = this.output_hook; - } - return ohook(parts.join(" "), level); - }; - - Logger.prototype.make_child = function(d) { - return new Logger(d); - }; - - return Logger; - - })(); - - default_logger_class = Logger; - - exports.set_default_level = function(l) { - return default_level = l; - }; - - exports.set_default_logger_class = function(k) { - return default_logger_class = k; - }; - - exports.new_default_logger = function(d) { - if (d == null) { - d = {}; - } - return new default_logger_class(d); - }; - -}).call(this); diff --git a/lib/main.js b/lib/main.js deleted file mode 100644 index e596caa..0000000 --- a/lib/main.js +++ /dev/null @@ -1,61 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var client, debug, errors, log, pack, server, transport, version; - - exports.server = server = require('./server'); - - exports.client = client = require('./client'); - - exports.transport = transport = require('./transport'); - - exports.log = log = require('./log'); - - exports.debug = debug = require('./debug'); - - exports.pack = pack = require('./pack'); - - exports.errors = errors = require('./errors'); - - exports.dispatch = require('./dispatch'); - - exports.listener = require('./listener'); - - exports.Server = server.Server; - - exports.SimpleServer = server.SimpleServer; - - exports.Client = client.Client; - - exports.Transport = transport.Transport; - - exports.RobustTransport = transport.RobustTransport; - - exports.Logger = log.Logger; - - exports.createTransport = transport.createTransport; - - exports.version = version = require('./version').version; - - exports.at_version = function(v) { - var A, B, a, b; - A = version.split('.'); - B = v.split('.'); - while (A.length && B.length) { - a = parseInt(A.shift()); - b = parseInt(B.shift()); - if (a < b) { - return false; - } else if (a > b) { - return true; - } - } - if (A.length === 0 && B.length > 0) { - return false; - } else if (A.length > 0 && B.length === 0) { - return true; - } else { - return true; - } - }; - -}).call(this); diff --git a/lib/pack.js b/lib/pack.js deleted file mode 100644 index 0aa7d61..0000000 --- a/lib/pack.js +++ /dev/null @@ -1,112 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var e, get_encode_lib, mp, mpmp, pp, set_opt, set_opts, _opts; - - try { - mp = require('msgpack'); - } catch (_error) { - e = _error; - } - - try { - pp = require('purepack'); - } catch (_error) { - e = _error; - } - - try { - mpmp = require('@msgpack/msgpack'); - } catch (_error) { - e = _error; - } - - if ((mp == null) && (pp == null) && (mpmp == null)) { - throw new Error("Need either msgpack, purepack, or @msgpack/msgpack to run"); - } - - _opts = {}; - - exports.set_opt = set_opt = function(k, v) { - return _opts[k] = v; - }; - - exports.set_opts = set_opts = function(o) { - return _opts = o; - }; - - exports.get_encode_lib = get_encode_lib = function() { - var encode_lib; - encode_lib = _opts.encode_lib || "@msgpack/msgpack"; - switch (encode_lib) { - case "purepack": - case "msgpack": - case "@msgpack/msgpack": - return encode_lib; - } - throw new Error("Unsupported encode library " + encode_lib); - }; - - exports.use_byte_arrays = function() { - var encode_lib, err; - if (pp == null) { - try { - encode_lib = get_encode_lib(); - switch (encode_lib) { - case "purepack": - return pp = require("purepack"); - case "msgpack": - throw new Error("not supported"); - break; - case "@msgpack/msgpack": - return mpmp = require("@msgpack/msgpack"); - } - } catch (_error) { - err = _error; - throw new Error("Cannot use_byte_arrays without 'purepack' or '@msgpack/msgpack!'"); - } - } - }; - - exports.pack = function(b) { - var encode_lib; - encode_lib = get_encode_lib(); - switch (encode_lib) { - case "purepack": - return pp.pack(b); - case "msgpack": - return mp.pack(b); - case "@msgpack/msgpack": - return mpmp.encode(b); - } - }; - - exports.unpack = function(b) { - var dat, encode_lib, err; - err = dat = null; - encode_lib = get_encode_lib(); - switch (encode_lib) { - case "purepack": - try { - dat = pp.unpack(b); - } catch (_error) { - err = _error; - } - break; - case "msgpack": - try { - dat = mp.unpack(b); - } catch (_error) { - err = _error; - } - break; - case "@msgpack/msgpack": - try { - dat = mpmp.decode(b); - } catch (_error) { - err = _error; - } - } - return [err, dat]; - }; - -}).call(this); diff --git a/lib/packetizer.js b/lib/packetizer.js deleted file mode 100644 index 91a605e..0000000 --- a/lib/packetizer.js +++ /dev/null @@ -1,136 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Packetizer, Ring, is_array, msgpack_frame_len, pack, unpack, _ref; - - _ref = require('./pack'), unpack = _ref.unpack, pack = _ref.pack; - - Ring = require('./ring').Ring; - - msgpack_frame_len = function(buf) { - var b; - b = buf[0]; - if (b < 0x80) { - return 1; - } else if (b === 0xcc) { - return 2; - } else if (b === 0xcd) { - return 3; - } else if (b === 0xce) { - return 5; - } else { - return 0; - } - }; - - is_array = function(a) { - return (typeof a === 'object') && Array.isArray(a); - }; - - exports.Packetizer = Packetizer = (function() { - "A packetizer that is used to read and write to an underlying stream\n(like a Transport below). Should be inherited by such a class.\nThe subclasses should implement:\n\n @_raw_write(msg,enc) - write this msg to the stream with the\n given encoding. Typically handled at the transport level\n (2 classes higher in the inheritance graph)\n\n @_packetize_error(err) - report an error with the stream. Typically\n calls up to the Transport class (2 classes higher).\n\n @_dispatch(msg) - emit a packetized incoming message. Typically\n handled by the Dispatcher (1 class higher in the inheritance\n graph).\n\nThe subclass should call @packetize_data(m) whenever it has data to stuff\ninto the packetizer's input path, and call @send(m) whenever it wants\nto stuff data into the packterizer's output path.\n"; - Packetizer.prototype.FRAME = 1; - - Packetizer.prototype.DATA = 2; - - Packetizer.prototype.OK = 0; - - Packetizer.prototype.WAIT = 1; - - Packetizer.prototype.ERR = -1; - - function Packetizer() { - this._ring = new Ring(); - this._state = this.FRAME; - this._next_msg_len = 0; - } - - Packetizer.prototype._buf_write = function(b) { - var i, s, _i, _ref1; - s = ''; - for (i = _i = 0, _ref1 = b.length; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; i = 0 <= _ref1 ? ++_i : --_i) { - s += String.fromCharCode(b[i]); - } - return this._raw_write(s, 'binary'); - }; - - Packetizer.prototype.send = function(msg) { - var b1, b2; - b2 = pack(msg); - b1 = pack(b2.length); - this._buf_write(b1); - this._buf_write(b2); - return true; - }; - - Packetizer.prototype._get_frame = function() { - var f0, f_full, frame_len, r, res, typ, w, _ref1; - if (!(this._ring.len() > 0)) { - return this.WAIT; - } - f0 = this._ring.grab(1); - if (!f0) { - return this.WAIT; - } - frame_len = msgpack_frame_len(f0); - if (!frame_len) { - this._packetize_error("Bad frame header received"); - return this.ERR; - } - if ((f_full = this._ring.grab(frame_len)) == null) { - return this.WAIT; - } - _ref1 = unpack(f_full.subarray(0, frame_len)), w = _ref1[0], r = _ref1[1]; - if (w != null) { - this._packetize_warning(w); - } - res = (function() { - switch ((typ = typeof r)) { - case 'number': - if (r < 0) { - throw new Error("Negative len " + len + " should not have happened"); - } - this._ring.consume(frame_len); - this._next_msg_len = r; - this._state = this.DATA; - return this.OK; - case 'undefined': - return this.WAIT; - default: - this._packetize_error("bad frame; got type=" + typ + ", which is wrong"); - return this.ERR; - } - }).call(this); - return res; - }; - - Packetizer.prototype._get_msg = function() { - var b, l, msg, pw, ret, _ref1; - l = this._next_msg_len; - ret = l > this._ring.len() || ((b = this._ring.grab(l)) == null) ? this.WAIT : ((_ref1 = unpack(b.subarray(0, l)), pw = _ref1[0], msg = _ref1[1], _ref1) == null) || (msg == null) ? (this._packetize_error("bad encoding found in data/payload; len=" + l), this.ERR) : !is_array(msg) ? (this._packetize_error("non-array found in data stream: " + (JSON.stringify(msg))), this.ERR) : (this._ring.consume(l), this._state = this.FRAME, this._dispatch(msg), this.OK); - if (pw != null) { - this._packetize_warning(pw); - } - return ret; - }; - - Packetizer.prototype.packetize_data = function(m) { - var go, _results; - this._ring.buffer(m); - go = this.OK; - _results = []; - while (go === this.OK) { - _results.push(go = this._state === this.FRAME ? this._get_frame() : this._get_msg()); - } - return _results; - }; - - Packetizer.prototype._packetizer_reset = function() { - this._state = this.FRAME; - return this._ring = new Ring(); - }; - - return Packetizer; - - })(); - -}).call(this); diff --git a/lib/ring.js b/lib/ring.js deleted file mode 100644 index 18f9a60..0000000 --- a/lib/ring.js +++ /dev/null @@ -1,72 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Ring; - - exports.Ring = Ring = (function() { - "A Ring of buffers. Every so often you'll have to compress buffers into\nsmaller buffers, but try to limit that as much as possible...."; - function Ring() { - this._bufs = []; - this._len = 0; - } - - Ring.prototype.buffer = function(b) { - this._bufs.push(b); - return this._len += b.length; - }; - - Ring.prototype.len = function() { - return this._len; - }; - - Ring.prototype.grab = function(n_wanted) { - var b, first_pos, n, n_grabbed, num_bufs, ret, sub, _i, _j, _len, _len1, _ref, _ref1; - if (!(n_wanted <= this.len())) { - return null; - } - if (this._bufs.length && this._bufs[0].length >= n_wanted) { - return this._bufs[0]; - } - n_grabbed = 0; - num_bufs = 0; - _ref = this._bufs; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - b = _ref[_i]; - n_grabbed += b.length; - num_bufs++; - if (n_grabbed >= n_wanted) { - break; - } - } - ret = new Uint8Array(n_grabbed); - n = 0; - _ref1 = this._bufs.slice(0, num_bufs); - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - b = _ref1[_j]; - sub = ret.subarray(n, n + b.length); - sub.set(b); - n += b.length; - } - first_pos = num_bufs - 1; - this._bufs[first_pos] = ret; - this._bufs.splice(0, first_pos); - return ret; - }; - - Ring.prototype.consume = function(n) { - var b; - if (this._bufs.length === 0 || (b = this._bufs[0]).length < n) { - throw new Error("Ring underflow; can't remove " + n + " bytes"); - } - if (b.length === n) { - this._bufs.splice(0, 1); - } else { - this._bufs[0] = b.subarray(n); - } - return this._len -= n; - }; - - return Ring; - - })(); - -}).call(this); diff --git a/lib/server.js b/lib/server.js deleted file mode 100644 index 2a3b6b2..0000000 --- a/lib/server.js +++ /dev/null @@ -1,149 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var ContextualServer, Handler, Listener, Server, SimpleServer, collect_hooks, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - - Listener = require('./listener').Listener; - - exports.collect_hooks = collect_hooks = function(proto) { - var hooks, k, m, re, v; - re = /^h_(.*)$/; - hooks = {}; - for (k in proto) { - v = proto[k]; - if ((m = k.match(re)) != null) { - hooks[m[1]] = v; - } - } - return hooks; - }; - - exports.Server = Server = (function(_super) { - "This server is connection-centric. When the handlers of the\npassed programs are invoked, the 'this' object to the handler will\nbe the Transport that's handling that client. This server is available\nvia this.parent.\n\nNote you can pass a TransportClass to use instead of the Transport.\nIt should be a subclass of Transport."; - __extends(Server, _super); - - function Server(d) { - Server.__super__.constructor.call(this, d); - this.programs = d.programs; - } - - Server.prototype.got_new_connection = function(c) { - return c.add_programs(this.programs); - }; - - return Server; - - })(Listener); - - exports.SimpleServer = SimpleServer = (function(_super) { - __extends(SimpleServer, _super); - - function SimpleServer(d) { - SimpleServer.__super__.constructor.call(this, d); - this._program = d.program; - } - - SimpleServer.prototype.get_hook_wrapper = function() { - return null; - }; - - SimpleServer.prototype.got_new_connection = function(c) { - this._hooks = collect_hooks(this); - return c.add_program(this.get_program_name(), this._hooks); - }; - - SimpleServer.prototype.set_program_name = function(p) { - return this._program = p; - }; - - SimpleServer.prototype.get_program_name = function() { - var r; - r = this._program; - if (r == null) { - throw new Error("No 'program' given"); - } - return r; - }; - - SimpleServer.prototype.make_new_transport = function(c) { - var x; - x = SimpleServer.__super__.make_new_transport.call(this, c); - x.get_handler_this = (function(_this) { - return function(m) { - return _this; - }; - })(this); - x.get_hook_wrapper = (function(_this) { - return function() { - return _this.get_hook_wrapper(); - }; - })(this); - return x; - }; - - return SimpleServer; - - })(Listener); - - exports.Handler = Handler = (function() { - function Handler(_arg) { - this.transport = _arg.transport, this.server = _arg.server; - } - - Handler.collect_hooks = function() { - return collect_hooks(this.prototype); - }; - - return Handler; - - })(); - - exports.ContextualServer = ContextualServer = (function(_super) { - "This exposes a slightly different object as `this` to RPC\nhandlers -- in this case, it a Handler object that points to be both\nthe parent server, and also the child transport. So both are accessible\nvia 'has-a' rather than 'is-a' relationships."; - __extends(ContextualServer, _super); - - function ContextualServer(d) { - var klass, n, _ref; - ContextualServer.__super__.constructor.call(this, d); - this.programs = {}; - this.classes = d.classes; - _ref = this.classes; - for (n in _ref) { - klass = _ref[n]; - this.programs[n] = klass.collect_hooks(); - } - } - - ContextualServer.prototype.got_new_connection = function(c) { - return c.add_programs(this.programs); - }; - - ContextualServer.prototype.make_new_transport = function(c) { - var ctx, klass, n, x, _ref; - x = ContextualServer.__super__.make_new_transport.call(this, c); - ctx = {}; - _ref = this.classes; - for (n in _ref) { - klass = _ref[n]; - ctx[n] = new klass({ - transport: x, - server: this - }); - } - x.get_handler_this = function(m) { - var obj, pn; - pn = m.split(".").slice(0, -1).join("."); - if ((obj = ctx[pn]) == null) { - throw new Error("Couldn't find prog " + pn); - } - return obj; - }; - return x; - }; - - return ContextualServer; - - })(Listener); - -}).call(this); diff --git a/lib/timer.js b/lib/timer.js deleted file mode 100644 index dcb6375..0000000 --- a/lib/timer.js +++ /dev/null @@ -1,50 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Timer, time; - - exports.time = time = function() { - return (new Date()).getTime(); - }; - - exports.Timer = Timer = (function() { - function Timer(opts) { - this.reset(); - if (opts != null ? opts.start : void 0) { - this.start(); - } - } - - Timer.prototype.start = function() { - if (!this._running) { - this._ts = time(); - return this._running = true; - } - }; - - Timer.prototype.stop = function() { - if (this._running) { - this._total += time() - this._ts; - this._running = false; - } - return this._total; - }; - - Timer.prototype.is_running = function() { - return this._running; - }; - - Timer.prototype.total = function() { - return this._total; - }; - - Timer.prototype.reset = function() { - this._running = false; - this._total = 0; - return this._ts = 0; - }; - - return Timer; - - })(); - -}).call(this); diff --git a/lib/transport.js b/lib/transport.js deleted file mode 100644 index 6a42942..0000000 --- a/lib/transport.js +++ /dev/null @@ -1,802 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - var Dispatch, Lock, RobustTransport, StreamWrapper, Timer, Transport, dbg, iced, log, net, pp, process, tls, __iced_k, __iced_k_noop, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - __slice = [].slice; - - iced = require('iced-runtime'); - __iced_k = __iced_k_noop = function() {}; - - net = require('net'); - - tls = require('tls'); - - pp = require('path-parse'); - - process = require('process'); - - Lock = require('./lock').Lock; - - Dispatch = require('./dispatch').Dispatch; - - log = require('./log'); - - Timer = require('./timer').Timer; - - iced = require('./iced').runtime; - - dbg = require('./debug'); - - StreamWrapper = (function() { - function StreamWrapper(_net_stream, _parent) { - this._net_stream = _net_stream; - this._parent = _parent; - this._generation = this._parent.next_generation(); - this._write_closed_warn = false; - } - - StreamWrapper.prototype.close = function() { - var ret, x; - ret = false; - if ((x = this._net_stream) != null) { - ret = true; - this._net_stream = null; - this._parent._dispatch_reset(); - this._parent._packetizer_reset(); - x.end(); - } - return ret; - }; - - StreamWrapper.prototype.write = function(msg, enc) { - if (this._net_stream) { - return this._net_stream.write(msg, enc); - } else if (!this._write_closed_warn) { - this._write_closed_warn = true; - return this._parent._warn("write on closed socket..."); - } - }; - - StreamWrapper.prototype.stream = function() { - return this._net_stream; - }; - - StreamWrapper.prototype.is_connected = function() { - return !!this._net_stream; - }; - - StreamWrapper.prototype.get_generation = function() { - return this._generation; - }; - - StreamWrapper.prototype.remote_address = function() { - if (this._net_stream) { - return this._net_stream.remoteAddress; - } else { - return null; - } - }; - - StreamWrapper.prototype.remote_port = function() { - if (this._net_stream) { - return this._net_stream.remotePort; - } else { - return null; - } - }; - - return StreamWrapper; - - })(); - - exports.Transport = Transport = (function(_super) { - __extends(Transport, _super); - - function Transport(_arg) { - var connect_timeout, dbgr, net_stream; - this.port = _arg.port, this.host = _arg.host, this.net_opts = _arg.net_opts, net_stream = _arg.net_stream, this.log_obj = _arg.log_obj, this.parent = _arg.parent, this.do_tcp_delay = _arg.do_tcp_delay, this.hooks = _arg.hooks, dbgr = _arg.dbgr, this.path = _arg.path, this.tls_opts = _arg.tls_opts, connect_timeout = _arg.connect_timeout; - Transport.__super__.constructor.apply(this, arguments); - if (!this.host || this.host === "-") { - this.host = "localhost"; - } - if (!this.net_opts) { - this.net_opts = {}; - } - this.net_opts.host = this.host; - this.net_opts.port = this.port; - this.net_opts.path = this.path; - this._explicit_close = false; - this._remote_str = [this.host, this.port].join(":"); - this.set_logger(this.log_obj); - this._lock = new Lock(); - this._generation = 1; - this._dbgr = dbgr; - this._connect_timeout = connect_timeout || 10 * 1000; - this._netw = null; - if (net_stream) { - this._activate_stream(net_stream); - } - } - - Transport.prototype.set_debugger = function(d) { - return this._dbgr = d; - }; - - Transport.prototype.set_debug_flags = function(d) { - return this.set_debugger(dbg.make_debugger(d, this.log_obj)); - }; - - Transport.prototype.next_generation = function() { - "To be called by StreamWrapper objects but not by\naverage users."; - var ret; - ret = this._generation; - this._generation++; - return ret; - }; - - Transport.prototype.get_generation = function() { - if (this._netw) { - return this._netw.get_generation(); - } else { - return -1; - } - }; - - Transport.prototype.remote_address = function() { - if (this._netw != null) { - return this._netw.remote_address(); - } else { - return null; - } - }; - - Transport.prototype.remote_port = function() { - if (this._netw != null) { - return this._netw.remote_port(); - } else { - return null; - } - }; - - Transport.prototype.set_logger = function(o) { - if (!o) { - o = log.new_default_logger(); - } - this.log_obj = o; - return this.log_obj.set_remote(this._remote_str); - }; - - Transport.prototype.get_logger = function() { - return this.log_obj; - }; - - Transport.prototype.is_connected = function() { - var _ref; - return (_ref = this._netw) != null ? _ref.is_connected() : void 0; - }; - - Transport.prototype.connect = function(cb) { - var err, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "Transport.connect" - }); - _this._lock.acquire(__iced_deferrals.defer({ - lineno: 136 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - (function(__iced_k) { - if (!_this.is_connected()) { - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "Transport.connect" - }); - _this._connect_critical_section(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return err = arguments[0]; - }; - })(), - lineno: 138 - })); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - return __iced_k(err = null); - } - })(function() { - _this._lock.release(); - if (cb) { - cb(err); - } - if (typeof err !== "undefined" && err !== null) { - return _this._reconnect(true); - } - }); - }; - })(this)); - }; - - Transport.prototype.reset = function(w) { - if (!w) { - w = this._netw; - } - return this._close(w); - }; - - Transport.prototype.close = function() { - this._explicit_close = true; - if (this._netw) { - this._netw.close(); - return this._netw = null; - } - }; - - Transport.prototype._warn = function(e) { - return this.log_obj.warn(e); - }; - - Transport.prototype._info = function(e) { - return this.log_obj.info(e); - }; - - Transport.prototype._fatal = function(e) { - return this.log_obj.fatal(e); - }; - - Transport.prototype._debug = function(e) { - return this.log_obj.debug(e); - }; - - Transport.prototype._error = function(e) { - return this.log_obj.error(e); - }; - - Transport.prototype._close = function(netw) { - var _ref; - if ((_ref = this.hooks) != null) { - if (typeof _ref.eof === "function") { - _ref.eof(netw); - } - } - if (netw != null ? netw.close() : void 0) { - return this._reconnect(false); - } - }; - - Transport.prototype._handle_error = function(e, netw) { - this._error(e); - return this._close(netw); - }; - - Transport.prototype._packetize_error = function(err) { - return this._handle_error("In packetizer: " + err, this._netw); - }; - - Transport.prototype._packetize_warning = function(w) { - return this._warn("In packetizer: " + w); - }; - - Transport.prototype._handle_close = function(netw) { - if (!this._explicit_close) { - this._info("EOF on transport"); - } - this._close(netw); - if (this.parent) { - return this.parent.close_child(this); - } - }; - - Transport.prototype._reconnect = function(first_time) { - return null; - }; - - Transport.prototype._activate_stream = function(x) { - var w, _ref; - this._info("connection established"); - w = new StreamWrapper(x, this); - this._netw = w; - if ((_ref = this.hooks) != null) { - _ref.connected(w); - } - x.on('error', (function(_this) { - return function(err) { - return _this._handle_error(err, w); - }; - })(this)); - x.on('close', (function(_this) { - return function() { - return _this._handle_close(w); - }; - })(this)); - return x.on('data', (function(_this) { - return function(msg) { - return _this.packetize_data(new Uint8Array(msg.buffer, msg.byteOffset, msg.byteLength)); - }; - })(this)); - }; - - Transport.prototype._connect_critical_section = function(cb) { - var CLS, CON, ERR, TMO, connect_event_name, err, ex, name, ok, oldCwd, opts, path_info, rv, rv_id, val, x, ___iced_passed_deferral, __iced_deferrals, __iced_k, _ref, _ref1, _ref2; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - if (this.tls_opts != null) { - opts = {}; - _ref = this.net_opts; - for (name in _ref) { - val = _ref[name]; - opts[name] = val; - } - _ref1 = this.tls_opts; - for (name in _ref1) { - val = _ref1[name]; - opts[name] = val; - } - x = tls.connect(opts); - connect_event_name = 'secureConnect'; - } else { - opts = this.net_opts; - if ((this.net_opts.path != null) && this.net_opts.path.length >= 103) { - oldCwd = process.cwd(); - path_info = pp(this.net_opts.path); - try { - process.chdir(path_info.dir); - } catch (_error) { - ex = _error; - this._warn("could not cd close to socket path: " + ex.code + " " + ex.message); - return cb(new Error("error in connection (cd to long socket path)")); - } - opts = Object.assign({}, this.net_opts); - opts.path = path_info.base; - } - x = net.connect(opts); - connect_event_name = 'connect'; - } - if (!this.do_tcp_delay) { - x.setNoDelay(true); - } - _ref2 = [0, 1, 2, 3], CON = _ref2[0], ERR = _ref2[1], CLS = _ref2[2], TMO = _ref2[3]; - rv = new iced.Rendezvous; - x.once(connect_event_name, rv.id(CON).defer({ - lineno: 282, - context: __iced_deferrals - })); - x.once('error', rv.id(ERR).defer({ - assign_fn: (function(_this) { - return function() { - return function() { - return err = arguments[0]; - }; - }; - })(this)(), - lineno: 283, - context: __iced_deferrals - })); - x.once('close', rv.id(CLS).defer({ - lineno: 284, - context: __iced_deferrals - })); - setTimeout(rv.id(TMO).defer({ - lineno: 288, - context: __iced_deferrals - }), this._connect_timeout); - ok = false; - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "Transport._connect_critical_section" - }); - rv.wait(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return rv_id = arguments[0]; - }; - })(), - lineno: 291 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - if (oldCwd != null) { - try { - process.chdir(oldCwd); - } catch (_error) { - ex = _error; - _this._warn("could not recover cwd: " + ex.code + " " + ex.message); - return cb(new Error("error in connection (changed cwd)")); - } - } - switch (rv_id) { - case CON: - ok = true; - break; - case ERR: - _this._warn(err); - break; - case CLS: - _this._warn("connection closed during open"); - break; - case TMO: - _this._warn("connection timed out after " + _this._connect_timeout + "s"); - } - (function(__iced_k) { - if (ok) { - _this._activate_stream(x); - err = null; - (function(__iced_k) { - var _ref3; - if (((_ref3 = _this.hooks) != null ? _ref3.after_connect : void 0) != null) { - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "Transport._connect_critical_section" - }); - _this.hooks.after_connect(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return err = arguments[0]; - }; - })(), - lineno: 311 - })); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - return __iced_k(); - } - })(__iced_k); - } else { - return __iced_k(err == null ? err = new Error("error in connection") : void 0); - } - })(function() { - return cb(err); - }); - }; - })(this)); - }; - - Transport.prototype._raw_write = function(msg, encoding) { - if (this._netw == null) { - return this._warn("write attempt with no active stream"); - } else { - return this._netw.write(msg, encoding); - } - }; - - return Transport; - - })(Dispatch); - - exports.RobustTransport = RobustTransport = (function(_super) { - __extends(RobustTransport, _super); - - function RobustTransport(sd, d) { - var x; - if (d == null) { - d = {}; - } - RobustTransport.__super__.constructor.call(this, sd); - this.queue_max = d.queue_max, this.warn_threshhold = d.warn_threshhold, this.error_threshhold = d.error_threshhold; - this.reconnect_delay = (x = d.reconnect_delay) ? x : 1; - if (this.queue_max == null) { - this.queue_max = 1000; - } - this._time_rpcs = (this.warn_threshhold != null) || (this.error_threshhold != null); - this._waiters = []; - } - - RobustTransport.prototype._reconnect = function(first_time) { - if (!this._explicit_close) { - return this._connect_loop(first_time); - } - }; - - RobustTransport.prototype._flush_queue = function() { - var tmp, w, _i, _len, _results; - tmp = this._waiters; - this._waiters = []; - _results = []; - for (_i = 0, _len = tmp.length; _i < _len; _i++) { - w = tmp[_i]; - _results.push(this.invoke.apply(this, w)); - } - return _results; - }; - - RobustTransport.prototype._connect_critical_section = function(cb) { - var err, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._connect_critical_section" - }); - RobustTransport.__super__._connect_critical_section.call(_this, __iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return err = arguments[0]; - }; - })(), - lineno: 387 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - if (typeof err === "undefined" || err === null) { - _this._flush_queue(); - } - return cb(err); - }; - })(this)); - }; - - RobustTransport.prototype._connect_loop = function(first_time, cb) { - var err, first_through_loop, go, i, prfx, s, ___iced_passed_deferral, __iced_deferrals, __iced_k; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - if (first_time == null) { - first_time = false; - } - prfx = first_time ? "" : "re"; - i = 0; - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._connect_loop" - }); - _this._lock.acquire(__iced_deferrals.defer({ - lineno: 398 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - go = true; - first_through_loop = true; - (function(__iced_k) { - var _while; - _while = function(__iced_k) { - var _break, _continue, _next; - _break = __iced_k; - _continue = function() { - return iced.trampoline(function() { - return _while(__iced_k); - }); - }; - _next = _continue; - if (!go) { - return _break(); - } else { - (function(__iced_k) { - if (_this.is_connected() || _this._explicit_close) { - return __iced_k(go = false); - } else { - (function(__iced_k) { - if (first_through_loop && !first_time) { - first_through_loop = false; - _this._info("reconnect loop started, initial delay..."); - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._connect_loop" - }); - setTimeout(__iced_deferrals.defer({ - lineno: 409 - }), _this.reconnect_delay * 1000); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - i++; - _this._info("" + prfx + "connecting (attempt " + i + ")"); - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._connect_loop" - }); - _this._connect_critical_section(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return err = arguments[0]; - }; - })(), - lineno: 413 - })); - __iced_deferrals._fulfill(); - })(function() { - (function(__iced_k) { - if (typeof err !== "undefined" && err !== null) { - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._connect_loop" - }); - setTimeout(__iced_deferrals.defer({ - lineno: 415 - }), _this.reconnect_delay * 1000); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - return __iced_k(go = false); - } - })(__iced_k); - }); - } - })(__iced_k); - } - })(_next); - } - }; - _while(__iced_k); - })(function() { - if (_this.is_connected()) { - s = i === 1 ? "" : "s"; - _this._warn("" + prfx + "connected after " + i + " attempt" + s); - } - _this._lock.release(); - if (cb) { - return cb(); - } - }); - }; - })(this)); - }; - - RobustTransport.prototype._timed_invoke = function(arg, cb) { - var OK, TIMEOUT, dur, et, flag, m, meth, rpc_res, rv, tm, to, which, wt, ___iced_passed_deferral, __iced_deferrals, __iced_k, _ref; - __iced_k = __iced_k_noop; - ___iced_passed_deferral = iced.findDeferral(arguments); - _ref = [0, 1], OK = _ref[0], TIMEOUT = _ref[1]; - tm = new Timer({ - start: true - }); - rv = new iced.Rendezvous; - meth = this.make_method(arg.program, arg.method); - et = this.error_threshhold ? this.error_threshhold * 1000 : 0; - wt = this.warn_threshhold ? this.warn_threshhold * 1000 : 0; - if (et) { - to = setTimeout(rv.id(TIMEOUT).defer({ - lineno: 439, - context: __iced_deferrals - }), et); - } - Dispatch.prototype.invoke.call(this, arg, rv.id(OK).defer({ - assign_fn: (function(_this) { - return function() { - return function() { - return rpc_res = __slice.call(arguments, 0); - }; - }; - })(this)(), - lineno: 442, - context: __iced_deferrals - })); - (function(_this) { - return (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._timed_invoke" - }); - rv.wait(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return which = arguments[0]; - }; - })(), - lineno: 445 - })); - __iced_deferrals._fulfill(); - }); - })(this)((function(_this) { - return function() { - flag = true; - (function(__iced_k) { - var _while; - _while = function(__iced_k) { - var _break, _continue, _next; - _break = __iced_k; - _continue = function() { - return iced.trampoline(function() { - return _while(__iced_k); - }); - }; - _next = _continue; - if (!flag) { - return _break(); - } else { - (function(__iced_k) { - if (which === TIMEOUT) { - _this._error("RPC call to '" + meth + "' is taking > " + (et / 1000) + "s"); - (function(__iced_k) { - __iced_deferrals = new iced.Deferrals(__iced_k, { - parent: ___iced_passed_deferral, - filename: "/Users/chrisnojima/go/src/github.com/keybase/node-framed-msgpack-rpc/src/transport.iced", - funcname: "RobustTransport._timed_invoke" - }); - rv.wait(__iced_deferrals.defer({ - assign_fn: (function() { - return function() { - return which = arguments[0]; - }; - })(), - lineno: 453 - })); - __iced_deferrals._fulfill(); - })(__iced_k); - } else { - clearTimeout(to); - return __iced_k(flag = false); - } - })(_next); - } - }; - _while(__iced_k); - })(function() { - dur = tm.stop(); - m = et && dur >= et ? _this._error : wt && dur >= wt ? _this._warn : null; - if (m) { - m.call(_this, "RPC call to '" + meth + "' finished in " + (dur / 1000) + "s"); - } - return cb.apply(null, rpc_res); - }); - }; - })(this)); - }; - - RobustTransport.prototype.invoke = function(arg, cb) { - var meth; - meth = this.make_method(arg.program, arg.method); - if (this.is_connected()) { - if (this._time_rpcs) { - return this._timed_invoke(arg, cb); - } else { - return RobustTransport.__super__.invoke.call(this, arg, cb); - } - } else if (this._explicit_close) { - this._warn("invoke call after explicit close"); - return cb("socket was closed", {}); - } else if (this._waiters.length < this.queue_max) { - this._waiters.push([arg, cb]); - return this._info("Queuing call to " + meth + " (num queued: " + this._waiters.length + ")"); - } else if (this.queue_max > 0) { - return this._warn("Queue overflow for " + meth); - } - }; - - return RobustTransport; - - })(Transport); - - exports.createTransport = function(opts) { - if (opts.robust) { - return new RobustTransport(opts, opts); - } else { - return new Transport(opts); - } - }; - -}).call(this); diff --git a/lib/version.js b/lib/version.js deleted file mode 100644 index 0b45c97..0000000 --- a/lib/version.js +++ /dev/null @@ -1,5 +0,0 @@ -// Generated by IcedCoffeeScript 108.0.12 -(function() { - exports.version = "1.1.19"; - -}).call(this); diff --git a/package-lock.json b/package-lock.json index 525d04c..e26cf5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,25 +8,471 @@ "name": "framed-msgpack-rpc", "version": "1.1.24", "dependencies": { - "iced-error": "0.0.13", - "iced-lock": "2.0.1", - "iced-runtime": "1.0.4", "pako": "2.1.0", - "path-parse": "1.0.7", - "typedarray-to-buffer": "4.0.0" + "path-parse": "1.0.7" }, "devDependencies": { - "colors": "1.4.0", + "@types/node": "^20.0.0", + "@types/pako": "^2.0.3", "deep-equal": "2.2.2", - "iced-coffee-script": "108.0.14", - "optimist": "0.6.1", - "random-json": "3.0.2" + "random-json": "3.0.2", + "typescript": "^5.4.0", + "vitest": "^3.0.0" }, "optionalDependencies": { "@msgpack/msgpack": "2.8.0", "purepack": ">=1" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@msgpack/msgpack": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", @@ -36,6 +482,513 @@ "node": ">= 10" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -49,6 +1002,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -61,6 +1024,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -75,13 +1048,59 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">=6" } }, "node_modules/deep-equal": { @@ -164,6 +1183,93 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -173,6 +1279,21 @@ "is-callable": "^1.1.3" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -290,41 +1411,6 @@ "node": ">= 0.4" } }, - "node_modules/iced-coffee-script": { - "version": "108.0.14", - "resolved": "https://registry.npmjs.org/iced-coffee-script/-/iced-coffee-script-108.0.14.tgz", - "integrity": "sha512-e0CNmz51UGWRa2glPnUMnJM7oKQE81cxeC0WAgCjJDRImv3FDHldZr/Ngkbrgdbf1drGGzYWp+PWeJwXIfHwDw==", - "dev": true, - "dependencies": { - "iced-runtime": ">=0.0.1", - "uglify-js": "^3.5.9" - }, - "bin": { - "icake": "bin/cake", - "iced": "bin/coffee" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/iced-error": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.13.tgz", - "integrity": "sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA==" - }, - "node_modules/iced-lock": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-2.0.1.tgz", - "integrity": "sha512-J6dnGMpAoHNyACUYJYhiJkLY7YFRTa7NMZ8ZygpYB3HNDOGWtzv55+kT2u1zItRi4Y1EXruG9d1VDsx8R5faTw==", - "dependencies": { - "iced-runtime": "^1.0.0" - } - }, - "node_modules/iced-runtime": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.4.tgz", - "integrity": "sha512-rgiJXNF6ZgF2Clh/TKUlBDW3q51YPDJUXmxGQXx1b8tbZpVpTn+1RX9q1sjNkujXIIaVxZByQzPHHORg7KV51g==" - }, "node_modules/iced-runtime-3": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/iced-runtime-3/-/iced-runtime-3-3.0.5.tgz", @@ -564,11 +1650,55 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, - "node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "dev": true + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, "node_modules/object-inspect": { "version": "1.13.1", @@ -622,16 +1752,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", - "dev": true, - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -642,6 +1762,72 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/purepack": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/purepack/-/purepack-1.0.6.tgz", @@ -680,6 +1866,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -723,6 +1954,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -735,35 +1997,270 @@ "node": ">= 0.4" } }, - "node_modules/typedarray-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz", - "integrity": "sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "jiti": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } - ] + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, "bin": { - "uglifyjs": "bin/uglifyjs" + "vitest": "vitest.mjs" }, "engines": { - "node": ">=0.8.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, "node_modules/which-boxed-primitive": { @@ -816,13 +2313,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, "engines": { - "node": ">=0.4.0" + "node": ">=8" } } } diff --git a/package.json b/package.json index 4af0d9c..40ac748 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Framed Msgpack RPC for node.js", "homepage": "https://github.com/keybase/node-framed-msgpack-rpc", "main": "./lib/main", + "types": "./lib/main.d.ts", "repository": { "type": "git", "url": "https://github.com/keybase/node-framed-msgpack-rpc.git" @@ -12,28 +13,24 @@ "lib": "lib" }, "scripts": { - "build": "iced -c -o lib/ src/", - "test": "node node_modules/.bin/iced test/all.iced", - "test-debug": "node --inspect-brk node_modules/.bin/iced test/all.iced" + "build": "tsc", + "test": "vitest run" }, "dependencies": { - "iced-error": "0.0.13", - "iced-lock": "2.0.1", - "iced-runtime": "1.0.4", "pako": "2.1.0", - "path-parse": "1.0.7", - "typedarray-to-buffer": "4.0.0" + "path-parse": "1.0.7" }, "optionalDependencies": { "@msgpack/msgpack": "2.8.0", "purepack": ">=1" }, "devDependencies": { - "colors": "1.4.0", - "random-json": "3.0.2", - "iced-coffee-script": "108.0.14", + "@types/node": "^20.0.0", + "@types/pako": "^2.0.3", "deep-equal": "2.2.2", - "optimist": "0.6.1" + "random-json": "3.0.2", + "typescript": "^5.4.0", + "vitest": "^3.0.0" }, "licenses": [ { diff --git a/src/client.iced b/src/client.iced deleted file mode 100644 index 5252312..0000000 --- a/src/client.iced +++ /dev/null @@ -1,31 +0,0 @@ - -iced = require('./iced').runtime - -##======================================================================== - -exports.Client = class Client - - #----------------------------------------- - - constructor : (@transport, @program = null) -> - - #----------------------------------------- - - invoke : (method, args, cb) -> - arg = { @program, method, args, notify : false } - await @transport.invoke arg, defer err, res - cb err, res - - invoke_compressed : (method, ctype, args, cb) -> - arg = { @program, method, ctype, args, notify : false } - await @transport.invoke arg, defer err, res - cb err, res - - #----------------------------------------- - - notify : (method, args) -> - method = @make_method method - program = @_program - @transport.invoke { @program, method, args, notify : true } - -##======================================================================== diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..4319ced --- /dev/null +++ b/src/client.ts @@ -0,0 +1,23 @@ +import type {Dispatch} from './dispatch' + +export class Client { + transport: Dispatch + program: string | null + + constructor(transport: Dispatch, program: string | null = null) { + this.transport = transport + this.program = program + } + + async invoke(method: string, args: any): Promise<{error: any; result: any}> { + return this.transport.invoke({program: this.program, method, args, notify: false}) + } + + async invoke_compressed(method: string, ctype: number, args: any): Promise<{error: any; result: any}> { + return this.transport.invoke({program: this.program, method, ctype, args, notify: false}) + } + + notify(method: string, args: any): void { + this.transport.invoke({program: this.program, method, args, notify: true}) + } +} diff --git a/src/debug.iced b/src/debug.iced deleted file mode 100644 index 57efc5c..0000000 --- a/src/debug.iced +++ /dev/null @@ -1,190 +0,0 @@ - -##======================================================================= - -log = require "./log" - -##======================================================================= - -# Flags for what fields are in our debug messages -F = - NONE : 0 - METHOD : 0x1 - REMOTE : 0x2 - SEQID : 0x4 - TIMESTAMP : 0x8 - ERR : 0x10 - ARG : 0x20 - RES : 0x40 - TYPE : 0x80 - COMPRESSION_TYPE: 0x90 - DIR : 0x100 - PORT : 0x200 - VERBOSE : 0x400 - ALL : 0xfffffff - -F.LEVEL_0 = F.NONE -F.LEVEL_1 = F.METHOD | F.TYPE | F.DIR | F.TYPE | F.COMPRESSION_TYPE -F.LEVEL_2 = F.LEVEL_1 | F.SEQID | F.TIMESTAMP | F.REMOTE | F.PORT -F.LEVEL_3 = F.LEVEL_2 | F.ERR -F.LEVEL_4 = F.LEVEL_3 | F.RES | F.ARG - -##======================================================================= - -# String versions of these flags -SF = - m : F.METHOD - a : F.REMOTE - s : F.SEQID - t : F.TIMESTAMP - p : F.ARG - r : F.RES - e : F.ERR - c : F.TYPE - C : F.COMPRESSION_TYPE - d : F.DIRECTION - v : F.VERBOSE - P : F.PORT - A : F.ALL - 0 : F.LEVEL_0 - 1 : F.LEVEL_1 - 2 : F.LEVEL_2 - 3 : F.LEVEL_3 - 4 : F.LEVEL_4 - -##======================================================================= - -dir = - INCOMING : 1 - OUTGOING : 2 - -flip_dir = (d) -> if d is dir.INCOMING then dir.OUTGOING else dir.INCOMING - -##======================================================================= - -type = - SERVER : 1 - CLIENT_NOTIFY : 2 - CLIENT_INVOKE : 3 - CLIENT_INVOKE_COMPRESSED : 4 - -##======================================================================= - -F2S = {} -F2S[F.DIR] = {} -F2S[F.DIR][dir.INCOMING] = "in"; -F2S[F.DIR][dir.OUTGOING] = "out"; -F2S[F.TYPE] = {}; -F2S[F.TYPE][type.SERVER] = "server"; -F2S[F.TYPE][type.CLIENT_NOTIFY] = "cli.notify"; -F2S[F.TYPE][type.CLIENT_INVOKE] = "cli.invoke"; -F2S[F.TYPE][type.CLIENT_INVOKE_COMPRESSED] = "cli.invokeCompresed"; - -##======================================================================= - -# Finally, export all of these constants... -exports.constants = - type : type - dir : dir - flags : F - sflags : SF - field_to_string : F2S - -##======================================================================= - -# -# Convert a string of the form "1r" to the OR of those -# consituent bitfields. -# -exports.sflags_to_flags = sflags_to_flags = (s) -> - s = "#{s}" - res = 0 - for i in [0...s.length] - c = s.charAt i - res |= SF[c] - return res - -##======================================================================= - -exports.Debugger = class Debugger - - constructor : (flags, @log_obj, @log_obj_mthd) -> - # Potentially convert from strings to integer flags - @flags = if typeof flags is 'string' then sflags_to_flags flags else flags - if not @log_obj - @log_obj = log.new_default_logger() - @log_obj_mthd = @log_obj.info - - new_message : (dict) -> - return new Message dict, @ - - _output : (json_msg) -> - @log_obj_mthd.call @log_obj, JSON.stringify json_msg - - _skip_flag : (f) -> - return f & (F.REMOTE | F.PORT) - - call : (msg) -> - new_json_msg = {} - - # Usually don't copy the arg or res if it's in the other direction, - # but this can overpower that - V = @flags & F.VERBOSE - - if (@flags & F.TIMESTAMP) - new_json_msg.timestamp = (new Date()).getTime() / 1000.0 - - for key,val of msg.to_json_object() - uck = key.toUpperCase() - flag = F[uck] - - do_copy = if @_skip_flag flag then false - else if (@flags & flag) is 0 then false - else if key is "res" then msg.show_res V - else if key is "arg" then msg.show_arg V - else true - - if do_copy - val = f2s[val] if (f2s = F2S[flag])? - new_json_msg[key] = val - - @_output new_json_msg - -##======================================================================= - -exports.Message = class Message - """A debug message --- a wrapper around a dictionary object, with - a few additional methods.""" - - constructor : (@_msg, @_debugger = null) -> - - response : (error, result) -> - @_msg.err = error - @_msg.res = result - @_msg.dir = flip_dir @_msg.dir - return @ - - to_json_object : -> @_msg - - call : -> @_debugger.call @ - - set : (k,v) -> @msg[k] = v - - is_server : -> @_msg.type is type.SERVER - is_client : -> not @is_server() - is_incoming : -> @_msg.dir is dir.INCOMING - is_outgoing : -> not @is_incoming() - - show_arg : (V) -> - (V or (@is_server() and @is_incoming()) or - (@is_client() and @is_outgoing())) - - show_res : (V) -> - (V or (@is_server() and @is_outgoing()) or - (@is_client() and @is_incoming())) - -##======================================================================= - -exports.make_debugger = (d, lo) -> - if d is 0 then null else new Debugger d, lo, lo.debug - -##======================================================================= diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..fc16370 --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,231 @@ +import * as log from './log' + +// Flags for what fields are in our debug messages +const F = { + NONE: 0, + METHOD: 0x1, + REMOTE: 0x2, + SEQID: 0x4, + TIMESTAMP: 0x8, + ERR: 0x10, + ARG: 0x20, + RES: 0x40, + TYPE: 0x80, + COMPRESSION_TYPE: 0x90, + DIR: 0x100, + PORT: 0x200, + VERBOSE: 0x400, + ALL: 0xfffffff, + LEVEL_0: 0, + LEVEL_1: 0, + LEVEL_2: 0, + LEVEL_3: 0, + LEVEL_4: 0, +} as Record + +F.LEVEL_0 = F.NONE +F.LEVEL_1 = F.METHOD | F.TYPE | F.DIR | F.TYPE | F.COMPRESSION_TYPE +F.LEVEL_2 = F.LEVEL_1 | F.SEQID | F.TIMESTAMP | F.REMOTE | F.PORT +F.LEVEL_3 = F.LEVEL_2 | F.ERR +F.LEVEL_4 = F.LEVEL_3 | F.RES | F.ARG + +// String versions of these flags +const SF: Record = { + m: F.METHOD, + a: F.REMOTE, + s: F.SEQID, + t: F.TIMESTAMP, + p: F.ARG, + r: F.RES, + e: F.ERR, + c: F.TYPE, + C: F.COMPRESSION_TYPE, + d: F.DIR, + v: F.VERBOSE, + P: F.PORT, + A: F.ALL, + '0': F.LEVEL_0, + '1': F.LEVEL_1, + '2': F.LEVEL_2, + '3': F.LEVEL_3, + '4': F.LEVEL_4, +} + +const dir = { + INCOMING: 1, + OUTGOING: 2, +} as const + +function flip_dir(d: number): number { + return d === dir.INCOMING ? dir.OUTGOING : dir.INCOMING +} + +const type = { + SERVER: 1, + CLIENT_NOTIFY: 2, + CLIENT_INVOKE: 3, + CLIENT_INVOKE_COMPRESSED: 4, +} as const + +const F2S: Record> = {} +F2S[F.DIR] = {} +F2S[F.DIR][dir.INCOMING] = 'in' +F2S[F.DIR][dir.OUTGOING] = 'out' +F2S[F.TYPE] = {} +F2S[F.TYPE][type.SERVER] = 'server' +F2S[F.TYPE][type.CLIENT_NOTIFY] = 'cli.notify' +F2S[F.TYPE][type.CLIENT_INVOKE] = 'cli.invoke' +F2S[F.TYPE][type.CLIENT_INVOKE_COMPRESSED] = 'cli.invokeCompresed' + +export const constants = { + type, + dir, + flags: F, + sflags: SF, + field_to_string: F2S, +} + +export function sflags_to_flags(s: string | number): number { + const str = `${s}` + let res = 0 + for (let i = 0; i < str.length; i++) { + const c = str.charAt(i) + res |= SF[c] || 0 + } + return res +} + +export interface DebugMessageDict { + method?: string + seqid?: number + ctype?: number + arg?: any + res?: any + err?: any + dir?: number + remote?: string | null + port?: number | null + type?: number + [key: string]: any +} + +export class Debugger { + flags: number + log_obj: log.Logger + log_obj_mthd: (m: unknown) => void + + constructor(flags: number | string, log_obj?: log.Logger, log_obj_mthd?: (m: unknown) => void) { + this.flags = typeof flags === 'string' ? sflags_to_flags(flags) : flags + if (!log_obj) { + this.log_obj = log.new_default_logger() + this.log_obj_mthd = this.log_obj.info.bind(this.log_obj) + } else { + this.log_obj = log_obj + this.log_obj_mthd = log_obj_mthd || log_obj.info.bind(log_obj) + } + } + + new_message(dict: DebugMessageDict): Message { + return new Message(dict, this) + } + + _output(json_msg: any): void { + this.log_obj_mthd.call(this.log_obj, JSON.stringify(json_msg)) + } + + _skip_flag(f: number): boolean { + return !!(f & (F.REMOTE | F.PORT)) + } + + call(msg: Message): void { + const new_json_msg: Record = {} + + // Usually don't copy the arg or res if it's in the other direction, + // but this can overpower that + const V = !!(this.flags & F.VERBOSE) + + if (this.flags & F.TIMESTAMP) { + new_json_msg.timestamp = new Date().getTime() / 1000.0 + } + + const json_obj = msg.to_json_object() + for (const key of Object.keys(json_obj)) { + let val = json_obj[key] + const uck = key.toUpperCase() + const flag = F[uck] + + let do_copy: boolean + if (this._skip_flag(flag)) do_copy = false + else if ((this.flags & flag) === 0) do_copy = false + else if (key === 'res') do_copy = msg.show_res(V) + else if (key === 'arg') do_copy = msg.show_arg(V) + else do_copy = true + + if (do_copy) { + const f2s = F2S[flag] + if (f2s != null) val = f2s[val] ?? val + new_json_msg[key] = val + } + } + + this._output(new_json_msg) + } +} + +export class Message { + private _msg: DebugMessageDict + private _debugger: Debugger | null + + constructor(msg: DebugMessageDict, dbgr: Debugger | null = null) { + this._msg = msg + this._debugger = dbgr + } + + response(error: any, result: any): Message { + this._msg.err = error + this._msg.res = result + this._msg.dir = flip_dir(this._msg.dir!) + return this + } + + to_json_object(): DebugMessageDict { + return this._msg + } + + call(): void { + this._debugger!.call(this) + } + + set(k: string, v: any): void { + this._msg[k] = v + } + + is_server(): boolean { + return this._msg.type === type.SERVER + } + + is_client(): boolean { + return !this.is_server() + } + + is_incoming(): boolean { + return this._msg.dir === dir.INCOMING + } + + is_outgoing(): boolean { + return !this.is_incoming() + } + + show_arg(V: boolean): boolean { + return V || (this.is_server() && this.is_incoming()) || (this.is_client() && this.is_outgoing()) + } + + show_res(V: boolean): boolean { + return V || (this.is_server() && this.is_outgoing()) || (this.is_client() && this.is_incoming()) + } +} + +export function make_debugger(d: number | string, lo: log.Logger): Debugger | null { + if (d === 0) return null + return new Debugger(d, lo, lo.debug.bind(lo)) +} diff --git a/src/dispatch.iced b/src/dispatch.iced deleted file mode 100644 index f6939d0..0000000 --- a/src/dispatch.iced +++ /dev/null @@ -1,267 +0,0 @@ -{Packetizer} = require './packetizer' -{unpack,pack} = require './pack' -dbg = require './debug' -E = require './errors' -pako = require('pako') - -iced = require('./iced').runtime - -exports.COMPRESSION_TYPE_NONE = COMPRESSION_TYPE_NONE = 0 -exports.COMPRESSION_TYPE_GZIP = COMPRESSION_TYPE_GZIP = 1 - -compress = (ctype, data) -> - unless ctype? - return data - switch ctype - when COMPRESSION_TYPE_NONE - return data - when COMPRESSION_TYPE_GZIP - data = pack data - # set windowBits to 31 = 15 (the default) + 16 (for gzip header) - # https://zlib.net/manual.html#Advanced - data = pako.deflate data, {windowBits: 31} - return data - else - throw new Error "Compress: unknown compression type #{ctype}" - -uncompress = (ctype, data) -> - unless ctype? - return data - switch ctype - when COMPRESSION_TYPE_NONE - return data - when COMPRESSION_TYPE_GZIP - data = pako.inflate data - [err, data] = unpack data - unless err? - return data - throw err - else - throw new Error "Uncompress: unknown compression type #{ctype}" - -##======================================================================= - -exports.Response = class Response - constructor : (@dispatch, @seqid, @ctype) -> - @debug_msg = null - - result : (res) -> - res = compress @ctype, res - @debug_msg.response(null, res).call() if @debug_msg - @dispatch.respond @seqid, null, res - - error : (err) -> - @debug_msg.response(err, null).call() if @debug_msg - @dispatch.respond @seqid, err, null - -##======================================================================= - -exports.Dispatch = class Dispatch extends Packetizer - - INVOKE : 0 - RESPONSE : 1 - NOTIFY : 2 - # CANCEL : 3, not implemented - INVOKE_COMPRESSED : 4 - - ##----------------------------------------- - - constructor : () -> - @_invocations = {} - @_handlers = {} - @_seqid = 1 - @_dbgr = null - @_generic_handler = null - super - - ##----------------------------------------- - - set_debugger : (d) -> @_dbgr = d - set_generic_handler : (h) -> @_generic_handler = h - - ##----------------------------------------- - - _dispatch : (msg) -> - - # We can escape from this, but it's not great... - if not (msg instanceof Array) or msg.length < 2 - @_warn "Bad input packet in dispatch" - else - switch (type = msg.shift()) - when @INVOKE - [seqid,method,param] = msg - response = new Response @, seqid - @_serve { method, param, response } - when @NOTIFY - [method,param] = msg - @_serve { method, param } - when @RESPONSE - [seqid,error,result] = msg - @_dispatch_handle_response { seqid, error, result } - when @INVOKE_COMPRESSED - [seqid,ctype,method,param] = msg - param = uncompress ctype, param - response = new Response @, seqid, ctype - @_serve { method, param, response } - else - @_warn "Unknown message type: #{type}" - - ##----------------------------------------- - - _dispatch_handle_response : ({seqid, error, result}) -> - @_call_cb { seqid, error, result } - - ##----------------------------------------- - - _call_cb : ({seqid, error, result}) -> - cb = @_invocations[seqid] - if cb - delete @_invocations[seqid] - error = @unwrap_incoming_error error - cb error, result - - ##----------------------------------------- - - cancel : (seqid) -> @_call_cb { seqid, error : "cancelled", result : null } - - ##----------------------------------------- - - _next_seqid : () -> - ret = @_seqid - @_seqid++ - return ret - - ##----------------------------------------- - - make_method : (prog, meth) -> - if prog then [ prog, meth ].join "." else meth - - ##----------------------------------------- - - respond : (seqid, error, result) -> - msg = [ @RESPONSE, seqid, error, result ] - @send msg - - ##----------------------------------------- - - invoke : ({program, ctype, method, args, notify}, cb, out) -> - - method = @make_method program, method - - seqid = @_next_seqid() - - if notify - type = @NOTIFY - dtype = dbg.constants.type.CLIENT_NOTIFY - else if ctype? - type = @INVOKE_COMPRESSED - dtype = dbg.constants.type.CLIENT_INVOKE_COMPRESSED - else - type = @INVOKE - dtype = dbg.constants.type.CLIENT_INVOKE - - if ctype? - args = compress ctype, args - msg = [ type, seqid, ctype, method, args ] - else - msg = [ type, seqid, method, args ] - - if @_dbgr - debug_msg = @_dbgr.new_message { - method, - seqid, - ctype, - arg : args, - dir : dbg.constants.dir.OUTGOING, - remote : @remote_address(), - port : @remote_port(), - type : dtype - } - debug_msg.call() - - - # Down to the packetizer, which will jump back up to the Transport! - @send msg - - if cb? or not notify - - if out? - out.cancel = () => @cancel seqid - - await (@_invocations[seqid] = defer(error,result) ) - - if ctype? and not error - result = uncompress ctype, result - - debug_msg.response(error, result).call() if debug_msg - - cb error, result if cb - - ##----------------------------------------- - - _dispatch_reset : () -> - inv = @_invocations - @_invocations = {} - for key,cb of inv - cb new E.EofError(), {} - - ##----------------------------------------- - - _serve : ({method, param, response}) -> - - pair = @get_handler_pair method - - if @_dbgr - debug_msg = @_dbgr.new_message { - method - seqid : response.seqid - arg : param - dir : dbg.constants.dir.INCOMING - remote : @remote_address() - port : @remote_port() - type : dbg.constants.type.SERVER - error : if pair then null else "unknown method" - } - - response.debug_msg = debug_msg if response - debug_msg.call() - - if @_generic_handler? - @_generic_handler { method, param, response, dispatch : @ } - else if pair? and (hw = @get_hook_wrapper())? - hw { method : pair[1], thisobj : pair[0], param, response, dispatch : @ } - else if pair then pair[1].call pair[0], param, response, @ - else if response? - err = new E.UnknownMethodError method - err.method = method - response.error @wrap_outgoing_error err - - ##----------------------------------------- - - # please override me! - get_handler_this : (m) -> @ - get_hook_wrapper : () -> null - - wrap_outgoing_error : (s) -> s.toString() - unwrap_incoming_error : (s) -> if typeof(s) is 'string' then new Error(s) else s - - # please override me! - get_handler_pair : (m) -> - h = @_handlers[m] - if h then [ @get_handler_this(m), h ] - else null - - add_handler : (method, hook, program = null) -> - method = @make_method program, method - @_handlers[method] = hook - - add_program : (program, hooks) -> - for method,hook of hooks - @add_handler method, hook, program - - add_programs : (programs) -> - for program, hooks of programs - @add_program program, hooks - - # - ##----------------------------------------- diff --git a/src/dispatch.ts b/src/dispatch.ts new file mode 100644 index 0000000..fe06b86 --- /dev/null +++ b/src/dispatch.ts @@ -0,0 +1,331 @@ +import {Packetizer} from './packetizer' +import {unpack, pack} from './pack' +import * as dbg from './debug' +import * as E from './errors' +import pako from 'pako' + +export const COMPRESSION_TYPE_NONE = 0 +export const COMPRESSION_TYPE_GZIP = 1 + +function compress(ctype: number | undefined | null, data: any): any { + if (ctype == null) return data + switch (ctype) { + case COMPRESSION_TYPE_NONE: + return data + case COMPRESSION_TYPE_GZIP: { + let packed = pack(data) + return pako.deflate(packed, {windowBits: 31}) + } + default: + throw new Error(`Compress: unknown compression type ${ctype}`) + } +} + +function uncompress(ctype: number | undefined | null, data: any): any { + if (ctype == null) return data + switch (ctype) { + case COMPRESSION_TYPE_NONE: + return data + case COMPRESSION_TYPE_GZIP: { + const inflated = pako.inflate(data) + const [err, result] = unpack(inflated) + if (err == null) return result + throw err + } + default: + throw new Error(`Uncompress: unknown compression type ${ctype}`) + } +} + +export interface InvokeArgs { + program?: string | null + ctype?: number + method: string + args: any + notify: boolean +} + +export class Response { + dispatch: Dispatch + seqid: number + ctype: number | undefined + debug_msg: dbg.Message | null = null + + constructor(dispatch: Dispatch, seqid: number, ctype?: number) { + this.dispatch = dispatch + this.seqid = seqid + this.ctype = ctype + } + + result(res: any): void { + res = compress(this.ctype, res) + if (this.debug_msg) this.debug_msg.response(null, res).call() + this.dispatch.respond(this.seqid, null, res) + } + + error(err: any): void { + if (this.debug_msg) this.debug_msg.response(err, null).call() + this.dispatch.respond(this.seqid, err, null) + } +} + +export interface ServeArgs { + method: string + param: any + response?: Response +} + +export interface HookWrapperArgs { + method: any + thisobj: any + param: any + response?: Response + dispatch: Dispatch +} + +export abstract class Dispatch extends Packetizer { + INVOKE = 0 + RESPONSE = 1 + NOTIFY = 2 + INVOKE_COMPRESSED = 4 + + _invocations: Record void> = {} + _handlers: Record = {} + _seqid: number = 1 + _dbgr: dbg.Debugger | null = null + _generic_handler: ((args: {method: string; param: any; response?: Response; dispatch: Dispatch}) => void) | null = + null + + constructor() { + super() + } + + set_debugger(d: dbg.Debugger | null): void { + this._dbgr = d + } + + set_generic_handler( + h: ((args: {method: string; param: any; response?: Response; dispatch: Dispatch}) => void) | null + ): void { + this._generic_handler = h + } + + _dispatch(msg: any[]): void { + if (!Array.isArray(msg) || msg.length < 2) { + this._warn('Bad input packet in dispatch') + } else { + const type = msg.shift() + switch (type) { + case this.INVOKE: { + const [seqid, method, param] = msg + const response = new Response(this, seqid) + this._serve({method, param, response}) + break + } + case this.NOTIFY: { + const [method, param] = msg + this._serve({method, param}) + break + } + case this.RESPONSE: { + const [seqid, error, result] = msg + this._dispatch_handle_response({seqid, error, result}) + break + } + case this.INVOKE_COMPRESSED: { + const [seqid, ctype, method, param] = msg + const uncompressed = uncompress(ctype, param) + const response = new Response(this, seqid, ctype) + this._serve({method, param: uncompressed, response}) + break + } + default: + this._warn(`Unknown message type: ${type}`) + } + } + } + + _dispatch_handle_response({seqid, error, result}: {seqid: number; error: any; result: any}): void { + this._call_cb({seqid, error, result}) + } + + _call_cb({seqid, error, result}: {seqid: number; error: any; result: any}): void { + const cb = this._invocations[seqid] + if (cb) { + delete this._invocations[seqid] + error = this.unwrap_incoming_error(error) + cb(error, result) + } + } + + cancel(seqid: number): void { + this._call_cb({seqid, error: 'cancelled', result: null}) + } + + _next_seqid(): number { + const ret = this._seqid + this._seqid++ + return ret + } + + make_method(prog: string | null | undefined, meth: string): string { + return prog ? [prog, meth].join('.') : meth + } + + respond(seqid: number, error: any, result: any): void { + const msg = [this.RESPONSE, seqid, error, result] + this.send(msg) + } + + invoke( + {program, ctype, method, args, notify}: InvokeArgs, + _cb?: (error: any, result: any) => void, + out?: {cancel?: () => void} + ): Promise<{error: any; result: any}> { + method = this.make_method(program, method) + + const seqid = this._next_seqid() + + let type: number + let dtype: number + if (notify) { + type = this.NOTIFY + dtype = dbg.constants.type.CLIENT_NOTIFY + } else if (ctype != null) { + type = this.INVOKE_COMPRESSED + dtype = dbg.constants.type.CLIENT_INVOKE_COMPRESSED + } else { + type = this.INVOKE + dtype = dbg.constants.type.CLIENT_INVOKE + } + + let msg: any[] + if (ctype != null) { + args = compress(ctype, args) + msg = [type, seqid, ctype, method, args] + } else { + msg = [type, seqid, method, args] + } + + let debug_msg: dbg.Message | undefined + if (this._dbgr) { + debug_msg = this._dbgr.new_message({ + method, + seqid, + ctype, + arg: args, + dir: dbg.constants.dir.OUTGOING, + remote: this.remote_address(), + port: this.remote_port(), + type: dtype, + }) + debug_msg.call() + } + + // Down to the packetizer, which will jump back up to the Transport! + this.send(msg) + + if (notify) { + return Promise.resolve({error: undefined, result: undefined}) + } + + return new Promise<{error: any; result: any}>(resolve => { + if (out) { + out.cancel = () => this.cancel(seqid) + } + + this._invocations[seqid] = (error: any, result: any) => { + if (ctype != null && !error) { + result = uncompress(ctype, result) + } + if (debug_msg) debug_msg.response(error, result).call() + resolve({error, result}) + } + }) + } + + _dispatch_reset(): void { + const inv = this._invocations + this._invocations = {} + for (const key of Object.keys(inv)) { + inv[Number(key)](new E.EofError(), {}) + } + } + + _serve({method, param, response}: ServeArgs): void { + const pair = this.get_handler_pair(method) + + if (this._dbgr) { + const debug_msg = this._dbgr.new_message({ + method, + seqid: response?.seqid, + arg: param, + dir: dbg.constants.dir.INCOMING, + remote: this.remote_address(), + port: this.remote_port(), + type: dbg.constants.type.SERVER, + err: pair ? null : 'unknown method', + }) + + if (response) response.debug_msg = debug_msg + debug_msg.call() + } + + if (this._generic_handler != null) { + this._generic_handler({method, param, response, dispatch: this}) + } else if (pair != null && this.get_hook_wrapper() != null) { + this.get_hook_wrapper()!({method: pair[1], thisobj: pair[0], param, response, dispatch: this}) + } else if (pair) { + pair[1].call(pair[0], param, response, this) + } else if (response != null) { + const err = new E.UnknownMethodError(method) + err.method = method + response.error(this.wrap_outgoing_error(err)) + } + } + + // please override me! + get_handler_this(_m: string): any { + return this + } + + get_hook_wrapper(): ((args: HookWrapperArgs) => void) | null { + return null + } + + wrap_outgoing_error(s: any): any { + return s.toString() + } + + unwrap_incoming_error(s: any): any { + return typeof s === 'string' ? new Error(s) : s + } + + // please override me! + get_handler_pair(m: string): [any, Function] | null { + const h = this._handlers[m] + if (h) return [this.get_handler_this(m), h] + return null + } + + add_handler(method: string, hook: Function, program: string | null = null): void { + method = this.make_method(program, method) + this._handlers[method] = hook + } + + add_program(program: string, hooks: Record): void { + for (const [method, hook] of Object.entries(hooks)) { + this.add_handler(method, hook, program) + } + } + + add_programs(programs: Record>): void { + for (const [program, hooks] of Object.entries(programs)) { + this.add_program(program, hooks) + } + } + + abstract remote_address(): string | null + abstract remote_port(): number | null + abstract _warn(e: any): void +} diff --git a/src/errors.iced b/src/errors.iced deleted file mode 100644 index 85b5454..0000000 --- a/src/errors.iced +++ /dev/null @@ -1,11 +0,0 @@ - -ie = require 'iced-error' - -module.exports = E = ie.make_errors - UNKNOWN_METHOD : "No method available" - EOF : "EOF from server" - -# Specify a toString() method to be compatible with the old version of our error -E.UnknownMethodError.prototype.toString = () -> "unknown method: #{@method}" - - diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..4ecab30 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,26 @@ +export const UNKNOWN_METHOD = 100 +export const EOF = 101 + +export class EofError extends Error { + code: number + constructor(message?: string) { + super(message || 'EOF from server') + this.name = 'EofError' + this.code = EOF + } +} + +export class UnknownMethodError extends Error { + code: number + method: string + constructor(method: string) { + super(`unknown method: ${method}`) + this.name = 'UnknownMethodError' + this.code = UNKNOWN_METHOD + this.method = method + } + + toString(): string { + return `unknown method: ${this.method}` + } +} diff --git a/src/iced.iced b/src/iced.iced deleted file mode 100644 index 38133b8..0000000 --- a/src/iced.iced +++ /dev/null @@ -1,2 +0,0 @@ - -exports.runtime = require('iced-runtime') diff --git a/src/list.iced b/src/list.iced deleted file mode 100644 index 7d224c3..0000000 --- a/src/list.iced +++ /dev/null @@ -1,45 +0,0 @@ - -##======================================================================= - -exports.List = class List - - #----------------------------------------- - - constructor: -> - @_head = null - @_tail = null - - #----------------------------------------- - - push : (o) -> - o.__list_prev = @_tail - o.__list_next = null - @_tail.__list_next = o if @_tail - @_tail = o - @_head = o unless @_head - - #----------------------------------------- - - walk : (fn) -> - p = @_head - while p - next = p.__list_next - fn p - p = next - - #----------------------------------------- - - remove : (w) -> - next = w.__list_next - prev = w.__list_prev - - if prev then prev.__list_next = next - else @_head = next - - if next then next.__list_prev = prev - else @_tail = prev - - w.__list_next = null - w.__list_prev = null - -##======================================================================= diff --git a/src/list.ts b/src/list.ts new file mode 100644 index 0000000..a007b5d --- /dev/null +++ b/src/list.ts @@ -0,0 +1,40 @@ +export interface ListNode { + __list_prev: ListNode | null + __list_next: ListNode | null +} + +export class List { + private _head: T | null = null + private _tail: T | null = null + + push(o: T): void { + o.__list_prev = this._tail + o.__list_next = null + if (this._tail) this._tail.__list_next = o + this._tail = o + if (!this._head) this._head = o + } + + walk(fn: (node: T) => void): void { + let p = this._head + while (p) { + const next = p.__list_next as T | null + fn(p) + p = next + } + } + + remove(w: T): void { + const next = w.__list_next as T | null + const prev = w.__list_prev as T | null + + if (prev) prev.__list_next = next + else this._head = next + + if (next) next.__list_prev = prev + else this._tail = prev + + w.__list_next = null + w.__list_prev = null + } +} diff --git a/src/listener.iced b/src/listener.iced deleted file mode 100644 index d40a125..0000000 --- a/src/listener.iced +++ /dev/null @@ -1,182 +0,0 @@ - -net = require 'net' -tls = require 'tls' -{Transport} = require './transport' -{List} = require './list' -log = require './log' -dbg = require './debug' - -iced = require('./iced').runtime - -##======================================================================= - -exports.Listener = class Listener - - ##----------------------------------------- - - constructor : ({@port, @host, @path, @TransportClass, log_obj, @tls_opts}) -> - @TransportClass = Transport unless @TransportClass - @set_logger log_obj - @_children = new List - @_dbgr = null - - ##----------------------------------------- - - _default_logger : -> - l = log.new_default_logger() - l.set_prefix "RPC-Server" - h = @host or "0.0.0.0" - if @port? - l.set_remote "#{h}:#{@port}" - else if @path? - l.set_remote @path - return l - - ##----------------------------------------- - - # You actually don't want to apply this to children, - # since the children will need different loggers and therefore - # different debuggers. - set_debugger : (d) -> - @_dbgr = d - - ##----------------------------------------- - - set_debug_flags : (f, apply_to_children) -> - @set_debugger dbg.make_debugger f, @log_obj - if apply_to_children - @walk_children (c) => c.set_debug_flags f - - ##----------------------------------------- - - set_logger : (o) -> - o = @_default_logger() unless o? - @log_obj = o - - ##----------------------------------------- - - # Feel free to change this for your needs (if you want to wrap a connection - # with something else)... - make_new_transport : (c) -> - # Disable Nagle by default - c.setNoDelay true unless @do_tcp_delay - - x = new @TransportClass - net_stream : c - host : c.remoteAddress - port : c.remotePort - parent : @ - log_obj : @make_new_log_object c - dbgr : @_dbgr - @_children.push x - return x - - ##----------------------------------------- - - make_new_log_object : (c) -> - a = c.address() - r = [ c.address, c.port ].join ":" - @log_obj.make_child { prefix : "RPC", remote : r } - - ##----------------------------------------- - - walk_children : (fn) -> @_children.walk fn - - ##----------------------------------------- - - close_child : (c) -> @_children.remove c - - ##----------------------------------------- - - set_port : (p) -> - @port = p - - ##----------------------------------------- - - _got_new_connection : (c) -> - # Call down to a subclass - x = @make_new_transport c - - # pure virtual, that a Server-like class will implement - @got_new_connection x - - ##----------------------------------------- - - got_new_connection : (x) -> - throw new Error "@got_new_connection() is pure virtual; please implement!" - - ##----------------------------------------- - - _make_server : () -> - # The presence of tls_opts determines we do a regular net.createServer or a - # tls.createServer. - if @tls_opts? - @_net_server = tls.createServer @tls_opts, (c) => @_got_new_connection c - else - @_net_server = net.createServer (c) => @_got_new_connection c - - ##----------------------------------------- - - close : (cb) -> - await @_net_server.close defer() if @_net_server - @_net_server = null - cb() - - ##----------------------------------------- - - handle_close : () -> - @log_obj.info "listener closing down" - - ##----------------------------------------- - - # A sensible default handler - handle_error : (err) -> - @_net_server = null - @log_obj.error "error in listener: #{err}" - - ##----------------------------------------- - - _set_hooks : () -> - @_net_server.on 'error', (err) => @handle_error err - @_net_server.on 'close', (err) => @handle_close() - - ##----------------------------------------- - - listen : (cb) -> - @_make_server() - - [ OK, ERR ] = [0..1] - rv = new iced.Rendezvous - x = @_net_server - if @port? then x.listen @port, @host - else x.listen @path - - x.on 'error', rv.id(ERR).defer err - x.on 'listening', rv.id(OK).defer() - - await rv.wait defer which - if which is OK - err = null - @_set_hooks() - else - @log_obj.error err - @_net_server = null - - cb err - - ##----------------------------------------- - - # specify a delay in seconds, and retry every that many seconds - # if it fails, in a loop. - listen_retry : (delay, cb) -> - go = true - err = null - while go - await @listen defer err - if err?.code == 'EADDRINUSE' - @log_obj.warn err - await setTimeout defer(), delay*1000 - else go = false - cb err - - diff --git a/src/listener.ts b/src/listener.ts new file mode 100644 index 0000000..490b947 --- /dev/null +++ b/src/listener.ts @@ -0,0 +1,190 @@ +import net from 'net' +import tls from 'tls' +import {Transport} from './transport' +import type {TransportOpts} from './transport' +import {List, type ListNode} from './list' +import * as log from './log' +import * as dbg from './debug' + +export class Listener { + port?: number + host?: string + path?: string + TransportClass: new (opts: TransportOpts) => Transport + log_obj!: log.Logger + tls_opts?: Record + do_tcp_delay?: boolean + _children: List + _dbgr: dbg.Debugger | null = null + _net_server: net.Server | null = null + + constructor({ + port, + host, + path, + TransportClass, + log_obj, + tls_opts, + }: { + port?: number + host?: string + path?: string + TransportClass?: new (opts: TransportOpts) => Transport + log_obj?: log.Logger + tls_opts?: Record + }) { + this.port = port + this.host = host + this.path = path + this.TransportClass = TransportClass || (Transport as any) + this.tls_opts = tls_opts + this._children = new List() + this.set_logger(log_obj) + } + + _default_logger(): log.Logger { + const l = log.new_default_logger() + l.set_prefix('RPC-Server') + const h = this.host || '0.0.0.0' + if (this.port != null) { + l.set_remote(`${h}:${this.port}`) + } else if (this.path != null) { + l.set_remote(this.path) + } + return l + } + + set_debugger(d: dbg.Debugger | null): void { + this._dbgr = d + } + + set_debug_flags(f: number | string, apply_to_children?: boolean): void { + this.set_debugger(dbg.make_debugger(f, this.log_obj)) + if (apply_to_children) { + this.walk_children((c: any) => c.set_debug_flags(f)) + } + } + + set_logger(o?: log.Logger): void { + if (!o) o = this._default_logger() + this.log_obj = o + } + + make_new_transport(c: net.Socket | tls.TLSSocket): Transport & ListNode { + if (!this.do_tcp_delay) c.setNoDelay(true) + + const x = new this.TransportClass({ + net_stream: c, + host: (c as net.Socket).remoteAddress, + port: (c as net.Socket).remotePort, + parent: this, + log_obj: this.make_new_log_object(c), + dbgr: this._dbgr, + }) + this._children.push(x as Transport & ListNode) + return x as Transport & ListNode + } + + make_new_log_object(c: net.Socket | tls.TLSSocket): log.Logger { + const r = [(c as net.Socket).remoteAddress, (c as net.Socket).remotePort].join(':') + return this.log_obj.make_child({prefix: 'RPC', remote: r}) + } + + walk_children(fn: (node: Transport & ListNode) => void): void { + this._children.walk(fn) + } + + close_child(c: Transport & ListNode): void { + this._children.remove(c) + } + + set_port(p: number): void { + this.port = p + } + + _got_new_connection(c: net.Socket | tls.TLSSocket): void { + const x = this.make_new_transport(c) + this.got_new_connection(x) + } + + got_new_connection(_x: Transport): void { + throw new Error('got_new_connection() is pure virtual; please implement!') + } + + _make_server(): void { + if (this.tls_opts) { + this._net_server = tls.createServer(this.tls_opts, (c: tls.TLSSocket) => this._got_new_connection(c)) + } else { + this._net_server = net.createServer((c: net.Socket) => this._got_new_connection(c)) + } + } + + async close(): Promise { + if (this._net_server) { + await new Promise(resolve => this._net_server!.close(() => resolve())) + this._net_server = null + } + } + + handle_close(): void { + this.log_obj.info('listener closing down') + } + + handle_error(err: Error): void { + this._net_server = null + this.log_obj.error(`error in listener: ${err}`) + } + + _set_hooks(): void { + this._net_server!.on('error', (err: Error) => this.handle_error(err)) + this._net_server!.on('close', () => this.handle_close()) + } + + async listen(): Promise { + this._make_server() + + return new Promise((resolve, reject) => { + const x = this._net_server! + + const onError = (err: Error) => { + x.removeListener('listening', onListening) + this.log_obj.error(err) + this._net_server = null + reject(err) + } + + const onListening = () => { + x.removeListener('error', onError) + this._set_hooks() + resolve() + } + + x.once('error', onError) + x.once('listening', onListening) + + if (this.port != null) { + x.listen(this.port, this.host) + } else { + x.listen(this.path) + } + }) + } + + async listen_retry(delay: number): Promise { + let go = true + while (go) { + try { + await this.listen() + go = false + } catch (err: any) { + if (err?.code === 'EADDRINUSE') { + this.log_obj.warn(err) + await new Promise(resolve => setTimeout(resolve, delay * 1000)) + } else { + go = false + throw err + } + } + } + } +} diff --git a/src/lock.iced b/src/lock.iced deleted file mode 100644 index a0394d0..0000000 --- a/src/lock.iced +++ /dev/null @@ -1,2 +0,0 @@ - -module.exports = require('iced-lock') diff --git a/src/lock.ts b/src/lock.ts new file mode 100644 index 0000000..9c0efd7 --- /dev/null +++ b/src/lock.ts @@ -0,0 +1,23 @@ +export class Lock { + private _locked: boolean = false + private _waiters: Array<() => void> = [] + + acquire(): Promise { + if (!this._locked) { + this._locked = true + return Promise.resolve() + } + return new Promise(resolve => { + this._waiters.push(resolve) + }) + } + + release(): void { + if (this._waiters.length > 0) { + const next = this._waiters.shift()! + next() + } else { + this._locked = false + } + } +} diff --git a/src/log.iced b/src/log.iced deleted file mode 100644 index f8e0cfb..0000000 --- a/src/log.iced +++ /dev/null @@ -1,69 +0,0 @@ -# -# The standard logger for saying that things went wrong, or state changed, -# inside the RPC system. You can of course change this to be whatever you'd -# like via the @log_obj member of the Transport class. -# - -exports.levels = L = - NONE : 0 - DEBUG : 1 - INFO : 2 - WARN : 3 - ERROR : 4 - FATAL : 5 - TOP : 6 - -default_level = L.INFO - -##======================================================================= - -stringify = (o) -> - if not o? then "" - else if o instanceof Uint8Array then new TextDecoder().decode(o) - else if Error.isError(o) then o.toString() - else ("" + o) - -##======================================================================= - -exports.Logger = class Logger - constructor : ({@prefix, @remote, @level}) -> - @prefix = "RPC" unless @prefix - @remote = "-" unless @remote - @output_hook = (m) -> console.log m - @level = if @level? then @level else default_level - - set_level : (l) -> @level = l - set_remote : (r) -> @remote = r - set_prefix : (p) -> @prefix = p - - debug : (m) -> @_log m, "D", null, L.DEBUG if @level <= L.DEBUG - info : (m) -> @_log m, "I", null, L.INFO if @level <= L.INFO - warn : (m) -> @_log m, "W", null, L.WARN if @level <= L.WARN - error : (m) -> @_log m, "E", null, L.ERROR if @level <= L.ERROR - fatal : (m) -> @_log m, "F", null, L.FATAL if @level <= L.FATAL - - _log : (m, l, ohook, level) -> - parts = [] - parts.push @prefix if @prefix? - parts.push "[#{l}]" if l - parts.push @remote if @remote - parts.push stringify m - ohook = @output_hook unless ohook - ohook (parts.join " "), level - - make_child : (d) -> return new Logger d - -##======================================================================= - -default_logger_class = Logger - -##======================================================================= - -exports.set_default_level = (l) -> default_level = l -exports.set_default_logger_class = (k) -> default_logger_class = k - -##======================================================================= - -exports.new_default_logger = (d = {}) -> return new default_logger_class d - -##======================================================================= diff --git a/src/log.ts b/src/log.ts new file mode 100644 index 0000000..d408081 --- /dev/null +++ b/src/log.ts @@ -0,0 +1,93 @@ +export const levels = { + NONE: 0, + DEBUG: 1, + INFO: 2, + WARN: 3, + ERROR: 4, + FATAL: 5, + TOP: 6, +} as const + +export type LogLevel = (typeof levels)[keyof typeof levels] + +let default_level: LogLevel = levels.INFO + +function stringify(o: unknown): string { + if (o == null) return '' + else if (o instanceof Uint8Array) return new TextDecoder().decode(o) + else if ((Error as any).isError ? (Error as any).isError(o) : o instanceof Error) return o.toString() + else return '' + o +} + +export class Logger { + prefix: string + remote: string + level: LogLevel + output_hook: (m: string, level?: LogLevel) => void + + constructor({prefix, remote, level}: {prefix?: string; remote?: string; level?: LogLevel} = {}) { + this.prefix = prefix || 'RPC' + this.remote = remote || '-' + this.output_hook = (m: string) => console.log(m) + this.level = level != null ? level : default_level + } + + set_level(l: LogLevel): void { + this.level = l + } + set_remote(r: string): void { + this.remote = r + } + set_prefix(p: string): void { + this.prefix = p + } + + debug(m: unknown): void { + if (this.level <= levels.DEBUG) this._log(m, 'D', null, levels.DEBUG) + } + info(m: unknown): void { + if (this.level <= levels.INFO) this._log(m, 'I', null, levels.INFO) + } + warn(m: unknown): void { + if (this.level <= levels.WARN) this._log(m, 'W', null, levels.WARN) + } + error(m: unknown): void { + if (this.level <= levels.ERROR) this._log(m, 'E', null, levels.ERROR) + } + fatal(m: unknown): void { + if (this.level <= levels.FATAL) this._log(m, 'F', null, levels.FATAL) + } + + _log( + m: unknown, + l: string | null, + ohook: ((m: string, level?: LogLevel) => void) | null, + level: LogLevel + ): void { + const parts: string[] = [] + if (this.prefix != null) parts.push(this.prefix) + if (l) parts.push(`[${l}]`) + if (this.remote) parts.push(this.remote) + parts.push(stringify(m)) + if (!ohook) ohook = this.output_hook + ohook(parts.join(' '), level) + } + + make_child(d: {prefix?: string; remote?: string; level?: LogLevel}): Logger { + return new Logger(d) + } +} + +let default_logger_class: new (d?: any) => Logger = Logger + +export function set_default_level(l: LogLevel): void { + default_level = l +} + +export function set_default_logger_class(k: new (d?: any) => Logger): void { + default_logger_class = k +} + +export function new_default_logger(d: {prefix?: string; remote?: string; level?: LogLevel} = {}): Logger { + return new default_logger_class(d) +} diff --git a/src/main.iced b/src/main.iced deleted file mode 100644 index 9cb69ea..0000000 --- a/src/main.iced +++ /dev/null @@ -1,39 +0,0 @@ - -exports.server = server = require './server' -exports.client = client = require './client' -exports.transport = transport = require './transport' -exports.log = log = require './log' -exports.debug = debug = require './debug' -exports.pack = pack = require './pack' -exports.errors = errors = require './errors' - -exports.dispatch = require './dispatch' -exports.listener = require './listener' - -exports.Server = server.Server -exports.SimpleServer = server.SimpleServer -exports.Client = client.Client -exports.Transport = transport.Transport -exports.RobustTransport = transport.RobustTransport -exports.Logger = log.Logger -exports.createTransport = transport.createTransport - -##======================================================================= -# Version management... - -exports.version = version = require('./version').version - -exports.at_version = (v) -> - A = version.split '.' - B = v.split '.' - while A.length and B.length - a = parseInt A.shift() - b = parseInt B.shift() - if a < b then return false - else if a > b then return true - if A.length is 0 and B.length > 0 then false - else if A.length > 0 and B.length is 0 then true - else true - -# -##======================================================================= diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..68a01b2 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,39 @@ +import * as server from './server' +import * as client from './client' +import * as transport from './transport' +import * as log from './log' +import * as debug from './debug' +import * as pack from './pack' +import * as errors from './errors' +import * as dispatch from './dispatch' +import * as listener from './listener' + +export {server, client, transport, log, debug, pack, errors, dispatch, listener} + +export const Server = server.Server +export const SimpleServer = server.SimpleServer +export const ContextualServer = server.ContextualServer +export const Handler = server.Handler +export const Client = client.Client +export const Transport = transport.Transport +export const RobustTransport = transport.RobustTransport +export const Logger = log.Logger +export const createTransport = transport.createTransport + +export {version} from './version' + +import {version} from './version' + +export function at_version(v: string): boolean { + const A = version.split('.') + const B = v.split('.') + while (A.length && B.length) { + const a = parseInt(A.shift()!) + const b = parseInt(B.shift()!) + if (a < b) return false + else if (a > b) return true + } + if (A.length === 0 && B.length > 0) return false + else if (A.length > 0 && B.length === 0) return true + return true +} diff --git a/src/pack.iced b/src/pack.iced deleted file mode 100644 index a63b296..0000000 --- a/src/pack.iced +++ /dev/null @@ -1,69 +0,0 @@ -try - mp = require 'msgpack' -catch e - -try - pp = require 'purepack' -catch e - -try - mpmp = require '@msgpack/msgpack' -catch e - -if not mp? and not pp? and not mpmp? - throw new Error "Need either msgpack, purepack, or @msgpack/msgpack to run" - -##============================================================================== - -_opts = {} - -exports.set_opt = set_opt = (k,v) -> _opts[k] = v -exports.set_opts = set_opts = (o) -> _opts = o -exports.get_encode_lib = get_encode_lib = () -> - encode_lib = _opts.encode_lib or "@msgpack/msgpack" - switch encode_lib - when "purepack", "msgpack", "@msgpack/msgpack" - return encode_lib - throw new Error "Unsupported encode library #{encode_lib}" - -# If we want to use byte arrays, we need purepack and not msgpack4 or msgpack! -exports.use_byte_arrays = () -> - if not pp? - try - encode_lib = get_encode_lib() - switch encode_lib - when "purepack" - pp = require "purepack" - when "msgpack" - throw new Error "not supported" - when "@msgpack/msgpack" - mpmp = require "@msgpack/msgpack" - catch err - throw new Error "Cannot use_byte_arrays without 'purepack' or '@msgpack/msgpack!'" - -exports.pack = (b) -> - encode_lib = get_encode_lib() - switch encode_lib - when "purepack" - return pp.pack b - when "msgpack" - return mp.pack b - when "@msgpack/msgpack" - return mpmp.encode(b) - -exports.unpack = (b) -> - err = dat = null - encode_lib = get_encode_lib() - switch encode_lib - when "purepack" - try dat = pp.unpack b - catch err - when "msgpack" - try dat = mp.unpack b - catch err - when "@msgpack/msgpack" - try dat = mpmp.decode b - catch err - [err, dat] - -##============================================================================== diff --git a/src/pack.ts b/src/pack.ts new file mode 100644 index 0000000..6de32c6 --- /dev/null +++ b/src/pack.ts @@ -0,0 +1,103 @@ +let mp: any = null +let pp: any = null +let mpmp: any = null + +try { + mp = require('msgpack') +} catch {} + +try { + pp = require('purepack') +} catch {} + +try { + mpmp = require('@msgpack/msgpack') +} catch {} + +if (!mp && !pp && !mpmp) { + throw new Error('Need either msgpack, purepack, or @msgpack/msgpack to run') +} + +let _opts: Record = {} + +export function set_opt(k: string, v: any): void { + _opts[k] = v +} + +export function set_opts(o: Record): void { + _opts = o +} + +export function get_encode_lib(): string { + const encode_lib = _opts.encode_lib || '@msgpack/msgpack' + switch (encode_lib) { + case 'purepack': + case 'msgpack': + case '@msgpack/msgpack': + return encode_lib + } + throw new Error(`Unsupported encode library ${encode_lib}`) +} + +export function use_byte_arrays(): void { + if (!pp) { + try { + const encode_lib = get_encode_lib() + switch (encode_lib) { + case 'purepack': + pp = require('purepack') + break + case 'msgpack': + throw new Error('not supported') + case '@msgpack/msgpack': + mpmp = require('@msgpack/msgpack') + break + } + } catch { + throw new Error("Cannot use_byte_arrays without 'purepack' or '@msgpack/msgpack!'") + } + } +} + +export function pack(b: any): Uint8Array { + const encode_lib = get_encode_lib() + switch (encode_lib) { + case 'purepack': + return pp.pack(b) + case 'msgpack': + return mp.pack(b) + case '@msgpack/msgpack': + return mpmp.encode(b) + } + throw new Error(`Unsupported encode library ${encode_lib}`) +} + +export function unpack(b: Uint8Array): [any, any] { + let err: any = null + let dat: any = null + const encode_lib = get_encode_lib() + switch (encode_lib) { + case 'purepack': + try { + dat = pp.unpack(b) + } catch (e) { + err = e + } + break + case 'msgpack': + try { + dat = mp.unpack(b) + } catch (e) { + err = e + } + break + case '@msgpack/msgpack': + try { + dat = mpmp.decode(b) + } catch (e) { + err = e + } + break + } + return [err, dat] +} diff --git a/src/packetizer.iced b/src/packetizer.iced deleted file mode 100644 index d69070a..0000000 --- a/src/packetizer.iced +++ /dev/null @@ -1,158 +0,0 @@ - -{unpack,pack} = require './pack' -{Ring} = require './ring' - -##======================================================================= - -# This is a hack of sorts, in which we've taken the important -# parts of the Msgpack spec for reading an int from the string. -msgpack_frame_len = (buf) -> - b = buf[0] - if b < 0x80 then 1 - else if b is 0xcc then 2 - else if b is 0xcd then 3 - else if b is 0xce then 5 - else 0 - -##======================================================================= - -is_array = (a) -> (typeof a is 'object') and Array.isArray a - -##======================================================================= - -exports.Packetizer = class Packetizer - """ - A packetizer that is used to read and write to an underlying stream - (like a Transport below). Should be inherited by such a class. - The subclasses should implement: - - @_raw_write(msg,enc) - write this msg to the stream with the - given encoding. Typically handled at the transport level - (2 classes higher in the inheritance graph) - - @_packetize_error(err) - report an error with the stream. Typically - calls up to the Transport class (2 classes higher). - - @_dispatch(msg) - emit a packetized incoming message. Typically - handled by the Dispatcher (1 class higher in the inheritance - graph). - - The subclass should call @packetize_data(m) whenever it has data to stuff - into the packetizer's input path, and call @send(m) whenever it wants - to stuff data into the packterizer's output path. - - """ - - # The two states we can be in - FRAME : 1 - DATA : 2 - - # results of getting - OK : 0 - WAIT : 1 - ERR : -1 - - ##----------------------------------------- - - constructor : -> - @_ring = new Ring() - @_state = @FRAME - @_next_msg_len = 0 - - ##----------------------------------------- - _buf_write : (b) -> - s = '' - for i in [0...b.length] - s += String.fromCharCode(b[i]) - @_raw_write s, 'binary' - - - send : (msg) -> - b2 = pack msg - b1 = pack b2.length - @_buf_write b1 - @_buf_write b2 - return true - - ##----------------------------------------- - - _get_frame : () -> - # We need at least one byte to get started - return @WAIT unless @_ring.len() > 0 - - # First get the frame's framing byte! This will tell us - # how many more bytes we need to grab. This is a bit of - # an abstraction violation, but one worth it for implementation - # simplicity and efficiency. - f0 = @_ring.grab 1 - return @WAIT unless f0 - - frame_len = msgpack_frame_len f0 - unless frame_len - @_packetize_error "Bad frame header received" - return @ERR - - # We now know how many bytes to suck in just to get the frame - # header. If we can't get that much, we'll just have to wait! - return @WAIT unless (f_full = @_ring.grab frame_len)? - - [w,r] = unpack f_full.subarray 0, frame_len - @_packetize_warning w if w? - - res = switch (typ = typeof r) - when 'number' - - # See implementation of msgpack_frame_len above; this shouldn't - # happen - throw new Error "Negative len #{len} should not have happened" if r < 0 - - @_ring.consume frame_len - @_next_msg_len = r - @_state = @DATA - @OK - when 'undefined' - @WAIT - else - @_packetize_error "bad frame; got type=#{typ}, which is wrong" - @ERR - - return res - - ##----------------------------------------- - - _get_msg: () -> - l = @_next_msg_len - - ret = if l > @_ring.len() or not (b = @_ring.grab l)? - @WAIT - else if not ([pw,msg] = unpack b.subarray 0, l)? or not msg? - @_packetize_error "bad encoding found in data/payload; len=#{l}" - @ERR - else if not is_array msg - @_packetize_error "non-array found in data stream: #{JSON.stringify msg}" - @ERR - else - @_ring.consume l - @_state = @FRAME - # Call down one level in the class hierarchy to the dispatcher - @_dispatch msg - @OK - @_packetize_warning pw if pw? - return ret - - ##----------------------------------------- - - packetize_data : (m) -> - @_ring.buffer m - go = @OK - while go is @OK - go = if @_state is @FRAME then @_get_frame() else @_get_msg() - - ##----------------------------------------- - - # On error we need to flush this guy out. - _packetizer_reset : () -> - @_state = @FRAME - @_ring = new Ring() - -##======================================================================= diff --git a/src/packetizer.ts b/src/packetizer.ts new file mode 100644 index 0000000..5bd62cf --- /dev/null +++ b/src/packetizer.ts @@ -0,0 +1,144 @@ +import {unpack, pack} from './pack' +import {Ring} from './ring' + +function msgpack_frame_len(buf: Uint8Array): number { + const b = buf[0] + if (b < 0x80) return 1 + else if (b === 0xcc) return 2 + else if (b === 0xcd) return 3 + else if (b === 0xce) return 5 + else return 0 +} + +function is_array(a: unknown): a is any[] { + return typeof a === 'object' && Array.isArray(a) +} + +// States +const FRAME = 1 +const DATA = 2 + +// Results +const OK = 0 +const WAIT = 1 +const ERR = -1 + +export abstract class Packetizer { + FRAME = FRAME + DATA = DATA + OK = OK + WAIT = WAIT + ERR = ERR + + private _ring: Ring + private _state: number + private _next_msg_len: number + + constructor() { + this._ring = new Ring() + this._state = FRAME + this._next_msg_len = 0 + } + + abstract _raw_write(msg: string, enc: string): void + abstract _packetize_error(err: string): void + abstract _dispatch(msg: any[]): void + + _packetize_warning?(w: string): void + + private _buf_write(b: Uint8Array): void { + let s = '' + for (let i = 0; i < b.length; i++) { + s += String.fromCharCode(b[i]) + } + this._raw_write(s, 'binary') + } + + send(msg: any): boolean { + const b2 = pack(msg) + const b1 = pack(b2.length) + this._buf_write(b1) + this._buf_write(b2) + return true + } + + private _get_frame(): number { + // We need at least one byte to get started + if (this._ring.len() <= 0) return WAIT + + // First get the frame's framing byte + const f0 = this._ring.grab(1) + if (!f0) return WAIT + + const frame_len = msgpack_frame_len(f0) + if (!frame_len) { + this._packetize_error('Bad frame header received') + return ERR + } + + // We now know how many bytes to suck in just to get the frame header + const f_full = this._ring.grab(frame_len) + if (f_full == null) return WAIT + + const [w, r] = unpack(f_full.subarray(0, frame_len)) + if (w != null && this._packetize_warning) this._packetize_warning(w) + + const typ = typeof r + switch (typ) { + case 'number': + if (r < 0) throw new Error(`Negative len ${r} should not have happened`) + this._ring.consume(frame_len) + this._next_msg_len = r + this._state = DATA + return OK + case 'undefined': + return WAIT + default: + this._packetize_error(`bad frame; got type=${typ}, which is wrong`) + return ERR + } + } + + private _get_msg(): number { + const l = this._next_msg_len + + let ret: number + if (l > this._ring.len()) { + ret = WAIT + } else { + const b = this._ring.grab(l) + if (b == null) { + ret = WAIT + } else { + const [pw, msg] = unpack(b.subarray(0, l)) + if (msg == null) { + this._packetize_error(`bad encoding found in data/payload; len=${l}`) + ret = ERR + } else if (!is_array(msg)) { + this._packetize_error(`non-array found in data stream: ${JSON.stringify(msg)}`) + ret = ERR + } else { + this._ring.consume(l) + this._state = FRAME + this._dispatch(msg) + ret = OK + } + if (pw != null && this._packetize_warning) this._packetize_warning(pw) + } + } + return ret + } + + packetize_data(m: Uint8Array): void { + this._ring.buffer(m) + let go = OK + while (go === OK) { + go = this._state === FRAME ? this._get_frame() : this._get_msg() + } + } + + _packetizer_reset(): void { + this._state = FRAME + this._ring = new Ring() + } +} diff --git a/src/ring.iced b/src/ring.iced deleted file mode 100644 index a8629a2..0000000 --- a/src/ring.iced +++ /dev/null @@ -1,70 +0,0 @@ -##======================================================================= - -exports.Ring = class Ring - """ - A Ring of buffers. Every so often you'll have to compress buffers into - smaller buffers, but try to limit that as much as possible.... - """ - - #----------------------------------------- - - constructor : -> - @_bufs = [] - @_len = 0 - - #----------------------------------------- - - buffer : (b) -> - @_bufs.push b - @_len += b.length - - #----------------------------------------- - - len : () -> @_len - - #----------------------------------------- - - grab : (n_wanted) -> - - # Fail fast if there just aren't enough bytes... - return null unless n_wanted <= @len() - - # fast-path is that we're already set up - return @_bufs[0] if @_bufs.length and @_bufs[0].length >= n_wanted - - n_grabbed = 0 - - num_bufs = 0 - for b in @_bufs - n_grabbed += b.length - num_bufs++ - if n_grabbed >= n_wanted - break - - # now make a buffer that's potentially bigger than what we wanted - ret = new Uint8Array n_grabbed - n = 0 - - # now copy all of those num_bufs into ret - for b in @_bufs[0...num_bufs] - sub = ret.subarray n, n + b.length - sub.set b - n += b.length - - # this first buffer that we'll be keeping (the returned buffer) - first_pos = num_bufs - 1 - @_bufs[first_pos] = ret - @_bufs.splice 0, first_pos - - return ret - - #----------------------------------------- - - consume : (n) -> - if @_bufs.length is 0 or (b = @_bufs[0]).length < n - throw new Error "Ring underflow; can't remove #{n} bytes" - if b.length == n - @_bufs.splice 0, 1 - else - @_bufs[0] = b.subarray(n) - @_len -= n diff --git a/src/ring.ts b/src/ring.ts new file mode 100644 index 0000000..3d6a5f7 --- /dev/null +++ b/src/ring.ts @@ -0,0 +1,69 @@ +/** + * A Ring of buffers. Every so often you'll have to compress buffers into + * smaller buffers, but try to limit that as much as possible. + */ +export class Ring { + private _bufs: Uint8Array[] = [] + private _len: number = 0 + + buffer(b: Uint8Array): void { + this._bufs.push(b) + this._len += b.length + } + + len(): number { + return this._len + } + + grab(n_wanted: number): Uint8Array | null { + // Fail fast if there just aren't enough bytes + if (n_wanted > this.len()) return null + + // fast-path is that we're already set up + if (this._bufs.length && this._bufs[0].length >= n_wanted) { + return this._bufs[0] + } + + let n_grabbed = 0 + let num_bufs = 0 + + for (const b of this._bufs) { + n_grabbed += b.length + num_bufs++ + if (n_grabbed >= n_wanted) { + break + } + } + + // now make a buffer that's potentially bigger than what we wanted + const ret = new Uint8Array(n_grabbed) + let n = 0 + + // now copy all of those num_bufs into ret + for (const b of this._bufs.slice(0, num_bufs)) { + const sub = ret.subarray(n, n + b.length) + sub.set(b) + n += b.length + } + + // this first buffer that we'll be keeping (the returned buffer) + const first_pos = num_bufs - 1 + this._bufs[first_pos] = ret + this._bufs.splice(0, first_pos) + + return ret + } + + consume(n: number): void { + if (this._bufs.length === 0 || this._bufs[0].length < n) { + throw new Error(`Ring underflow; can't remove ${n} bytes`) + } + const b = this._bufs[0] + if (b.length === n) { + this._bufs.splice(0, 1) + } else { + this._bufs[0] = b.subarray(n) + } + this._len -= n + } +} diff --git a/src/server.iced b/src/server.iced deleted file mode 100644 index 26f14f9..0000000 --- a/src/server.iced +++ /dev/null @@ -1,131 +0,0 @@ - -{Listener} = require './listener' - -##======================================================================= - -# Collect all methods that start with "h_"s. These are handler -# hooks and will automatically assume a program with this function -exports.collect_hooks = collect_hooks = (proto) -> - re = /^h_(.*)$/ - hooks = {} - for k,v of proto - if (m = k.match re)? - hooks[m[1]] = v - return hooks - -##======================================================================= - -exports.Server = class Server extends Listener - """This server is connection-centric. When the handlers of the - passed programs are invoked, the 'this' object to the handler will - be the Transport that's handling that client. This server is available - via this.parent. - - Note you can pass a TransportClass to use instead of the Transport. - It should be a subclass of Transport. - """ - - #----------------------------------------- - - constructor : (d) -> - super d - @programs = d.programs - - #----------------------------------------- - - got_new_connection : (c) -> - # c inherits from Dispatch, so it should have an add_programs - # method. We're just going to shove into it - c.add_programs @programs - -##======================================================================= - -exports.SimpleServer = class SimpleServer extends Listener - - constructor : (d) -> - super d - @_program = d.program - - get_hook_wrapper : () -> null - - got_new_connection : (c) -> - @_hooks = collect_hooks @ - c.add_program @get_program_name(), @_hooks - - set_program_name : (p) -> - @_program = p - - get_program_name : () -> - r = @_program - throw new Error "No 'program' given" unless r? - return r - - make_new_transport : (c) -> - x = super c - x.get_handler_this = (m) => @ - x.get_hook_wrapper = () => @get_hook_wrapper() - return x - -##======================================================================= - -# -# Your class can be much cooler, maybe this is where you put all of your -# application logic. -# -# If you put hooks of the form: -# -# h_foo : (arg,res) -> -# -# Then they will be automatically rolled up into a program, handled by -# this class. -# -exports.Handler = class Handler - constructor : ({@transport, @server}) -> - - # Collect all methods that start with "h_"s. These are handler - # hooks and will automatically assume a program with this function - @collect_hooks : () -> collect_hooks @prototype - -##======================================================================= - -exports.ContextualServer = class ContextualServer extends Listener - """This exposes a slightly different object as `this` to RPC - handlers -- in this case, it a Handler object that points to be both - the parent server, and also the child transport. So both are accessible - via 'has-a' rather than 'is-a' relationships.""" - - constructor : (d) -> - super d - @programs = {} - @classes = d.classes - for n,klass of @classes - @programs[n] = klass.collect_hooks() - - #----------------------------------------- - - got_new_connection : (c) -> - # c inherits from Dispatch, so it should have an add_programs - # method. We're just going to shove into it - c.add_programs @programs - - #----------------------------------------- - - make_new_transport : (c) -> - x = super c - - ctx = {} - for n,klass of @classes - ctx[n] = new klass { transport : x, server: @ } - - # This is sort of a hack, but it should work and override the - # prototype. The alternative is to bubble classes up and down the - # class hierarchy, but this is much less code. - x.get_handler_this = (m) -> - pn = m.split(".")[0...-1].join(".") - # This really ought not happen - throw new Error "Couldn't find prog #{pn}" unless (obj = ctx[pn])? - return obj - - return x - -##======================================================================= diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..dd09829 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,142 @@ +import {Listener} from './listener' +import {Transport} from './transport' +import type {TransportOpts} from './transport' +import type {ListNode} from './list' +import type {HookWrapperArgs} from './dispatch' +import net from 'net' +import tls from 'tls' + +export function collect_hooks(proto: any): Record { + const re = /^h_(.*)$/ + const hooks: Record = {} + for (const k of Object.getOwnPropertyNames(proto)) { + const m = k.match(re) + if (m) { + hooks[m[1]] = proto[k] + } + } + return hooks +} + +export class Server extends Listener { + programs: Record> + + constructor(d: { + port?: number + host?: string + path?: string + TransportClass?: new (opts: TransportOpts) => Transport + log_obj?: any + tls_opts?: Record + programs: Record> + }) { + super(d) + this.programs = d.programs + } + + got_new_connection(c: Transport): void { + c.add_programs(this.programs) + } +} + +export class SimpleServer extends Listener { + _program: string | undefined + _hooks: Record | undefined + + constructor(d: { + port?: number + host?: string + path?: string + TransportClass?: new (opts: TransportOpts) => Transport + log_obj?: any + tls_opts?: Record + program?: string + }) { + super(d) + this._program = d.program + } + + get_hook_wrapper(): ((args: HookWrapperArgs) => void) | null { + return null + } + + got_new_connection(c: Transport): void { + this._hooks = collect_hooks(Object.getPrototypeOf(this)) + c.add_program(this.get_program_name(), this._hooks) + } + + set_program_name(p: string): void { + this._program = p + } + + get_program_name(): string { + const r = this._program + if (r == null) throw new Error("No 'program' given") + return r + } + + make_new_transport(c: net.Socket | tls.TLSSocket): Transport & ListNode { + const x = super.make_new_transport(c) + const self = this + ;(x as any).get_handler_this = (_m: string) => self + ;(x as any).get_hook_wrapper = () => self.get_hook_wrapper() + return x + } +} + +export class Handler { + transport: Transport + server: Listener + + constructor({transport, server}: {transport: Transport; server: Listener}) { + this.transport = transport + this.server = server + } + + static collect_hooks(): Record { + return collect_hooks(this.prototype) + } +} + +export class ContextualServer extends Listener { + programs: Record> = {} + classes: Record + + constructor(d: { + port?: number + host?: string + path?: string + TransportClass?: new (opts: TransportOpts) => Transport + log_obj?: any + tls_opts?: Record + classes: Record + }) { + super(d) + this.classes = d.classes + for (const [n, klass] of Object.entries(this.classes)) { + this.programs[n] = klass.collect_hooks() + } + } + + got_new_connection(c: Transport): void { + c.add_programs(this.programs) + } + + make_new_transport(c: net.Socket | tls.TLSSocket): Transport & ListNode { + const x = super.make_new_transport(c) + + const ctx: Record = {} + for (const [n, klass] of Object.entries(this.classes)) { + ctx[n] = new klass({transport: x, server: this}) + } + + ;(x as any).get_handler_this = (m: string) => { + const pn = m.split('.').slice(0, -1).join('.') + const obj = ctx[pn] + if (!obj) throw new Error(`Couldn't find prog ${pn}`) + return obj + } + + return x + } +} diff --git a/src/timer.iced b/src/timer.iced deleted file mode 100644 index f3cff97..0000000 --- a/src/timer.iced +++ /dev/null @@ -1,28 +0,0 @@ - - -exports.time = time = () -> (new Date()).getTime() - -exports.Timer = class Timer - constructor : (opts) -> - @reset() - @start() if opts?.start - - start : () -> - if not @_running - @_ts = time() - @_running = true - - stop : () -> - if @_running - @_total += (time() - @_ts) - @_running = false - @_total - - is_running : () -> @_running - - total : () -> @_total - - reset : () -> - @_running = false - @_total = 0 - @_ts = 0 diff --git a/src/timer.ts b/src/timer.ts new file mode 100644 index 0000000..ac89e8a --- /dev/null +++ b/src/timer.ts @@ -0,0 +1,43 @@ +export function time(): number { + return new Date().getTime() +} + +export class Timer { + private _running: boolean = false + private _total: number = 0 + private _ts: number = 0 + + constructor(opts?: {start?: boolean}) { + this.reset() + if (opts?.start) this.start() + } + + start(): void { + if (!this._running) { + this._ts = time() + this._running = true + } + } + + stop(): number { + if (this._running) { + this._total += time() - this._ts + this._running = false + } + return this._total + } + + is_running(): boolean { + return this._running + } + + total(): number { + return this._total + } + + reset(): void { + this._running = false + this._total = 0 + this._ts = 0 + } +} diff --git a/src/transport.iced b/src/transport.iced deleted file mode 100644 index c33da69..0000000 --- a/src/transport.iced +++ /dev/null @@ -1,492 +0,0 @@ -net = require 'net' -tls = require 'tls' -pp = require 'path-parse' -process = require 'process' -{Lock} = require './lock' -{Dispatch} = require './dispatch' -log = require './log' -{Timer} = require './timer' -iced = require('./iced').runtime -dbg = require './debug' - -##======================================================================= - -# -# A shared wrapper object for which close is idempotent -# -class StreamWrapper - constructor : (@_net_stream, @_parent) -> - @_generation = @_parent.next_generation() - @_write_closed_warn = false - - # Return true if we did the actual close, and false otherwise - close : -> - ret = false - if (x = @_net_stream)? - ret = true - @_net_stream = null - @_parent._dispatch_reset() - @_parent._packetizer_reset() - x.end() - return ret - - write : (msg, enc) -> - if @_net_stream - @_net_stream.write msg, enc - else if not @_write_closed_warn - @_write_closed_warn = true - @_parent._warn "write on closed socket..." - - stream : -> @_net_stream - is_connected : -> !! @_net_stream - get_generation : -> @_generation - - remote_address : () -> - if @_net_stream then @_net_stream.remoteAddress else null - remote_port : () -> - if @_net_stream then @_net_stream.remotePort else null - - -##======================================================================= - -exports.Transport = class Transport extends Dispatch - - ##----------------------------------------- - # Public API - # - - constructor : ({ @port, @host, @net_opts, net_stream, @log_obj, - @parent, @do_tcp_delay, @hooks, dbgr, @path, - @tls_opts, connect_timeout}) -> - super - - @host = "localhost" if not @host or @host is "-" - @net_opts = {} unless @net_opts - @net_opts.host = @host - @net_opts.port = @port - @net_opts.path = @path - @_explicit_close = false - - @_remote_str = [ @host, @port].join ":" - @set_logger @log_obj - - @_lock = new Lock() - @_generation = 1 - - @_dbgr = dbgr - - # Give up on a connection after 10s timeout - @_connect_timeout = connect_timeout or 10*1000 - - # We don't store the TCP stream directly, but rather, sadly, - # a **wrapper** around the TCP stream. This is to make stream - # closes idempotent. The case we want to avoid is two closes - # on us (due to errors), but the second closing the reconnected - # stream, rather than the original stream. This level of - # indirection solves this. - @_netw = null - - # potentially set @_netw to be non-null - @_activate_stream net_stream if net_stream - - ##----------------------------------------- - - set_debugger : (d) -> @_dbgr = d - - ##--------------------------------------- - - set_debug_flags : (d) -> - @set_debugger dbg.make_debugger d, @log_obj - - ##----------------------------------------- - - next_generation : () -> - """To be called by StreamWrapper objects but not by - average users.""" - ret = @_generation - @_generation++ - return ret - - ##----------------------------------------- - - get_generation : () -> if @_netw then @_netw.get_generation() else -1 - - ##----------------------------------------- - - remote_address : () -> if @_netw? then @_netw.remote_address() else null - remote_port : () -> if @_netw? then @_netw.remote_port() else null - - ##----------------------------------------- - - set_logger : (o) -> - o = log.new_default_logger() unless o - @log_obj = o - @log_obj.set_remote @_remote_str - - ##----------------------------------------- - - get_logger : () -> @log_obj - - ##----------------------------------------- - - is_connected : () -> @_netw?.is_connected() - - ##----------------------------------------- - - connect : (cb) -> - await @_lock.acquire defer() - if not @is_connected() - await @_connect_critical_section defer err - else - err = null - @_lock.release() - cb err if cb - @_reconnect true if err? - - ##----------------------------------------- - - reset : (w) -> - w = @_netw unless w - @_close w - - ##----------------------------------------- - - close : () -> - @_explicit_close = true - if @_netw - @_netw.close() - @_netw = null - - # - # /Public API - ##--------------------------------------------------- - - _warn : (e) -> @log_obj.warn e - _info : (e) -> @log_obj.info e - _fatal : (e) -> @log_obj.fatal e - _debug : (e) -> @log_obj.debug e - _error : (e) -> @log_obj.error e - - ##----------------------------------------- - - _close : (netw) -> - # If an optional close hook was specified, call it here... - @hooks?.eof? netw - @_reconnect false if netw?.close() - - ##----------------------------------------- - - _handle_error : (e, netw) -> - @_error e - @_close netw - - ##----------------------------------------- - - _packetize_error : (err) -> - # I think we'll always have the right TCP stream here - # if we grab the one in the this object. A packetizer - # error will happen before any errors in the underlying - # stream - @_handle_error "In packetizer: #{err}", @_netw - - _packetize_warning : (w) -> - @_warn "In packetizer: #{w}" - - ##----------------------------------------- - - _handle_close : (netw) -> - @_info "EOF on transport" unless @_explicit_close - @_close netw - - # for TCP connections that are children of Listeners, - # we close the connection here and disassociate - @parent.close_child @ if @parent - - ##----------------------------------------- - - # In other classes we can override this... - # See 'RobustTransport' - _reconnect : (first_time) -> null - - ##----------------------------------------- - - _activate_stream : (x) -> - - @_info "connection established" - - - # The current generation needs to be wrapped into this hook; - # this way we don't close the next generation of connection - # in the case of a reconnect.... - w = new StreamWrapper x, @ - @_netw = w - - # If optional hooks were specified, call them here; give as an - # argument the new StreamWrapper so that way the subclass can - # issue closes on the connection - @hooks?.connected w - - # - # MK 2012/12/20 -- Revisit me! - # - # It if my current belief that we don't have to listen to the event - # 'end', because a 'close' event will always follow it, and we do - # act on the close event. The distance between the two gives us - # the time to act on a TCP-half-close, which we are not doing. - # So for now, we are going to ignore the 'end' and just act - # on the 'close'. - # - x.on 'error', (err) => @_handle_error err, w - x.on 'close', () => @_handle_close w - x.on 'data', (msg) => @packetize_data new Uint8Array msg.buffer, msg.byteOffset, msg.byteLength - - ##----------------------------------------- - - _connect_critical_section : (cb) -> - # The presence of tls_opts determines we do a regular net.connect or a - # tls.connect. - if @tls_opts? - # Merge the net_opts and tls_opts. - opts = {} - for name, val of @net_opts - opts[name] = val - for name, val of @tls_opts - opts[name] = val - x = tls.connect opts - # We don't want to use the regular "connect" event with TLS, because that - # happens before the TLS handshake and so masks errors. - connect_event_name = 'secureConnect' - else - opts = @net_opts - # on many Unix-style OS's, there is a path limit on sockets that blocks - # us from successfully connecting. Try to workaround the problem - # by changing working directory first - if @net_opts.path? and @net_opts.path.length >= 103 - oldCwd = process.cwd() - path_info = pp(@net_opts.path) - try - process.chdir(path_info.dir) - catch ex - @_warn "could not cd close to socket path: #{ex.code} #{ex.message}" - return cb new Error "error in connection (cd to long socket path)" - opts = Object.assign({}, @net_opts) - opts.path = path_info.base - x = net.connect opts - connect_event_name = 'connect' - x.setNoDelay true unless @do_tcp_delay - - # Some local switch codes.... - [ CON, ERR, CLS, TMO ] = [0..3] - - # We'll take any one of these three events... - rv = new iced.Rendezvous - x.once connect_event_name, rv.id(CON).defer() - x.once 'error', rv.id(ERR).defer(err) - x.once 'close', rv.id(CLS).defer() - - # Also, if the connection times out, let's abandon ship - # and try again. By default, this is for 10s - setTimeout rv.id(TMO).defer(), @_connect_timeout - - ok = false - await rv.wait defer rv_id - - if oldCwd? # undo any cwd change from abov - try - process.chdir(oldCwd) - catch ex - @_warn "could not recover cwd: #{ex.code} #{ex.message}" - return cb new Error "error in connection (changed cwd)" - - switch rv_id - when CON then ok = true - when ERR then @_warn err - when CLS then @_warn "connection closed during open" - when TMO then @_warn "connection timed out after #{@_connect_timeout}s" - - if ok - # Now remap the event emitters - @_activate_stream x - err = null - if @hooks?.after_connect? - await @hooks.after_connect defer err - else if not err? - err = new Error "error in connection" - - cb err - - ##----------------------------------------- - # To fulfill the packetizer contract, the following... - - _raw_write : (msg, encoding) -> - if not @_netw? - @_warn "write attempt with no active stream" - else - @_netw.write msg, encoding - - ##----------------------------------------- - -##======================================================================= - -exports.RobustTransport = class RobustTransport extends Transport - - ##----------------------------------------- - - # Take two dictionaries -- the first is as in Transport, - # and the second is configuration parameters specific to this - # transport. - # - # reconnect_delay -- the number of seconds to delay between attempts - # to reconnect to a downed server. - # - # queue_max -- the limit to how many calls we'll queue while we're - # waiting on a reconnect. - # - # warn_threshhold -- if a call takes more than this number of seconds, - # a warning will be fired when the RPC completes. - # - # error_threshhold -- if a call *is taking* more than this number of - # seconds, we will make an error output while the RPC is outstanding, - # and then make an error after we know how long it took. - # - # - constructor : (sd, d = {}) -> - super sd - - { @queue_max, @warn_threshhold, @error_threshhold } = d - - # in seconds, provide a default of 1s for a reconnect delay - # if none was given. Also, 0 is not a valid value. - @reconnect_delay = if (x = d.reconnect_delay) then x else 1 - - # For @queue_max, a value of '0' means don't queue, but a null - # or unspecifed value means use a reasonable default, which we - # supply here as 1000. - @queue_max = 1000 unless @queue_max? - - @_time_rpcs = @warn_threshhold? or @error_threshhold? - - @_waiters = [] - - ##----------------------------------------- - - _reconnect : (first_time) -> - # Do not reconnect on an explicit close - @_connect_loop first_time if not @_explicit_close - - ##----------------------------------------- - - _flush_queue : () -> - tmp = @_waiters - @_waiters = [] - for w in tmp - @invoke w... - - ##----------------------------------------- - - _connect_critical_section : (cb) -> - await super defer err - unless err? - @_flush_queue() - cb err - - ##----------------------------------------- - - _connect_loop : (first_time = false, cb) -> - prfx = if first_time then "" else "re" - i = 0 - - await @_lock.acquire defer() - - go = true - first_through_loop = true - - while go - if @is_connected() or @_explicit_close - go = false - else if first_through_loop and not first_time - first_through_loop = false - @_info "reconnect loop started, initial delay..." - await setTimeout defer(), @reconnect_delay*1000 - else - i++ - @_info "#{prfx}connecting (attempt #{i})" - await @_connect_critical_section defer err - if err? - await setTimeout defer(), @reconnect_delay*1000 - else - go = false - - if @is_connected() - s = if i is 1 then "" else "s" - @_warn "#{prfx}connected after #{i} attempt#{s}" - - @_lock.release() - cb() if cb - - ##----------------------------------------- - - _timed_invoke : (arg, cb) -> - - [ OK, TIMEOUT ] = [0..1] - tm = new Timer start : true - rv = new iced.Rendezvous - meth = @make_method arg.program, arg.method - - et = if @error_threshhold then @error_threshhold*1000 else 0 - wt = if @warn_threshhold then @warn_threshhold*1000 else 0 - - # Keep a handle to this timeout so we can clear it later on success - to = setTimeout rv.id(TIMEOUT).defer(), et if et - - # Make the actual RPC - Dispatch.prototype.invoke.call @, arg, rv.id(OK).defer rpc_res... - - # Wait for the first one... - await rv.wait defer which - - # will we leak memory for the calls that never come back? - flag = true - - while flag - if which is TIMEOUT - @_error "RPC call to '#{meth}' is taking > #{et/1000}s" - await rv.wait defer which - else - clearTimeout to - flag = false - - dur = tm.stop() - - m = if et and dur >= et then @_error - else if wt and dur >= wt then @_warn - else null - - m.call @, "RPC call to '#{meth}' finished in #{dur/1000}s" if m - - cb rpc_res... - - ##----------------------------------------- - - invoke : (arg, cb) -> - meth = @make_method arg.program, arg.method - if @is_connected() - if @_time_rpcs then @_timed_invoke arg, cb - else super arg, cb - else if @_explicit_close - @_warn "invoke call after explicit close" - cb "socket was closed", {} - else if @_waiters.length < @queue_max - @_waiters.push [ arg, cb ] - @_info "Queuing call to #{meth} (num queued: #{@_waiters.length})" - else if @queue_max > 0 - @_warn "Queue overflow for #{meth}" - -##======================================================================= - -exports.createTransport = (opts) -> - if opts.robust then new RobustTransport opts, opts - else new Transport opts - -##======================================================================= - diff --git a/src/transport.ts b/src/transport.ts new file mode 100644 index 0000000..dfa657e --- /dev/null +++ b/src/transport.ts @@ -0,0 +1,509 @@ +import net from 'net' +import tls from 'tls' +// @ts-ignore -- no type declarations available +import pp from 'path-parse' +import process from 'process' +import {Lock} from './lock' +import {Dispatch} from './dispatch' +import type {InvokeArgs, HookWrapperArgs} from './dispatch' +import * as log from './log' +import {Timer} from './timer' +import * as dbg from './debug' + +export interface TransportHooks { + eof?: (netw: StreamWrapper) => void + connected?: (netw: StreamWrapper) => void + after_connect?: () => Promise +} + +export interface TransportOpts { + port?: number + host?: string + net_opts?: Record + net_stream?: net.Socket | tls.TLSSocket + log_obj?: log.Logger + parent?: any + do_tcp_delay?: boolean + hooks?: TransportHooks + dbgr?: dbg.Debugger | null + path?: string + tls_opts?: Record + connect_timeout?: number +} + +export class StreamWrapper { + _net_stream: (net.Socket | tls.TLSSocket) | null + private _parent: Transport + private _generation: number + private _write_closed_warn: boolean = false + + constructor(net_stream: net.Socket | tls.TLSSocket, parent: Transport) { + this._net_stream = net_stream + this._parent = parent + this._generation = this._parent.next_generation() + } + + close(): boolean { + let ret = false + const x = this._net_stream + if (x) { + ret = true + this._net_stream = null + this._parent._dispatch_reset() + this._parent._packetizer_reset() + x.end() + } + return ret + } + + write(msg: string, enc: BufferEncoding): void { + if (this._net_stream) { + this._net_stream.write(msg, enc) + } else if (!this._write_closed_warn) { + this._write_closed_warn = true + this._parent._warn('write on closed socket...') + } + } + + stream(): (net.Socket | tls.TLSSocket) | null { + return this._net_stream + } + + is_connected(): boolean { + return !!this._net_stream + } + + get_generation(): number { + return this._generation + } + + remote_address(): string | null { + return this._net_stream ? this._net_stream.remoteAddress || null : null + } + + remote_port(): number | null { + return this._net_stream ? this._net_stream.remotePort || null : null + } +} + +export class Transport extends Dispatch { + port?: number + host: string + net_opts: Record + log_obj!: log.Logger + parent: any + do_tcp_delay?: boolean + hooks?: TransportHooks + path?: string + tls_opts?: Record + _explicit_close: boolean = false + _remote_str: string + _lock: Lock + _generation: number = 1 + _connect_timeout: number + _netw: StreamWrapper | null = null + + constructor(opts: TransportOpts) { + super() + + this.port = opts.port + this.host = !opts.host || opts.host === '-' ? 'localhost' : opts.host + this.net_opts = opts.net_opts || {} + this.net_opts.host = this.host + this.net_opts.port = this.port + this.net_opts.path = opts.path + this._explicit_close = false + this.parent = opts.parent + this.do_tcp_delay = opts.do_tcp_delay + this.hooks = opts.hooks + this.path = opts.path + this.tls_opts = opts.tls_opts + + this._remote_str = [this.host, this.port].join(':') + this.set_logger(opts.log_obj) + + this._lock = new Lock() + + this._dbgr = opts.dbgr || null + + this._connect_timeout = opts.connect_timeout || 10 * 1000 + + if (opts.net_stream) { + this._activate_stream(opts.net_stream) + } + } + + set_debugger(d: dbg.Debugger | null): void { + this._dbgr = d + } + + set_debug_flags(d: number | string): void { + this.set_debugger(dbg.make_debugger(d, this.log_obj)) + } + + next_generation(): number { + const ret = this._generation + this._generation++ + return ret + } + + get_generation(): number { + return this._netw ? this._netw.get_generation() : -1 + } + + remote_address(): string | null { + return this._netw ? this._netw.remote_address() : null + } + + remote_port(): number | null { + return this._netw ? this._netw.remote_port() : null + } + + set_logger(o?: log.Logger): void { + if (!o) o = log.new_default_logger() + this.log_obj = o + this.log_obj.set_remote(this._remote_str) + } + + get_logger(): log.Logger { + return this.log_obj + } + + is_connected(): boolean { + return this._netw?.is_connected() || false + } + + async connect(): Promise { + await this._lock.acquire() + let err: Error | null = null + if (!this.is_connected()) { + err = await this._connect_critical_section() + } + this._lock.release() + if (err) this._reconnect(true) + return err + } + + reset(w?: StreamWrapper): void { + if (!w) w = this._netw || undefined + if (w) this._close(w) + } + + close(): void { + this._explicit_close = true + if (this._netw) { + this._netw.close() + this._netw = null + } + } + + _warn(e: any): void { + this.log_obj.warn(e) + } + _info(e: any): void { + this.log_obj.info(e) + } + _fatal(e: any): void { + this.log_obj.fatal(e) + } + _debug(e: any): void { + this.log_obj.debug(e) + } + _error(e: any): void { + this.log_obj.error(e) + } + + _close(netw?: StreamWrapper): void { + if (netw) { + this.hooks?.eof?.(netw) + if (netw.close()) { + this._reconnect(false) + } + } + } + + _handle_error(e: any, netw?: StreamWrapper): void { + this._error(e) + this._close(netw) + } + + _packetize_error(err: string): void { + this._handle_error(`In packetizer: ${err}`, this._netw || undefined) + } + + _packetize_warning(w: string): void { + this._warn(`In packetizer: ${w}`) + } + + _handle_close(netw: StreamWrapper): void { + if (!this._explicit_close) this._info('EOF on transport') + this._close(netw) + if (this.parent) this.parent.close_child(this) + } + + _reconnect(_first_time: boolean): void { + // In other classes we can override this — see RobustTransport + } + + _activate_stream(x: net.Socket | tls.TLSSocket): void { + this._info('connection established') + + const w = new StreamWrapper(x, this) + this._netw = w + + this.hooks?.connected?.(w) + + x.on('error', (err: Error) => this._handle_error(err, w)) + x.on('close', () => this._handle_close(w)) + x.on('data', (msg: Buffer) => this.packetize_data(new Uint8Array(msg.buffer, msg.byteOffset, msg.byteLength))) + } + + async _connect_critical_section(): Promise { + let oldCwd: string | undefined + + let x: net.Socket | tls.TLSSocket + let connect_event_name: string + + if (this.tls_opts) { + const opts: Record = {} + for (const [name, val] of Object.entries(this.net_opts)) { + opts[name] = val + } + for (const [name, val] of Object.entries(this.tls_opts)) { + opts[name] = val + } + x = tls.connect(opts) + connect_event_name = 'secureConnect' + } else { + let opts = this.net_opts + if (this.net_opts.path != null && this.net_opts.path.length >= 103) { + oldCwd = process.cwd() + const path_info = pp(this.net_opts.path) + try { + process.chdir(path_info.dir) + } catch (ex: any) { + this._warn(`could not cd close to socket path: ${ex.code} ${ex.message}`) + return new Error('error in connection (cd to long socket path)') + } + opts = Object.assign({}, this.net_opts) + opts.path = path_info.base + } + x = net.connect(opts as net.NetConnectOpts) + connect_event_name = 'connect' + } + if (!this.do_tcp_delay) x.setNoDelay(true) + + type RaceResult = {id: 'connect'} | {id: 'error'; err: Error} | {id: 'close'} | {id: 'timeout'} + + const result = await Promise.race([ + new Promise(resolve => x.once(connect_event_name, () => resolve({id: 'connect'}))), + new Promise(resolve => x.once('error', (err: Error) => resolve({id: 'error', err}))), + new Promise(resolve => x.once('close', () => resolve({id: 'close'}))), + new Promise(resolve => + setTimeout(() => resolve({id: 'timeout'}), this._connect_timeout) + ), + ]) + + if (oldCwd != null) { + try { + process.chdir(oldCwd) + } catch (ex: any) { + this._warn(`could not recover cwd: ${ex.code} ${ex.message}`) + return new Error('error in connection (changed cwd)') + } + } + + let ok = false + let err: Error | null = null + + switch (result.id) { + case 'connect': + ok = true + break + case 'error': + this._warn(result.err) + err = result.err + break + case 'close': + this._warn('connection closed during open') + break + case 'timeout': + this._warn(`connection timed out after ${this._connect_timeout}s`) + break + } + + if (ok) { + this._activate_stream(x) + err = null + if (this.hooks?.after_connect) { + try { + await this.hooks.after_connect() + } catch (e: any) { + err = e + } + } + } else if (!err) { + err = new Error('error in connection') + } + + return err + } + + _raw_write(msg: string, encoding: string): void { + if (!this._netw) { + this._warn('write attempt with no active stream') + } else { + this._netw.write(msg, encoding as BufferEncoding) + } + } +} + +export interface RobustTransportOpts extends TransportOpts { + reconnect_delay?: number + queue_max?: number + warn_threshhold?: number + error_threshhold?: number + robust?: boolean +} + +export class RobustTransport extends Transport { + queue_max: number + warn_threshhold?: number + error_threshhold?: number + reconnect_delay: number + private _time_rpcs: boolean + private _waiters: Array<[InvokeArgs, ((result: {error: any; result: any}) => void) | undefined]> = [] + + constructor(sd: RobustTransportOpts, d: RobustTransportOpts = {}) { + super(sd) + + this.queue_max = d.queue_max != null ? d.queue_max : 1000 + this.warn_threshhold = d.warn_threshhold + this.error_threshhold = d.error_threshhold + this.reconnect_delay = d.reconnect_delay || 1 + + this._time_rpcs = this.warn_threshhold != null || this.error_threshhold != null + this._waiters = [] + } + + _reconnect(first_time: boolean): void { + if (!this._explicit_close) { + this._connect_loop(first_time) + } + } + + private _flush_queue(): void { + const tmp = this._waiters + this._waiters = [] + for (const [arg, resolveOuter] of tmp) { + this.invoke(arg).then(res => resolveOuter?.(res)) + } + } + + async _connect_critical_section(): Promise { + const err = await super._connect_critical_section() + if (!err) { + this._flush_queue() + } + return err + } + + private async _connect_loop(first_time: boolean = false): Promise { + const prfx = first_time ? '' : 're' + let i = 0 + + await this._lock.acquire() + + let go = true + let first_through_loop = true + + while (go) { + if (this.is_connected() || this._explicit_close) { + go = false + } else if (first_through_loop && !first_time) { + first_through_loop = false + this._info('reconnect loop started, initial delay...') + await new Promise(resolve => setTimeout(resolve, this.reconnect_delay * 1000)) + } else { + i++ + this._info(`${prfx}connecting (attempt ${i})`) + const err = await this._connect_critical_section() + if (err) { + await new Promise(resolve => setTimeout(resolve, this.reconnect_delay * 1000)) + } else { + go = false + } + } + } + + if (this.is_connected()) { + const s = i === 1 ? '' : 's' + this._warn(`${prfx}connected after ${i} attempt${s}`) + } + + this._lock.release() + } + + private async _timed_invoke(arg: InvokeArgs): Promise<{error: any; result: any}> { + const tm = new Timer({start: true}) + const meth = this.make_method(arg.program, arg.method) + + const et = this.error_threshhold ? this.error_threshhold * 1000 : 0 + const wt = this.warn_threshhold ? this.warn_threshhold * 1000 : 0 + + // Race: RPC vs periodic timeout warnings + let rpc_done = false + const rpcPromise = Dispatch.prototype.invoke.call(this, arg).then(res => { + rpc_done = true + return res + }) + + if (et) { + // periodically log while we wait + const logLoop = async () => { + while (!rpc_done) { + await new Promise(r => setTimeout(r, et)) + if (!rpc_done) { + this._error(`RPC call to '${meth}' is taking > ${et / 1000}s`) + } + } + } + logLoop() // fire and forget + } + + const result = await rpcPromise + const dur = tm.stop() + + let m: ((msg: any) => void) | null = null + if (et && dur >= et) m = this._error.bind(this) + else if (wt && dur >= wt) m = this._warn.bind(this) + + if (m) m(`RPC call to '${meth}' finished in ${dur / 1000}s`) + + return result + } + + invoke(arg: InvokeArgs): Promise<{error: any; result: any}> { + const meth = this.make_method(arg.program, arg.method) + if (this.is_connected()) { + if (this._time_rpcs) return this._timed_invoke(arg) + return super.invoke(arg) + } else if (this._explicit_close) { + this._warn('invoke call after explicit close') + return Promise.resolve({error: 'socket was closed', result: {}}) + } else if (this._waiters.length < this.queue_max) { + return new Promise<{error: any; result: any}>(resolve => { + this._waiters.push([arg, resolve]) + this._info(`Queuing call to ${meth} (num queued: ${this._waiters.length})`) + }) + } else if (this.queue_max > 0) { + this._warn(`Queue overflow for ${meth}`) + } + return Promise.resolve({error: 'queue overflow', result: {}}) + } +} + +export function createTransport(opts: RobustTransportOpts): Transport { + if (opts.robust) return new RobustTransport(opts, opts) + return new Transport(opts) +} diff --git a/src/version.iced b/src/version.iced deleted file mode 100644 index 3830a1d..0000000 --- a/src/version.iced +++ /dev/null @@ -1,5 +0,0 @@ - -# often wrong, but better than nothing... -# ...this is overwritten in lib/version.js with the right -# copy of the version (taken from package.json). -exports.version = "1.1.19" diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..b4d040f --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const version: string = require('../package.json').version diff --git a/test/all.iced b/test/all.iced deleted file mode 100644 index eb6f082..0000000 --- a/test/all.iced +++ /dev/null @@ -1,236 +0,0 @@ - - -fs = require 'fs' -path = require 'path' -colors = require 'colors' -deep_equal = require 'deep-equal' -{debug,log,Logger,RobustTransport,Transport,Client} = require '../src/main' -iced = require('../src/iced').runtime - -##----------------------------------------------------------------------- - -argv = require('optimist') - .usage('Usage: $0 [-d] [ -t] [ ...]') - .boolean('d').argv - -##----------------------------------------------------------------------- - -CHECK = "\u2714" -FUUUU = "\u2716" -ARROW = "\u2192" - -##----------------------------------------------------------------------- - -class VLogger extends Logger - - @my_ohook : (c) -> (m) -> console.log " #{ARROW} #{m}"[c] - - info : (m) -> @_log m, "I", VLogger.my_ohook "cyan" - warn : (m) -> @_log m, "W", VLogger.my_ohook "yellow" - error : (m) -> @_log m, "E", VLogger.my_ohook "red" - debug : (m) -> @_log m, "D", VLogger.my_ohook "yellow" - -##----------------------------------------------------------------------- - -if argv.d then log.set_default_logger_class VLogger -else - console.log "ok then, set it! #{log.levels.TOP}" - log.set_default_level log.levels.TOP - -##----------------------------------------------------------------------- - -class GlobalTester - - connect : (port, prog, cb, rtopts) -> - err = null - - # We can also pass a Path to a unix domain socket here - if isNaN port - opts = { path : port } - else - opts = { port, host : "-" } - if argv.t? - opts.debug_hook = debug.make_hook String(argv.t), (m) -> - console.log "TRACE-#{argv.t}: #{JSON.stringify m}" - klass = if rtopts then RobustTransport else Transport - x = new klass opts, rtopts - await x.connect defer err - if err? - x = null - else - c = new Client x, prog - cb err, x, c - -##----------------------------------------------------------------------- - -class TestCase - constructor : (@_global) -> - @_ok = true - - logger : () -> @_global.logger() - - search : (s, re, msg) -> - if Error.isError(s) - s = s.toString() - @assert (s? and s.search(re) >= 0), msg - - assert : (f, what) -> - if not f - console.log "Assertion failed: #{what}" - @_ok = false - - equal : (a,b,what) -> - if not deep_equal a, b - console.log "In #{what}: #{JSON.stringify a} != #{JSON.stringify b}".red - @_ok = false - - test_rpc : (cli, method, arg, expected, cb) -> - full = [ cli.program , method ].join "." - await cli.invoke method, arg, defer error, result - @check_rpc full, error, result, expected - cb() - - test_rpc_compressed : (cli, method, ctype, arg, expected, cb) -> - full = [ cli.program , method ].join "." - await cli.invoke_compressed method, ctype, arg, defer error, result - @check_rpc full, error, result, expected - cb() - - error : (e) -> - console.log e.red - @_ok = false - - check_rpc: (name, error, result, expected) -> - if error then @error "In #{name}: #{JSON.stringify error}" - else @equal result, expected, "#{name} RPC result" - - is_ok : () -> @_ok - - connect : (port, prog, cb, rtopts) -> - await @_global.connect port, prog, defer(e,x,c), rtopts - @error e if e - cb x,c - -##----------------------------------------------------------------------- - -class Runner - - ##----------------------------------------- - - constructor : -> - @_files = [] - @_launches = 0 - @_tests = 0 - @_successes = 0 - @_rc = 0 - @_n_files = 0 - @_n_good_files = 0 - @_global_tester = new GlobalTester - - ##----------------------------------------- - - err : (e) -> - console.log e.red - @_rc = -1 - - ##----------------------------------------- - - load_files : (cb) -> - @_dir = path.dirname __filename - if argv._.length - ok = true - @_files = argv._ - else - base = path.basename __filename - await fs.readdir @_dir, defer err, files - if err? - ok = false - @err "In reading #{@_dir}: #{err}" - else - ok = true - re = /test.*\.(iced|coffee)$/ - for file in files when file.match(re) - @_files.push file - @_files.sort() - cb ok - - ##----------------------------------------- - - run_files : (cb) -> - for f in @_files - await @run_file f, defer() - cb() - - ##----------------------------------------- - - create_tester : () -> new TestCase @_global_tester - - ##----------------------------------------- - - run_code : (f, code, cb) -> - if code.init? - await code.init defer(err), @_global_tester - destroy = code.destroy - delete code["init"] - delete code["destroy"] - @_n_files++ - if err - @err "Failed to initialize file #{f}: #{err}" - else - @_n_good_files++ - for k,v of code - @_tests++ - T = @create_tester() - await v T, defer err - if err - @err "In #{f}/#{k}: #{err}" - else if T.is_ok() - @_successes++ - console.log "#{CHECK} #{f}: #{k}".green - else - console.log "#{FUUUU} #{f}: #{k}".bold.red - await destroy defer(), @_global_tester if destroy - cb() - - ##----------------------------------------- - - run_file : (f, cb) -> - try - dat = require path.join @_dir, f - await @run_code f, dat, defer() - catch e - @err "In reading #{f}: #{e}\n#{e.stack}" - cb() - - ##----------------------------------------- - - run : (cb) -> - await @load_files defer ok - await @run_files defer() if ok - @report() - cb @_rc - - ##----------------------------------------- - - report : () -> - if @_rc < 0 - console.log "#{FUUUU} Failure due to test configuration issues".red - @_rc = -1 unless @_tests is @_successes - f = if @_rc is 0 then colors.green else colors.red - - console.log f "Tests: #{@_successes}/#{@_tests} passed".bold - - if @_n_files isnt @_n_good_files - console.log (" -> Only #{@_n_good_files}/#{@_n_files}" + - " files ran properly").red.bold - return @_rc - - ##----------------------------------------- - -##----------------------------------------------------------------------- - -runner = new Runner() -await runner.run defer rc -process.exit rc - -##----------------------------------------------------------------------- diff --git a/test/helpers.ts b/test/helpers.ts new file mode 100644 index 0000000..675763a --- /dev/null +++ b/test/helpers.ts @@ -0,0 +1,43 @@ +import {Transport, RobustTransport, Client, log} from '../src/main' + +log.set_default_level(log.levels.TOP) + +export const PORT = 8881 + +export interface RobustOpts { + reconnect_delay?: number + queue_max?: number + warn_threshhold?: number + error_threshhold?: number +} + +export async function connect( + port: number | string, + prog: string, + rtopts?: RobustOpts +): Promise<{x: Transport | RobustTransport; c: Client}> { + let opts: any + if (typeof port === 'string' || isNaN(port as number)) { + opts = {path: port} + } else { + opts = {port, host: '-'} + } + const klass = rtopts ? RobustTransport : Transport + const x = new klass(opts, rtopts) + const err = await x.connect() + if (err) { + throw err + } + const c = new Client(x, prog) + return {x, c} +} + +export async function test_rpc( + cli: Client, + method: string, + arg: any, + expected: any +): Promise<{error: any; result: any}> { + const {error, result} = await cli.invoke(method, arg) + return {error, result} +} diff --git a/test/support/jenky_server.js b/test/support/jenky_server.js deleted file mode 100644 index cf79260..0000000 --- a/test/support/jenky_server.js +++ /dev/null @@ -1,3 +0,0 @@ -require('iced-coffee-script').register(); -var main = require('./jenky_server_main').main; -main() diff --git a/test/support/jenky_server_main.iced b/test/support/jenky_server_main.iced deleted file mode 100644 index 42f1fbd..0000000 --- a/test/support/jenky_server_main.iced +++ /dev/null @@ -1,24 +0,0 @@ -{log,server,ReconnectTransport,Client} = require '../../src/main' - -exports.main = () -> - - PORT = 8881 - - # Since we're being forked, do this. We shouldn't really - # be doing this in "-d" mode to all.iced, but it's OK for now. - log.set_default_level log.levels.WARN - - # this is a jenky server that crashes every time it does anything! - # useful for testing the reconnecting client... - class P_v1 extends server.Handler - h_foo : (arg, res) -> - res.result { y : arg.i + 2 } - process.exit 0 - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - await s.listen defer err - process.send { ok : true } - diff --git a/test/test1.iced b/test/test1.iced deleted file mode 100644 index 8ff88cb..0000000 --- a/test/test1.iced +++ /dev/null @@ -1,42 +0,0 @@ -{server,transport,client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -PORT = 8881 - -s = null - -exports.init = (cb) -> - - s = new server.Server - port : PORT - programs : - "P.1" : - foo : (arg, res) -> res.result { y : arg.i + 2 } - bar : (arg, res) -> res.result { y : arg.j * arg.k } - - await s.listen defer err - cb err - -exports.test1 = (T, cb) -> test_A T, cb -exports.test2 = (T, cb) -> test_A T, cb - -test_A = (T, cb) -> - x = new transport.Transport { port : PORT, host : "-" } - await x.connect defer err - if err? - console.log "Failed to connect in Transport..." - else - ok = false - c = new client.Client x, "P.1" - await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() - await T.test_rpc c, "bar", { j : 2, k : 7 }, { y : 14}, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 4 } , { y : 6 }, defer() - await T.test_rpc_compressed c, "bar", COMPRESSION_TYPE_GZIP, { j : 2, k : 7 }, { y : 14}, defer() - x.close() - x = c = null - cb ok - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test1.test.ts b/test/test1.test.ts new file mode 100644 index 0000000..359e436 --- /dev/null +++ b/test/test1.test.ts @@ -0,0 +1,60 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, transport, client} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' + +const PORT = 8881 + +describe('test1 - basic RPC', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.Server({ + port: PORT, + programs: { + 'P.1': { + foo: (arg: any, res: any) => res.result({y: arg.i + 2}), + bar: (arg: any, res: any) => res.result({y: arg.j * arg.k}), + }, + }, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + async function test_A() { + const x = new transport.Transport({port: PORT, host: '-'}) + const err = await x.connect() + expect(err).toBeNull() + + const c = new client.Client(x, 'P.1') + + let res = await c.invoke('foo', {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke('bar', {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + res = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke_compressed('bar', COMPRESSION_TYPE_GZIP, {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + x.close() + } + + it('test1', async () => { + await test_A() + }) + + it('test2', async () => { + await test_A() + }) +}) diff --git a/test/test10.iced b/test/test10.iced deleted file mode 100644 index 1954695..0000000 --- a/test/test10.iced +++ /dev/null @@ -1,58 +0,0 @@ -{server,Transport,Client,debug} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -# The same as test9, but over Unix Domain sockets, and not over -# TCP Ports... - -s = null -SOCK = "/tmp/rpc.test10.sock" - -##----------------------------------------------------------------------- - -class P_v1 extends server.Handler - h_foo : (arg, res) -> res.result { y : arg.i + 2 } - h_bar : (arg, res) -> res.result { y : arg.j * arg.k } - -##----------------------------------------------------------------------- - -exports.init = (cb) -> - - s = new server.ContextualServer - path : SOCK - classes : - "P.1" : P_v1 - - s.set_debugger new debug.Debugger debug.constants.flags.LEVEL_4 - - await s.listen defer err - cb err - -##----------------------------------------------------------------------- - -exports.test1 = (T, cb) -> test_A T, cb -exports.test2 = (T, cb) -> test_A T, cb - -##----------------------------------------------------------------------- - -test_A = (T, cb) -> - await T.connect SOCK, "P.1", defer x, c - if x - await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() - await T.test_rpc c, "bar", { j : 2, k : 7 }, { y : 14}, defer() - - bad = "XXyyXX" - await c.invoke bad, {}, defer err, res - T.search err.toString(), /unknown method/, "method '#{bad}' should not be found" - - await c.invoke_compressed bad, COMPRESSION_TYPE_GZIP, {}, defer err, res - T.search err.toString(), /unknown method/, "method '#{bad}' should not be found" - - x.close() - x = c = null - cb() - -##----------------------------------------------------------------------- -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test10.test.ts b/test/test10.test.ts new file mode 100644 index 0000000..8585c22 --- /dev/null +++ b/test/test10.test.ts @@ -0,0 +1,65 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, debug} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const SOCK = '/tmp/rpc.test10.sock' + +class P_v1 extends server.Handler { + h_foo(arg: any, res: any) { + res.result({y: arg.i + 2}) + } + h_bar(arg: any, res: any) { + res.result({y: arg.j * arg.k}) + } +} + +describe('test10 - Unix domain sockets', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.ContextualServer({ + path: SOCK, + classes: { + 'P.1': P_v1 as any, + }, + }) + s.set_debugger(new debug.Debugger(debug.constants.flags.LEVEL_4)) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + async function test_A() { + const {x, c} = await connect(SOCK, 'P.1') + + let res = await c.invoke('foo', {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke('bar', {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + const bad = 'XXyyXX' + res = await c.invoke(bad, {}) + expect(res.error).toBeTruthy() + expect(res.error.toString()).toMatch(/unknown method/) + + res = await c.invoke_compressed(bad, COMPRESSION_TYPE_GZIP, {}) + expect(res.error).toBeTruthy() + expect(res.error.toString()).toMatch(/unknown method/) + + x.close() + } + + it('test1', async () => { + await test_A() + }) + + it('test2', async () => { + await test_A() + }) +}) diff --git a/test/test11.iced b/test/test11.iced deleted file mode 100644 index c949842..0000000 --- a/test/test11.iced +++ /dev/null @@ -1,127 +0,0 @@ - -{log,errors,server,transport,client} = require "../" -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -PORT = 19983 - -s = null - -# A basic test in which client and server swap roles! -#------------------------------------------------------ - -exports.test_1 = (T,cb) -> - - s = new server.Server - port : PORT - programs : - "P.1" : - foo : (arg, res) -> - await setTimeout defer(), 10 - c = new client.Client @, "Q.1" - await c.invoke "cb", { i : arg.i, j : 15 }, defer err, res2 - if err? - res.error err - else - res.result { y : res2.y*40 } - - await s.listen defer err - return cb err if err? - - x = new transport.Transport { port : PORT, host : "-" } - - await x.connect defer err - if err? - console.log "Failed to connect in Transport..." - else - c = new client.Client x, "P.1" - c.transport.add_programs { - "Q.1" : - cb : (arg, res) -> - await setTimeout defer(), 10 - res.result { y : arg.i - arg.j } - } - await T.test_rpc c, "foo", { i : 20 }, { y : 200 }, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 20 }, { y : 200 }, defer() - x.close() - x = c = null - - await s.close defer() - cb err - -# A more feature-ful test, in which errors are wrapped/unwrapped -# before they are sent over the wire. -#------------------------------------------------------ - -# This is our specialization of a server that just uses got_new_connection to specialize -# those the given 2 hooks. This is a little bit of a hack, to operate on the instance, -# rather than the prototype, but it works, so let's go with it until we have a problem. -class MyServer extends server.Server - - got_new_connection : (c) -> - super c - c.get_handler_this = (m) => { conn : c, server : @ } - c.wrap_outgoing_error = (e) -> { message : e.message, code : e.code, method : e.method } - -class MyTransport extends transport.Transport - - unwrap_incoming_error : (o) -> - if not o? then o - else if typeof o is 'object' - switch o.code - when errors.UNKNOWN_METHOD - err = new errors.UnknownMethodError o.message - err.method = o.method - err - else - new Error err.message - else - new Error o - -#-------------------- - -exports.test_2 = (T,cb) -> - - myServer = new MyServer - port : PORT - programs : - "P.1" : - foo : (arg, res) -> - await setTimeout defer(), 10 - c = new client.Client @conn, "Q.1" - await c.invoke "cb", { i : arg.i, j : 15 }, defer err, res2 - if err? - res.error err - else - res.result { y : res2.y*40 } - - await myServer.listen defer err - return cb err if err? - - x = new MyTransport { port : PORT, host : "-" } - - await x.connect defer err - if err? - console.log "Failed to connect in Transport..." - else - c = new client.Client x, "P.1" - c.transport.add_programs { - "Q.1" : - cb : (arg, res) -> - await setTimeout defer(), 10 - res.result { y : arg.i - arg.j } - } - await T.test_rpc c, "foo", { i : 20 }, { y : 200 }, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 20 }, { y : 200 }, defer() - - # Now check that we've succesfully gotten an error, and the error - # went through the wrapping/unwrapping system as we expect - await c.invoke "bar", {}, defer e2, res - T.assert (e2 instanceof errors.UnknownMethodError), "the right error message" - T.equal e2.method, "P.1.bar", "method name equality" - - x.close() - x = c = null - - await myServer.close defer() - cb err - diff --git a/test/test11.test.ts b/test/test11.test.ts new file mode 100644 index 0000000..6eac82a --- /dev/null +++ b/test/test11.test.ts @@ -0,0 +1,132 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {log, errors, server, transport, client} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' + +const PORT = 19983 + +describe('test11 - bidirectional RPC + error wrapping', () => { + it('test_1 - basic bidirectional', async () => { + const s = new server.Server({ + port: PORT, + programs: { + 'P.1': { + foo: async function (this: any, arg: any, res: any) { + await new Promise(r => setTimeout(r, 10)) + const c = new client.Client(this, 'Q.1') + const {error: err, result: res2} = await c.invoke('cb', {i: arg.i, j: 15}) + if (err) { + res.error(err) + } else { + res.result({y: res2.y * 40}) + } + }, + }, + }, + }) + + await s.listen() + + const x = new transport.Transport({port: PORT, host: '-'}) + const err = await x.connect() + expect(err).toBeNull() + + const c = new client.Client(x, 'P.1') + c.transport.add_programs({ + 'Q.1': { + cb: async (arg: any, res: any) => { + await new Promise(r => setTimeout(r, 10)) + res.result({y: arg.i - arg.j}) + }, + }, + }) + + let result = await c.invoke('foo', {i: 20}) + expect(result.error).toBeFalsy() + expect(result.result).toEqual({y: 200}) + + result = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 20}) + expect(result.error).toBeFalsy() + expect(result.result).toEqual({y: 200}) + + x.close() + await s.close() + }) + + it('test_2 - error wrapping', async () => { + class MyServer extends server.Server { + got_new_connection(c: any) { + super.got_new_connection(c) + c.get_handler_this = (_m: string) => ({conn: c, server: this}) + c.wrap_outgoing_error = (e: any) => ({message: e.message, code: e.code, method: e.method}) + } + } + + class MyTransport extends transport.Transport { + unwrap_incoming_error(o: any): any { + if (o == null) return o + if (typeof o === 'object') { + switch (o.code) { + case errors.UNKNOWN_METHOD: { + const err = new errors.UnknownMethodError(o.message) + err.method = o.method + return err + } + default: + return new Error(o.message) + } + } + return new Error(o) + } + } + + const myServer = new MyServer({ + port: PORT, + programs: { + 'P.1': { + foo: async function (this: any, arg: any, res: any) { + await new Promise(r => setTimeout(r, 10)) + const c = new client.Client(this.conn, 'Q.1') + const {error: err, result: res2} = await c.invoke('cb', {i: arg.i, j: 15}) + if (err) { + res.error(err) + } else { + res.result({y: res2.y * 40}) + } + }, + }, + }, + }) + + await myServer.listen() + + const x = new MyTransport({port: PORT, host: '-'}) + const err = await x.connect() + expect(err).toBeNull() + + const c = new client.Client(x, 'P.1') + c.transport.add_programs({ + 'Q.1': { + cb: async (arg: any, res: any) => { + await new Promise(r => setTimeout(r, 10)) + res.result({y: arg.i - arg.j}) + }, + }, + }) + + let result = await c.invoke('foo', {i: 20}) + expect(result.error).toBeFalsy() + expect(result.result).toEqual({y: 200}) + + result = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 20}) + expect(result.error).toBeFalsy() + expect(result.result).toEqual({y: 200}) + + // Now check error wrapping/unwrapping + const e2result = await c.invoke('bar', {}) + expect(e2result.error).toBeInstanceOf(errors.UnknownMethodError) + expect(e2result.error.method).toBe('P.1.bar') + + x.close() + await myServer.close() + }) +}) diff --git a/test/test12.iced b/test/test12.iced deleted file mode 100644 index afde642..0000000 --- a/test/test12.iced +++ /dev/null @@ -1,60 +0,0 @@ -# Test connecting over TLS with a hardcoded CA cert. - -fs = require 'fs' -path = require 'path' -{server,transport,client} = require '../src/main' - -PORT = 8881 - -s = null - -exports.init = (cb) -> - tls_opts = { - key: fs.readFileSync(path.join(__dirname, 'ca/good/server-key.pem')), - cert: fs.readFileSync(path.join(__dirname, 'ca/good/server-crt.pem')), - ca: fs.readFileSync(path.join(__dirname, 'ca/good/ca-crt.pem')), - } - # Similar to test1.iced, but over TLS this time. - s = new server.Server { - port : PORT - programs : - "P.1" : - question : (arg, res) -> res.result "ANSWER!" - tls_opts - } - await s.listen defer err - cb err - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() - -exports.test_good_hardcoded_CA_cert = (T, cb) -> - tls_opts = { - ca: fs.readFileSync(path.join(__dirname, 'ca/good/ca-crt.pem')), - } - # Similar to test1.iced, but over TLS this time. - x = new transport.Transport { port : PORT, host : "-", tls_opts } - await x.connect defer err - if err? - console.log "Failed to connect in Transport..." - T.error(err) - else - c = new client.Client x, "P.1" - await T.test_rpc c, "question", {} , "ANSWER!", defer() - x.close() - cb() - -exports.test_bad_hardcoded_CA_cert = (T, cb) -> - tls_opts = { - ca: fs.readFileSync(path.join(__dirname, 'ca/bad/ca-crt.pem')), - } - # Similar to test1.iced, but over TLS this time. - x = new transport.Transport { port : PORT, host : "-", tls_opts } - await x.connect defer err - T.assert(err?, "connect should return an error") - if err? - T.assert(err.code == 'CERT_SIGNATURE_FAILURE', "error should be because of the bad cert, found: " + err) - x.close() - cb() diff --git a/test/test12.test.ts b/test/test12.test.ts new file mode 100644 index 0000000..ac06824 --- /dev/null +++ b/test/test12.test.ts @@ -0,0 +1,58 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import fs from 'fs' +import path from 'path' +import {server, transport, client} from '../src/main' + +const PORT = 8881 + +describe('test12 - TLS connections', () => { + let s: InstanceType + + beforeAll(async () => { + const tls_opts = { + key: fs.readFileSync(path.join(__dirname, 'ca/good/server-key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'ca/good/server-crt.pem')), + ca: fs.readFileSync(path.join(__dirname, 'ca/good/ca-crt.pem')), + } + s = new server.Server({ + port: PORT, + programs: { + 'P.1': { + question: (_arg: any, res: any) => res.result('ANSWER!'), + }, + }, + tls_opts, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + it('test_good_hardcoded_CA_cert', async () => { + const tls_opts = { + ca: fs.readFileSync(path.join(__dirname, 'ca/good/ca-crt.pem')), + } + const x = new transport.Transport({port: PORT, host: '-', tls_opts}) + const err = await x.connect() + expect(err).toBeNull() + + const c = new client.Client(x, 'P.1') + const res = await c.invoke('question', {}) + expect(res.error).toBeFalsy() + expect(res.result).toBe('ANSWER!') + x.close() + }) + + it('test_bad_hardcoded_CA_cert', async () => { + const tls_opts = { + ca: fs.readFileSync(path.join(__dirname, 'ca/bad/ca-crt.pem')), + } + const x = new transport.Transport({port: PORT, host: '-', tls_opts}) + const err = await x.connect() + expect(err).toBeTruthy() + expect(err!.code).toBe('CERT_SIGNATURE_FAILURE') + x.close() + }) +}) diff --git a/test/test2.iced b/test/test2.iced deleted file mode 100644 index 4d04725..0000000 --- a/test/test2.iced +++ /dev/null @@ -1,39 +0,0 @@ -{Server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -exports.init = (cb) -> - - s = new Server - port : PORT - programs : - "P.1" : - foo : (arg, res) -> res.result { y : arg.i + 2 } - bar : (arg, res) -> res.result { y : arg.j * arg.k } - - await s.listen defer err - cb err - -exports.test1 = (T, cb) -> test_A T, cb -exports.test2 = (T, cb) -> test_A T, cb - -test_A = (T, cb) -> - await T.connect PORT, "P.1", defer x, c - if x - await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() - await T.test_rpc c, "bar", { j : 2, k : 7 }, { y : 14}, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 4 } , { y : 6 }, defer() - await T.test_rpc_compressed c, "bar", COMPRESSION_TYPE_GZIP, { j : 2, k : 7 }, { y : 14}, defer() - x.close() - x = c = null - cb() - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test2.test.ts b/test/test2.test.ts new file mode 100644 index 0000000..8238939 --- /dev/null +++ b/test/test2.test.ts @@ -0,0 +1,57 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {Server, Transport, Client} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const PORT = 8881 + +describe('test2 - rebind', () => { + let s: InstanceType + + beforeAll(async () => { + s = new Server({ + port: PORT, + programs: { + 'P.1': { + foo: (arg: any, res: any) => res.result({y: arg.i + 2}), + bar: (arg: any, res: any) => res.result({y: arg.j * arg.k}), + }, + }, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + async function test_A() { + const {x, c} = await connect(PORT, 'P.1') + + let res = await c.invoke('foo', {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke('bar', {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + res = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke_compressed('bar', COMPRESSION_TYPE_GZIP, {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + x.close() + } + + it('test1', async () => { + await test_A() + }) + + it('test2', async () => { + await test_A() + }) +}) diff --git a/test/test3.iced b/test/test3.iced deleted file mode 100644 index d07b1ea..0000000 --- a/test/test3.iced +++ /dev/null @@ -1,46 +0,0 @@ -{server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -class P_v1 extends server.Handler - h_foo : (arg, res) -> res.result { y : arg.i + 2 } - h_bar : (arg, res) -> res.result { y : arg.j * arg.k } - -exports.init = (cb) -> - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - - await s.listen defer err - cb err - -exports.test1 = (T, cb) -> test_A T, cb -exports.test2 = (T, cb) -> test_A T, cb - -test_A = (T, cb) -> - await T.connect PORT, "P.1", defer x, c - if x - await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() - await T.test_rpc c, "bar", { j : 2, k : 7 }, { y : 14}, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 4 } , { y : 6 }, defer() - await T.test_rpc_compressed c, "bar", COMPRESSION_TYPE_GZIP, { j : 2, k : 7 }, { y : 14}, defer() - - bad = "XXyyXX" - await c.invoke bad, {}, defer err, res - T.search err, /unknown method/, "method '#{bad}' should not be found" - - x.close() - x = c = null - cb() - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test3.test.ts b/test/test3.test.ts new file mode 100644 index 0000000..907ef56 --- /dev/null +++ b/test/test3.test.ts @@ -0,0 +1,68 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, Transport, Client} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const PORT = 8881 + +class P_v1 extends server.Handler { + h_foo(arg: any, res: any) { + res.result({y: arg.i + 2}) + } + h_bar(arg: any, res: any) { + res.result({y: arg.j * arg.k}) + } +} + +describe('test3 - ContextualServer + UnknownMethodError', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.ContextualServer({ + port: PORT, + classes: { + 'P.1': P_v1 as any, + }, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + async function test_A() { + const {x, c} = await connect(PORT, 'P.1') + + let res = await c.invoke('foo', {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke('bar', {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + res = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke_compressed('bar', COMPRESSION_TYPE_GZIP, {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + const bad = 'XXyyXX' + res = await c.invoke(bad, {}) + expect(res.error).toBeTruthy() + expect(res.error.toString()).toMatch(/unknown method/) + + x.close() + } + + it('test1', async () => { + await test_A() + }) + + it('test2', async () => { + await test_A() + }) +}) diff --git a/test/test4.iced b/test/test4.iced deleted file mode 100644 index 8013e44..0000000 --- a/test/test4.iced +++ /dev/null @@ -1,98 +0,0 @@ -{server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -crypto = require 'crypto' -rj = require 'random-json' - -##======================================================================= - -class P_v1 extends server.Handler - h_reflect : (arg, res) -> res.result arg - -##======================================================================= - -exports.init = (cb) -> - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - - await s.listen defer err - cb err - -##======================================================================= - -exports.volley_of_strings = (T, cb) -> - n = 100 - genfn = (rcb) -> - sz = 10000 - await crypto.randomBytes sz, defer ex, buf - rcb { r : buf.toString 'base64' } - - await run_test n, T, genfn, defer() - cb() - -##======================================================================= - -exports.volley_of_objects = (T, cb) -> - n = 400 - genfn = (rcb) -> - await rj.obj defer obj - rcb obj - await run_test n, T, genfn, defer() - cb() - -##======================================================================= - -run_test = (n, T, obj_gen, cb) -> - - await T.connect PORT, "P.1", defer x, c - - if x - - args = [] - res = [] - err = [] - - for i in [0..n] - await obj_gen defer args[i] - - # normal invoke - await - for a,i in args - c.invoke "reflect", a, defer err[i], res[i] - for a,i in args - T.check_rpc "reflect #{i}", err[i], res[i], args[i] - - # invoke compressed w/gzip - await - for a,i in args - c.invoke_compressed "reflect", COMPRESSION_TYPE_GZIP, a, defer err[i], res[i] - for a,i in args - T.check_rpc "reflect #{i}", err[i], res[i], args[i] - - # invoke compressed w/no compression - await - for a,i in args - c.invoke_compressed "reflect", COMPRESSION_TYPE_NONE, a, defer err[i], res[i] - for a,i in args - T.check_rpc "reflect #{i}", err[i], res[i], args[i] - - - x.close() - - cb() - -##======================================================================= - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test4.test.ts b/test/test4.test.ts new file mode 100644 index 0000000..e4b5720 --- /dev/null +++ b/test/test4.test.ts @@ -0,0 +1,100 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import crypto from 'crypto' +import {server} from '../src/main' +import {COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const PORT = 8881 + +class P_v1 extends server.Handler { + h_reflect(arg: any, res: any) { + res.result(arg) + } +} + +describe('test4 - random object volleys', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.ContextualServer({ + port: PORT, + classes: { + 'P.1': P_v1 as any, + }, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + it('volley_of_strings', async () => { + const n = 100 + const {x, c} = await connect(PORT, 'P.1') + + const args: any[] = [] + for (let i = 0; i <= n; i++) { + const buf = crypto.randomBytes(10000) + args.push({r: buf.toString('base64')}) + } + + // normal invoke - parallel + const results = await Promise.all(args.map(a => c.invoke('reflect', a))) + for (let i = 0; i < results.length; i++) { + expect(results[i].error).toBeFalsy() + expect(results[i].result).toEqual(args[i]) + } + + // compressed gzip - parallel + const results2 = await Promise.all(args.map(a => c.invoke_compressed('reflect', COMPRESSION_TYPE_GZIP, a))) + for (let i = 0; i < results2.length; i++) { + expect(results2[i].error).toBeFalsy() + expect(results2[i].result).toEqual(args[i]) + } + + // compressed none - parallel + const results3 = await Promise.all(args.map(a => c.invoke_compressed('reflect', COMPRESSION_TYPE_NONE, a))) + for (let i = 0; i < results3.length; i++) { + expect(results3[i].error).toBeFalsy() + expect(results3[i].result).toEqual(args[i]) + } + + x.close() + }) + + it('volley_of_objects', async () => { + const rj = require('random-json') + const n = 400 + const {x, c} = await connect(PORT, 'P.1') + + const args: any[] = [] + for (let i = 0; i <= n; i++) { + const obj = await new Promise(resolve => rj.obj(resolve)) + args.push(obj) + } + + // normal invoke - parallel + const results = await Promise.all(args.map(a => c.invoke('reflect', a))) + for (let i = 0; i < results.length; i++) { + expect(results[i].error).toBeFalsy() + expect(results[i].result).toEqual(args[i]) + } + + // compressed gzip - parallel + const results2 = await Promise.all(args.map(a => c.invoke_compressed('reflect', COMPRESSION_TYPE_GZIP, a))) + for (let i = 0; i < results2.length; i++) { + expect(results2[i].error).toBeFalsy() + expect(results2[i].result).toEqual(args[i]) + } + + // compressed none - parallel + const results3 = await Promise.all(args.map(a => c.invoke_compressed('reflect', COMPRESSION_TYPE_NONE, a))) + for (let i = 0; i < results3.length; i++) { + expect(results3[i].error).toBeFalsy() + expect(results3[i].result).toEqual(args[i]) + } + + x.close() + }) +}) diff --git a/test/test5.iced b/test/test5.iced deleted file mode 100644 index 08bf4fe..0000000 --- a/test/test5.iced +++ /dev/null @@ -1,42 +0,0 @@ -# TODO this wasn't working on master -# {RobustTransport,Client} = require '../src/main' -# {fork} = require 'child_process' -# path = require 'path' -# {COMPRESSION_TYPE_GZIP} = require '../src/dispatch' -# -# ## Do the same test as test1, a second time, must to make -# ## sure that we can rebind a second time... -# -# PORT = 8881 -# n = null -# restart = true -# -# jenky_server_loop = (cb) -> -# while restart -# n = fork path.join(__dirname,"support","jenky_server.js"), [], {} -# await n.on 'message', defer msg -# if cb? -# t = cb -# cb = null -# t() -# await n.on 'exit', defer() -# -# exports.init = (cb) -> -# await jenky_server_loop defer() -# cb null -# -# exports.reconnect = (T, cb) -> -# await T.connect PORT, "P.1", defer(x,c), {} -# if x -# tries = 4 -# for i in [0...tries] -# restart = (i isnt tries-1) -# if i % 2 -# await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() -# else -# await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 4 } , { y : 6 }, defer() -# await setTimeout defer(), 10 -# -# x.close() -# -# cb() diff --git a/test/test6.iced b/test/test6.iced deleted file mode 100644 index 7079948..0000000 --- a/test/test6.iced +++ /dev/null @@ -1,64 +0,0 @@ -{server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -crypto = require 'crypto' -rj = require 'random-json' - -SLOW = 500 - -##======================================================================= - -class P_v1 extends server.Handler - h_reflect : (arg, res) -> - await setTimeout defer(), SLOW - res.result arg - -##======================================================================= - -exports.init = (cb) -> - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - - await s.listen defer err - cb err - -##======================================================================= - -exports.slow_warnings = (T, cb) -> - - rtops = - warn_threshhold : SLOW / 4000 - error_threshhold : SLOW / 2000 - - await T.connect PORT, "P.1", defer(x, c), rtops - - if x - - arg = - x : "simple stuff here" - v : [0..100] - - n = 4 - for i in [0...n] - await T.test_rpc c, "reflect", arg, arg, defer() - await T.test_rpc_compressed c, "reflect", COMPRESSION_TYPE_GZIP, arg, arg, defer() - - x.close() - - cb() - -##======================================================================= - -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test6.test.ts b/test/test6.test.ts new file mode 100644 index 0000000..c475846 --- /dev/null +++ b/test/test6.test.ts @@ -0,0 +1,58 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const PORT = 8881 +const SLOW = 500 + +class P_v1 extends server.Handler { + h_reflect(arg: any, res: any) { + setTimeout(() => res.result(arg), SLOW) + } +} + +describe('test6 - RobustTransport threshold warnings', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.ContextualServer({ + port: PORT, + classes: { + 'P.1': P_v1 as any, + }, + }) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + it('slow_warnings', async () => { + const rtops = { + warn_threshhold: SLOW / 4000, + error_threshhold: SLOW / 2000, + } + + const {x, c} = await connect(PORT, 'P.1', rtops) + + const arg = { + x: 'simple stuff here', + v: Array.from({length: 101}, (_, i) => i), + } + + const n = 4 + for (let i = 0; i < n; i++) { + let res = await c.invoke('reflect', arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + + res = await c.invoke_compressed('reflect', COMPRESSION_TYPE_GZIP, arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + } + + x.close() + }) +}) diff --git a/test/test7.iced b/test/test7.iced deleted file mode 100644 index db2e605..0000000 --- a/test/test7.iced +++ /dev/null @@ -1,98 +0,0 @@ -{server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -crypto = require 'crypto' -rj = require 'random-json' - -##======================================================================= - -class P_v1 extends server.Handler - - h_buggy : (arg, res) -> - res.result arg - # Now, generate some random junk in the buffer, and then send it down - # the pipe! - @transport._raw_write Buffer.from [3...10] - h_good : (arg, res) -> - res.result arg - -##======================================================================= - -cli = null -clix = null -T_global = null - -##======================================================================= - -exports.init = (cb, gto) -> - - T_global = gto - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - - await s.listen defer err - if not err - await gto.connect PORT, "P.1", defer(err, x, c), {} - if x - clix = x - cli = c - - cb err - -##======================================================================= - -exports.reconnect_after_server_error = (T, cb) -> - - arg = - x : "simple stuff here" - v : [0..100] - - n = 4 - for i in [0...n] - await T.test_rpc cli, "buggy", arg, arg, defer() - await setTimeout defer(), 10 - for i in [0...n] - await T.test_rpc_compressed cli, "buggy", COMPRESSION_TYPE_GZIP, arg, arg, defer() - await setTimeout defer(), 10 - - cb() - -##======================================================================= - -exports.reconnect_after_client_error = (T, cb) -> - arg = - x : "simple stuff here" - v : [0..100] - n = 4 - for i in [0...n] - await T.test_rpc cli, "good", arg, arg, defer() - # Poop on ourselves... - clix._raw_write Buffer.from [3...10] - - await setTimeout defer(), 10 - - for i in [0...n] - await T.test_rpc_compressed cli, "good", COMPRESSION_TYPE_GZIP, arg, arg, defer() - # Poop on ourselves... - clix._raw_write Buffer.from [3...10] - - await setTimeout defer(), 10 - - cb() - -##======================================================================= - -exports.destroy = (cb) -> - clix.close() - await s.close defer() - s = null - cb() diff --git a/test/test7.test.ts b/test/test7.test.ts new file mode 100644 index 0000000..cc947c0 --- /dev/null +++ b/test/test7.test.ts @@ -0,0 +1,89 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, Client, RobustTransport} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' + +const PORT = 8881 + +class P_v1 extends server.Handler { + h_buggy(arg: any, res: any) { + res.result(arg) + // Generate random junk and send it down the pipe + ;(this as any).transport._raw_write(Buffer.from([3, 4, 5, 6, 7, 8, 9]).toString('binary'), 'binary') + } + h_good(arg: any, res: any) { + res.result(arg) + } +} + +describe('test7 - reconnect after server error', () => { + let s: InstanceType + let clix: RobustTransport + let cli: Client + + beforeAll(async () => { + s = new server.ContextualServer({ + port: PORT, + classes: { + 'P.1': P_v1 as any, + }, + }) + await s.listen() + + const x = new RobustTransport({port: PORT, host: '-'}, {}) + const err = await x.connect() + expect(err).toBeNull() + clix = x + cli = new Client(x, 'P.1') + }) + + afterAll(async () => { + clix.close() + await s.close() + }) + + it('reconnect_after_server_error', async () => { + const arg = { + x: 'simple stuff here', + v: Array.from({length: 101}, (_, i) => i), + } + + const n = 4 + for (let i = 0; i < n; i++) { + const res = await cli.invoke('buggy', arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + await new Promise(r => setTimeout(r, 10)) + } + for (let i = 0; i < n; i++) { + const res = await cli.invoke_compressed('buggy', COMPRESSION_TYPE_GZIP, arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + await new Promise(r => setTimeout(r, 10)) + } + }) + + it('reconnect_after_client_error', async () => { + const arg = { + x: 'simple stuff here', + v: Array.from({length: 101}, (_, i) => i), + } + const n = 4 + for (let i = 0; i < n; i++) { + const res = await cli.invoke('good', arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + // Poop on ourselves + clix._raw_write(Buffer.from([3, 4, 5, 6, 7, 8, 9]).toString('binary'), 'binary') + await new Promise(r => setTimeout(r, 10)) + } + + for (let i = 0; i < n; i++) { + const res = await cli.invoke_compressed('good', COMPRESSION_TYPE_GZIP, arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + // Poop on ourselves + clix._raw_write(Buffer.from([3, 4, 5, 6, 7, 8, 9]).toString('binary'), 'binary') + await new Promise(r => setTimeout(r, 10)) + } + }) +}) diff --git a/test/test8.iced b/test/test8.iced deleted file mode 100644 index c4ca7c3..0000000 --- a/test/test8.iced +++ /dev/null @@ -1,73 +0,0 @@ -{server,Transport,Client} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -PROT = "P.1" -s = null -CONST = 110 - -crypto = require 'crypto' -rj = require 'random-json' - -##======================================================================= - -class MyServer extends server.SimpleServer - - constructor : (d) -> - super d - @_x = CONST - - get_program_name : () -> PROT - h_reflect : (arg, res) -> res.result arg - h_get_x : (arg, res) -> - o = x : @_x - @_x++ - res.result o - -##======================================================================= - -cli = null -clix = null - -##======================================================================= - -exports.init = (cb, gto) -> - s = new MyServer { port : PORT } - await s.listen defer err - if not err - await gto.connect PORT, PROT, defer(err, x, c), {} - if x - clix = x - cli = c - - cb err - -##======================================================================= - -exports.test1 = (T, cb) -> - - arg = - x : "simple stuff here" - v : [0..100] - - n = 4 - for i in [0...n] - await T.test_rpc cli, "reflect", arg, arg, defer() - await T.test_rpc cli, "get_x", arg, { x : CONST+i } , defer() - - for i in [0...n] - await T.test_rpc_compressed cli, "reflect", COMPRESSION_TYPE_GZIP, arg, arg, defer() - await T.test_rpc_compressed cli, "get_x", COMPRESSION_TYPE_GZIP, arg, { x : CONST+i+n } , defer() - - cb() - -##======================================================================= - -exports.destroy = (cb) -> - clix.close() - await s.close defer() - s = null - cb() diff --git a/test/test8.test.ts b/test/test8.test.ts new file mode 100644 index 0000000..c68a3f4 --- /dev/null +++ b/test/test8.test.ts @@ -0,0 +1,77 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, Client, RobustTransport} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' + +const PORT = 8881 +const PROT = 'P.1' +const CONST = 110 + +class MyServer extends server.SimpleServer { + _x: number + constructor(d: any) { + super(d) + this._x = CONST + } + + get_program_name() { + return PROT + } + h_reflect(arg: any, res: any) { + res.result(arg) + } + h_get_x(arg: any, res: any) { + const o = {x: this._x} + this._x++ + res.result(o) + } +} + +describe('test8 - SimpleServer', () => { + let s: MyServer + let clix: RobustTransport + let cli: Client + + beforeAll(async () => { + s = new MyServer({port: PORT}) + await s.listen() + + const x = new RobustTransport({port: PORT, host: '-'}, {}) + const err = await x.connect() + expect(err).toBeNull() + clix = x + cli = new Client(x, PROT) + }) + + afterAll(async () => { + clix.close() + await s.close() + }) + + it('test1', async () => { + const arg = { + x: 'simple stuff here', + v: Array.from({length: 101}, (_, i) => i), + } + + const n = 4 + for (let i = 0; i < n; i++) { + let res = await cli.invoke('reflect', arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + + res = await cli.invoke('get_x', arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({x: CONST + i}) + } + + for (let i = 0; i < n; i++) { + let res = await cli.invoke_compressed('reflect', COMPRESSION_TYPE_GZIP, arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual(arg) + + res = await cli.invoke_compressed('get_x', COMPRESSION_TYPE_GZIP, arg) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({x: CONST + i + n}) + } + }) +}) diff --git a/test/test9.iced b/test/test9.iced deleted file mode 100644 index 48c6bd8..0000000 --- a/test/test9.iced +++ /dev/null @@ -1,59 +0,0 @@ -{server,Transport,Client,debug} = require '../src/main' -{COMPRESSION_TYPE_GZIP} = require '../src/dispatch' - -## Do the same test as test1, a second time, must to make -## sure that we can rebind a second time... - -PORT = 8881 -s = null - -##----------------------------------------------------------------------- - -class P_v1 extends server.Handler - h_foo : (arg, res) -> res.result { y : arg.i + 2 } - h_bar : (arg, res) -> res.result { y : arg.j * arg.k } - -##----------------------------------------------------------------------- - -exports.init = (cb) -> - - s = new server.ContextualServer - port : PORT - classes : - "P.1" : P_v1 - - s.set_debugger new debug.Debugger debug.constants.flags.LEVEL_4 - - await s.listen defer err - cb err - -##----------------------------------------------------------------------- - -exports.test1 = (T, cb) -> test_A T, cb -exports.test2 = (T, cb) -> test_A T, cb - -##----------------------------------------------------------------------- - -test_A = (T, cb) -> - await T.connect PORT, "P.1", defer x, c - if x - await T.test_rpc c, "foo", { i : 4 } , { y : 6 }, defer() - await T.test_rpc c, "bar", { j : 2, k : 7 }, { y : 14}, defer() - await T.test_rpc_compressed c, "foo", COMPRESSION_TYPE_GZIP, { i : 4 } , { y : 6 }, defer() - await T.test_rpc_compressed c, "bar", COMPRESSION_TYPE_GZIP, { j : 2, k : 7 }, { y : 14}, defer() - - bad = "XXyyXX" - await c.invoke bad, {}, defer err, res - T.search err, /unknown method/, "method '#{bad}' should not be found" - await c.invoke bad, {ctype: COMPRESSION_TYPE_GZIP}, defer err, res - T.search err, /unknown method/, "method '#{bad}' should not be found" - - x.close() - x = c = null - cb() - -##----------------------------------------------------------------------- -exports.destroy = (cb) -> - await s.close defer() - s = null - cb() diff --git a/test/test9.test.ts b/test/test9.test.ts new file mode 100644 index 0000000..97fdcc7 --- /dev/null +++ b/test/test9.test.ts @@ -0,0 +1,73 @@ +import {describe, it, expect, beforeAll, afterAll} from 'vitest' +import {server, debug} from '../src/main' +import {COMPRESSION_TYPE_GZIP} from '../src/dispatch' +import {connect} from './helpers' + +const PORT = 8881 + +class P_v1 extends server.Handler { + h_foo(arg: any, res: any) { + res.result({y: arg.i + 2}) + } + h_bar(arg: any, res: any) { + res.result({y: arg.j * arg.k}) + } +} + +describe('test9 - debug flags', () => { + let s: InstanceType + + beforeAll(async () => { + s = new server.ContextualServer({ + port: PORT, + classes: { + 'P.1': P_v1 as any, + }, + }) + s.set_debugger(new debug.Debugger(debug.constants.flags.LEVEL_4)) + await s.listen() + }) + + afterAll(async () => { + await s.close() + }) + + async function test_A() { + const {x, c} = await connect(PORT, 'P.1') + + let res = await c.invoke('foo', {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke('bar', {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + res = await c.invoke_compressed('foo', COMPRESSION_TYPE_GZIP, {i: 4}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 6}) + + res = await c.invoke_compressed('bar', COMPRESSION_TYPE_GZIP, {j: 2, k: 7}) + expect(res.error).toBeFalsy() + expect(res.result).toEqual({y: 14}) + + const bad = 'XXyyXX' + res = await c.invoke(bad, {}) + expect(res.error).toBeTruthy() + expect(res.error.toString()).toMatch(/unknown method/) + + res = await c.invoke(bad, {ctype: COMPRESSION_TYPE_GZIP}) + expect(res.error).toBeTruthy() + expect(res.error.toString()).toMatch(/unknown method/) + + x.close() + } + + it('test1', async () => { + await test_A() + }) + + it('test2', async () => { + await test_A() + }) +}) diff --git a/test/test_pack.iced b/test/test_pack.iced deleted file mode 100644 index 714d846..0000000 --- a/test/test_pack.iced +++ /dev/null @@ -1,14 +0,0 @@ -{pack} = require '../src/main' - - -exports.test_encode_lib = (T, cb) -> - T.equal '@msgpack/msgpack', pack.get_encode_lib() - pack.set_opt 'encode_lib', '@msgpack/msgpack' - T.equal '@msgpack/msgpack', pack.get_encode_lib() - pack.set_opt 'encode_lib', 'protobuf' - try - encode_lib = get_encode_lib() - catch err - T.assert err? - T.assert not encode_lib? - cb null diff --git a/test/test_pack.test.ts b/test/test_pack.test.ts new file mode 100644 index 0000000..33db6d4 --- /dev/null +++ b/test/test_pack.test.ts @@ -0,0 +1,20 @@ +import {describe, it, expect} from 'vitest' +import {pack} from '../src/main' + +describe('test_pack', () => { + it('test_encode_lib', () => { + expect(pack.get_encode_lib()).toBe('@msgpack/msgpack') + pack.set_opt('encode_lib', '@msgpack/msgpack') + expect(pack.get_encode_lib()).toBe('@msgpack/msgpack') + pack.set_opt('encode_lib', 'protobuf') + let encode_lib: string | undefined + try { + encode_lib = pack.get_encode_lib() + } catch (err) { + expect(err).toBeTruthy() + } + expect(encode_lib).toBeUndefined() + // Reset to default + pack.set_opt('encode_lib', '@msgpack/msgpack') + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6883434 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "outDir": "lib", + "rootDir": "src", + "strict": true, + "declaration": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "lib", "test"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..83dbc5d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + pool: 'forks', + poolOptions: { + forks: { + singleFork: true, + }, + }, + sequence: { + concurrent: false, + }, + testTimeout: 30000, + hookTimeout: 30000, + }, +})