diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..84fc03f --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,34 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - run: yarn + - run: yarn build + #- run: npm test + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: https://registry.npmjs.org/ + - run: yarn + - run: yarn publish + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f48878 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +dist/ +docs/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..06dd640 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7629b32 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + }, + "typescript.tsdk": ".yarn/sdks/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/.yarn/sdks/integrations.yml b/.yarn/sdks/integrations.yml new file mode 100644 index 0000000..aa9d0d0 --- /dev/null +++ b/.yarn/sdks/integrations.yml @@ -0,0 +1,5 @@ +# This file is automatically generated by @yarnpkg/sdks. +# Manual changes might be lost! + +integrations: + - vscode diff --git a/.yarn/sdks/typescript/bin/tsc b/.yarn/sdks/typescript/bin/tsc new file mode 100644 index 0000000..454b950 --- /dev/null +++ b/.yarn/sdks/typescript/bin/tsc @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsc + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsc your application uses +module.exports = absRequire(`typescript/bin/tsc`); diff --git a/.yarn/sdks/typescript/bin/tsserver b/.yarn/sdks/typescript/bin/tsserver new file mode 100644 index 0000000..d7a6056 --- /dev/null +++ b/.yarn/sdks/typescript/bin/tsserver @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsserver + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsserver your application uses +module.exports = absRequire(`typescript/bin/tsserver`); diff --git a/.yarn/sdks/typescript/lib/tsc.js b/.yarn/sdks/typescript/lib/tsc.js new file mode 100644 index 0000000..2f62fc9 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsc.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsc.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsc.js your application uses +module.exports = absRequire(`typescript/lib/tsc.js`); diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js new file mode 100644 index 0000000..bbb1e46 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsserver.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserver.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserver.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js new file mode 100644 index 0000000..a68f028 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserverlibrary.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserverlibrary.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/.yarn/sdks/typescript/lib/typescript.js b/.yarn/sdks/typescript/lib/typescript.js new file mode 100644 index 0000000..e14fa87 --- /dev/null +++ b/.yarn/sdks/typescript/lib/typescript.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/typescript.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/typescript.js your application uses +module.exports = absRequire(`typescript/lib/typescript.js`); diff --git a/.yarn/sdks/typescript/package.json b/.yarn/sdks/typescript/package.json new file mode 100644 index 0000000..925b839 --- /dev/null +++ b/.yarn/sdks/typescript/package.json @@ -0,0 +1,6 @@ +{ + "name": "typescript", + "version": "5.0.4-sdk", + "main": "./lib/typescript.js", + "type": "commonjs" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..28d9be8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Semantic Release Changelog + +## [1.0.3](https://github.com/tufcode/soketto-server/compare/v1.0.2...v1.0.3) (2023-04-20) + + +### Performance Improvements + +* removed ping & error ([9c37281](https://github.com/tufcode/soketto-server/commit/9c372813a675f93565ea959f2d21ddac9c33223d)) + +## [1.0.2](https://github.com/tufcode/soketto-server/compare/v1.0.1...v1.0.2) (2023-04-20) + + +### Performance Improvements + +* upgrade paketto version ([80feb11](https://github.com/tufcode/soketto-server/commit/80feb11e29ab3f8b9d1e7b3bd79d2207a4756b43)) + +## [1.0.1](https://github.com/tufcode/soketto-server/compare/v1.0.0...v1.0.1) (2023-04-19) + + +### Bug Fixes + +* things work now, hopefully ([7b65f9e](https://github.com/tufcode/soketto-server/commit/7b65f9ef26f42cb000c473463aa7732bffea8ba3)) + +# 1.0.0 (2023-04-18) + + +### Bug Fixes + +* **ci:** Move generated docs to repository ([b302cc3](https://github.com/tufcode/soketto-server/commit/b302cc3c0228bbd907ea122240e8197f13748823)) + +## [1.0.1](https://github.com/daisyengine/server/compare/v1.0.0...v1.0.1) (2023-04-06) + + +### Bug Fixes + +* **publish:** Almost forgot ([70db56e](https://github.com/daisyengine/server/commit/70db56e7275742837123987f1a0bb9f1d34d1f63)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f6df2c1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Tufan Meriç Uyguner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cb32f9 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# ソケット (soketto) server + +TBA + +## Documentation + +[API Docs](docs/README.md) diff --git a/package.json b/package.json new file mode 100644 index 0000000..854775b --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "soketto-server", + "version": "1.0.0-release", + "author": "Tufan Meriç Uyguner", + "license": "MIT", + "keywords": [ + "server", + "uwebsockets", + "multiplayer", + "realtime", + "socket", + "websocket", + "networking", + "transport", + "communication" + ], + "description": "A simple and flexible server framework for realtime applications.", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "files": [ + "dist", + "package.json", + "README.md", + "LICENSE" + ], + "scripts": { + "semantic-release": "semantic-release", + "build": "tsc && yarn typedoc" + }, + "devDependencies": { + "@types/node": "^16.4.6", + "ts-node": "^10.9.1", + "typedoc": "^0.24.4", + "typedoc-plugin-markdown": "^3.15.1", + "typescript": "^5.0.3" + }, + "dependencies": { + "paketto": "^1.0.0-release", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.15.0" + }, + "packageManager": "yarn@3.5.0", + "repository": { + "type": "git", + "url": "https://github.com/tufcode/soketto-server.git" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/src/Enums/ClientStatus.ts b/src/Enums/ClientStatus.ts new file mode 100644 index 0000000..498fb41 --- /dev/null +++ b/src/Enums/ClientStatus.ts @@ -0,0 +1,21 @@ +/** + * Connection status of a NetworkClient + */ +export enum ClientStatus { + /** + * Client is connected to the server + */ + CONNECTED, + /** + * Client is ready to send and receive messages + */ + READY, + /** + * Client is disconnecting from the server + */ + CLOSING, + /** + * Client is disconnected from the server + */ + CLOSED, +} diff --git a/src/Enums/Protocol.ts b/src/Enums/Protocol.ts new file mode 100644 index 0000000..a0747d4 --- /dev/null +++ b/src/Enums/Protocol.ts @@ -0,0 +1,14 @@ +/** + * Specifies packet identifiers. + * + * S = Server -> Client + * C = Client -> Server + * SC = Server <-> Client + * + * @internal + */ +export enum Protocol { + SC_Message = 10, + + S_CloseReason = 51, +} diff --git a/src/Server.ts b/src/Server.ts new file mode 100644 index 0000000..11ef704 --- /dev/null +++ b/src/Server.ts @@ -0,0 +1,211 @@ +import { PakettoInstance, paketto } from "paketto"; +import { ClientStatus } from "./Enums/ClientStatus"; +import { Protocol } from "./Enums/Protocol"; +import { SocketClient } from "./SocketClient"; +import { ITransport } from "./Transports/ITransport"; +import { WebSocketTransport } from "./Transports/WebSocket/WebSocketTransport"; + +export class Server { + /** + * @internal + */ + private _lastConnectionId = 1; + + /** + * @internal + */ + private readonly _buffer: Uint8Array; + + /** + * @internal + */ + private _paketto: PakettoInstance; + + get paketto() { + return this._paketto; + } + + /** + * Gets the next connection ID. + */ + get nextConnectionId() { + return this._lastConnectionId++; + } + + get buffer() { + return this._buffer; + } + + /** + * @internal + */ + private readonly _transports: Set = new Set(); + /** + * @internal + */ + private readonly _clients: Map = new Map(); + + /** + * Returns a list of transports. This is a copy of the internal list, so + * modifying it will not affect the server. + */ + get transports() { + return new Set(this._transports); + } + + /** + * Returns a list of clients. This is a copy of the internal list, so + * modifying it will not affect the server. + */ + get clients() { + return new Map(this._clients); + } + + /** + * Called when a client connects to the server. + * @param client The client that connected. + * @event + */ + onConnect?: (client: SocketClient) => void = () => {}; + + /** + * Creates a new server. + * @param pakettoInstance The paketto instance to use. Defaults to a new + * instance. + * @param preallocateBytes The number of bytes to preallocate for the + * packet buffer. Defaults to 1MB (1024 * 1024). + * @param transports The transports to use. If none are provided, a + * {@link WebSocketTransport} with port 3000 will be used. + */ + constructor( + pakettoInstance = paketto(), + preallocateBytes = 1024 * 1024, + ...transports: ITransport[] + ) { + this._paketto = pakettoInstance; + this._buffer = new Uint8Array(preallocateBytes); + if (transports.length === 0) { + const transport = new WebSocketTransport(); + transport.server = this; + this._transports.add(transport); + } else { + transports.forEach((transport) => { + transport.server = this; + this._transports.add(transport); + }); + } + } + + /** + * Starts the server. + */ + start() { + this._transports.forEach((transport) => { + transport.onConnect = (connectionId) => { + this._onConnect(connectionId, transport); + }; + transport.onDisconnect = this._onDisconnect.bind(this); + transport.onMessage = this._onMessage.bind(this); + transport.start(); + }); + } + + /** + * Stops the server. + */ + stop() { + this._transports.forEach((transport) => { + transport.stop(); + }); + } + + /** + * Broadcasts a message to all connected clients. + * @param event The event identifier. + * @param data The data to send. + */ + broadcast(event: unknown, data: unknown) { + this._clients.forEach((client) => { + client.send(event, data); + }); + } + + /** + * @internal + */ + private _onConnect(connectionId: number, transport: ITransport) { + const client = new SocketClient(connectionId, transport, this); + this._clients.set(connectionId, client); + + client.status = ClientStatus.READY; + this.onConnect(client); + } + + /** + * @internal + */ + private _onDisconnect(connectionId: number) { + const client = this._clients.get(connectionId); + if (!client) throw new Error("Client disconnected but was not connected."); + client.status = ClientStatus.CLOSED; + client.onClose(); + this._clients.delete(connectionId); + } + + /** + * @internal + */ + private _onMessage(connectionId: number, message: Uint8Array) { + const client = this._clients.get(connectionId); + if (!client) throw new Error("Message received from unknown client."); + + if (message.length === 0) { + console.warn("Received empty packet."); + client.close(); + return; + } + + const packetId = message[0]; + let offset = 1; + + switch (packetId) { + case Protocol.SC_Message: + if (client.status !== ClientStatus.READY) { + console.warn("Received message from client that is not ready."); + client.close(); + break; + } + if (message.length < 2) { + console.warn("Received message is too short."); + client.close(); + break; + } + + // Key + const deserializedKey = this._paketto.deserialize(message, offset); + offset = deserializedKey.offset; + + if (message.length < offset + 1) { + console.warn("Received message is too short."); + client.close(); + break; + } + + // Value + const deserializedValue = this._paketto.deserialize(message, offset); + offset = deserializedValue.offset; + + // Call the client's onMessage handler + client.onMessage( + deserializedKey.value as string, + deserializedValue.value as any + ); + + break; + default: + console.warn(`Received unknown packet ID: ${packetId}`); + client.close(); + break; + } + } +} diff --git a/src/SocketClient.ts b/src/SocketClient.ts new file mode 100644 index 0000000..284ff71 --- /dev/null +++ b/src/SocketClient.ts @@ -0,0 +1,84 @@ +import { serializeUInt8 } from "paketto"; +import { ClientStatus } from "./Enums/ClientStatus"; +import { Protocol } from "./Enums/Protocol"; +import { ITransport } from "./Transports/ITransport"; +import { Server } from "./Server"; + +export class SocketClient { + /** + * The id of this client + * @internal + */ + private readonly _id: number; + + /** + * The transport this client is using + * @internal + */ + private readonly _transport: ITransport; + + private _server: Server; + + get id() { + return this._id; + } + + get transport() { + return this._transport; + } + + /** + * Status of this client. Do not modify this value. + * @internal + */ + status: ClientStatus = ClientStatus.CONNECTED; + + /** + * Custom data associated with this client. You may set this to + * anything. + */ + data: unknown; + + onMessage: (event: string | number, data: unknown) => void = () => {}; + onClose: () => void = () => {}; + onError: () => void = () => {}; + + /** + * @internal + */ + constructor(id: number, transport: ITransport, server: Server) { + this._id = id; + this._transport = transport; + this._server = server; + } + + /** + * Disconnects this client + */ + close() { + if (this.status >= ClientStatus.CLOSING) return; + this.status = ClientStatus.CLOSING; + + this._transport.close(this._id); + } + + /** + * Sends a message to this client + * @param event Unique identifier for this event. + * + * It is best to use a string or number for this, but you can use + * any type that can be serialized. Recommended type is uint8 (any number + * between 0 and 255). + * + * @param data Data that will be sent to this client. + */ + send(event: unknown, data: unknown) { + if (this.status >= ClientStatus.CLOSING) return; + + let offset = serializeUInt8(this._server.buffer, Protocol.SC_Message); + offset = this._server.paketto.serialize(this._server.buffer, event, offset); + offset = this._server.paketto.serialize(this._server.buffer, data, offset); + + this._transport.send(this._id, this._server.buffer.subarray(0, offset)); + } +} diff --git a/src/Transports/ITransport.ts b/src/Transports/ITransport.ts new file mode 100644 index 0000000..51e174e --- /dev/null +++ b/src/Transports/ITransport.ts @@ -0,0 +1,52 @@ +import { Server } from "../Server"; + +export interface ITransport { + /** + * The server this transport is attached to. This is set by the server + * when the transport is added. + */ + server: Server; + + /** + * Should be called when a new connection is established. + * @param connectionId The ID of the connection. + */ + onConnect: (connectionId: number) => void; + /** + * Should be called when a connection is closed. + * @param connectionId The ID of the connection. + */ + onDisconnect: (connectionId: number) => void; + /** + * Should be called when a message is received. + * @param connectionId The ID of the connection. + * @param message The message received. + */ + onMessage: (connectionId: number, message: Uint8Array) => void; + + /** + * Sends a message to a connection. + * @param connectionId The ID of the connection. + * @param message The message to send. + */ + send(connectionId: number, message: Uint8Array): void; + /** + * Closes a connection. + * @param connectionId The ID of the connection. + */ + close(connectionId: number): void; + + /** + * Starts the transport. + */ + start(): void; + /** + * Stops the transport. + */ + stop(): void; + + /** + * Gets the IDs of all clients connected to this transport. + */ + getConnections(): number[]; +} diff --git a/src/Transports/WebSocket/WebSocketTransport.ts b/src/Transports/WebSocket/WebSocketTransport.ts new file mode 100644 index 0000000..9d7e91a --- /dev/null +++ b/src/Transports/WebSocket/WebSocketTransport.ts @@ -0,0 +1,127 @@ +import * as uWS from "uWebSockets.js"; +import { ITransport } from "../ITransport"; +import { Server } from "../../Server"; + +export class WebSocketTransport implements ITransport { + server: Server; + + /** + * @internal + */ + private _app: uWS.TemplatedApp; + /** + * @internal + */ + private _clients: Map; + /** + * @internal + */ + private _ids: Map; + /** + * @internal + */ + private _listen_socket: uWS.us_listen_socket; + /** + * @internal + */ + private _port = 3000; + + get port() { + return this._port; + } + + set port(port: number) { + if (this._listen_socket) { + throw new Error("Cannot change port while listening."); + } + this._port = port; + } + + onConnect: (connectionId: number) => void; + onDisconnect: (connectionId: number) => void; + onMessage: (connectionId: number, message: Uint8Array) => void; + + constructor( + maxPayloadLength = 2 * 1024 * 1024, + idleTimeout = 32, + port = 3000 + ) { + this._clients = new Map(); + this._ids = new Map(); + this._port = port; + + this._app = uWS + ./*SSL*/ App({}) + .ws("/", { + /* Options */ + compression: uWS.SHARED_COMPRESSOR, + maxPayloadLength, + idleTimeout, + /* Handlers */ + open: (ws) => { + const connectionId = this.server.nextConnectionId; + this._clients.set(connectionId, ws); + this._ids.set(ws, connectionId); + this.onConnect(connectionId); + }, + message: (ws, message, isBinary) => { + const connectionId = this._ids.get(ws); + if (connectionId === undefined) + throw new Error("Message from unknown connection?"); + + if (!isBinary) { + console.warn( + "Received non-binary message from client " + connectionId + ); + return; + } + + this.onMessage(connectionId, new Uint8Array(message)); + }, + drain: (ws) => { + // #TODO: HANDLE BACKPRESSURE + console.log("WebSocket backpressure: " + ws.getBufferedAmount()); + }, + close: (ws, code) => { + const connectionId = this._ids.get(ws); + if (connectionId === undefined) + throw new Error("Closing unknown connection?"); + + this.onDisconnect(connectionId); + this._clients.delete(connectionId); + this._ids.delete(ws); + }, + }) + .any("/*", (res, req) => { + res.end("[connect.js] Server, Transport: uWebSockets.js"); + }); + } + + send(connectionId: number, message: Uint8Array): void { + this._clients.get(connectionId)?.send(message, true); + } + + close(connectionId: number): void { + this._clients.get(connectionId)?.close(); + } + + start(): void { + this._app.listen(this._port, (token: uWS.us_listen_socket) => { + if (!token) { + throw new Error("Failed to listen to port " + this._port); + } + this._listen_socket = token; + }); + } + + stop(): void { + uWS.us_listen_socket_close(this._listen_socket); + this._listen_socket = null; + this._clients.clear(); + this._ids.clear(); + } + + getConnections(): number[] { + return Array.from(this._clients.keys()); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..8161574 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +export { Server } from "./Server"; +export { SocketClient } from "./SocketClient"; +export { ITransport } from "./Transports/ITransport"; +export { WebSocketTransport } from "./Transports/WebSocket/WebSocketTransport"; +export { ClientStatus } from "./Enums/ClientStatus"; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..eb65bdd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..ae009ba --- /dev/null +++ b/typedoc.json @@ -0,0 +1,13 @@ +{ + // Comments are supported, like tsconfig.json + "out": "docs", + "excludeInternal": true, + "readme": "none", + "name": "API Docs for soketto server", + "cleanOutputDir": false, + "entryPoints": ["src/index.ts"], + "plugin": ["typedoc-plugin-markdown"], + "githubPages": false, + "entryDocument": "README.md", + "hideBreadcrumbs": true +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..693104e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,390 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + checksum: 5718f267085ed8edb3e7ef210137241775e607ee18b77d95aa5bd7514f47f5019aa2d82d96b3bf342ef7aa890a346fa1044532ff7cc3009e7d24fce3ce6200fa + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: d89597752fd88d3f3480845691a05a44bd21faac18e2185b6f436c3b0fd0c5a859fbbd9aaa92050c4052caf325ad3e10e2e1d1b64327517471b7d51babc0ddef + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.9 + resolution: "@tsconfig/node10@npm:1.0.9" + checksum: a33ae4dc2a621c0678ac8ac4bceb8e512ae75dac65417a2ad9b022d9b5411e863c4c198b6ba9ef659e14b9fb609bbec680841a2e84c1172df7a5ffcf076539df + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 5ce29a41b13e7897a58b8e2df11269c5395999e588b9a467386f99d1d26f6c77d1af2719e407621412520ea30517d718d5192a32403b8dfcc163bf33e40a338a + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 19275fe80c4c8d0ad0abed6a96dbf00642e88b220b090418609c4376e1cef81bf16237bf170ad1b341452feddb8115d8dd2e5acdfdea1b27422071163dc9ba9d + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.3 + resolution: "@tsconfig/node16@npm:1.0.3" + checksum: 3a8b657dd047495b7ad23437d6afd20297ce90380ff0bdee93fc7d39a900dbd8d9e26e53ff6b465e7967ce2adf0b218782590ce9013285121e6a5928fbd6819f + languageName: node + linkType: hard + +"@types/node@npm:^16.4.6": + version: 16.18.23 + resolution: "@types/node@npm:16.18.23" + checksum: 00e51db28fc7a182747f37215b3f25400b1c7a8525e09fa14e55be5798891a118ebf636a49d3197335a3580fcb8222fd4ecc20c2ccff69f1c0d233fc5697465d + languageName: node + linkType: hard + +"acorn-walk@npm:^8.1.1": + version: 8.2.0 + resolution: "acorn-walk@npm:8.2.0" + checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 + languageName: node + linkType: hard + +"acorn@npm:^8.4.1": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + +"ansi-sequence-parser@npm:^1.1.0": + version: 1.1.0 + resolution: "ansi-sequence-parser@npm:1.1.0" + checksum: 75f4d3a4c555655a698aec05b5763cbddcd16ccccdbfd178fb0aa471ab74fdf98e031b875ef26e64be6a95cf970c89238744b26de6e34af97f316d5186b1df53 + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 544af8dd3f60546d3e4aff084d451b96961d2267d668670199692f8d054f0415d86fc5497d0e641e91546f0aa920e7c29e5250e99fc89f5552a34b5d93b77f43 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: ^1.0.0 + checksum: a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: f2c09b0ce4e6b301c221addd83bf3f454c0bc00caa3dd837cf6c127d6edf7223aa2bbe3b688feea110b7f262adbfc845b757c44c8a9f8c0c5b15d8fa9ce9d20d + languageName: node + linkType: hard + +"handlebars@npm:^4.7.7": + version: 4.7.7 + resolution: "handlebars@npm:4.7.7" + dependencies: + minimist: ^1.2.5 + neo-async: ^2.6.0 + source-map: ^0.6.1 + uglify-js: ^3.1.4 + wordwrap: ^1.0.0 + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 1e79a43f5e18d15742977cb987923eab3e2a8f44f2d9d340982bcb69e1735ed049226e534d7c1074eaddaf37e4fb4f471a8adb71cddd5bc8cf3f894241df5cee + languageName: node + linkType: hard + +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + +"lunr@npm:^2.3.9": + version: 2.3.9 + resolution: "lunr@npm:2.3.9" + checksum: 176719e24fcce7d3cf1baccce9dd5633cd8bdc1f41ebe6a180112e5ee99d80373fe2454f5d4624d437e5a8319698ca6837b9950566e15d2cae5f2a543a3db4b8 + languageName: node + linkType: hard + +"make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 + languageName: node + linkType: hard + +"marked@npm:^4.3.0": + version: 4.3.0 + resolution: "marked@npm:4.3.0" + bin: + marked: bin/marked.js + checksum: 0db6817893952c3ec710eb9ceafb8468bf5ae38cb0f92b7b083baa13d70b19774674be04db5b817681fa7c5c6a088f61300815e4dd75a59696f4716ad69f6260 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.0": + version: 9.0.0 + resolution: "minimatch@npm:9.0.0" + dependencies: + brace-expansion: ^2.0.1 + checksum: 7bd57899edd1d1b0560f50b5b2d1ea4ad2a366c5a2c8e0a943372cf2f200b64c256bae45a87a80915adbce27fa36526264296ace0da57b600481fe5ea3e372e5 + languageName: node + linkType: hard + +"minimist@npm:^1.2.5": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 + languageName: node + linkType: hard + +"neo-async@npm:^2.6.0": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 + languageName: node + linkType: hard + +"paketto@npm:^1.0.0-release": + version: 1.0.0-release + resolution: "paketto@npm:1.0.0-release" + dependencies: + tslib: ^2.5.0 + checksum: 20eeb71753dc31cd1f8ccc5d1cd5e2c3019cf73fdb37d50b7059c57395434b187be36561496323eb01e34e755ca89c975473d2269c5fd8d166acf7b6a8f19090 + languageName: node + linkType: hard + +"shiki@npm:^0.14.1": + version: 0.14.1 + resolution: "shiki@npm:0.14.1" + dependencies: + ansi-sequence-parser: ^1.1.0 + jsonc-parser: ^3.2.0 + vscode-oniguruma: ^1.7.0 + vscode-textmate: ^8.0.0 + checksum: b19ea337cc84da69d99ca39d109f82946e0c56c11cc4c67b3b91cc14a9479203365fd0c9e0dd87e908f493ab409dc6f1849175384b6ca593ce7da884ae1edca2 + languageName: node + linkType: hard + +"soketto-server@workspace:.": + version: 0.0.0-use.local + resolution: "soketto-server@workspace:." + dependencies: + "@types/node": ^16.4.6 + paketto: ^1.0.0-release + ts-node: ^10.9.1 + typedoc: ^0.24.4 + typedoc-plugin-markdown: ^3.15.1 + typescript: ^5.0.3 + uWebSockets.js: "github:uNetworking/uWebSockets.js#v20.15.0" + languageName: unknown + linkType: soft + +"source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 + languageName: node + linkType: hard + +"ts-node@npm:^10.9.1": + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" + dependencies: + "@cspotcode/source-map-support": ^0.8.0 + "@tsconfig/node10": ^1.0.7 + "@tsconfig/node12": ^1.0.7 + "@tsconfig/node14": ^1.0.0 + "@tsconfig/node16": ^1.0.2 + acorn: ^8.4.1 + acorn-walk: ^8.1.1 + arg: ^4.1.0 + create-require: ^1.1.0 + diff: ^4.0.1 + make-error: ^1.1.1 + v8-compile-cache-lib: ^3.0.1 + yn: 3.1.1 + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35 + languageName: node + linkType: hard + +"tslib@npm:^2.5.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + languageName: node + linkType: hard + +"typedoc-plugin-markdown@npm:^3.15.1": + version: 3.15.1 + resolution: "typedoc-plugin-markdown@npm:3.15.1" + dependencies: + handlebars: ^4.7.7 + peerDependencies: + typedoc: ">=0.24.0" + checksum: a80e43e8e8628fa6c65f44bfe20fc79b03e432e36c9a43c6dc419a0a0ec9658743ebb228cd05dd8491d2ad23e389d4903c00aef3d3de73340a86382b91db39b1 + languageName: node + linkType: hard + +"typedoc@npm:^0.24.4": + version: 0.24.4 + resolution: "typedoc@npm:0.24.4" + dependencies: + lunr: ^2.3.9 + marked: ^4.3.0 + minimatch: ^9.0.0 + shiki: ^0.14.1 + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x + bin: + typedoc: bin/typedoc + checksum: c6762aff2b40eb574fc348c82921471fa1c1dc8a83adc284457aab30ea61768770be469f424340ab646944472653290578fd8e6308d55ec2e611577472067dae + languageName: node + linkType: hard + +"typescript@npm:^5.0.3": + version: 5.0.4 + resolution: "typescript@npm:5.0.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 82b94da3f4604a8946da585f7d6c3025fff8410779e5bde2855ab130d05e4fd08938b9e593b6ebed165bda6ad9292b230984f10952cf82f0a0ca07bbeaa08172 + languageName: node + linkType: hard + +"typescript@patch:typescript@^5.0.3#~builtin": + version: 5.0.4 + resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=85af82" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: bb309d320c59a26565fb3793dba550576ab861018ff3fd1b7fccabbe46ae4a35546bc45f342c0a0b6f265c801ccdf64ffd68f548f117ceb7f0eac4b805cd52a9 + languageName: node + linkType: hard + +"uWebSockets.js@github:uNetworking/uWebSockets.js#v20.15.0": + version: 20.15.0 + resolution: "uWebSockets.js@https://github.com/uNetworking/uWebSockets.js.git#commit=77bc1fd5577ae42b56675912eb8481a31f3fefd2" + checksum: 8849e724744b4525424201687990ed68358b6dced55320918a4b641e03e4211582a3d27823a4ece8346ac97ff2e811f39925077bc8200f330f4d18237def380b + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 78089ad549e21bcdbfca10c08850022b22024cdcc2da9b168bcf5a73a6ed7bf01a9cebb9eac28e03cd23a684d81e0502797e88f3ccd27a32aeab1cfc44c39da0 + languageName: node + linkType: hard + +"vscode-oniguruma@npm:^1.7.0": + version: 1.7.0 + resolution: "vscode-oniguruma@npm:1.7.0" + checksum: 53519d91d90593e6fb080260892e87d447e9b200c4964d766772b5053f5699066539d92100f77f1302c91e8fc5d9c772fbe40fe4c90f3d411a96d5a9b1e63f42 + languageName: node + linkType: hard + +"vscode-textmate@npm:^8.0.0": + version: 8.0.0 + resolution: "vscode-textmate@npm:8.0.0" + checksum: 127780dfea89559d70b8326df6ec344cfd701312dd7f3f591a718693812b7852c30b6715e3cfc8b3200a4e2515b4c96f0843c0eacc0a3020969b5de262c2a4bb + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 2a44b2788165d0a3de71fd517d4880a8e20ea3a82c080ce46e294f0b68b69a2e49cff5f99c600e275c698a90d12c5ea32aff06c311f0db2eb3f1201f3e7b2a04 + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 + languageName: node + linkType: hard