├── .commitlintrc.js ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── publish.yml │ ├── semantic-pull-request.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── alifc │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── ali-req.ts │ │ ├── ali-res.ts │ │ ├── cli-config.ts │ │ ├── context.ts │ │ ├── index.ts │ │ └── startup.ts │ └── test │ │ ├── cli-config.test.ts │ │ ├── context.test.ts │ │ ├── error.test.ts │ │ ├── type │ │ ├── buffer.test.ts │ │ ├── json.test.ts │ │ ├── stream.test.ts │ │ └── text.test.ts │ │ └── utils.ts ├── base.gitignore ├── body │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── multipart.ts │ │ └── startup.ts │ └── test │ │ ├── json.test.ts │ │ ├── multipart.test.ts │ │ ├── text.test.ts │ │ ├── urlencoded.test.ts │ │ └── utils.ts ├── cookie │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ └── cookie.test.ts ├── core │ ├── package.json │ ├── src │ │ ├── context.ts │ │ ├── exception.ts │ │ ├── hook │ │ │ ├── hook.exec.ts │ │ │ ├── hook.item.ts │ │ │ ├── hook.manager.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── middlewares │ │ │ ├── compose.middleware.ts │ │ │ ├── index.ts │ │ │ ├── lambda.middleware.ts │ │ │ └── middleware.ts │ │ ├── register.ts │ │ ├── startup.ts │ │ └── utils │ │ │ ├── cli-assets.ts │ │ │ ├── index.ts │ │ │ ├── module.ts │ │ │ ├── net.ts │ │ │ ├── typeis.ts │ │ │ └── types.ts │ └── test │ │ ├── cli-assets.test.ts │ │ ├── context.test.ts │ │ ├── error.test.ts │ │ ├── hook │ │ ├── before.test.ts │ │ ├── begining.test.ts │ │ ├── constructor.test.ts │ │ ├── context.test.ts │ │ ├── error.test.ts │ │ ├── initialization.test.ts │ │ └── simple.test.ts │ │ ├── middleware │ │ ├── compose.test.ts │ │ ├── constructor.test.ts │ │ ├── context.test.ts │ │ ├── instanceof.test.ts │ │ ├── pipeline.test.ts │ │ ├── simple.test.ts │ │ ├── singleton.test.ts │ │ └── success.test.ts │ │ ├── module.test.ts │ │ ├── net.test.ts │ │ ├── startup.test.ts │ │ ├── test-startup.ts │ │ ├── tsconfig.json │ │ └── typeis.test.ts ├── cors │ ├── package.json │ ├── src │ │ ├── cors.middleware.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── cors.test.ts │ │ └── options.test.ts ├── env │ ├── package.json │ ├── src │ │ ├── cli-config.ts │ │ ├── index.ts │ │ ├── options.ts │ │ └── version.ts │ └── test │ │ ├── cli-config.test.ts │ │ ├── env.test.ts │ │ ├── envs │ │ ├── .env │ │ ├── .env.dev │ │ ├── .env.development │ │ ├── .env.prod │ │ ├── .env.production │ │ └── .env.stage │ │ ├── tsconfig.json │ │ ├── utils.ts │ │ ├── version.test.ts │ │ └── version │ │ └── package.json ├── filter │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── filters │ │ │ ├── action.filter.ts │ │ │ ├── authorization.filter.ts │ │ │ ├── exception.filter.ts │ │ │ ├── exec-filter.ts │ │ │ ├── filter.ts │ │ │ ├── index.ts │ │ │ └── resource.filter.ts │ │ ├── index.ts │ │ └── use-filters.decorator.ts │ └── test │ │ ├── actions │ │ └── auth.get.ts │ │ ├── authorization.test.ts │ │ ├── exception.test.ts │ │ ├── exec-named.test.ts │ │ ├── executing.test.ts │ │ ├── global.test.ts │ │ ├── order.test.ts │ │ └── tsconfig.json ├── http │ ├── package.json │ ├── src │ │ ├── context │ │ │ ├── header-handler.ts │ │ │ ├── http-error-message.ts │ │ │ ├── index.ts │ │ │ ├── request.ts │ │ │ ├── response.ts │ │ │ └── result-handler.ts │ │ ├── exceptions │ │ │ ├── bad-gateway-exception.ts │ │ │ ├── bad-request-exception.ts │ │ │ ├── conflict-exception.ts │ │ │ ├── forbidden-exception.ts │ │ │ ├── gateway-timeout-exception.ts │ │ │ ├── gone-exception.ts │ │ │ ├── http-exception.ts │ │ │ ├── http-version-not-supported-exception.ts │ │ │ ├── im-a-teapot-exception.ts │ │ │ ├── index.ts │ │ │ ├── internal-server-error-exception.ts │ │ │ ├── method-not-allowed-exception.ts │ │ │ ├── misdirected-exception.ts │ │ │ ├── not-acceptable-exception.ts │ │ │ ├── not-found-exception.ts │ │ │ ├── not-implemented-exception.ts │ │ │ ├── precondition-failed-exception.ts │ │ │ ├── request-timeout-exception.ts │ │ │ ├── request-too-long-exception.ts │ │ │ ├── service-unavailable-exception.ts │ │ │ ├── unauthorized-exception.ts │ │ │ ├── unprocessable-entity-exception.ts │ │ │ └── unsupported-media-type-exception.ts │ │ ├── index.ts │ │ ├── map-matcher.ts │ │ ├── methods.ts │ │ ├── middleware.ts │ │ ├── options.ts │ │ ├── startup.ts │ │ └── types.ts │ └── test │ │ ├── context.test.ts │ │ ├── exceptions │ │ ├── http-exception.test.ts │ │ └── throw.test.ts │ │ ├── header.test.ts │ │ ├── match.test.ts │ │ ├── method.test.ts │ │ ├── request │ │ ├── body.test.ts │ │ ├── method.test.ts │ │ └── query.test.ts │ │ ├── response │ │ ├── content-type.test.ts │ │ ├── custom-headers.test.ts │ │ ├── response-error.test.ts │ │ ├── result-handler.test.ts │ │ └── result-method.test.ts │ │ └── tsconfig.json ├── inject │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators │ │ │ ├── create-inject.ts │ │ │ ├── index.ts │ │ │ └── inject.decorator.ts │ │ ├── index.ts │ │ ├── inject-parser.ts │ │ ├── inject-type.ts │ │ ├── interfaces │ │ │ ├── index.ts │ │ │ ├── inject-custom.ts │ │ │ ├── inject-key.ts │ │ │ ├── inject-map.ts │ │ │ └── service.ts │ │ └── startup.ts │ └── test │ │ ├── class.test.ts │ │ ├── custom-inject.test.ts │ │ ├── derive.test.ts │ │ ├── dispose.test.ts │ │ ├── import.test.ts │ │ ├── initialize.test.ts │ │ ├── inject-params.test.ts │ │ ├── key-inject │ │ ├── chain.test.ts │ │ ├── constructor.test.ts │ │ ├── key-inject.test.ts │ │ ├── parse.test.ts │ │ └── type.test.ts │ │ ├── middleware-constructor.test.ts │ │ ├── object.test.ts │ │ ├── only-cons.test.ts │ │ ├── parse.test.ts │ │ ├── proptotype.test.ts │ │ ├── services.ts │ │ ├── simple.test.ts │ │ ├── singleton.test.ts │ │ ├── tsconfig.json │ │ └── type.test.ts ├── jest.config.js ├── jest.setup.js ├── jwt │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ ├── jwt-options.ts │ │ └── jwt.service.ts │ └── test │ │ ├── auth.test.ts │ │ ├── decorator.test.ts │ │ ├── error.test.ts │ │ ├── key.test.ts │ │ ├── prefix.test.ts │ │ ├── sign.test.ts │ │ ├── tsconfig.json │ │ └── utils.ts ├── knex │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── connect.test.ts │ │ ├── identity.test.ts │ │ └── tsconfig.json ├── koa │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── halsp.middleware.ts │ │ ├── index.ts │ │ ├── koa.middleware.ts │ │ ├── res-transform.ts │ │ └── trans-response.ts │ └── test │ │ ├── body.test.ts │ │ ├── header.test.ts │ │ ├── koa-ipare.test.ts │ │ ├── middleware.test.ts │ │ ├── pipe.test.ts │ │ ├── request-body.test.ts │ │ └── type.test.ts ├── lambda │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── cli-config.ts │ │ ├── context.ts │ │ ├── index.ts │ │ ├── response-struct.ts │ │ └── startup.ts │ └── test │ │ ├── buffer.test.ts │ │ ├── cli-config.test.ts │ │ ├── context.test.ts │ │ ├── query.test.ts │ │ ├── response.test.ts │ │ └── text-body.test.ts ├── logger │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── identity.test.ts │ │ ├── logger.test.ts │ │ ├── tsconfig.json │ │ └── utils.ts ├── micro-grpc │ ├── package.json │ ├── src │ │ ├── cli-config.ts │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── options.ts │ │ ├── index.ts │ │ ├── load-packages.ts │ │ ├── server │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── startup.ts │ │ └── stream.ts │ └── test │ │ ├── cli-config.test.ts │ │ ├── client │ │ ├── client.test.ts │ │ ├── error.test.ts │ │ ├── service.test.ts │ │ └── stream.test.ts │ │ ├── load-packages.test.ts │ │ ├── protos │ │ ├── stream.client.proto │ │ ├── stream.cs.proto │ │ ├── stream.server.proto │ │ └── test.proto │ │ ├── server │ │ └── startup.test.ts │ │ └── stream.test.ts ├── micro-mqtt │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── options.ts │ │ ├── index.ts │ │ └── server │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ ├── startup.ts │ │ │ └── topic.ts │ └── test │ │ ├── client │ │ ├── emit.test.ts │ │ ├── error.test.ts │ │ ├── prefix.test.ts │ │ ├── send.test.ts │ │ └── timeout.test.ts │ │ ├── startup.test.ts │ │ └── topic.test.ts ├── micro-nats │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── options.ts │ │ ├── index.ts │ │ └── server │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── startup.ts │ └── test │ │ ├── client │ │ ├── connect.test.ts │ │ ├── emit.test.ts │ │ ├── prefix.test.ts │ │ ├── return.test.ts │ │ ├── send.test.ts │ │ └── timeout.test.ts │ │ ├── error.test.ts │ │ ├── headers.test.ts │ │ └── startup.test.ts ├── micro-redis │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── options.ts │ │ ├── index.ts │ │ └── server │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── startup.ts │ └── test │ │ ├── client.test.ts │ │ └── startup.test.ts ├── micro-tcp │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── options.ts │ │ ├── index.ts │ │ ├── server │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── startup.ts │ │ └── tcp-parser.ts │ └── test │ │ ├── client.test.ts │ │ ├── parser-tcp.test.ts │ │ ├── startup.test.ts │ │ └── utils.ts ├── micro │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ ├── constant.ts │ │ │ ├── decorators.ts │ │ │ ├── index.ts │ │ │ └── use-client.ts │ │ ├── common.internal │ │ │ ├── index.ts │ │ │ ├── json-buffer.ts │ │ │ └── packet.ts │ │ ├── index.ts │ │ └── server │ │ │ ├── context.ts │ │ │ ├── exception.ts │ │ │ ├── index.ts │ │ │ └── startup.ts │ └── test │ │ ├── client │ │ ├── base.test.ts │ │ └── inject.test.ts │ │ ├── json-buffer.test.ts │ │ ├── server │ │ ├── body.test.ts │ │ ├── context.test.ts │ │ ├── exception.test.ts │ │ └── startup.test.ts │ │ └── tsconfig.json ├── mongoose │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── connect.test.ts │ │ ├── http-context.test.ts │ │ ├── identity.test.ts │ │ └── tsconfig.json ├── mva │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── mva-options.ts │ │ └── result.filter.ts │ └── test │ │ ├── auth.test.ts │ │ ├── error-page.test.ts │ │ ├── filter.test.ts │ │ ├── global.ts │ │ ├── methods.test.ts │ │ ├── mva.test.ts │ │ ├── mva │ │ ├── actions │ │ │ ├── _.get.ts │ │ │ ├── _.post.ts │ │ │ ├── filter.get.ts │ │ │ └── user │ │ │ │ ├── ^email.get.ts │ │ │ │ └── _.post.ts │ │ ├── auth.middleware.ts │ │ ├── mock.ts │ │ ├── models │ │ │ └── User.ts │ │ ├── tsconfig.json │ │ └── views │ │ │ ├── 403.ejs │ │ │ ├── 404.ejs │ │ │ ├── filter.ejs │ │ │ ├── index.ejs │ │ │ └── user │ │ │ └── ^email │ │ │ └── index.ejs │ │ └── tsconfig.json ├── native │ ├── package.json │ ├── src │ │ ├── context.ts │ │ ├── index.ts │ │ ├── options.ts │ │ └── startup.ts │ └── test │ │ ├── body │ │ ├── buffer.test.ts │ │ ├── empty.test.ts │ │ ├── json.test.ts │ │ ├── stream.test.ts │ │ └── text.test.ts │ │ └── startup.test.ts ├── pipe │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators │ │ │ ├── create-property-decorator.ts │ │ │ ├── create-req-decorator.ts │ │ │ ├── exec-pipes.ts │ │ │ ├── index.ts │ │ │ └── req.decorator.ts │ │ ├── global │ │ │ ├── global-pipe-item.ts │ │ │ ├── global-pipe-type.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── pipe-item.ts │ │ ├── pipe-req-record.ts │ │ ├── pipe-req-type.ts │ │ ├── pipe-transform.ts │ │ └── presets │ │ │ ├── default-value.pipe.ts │ │ │ ├── error.ts │ │ │ ├── index.ts │ │ │ ├── lambda.pipe.ts │ │ │ ├── parse-bool.pipe.ts │ │ │ ├── parse-float.pipe.ts │ │ │ ├── parse-int.pipe.ts │ │ │ └── trim.pipe.ts │ └── test │ │ ├── TestMiddleware.ts │ │ ├── create-decorator.test.ts │ │ ├── deep.test.ts │ │ ├── empty.test.ts │ │ ├── global-pipe.test.ts │ │ ├── pipe │ │ ├── default-value.test.ts │ │ ├── error-pipe.test.ts │ │ ├── lambda-pipe.test.ts │ │ ├── micro-pipe.test.ts │ │ ├── obj-pipe.test.ts │ │ ├── parse-bool.test.ts │ │ ├── parse-float.test.ts │ │ ├── parse-int.test.ts │ │ ├── trim.test.ts │ │ └── utils.ts │ │ ├── plain-to-class.test.ts │ │ ├── record.test.ts │ │ ├── simple.test.ts │ │ └── tsconfig.json ├── redis │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── connect.test.ts │ │ ├── http-context.test.ts │ │ ├── identity.test.ts │ │ └── tsconfig.json ├── router │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── action │ │ │ ├── action-metadata.ts │ │ │ ├── http-method.decorator.ts │ │ │ ├── index.ts │ │ │ ├── method-item.ts │ │ │ └── pattern.decorator.ts │ │ ├── blank.middleware.ts │ │ ├── constant.ts │ │ ├── index.ts │ │ ├── map │ │ │ ├── map-creater.ts │ │ │ ├── map-item.ts │ │ │ ├── map-parser.ts │ │ │ └── module.ts │ │ ├── postbuild.ts │ │ ├── router-options.ts │ │ └── startup.ts │ └── test │ │ ├── action.test.ts │ │ ├── actions │ │ ├── NullBody.ts │ │ ├── Router.ts │ │ ├── _.get.ts │ │ ├── decorator │ │ │ ├── method-base-path.ts │ │ │ ├── method-custom.ts │ │ │ ├── method-url.ts │ │ │ └── method.ts │ │ ├── err │ │ │ ├── _.1post.ts │ │ │ └── _.post1.ts │ │ ├── metadata │ │ │ ├── custom.get.ts │ │ │ └── set-metadata.get.ts │ │ ├── multiple.get.ts │ │ ├── otherClass.ts │ │ ├── otherFile.txt │ │ ├── otherObject.ts │ │ ├── restful │ │ │ ├── ^id.get.ts │ │ │ ├── ^id │ │ │ │ ├── .delete.head.options.NO.patch.post.put.trace.ts │ │ │ │ ├── _.connect.ts │ │ │ │ ├── _.custom.ts │ │ │ │ ├── animals.connect.delete.get.head.options.patch.post.trace.ts │ │ │ │ └── animals.trace.ts │ │ │ ├── _.any.ts │ │ │ ├── _.connect.ts │ │ │ ├── _.delete.head.options.patch.post.put.trace.ts │ │ │ ├── _.get.ts │ │ │ ├── method │ │ │ │ ├── ^params │ │ │ │ │ ├── _.post.ts │ │ │ │ │ └── miss │ │ │ │ │ │ └── _.post.ts │ │ │ │ ├── ^params2 │ │ │ │ │ └── ^nextParams.post.ts │ │ │ │ ├── miss │ │ │ │ │ ├── ^query.post.ts │ │ │ │ │ └── _.post.ts │ │ │ │ └── simple.ts │ │ │ ├── mostLike │ │ │ │ ├── ^id1 │ │ │ │ │ └── act.post.ts │ │ │ │ └── ^id2 │ │ │ │ │ └── act.post.ts │ │ │ └── sortest │ │ │ │ ├── sort.get.ts │ │ │ │ └── sort │ │ │ │ └── _.get.ts │ │ ├── simple │ │ │ ├── AdminAuth.ts │ │ │ ├── LoginAuth.ts │ │ │ ├── Router.ts │ │ │ └── deepActions │ │ │ │ └── Router.ts │ │ └── test.d.ts │ │ ├── cli-config.test.ts │ │ ├── deco-http-method.test.ts │ │ ├── def-actions │ │ └── actions │ │ │ └── _.get.ts │ │ ├── extend-decorators.test.ts │ │ ├── map.test.ts │ │ ├── metadata.test.ts │ │ ├── micro-nats.test.ts │ │ ├── micro-redis.test.ts │ │ ├── micro-tcp.test.ts │ │ ├── micro │ │ ├── path.ts │ │ ├── path2.message.ts │ │ └── pattern.ts │ │ ├── modules.test.ts │ │ ├── modules │ │ ├── decorators │ │ │ ├── actions │ │ │ │ └── deco.get.ts │ │ │ └── module.ts │ │ ├── def │ │ │ ├── _.get.ts │ │ │ └── error.get.ts │ │ ├── ignore │ │ │ ├── ignore.get.ts │ │ │ └── module.ts │ │ └── prefix │ │ │ ├── actions │ │ │ ├── _.get.ts │ │ │ └── deep │ │ │ │ ├── _.get.ts │ │ │ │ └── deco.ts │ │ │ └── module.js │ │ ├── post-build.test.ts │ │ ├── post-build │ │ ├── .gitignore │ │ ├── actions │ │ │ └── _.get.ts │ │ └── modules │ │ │ └── _.get.ts │ │ ├── prefix.test.ts │ │ ├── restful │ │ ├── any.test.ts │ │ ├── err.test.ts │ │ ├── match.test.ts │ │ ├── not-allowed.test.ts │ │ ├── params.test.ts │ │ ├── restful-method.test.ts │ │ └── root.test.ts │ │ ├── router.test.ts │ │ ├── tsconfig.json │ │ └── utils.ts ├── static │ ├── base.readme.md │ ├── html │ │ ├── dir.html │ │ └── error.html │ ├── package.json │ ├── src │ │ ├── cli.attach.serve.ts │ │ ├── cli.config.ts │ │ ├── constant.ts │ │ ├── index.ts │ │ ├── middlewares │ │ │ ├── base.middleware.ts │ │ │ ├── match.middleware.ts │ │ │ ├── method.middleware.ts │ │ │ ├── result.middleware.ts │ │ │ ├── status404.middleware.ts │ │ │ ├── status405.middleware.ts │ │ │ └── utils.ts │ │ └── options.ts │ └── test │ │ ├── cli.config.test.ts │ │ ├── default.req.path.test.ts │ │ ├── dir.test.ts │ │ ├── exclude.test.ts │ │ ├── method.test.ts │ │ ├── mime.test.ts │ │ ├── not.found.test.ts │ │ ├── path.test.ts │ │ ├── prefix.test.ts │ │ ├── serve.test.ts │ │ ├── serve │ │ ├── dir │ │ │ └── file.txt │ │ └── index.html │ │ ├── single.static.test.ts │ │ ├── static │ │ ├── 404.html │ │ ├── 405.html │ │ ├── dir │ │ │ └── index.html │ │ ├── index.html │ │ ├── index.un │ │ └── 中文.html │ │ └── utils.ts ├── swagger │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── options.ts │ │ ├── parser │ │ │ ├── index.ts │ │ │ ├── schema-dict.ts │ │ │ └── utils.ts │ │ ├── swagger.middleware.ts │ │ └── validator.decorator.ts │ ├── test-compiler │ │ ├── index.ts │ │ ├── src │ │ │ └── test.post.ts │ │ └── tsconfig.json │ ├── test-http │ │ ├── actions │ │ │ ├── multi.get.ts │ │ │ ├── test.get.put.ts │ │ │ └── test.post.ts │ │ ├── index.ts │ │ └── tsconfig.json │ └── test │ │ ├── decorator.test.ts │ │ ├── empty-version │ │ └── package.json │ │ ├── match.test.ts │ │ ├── options.test.ts │ │ ├── parser.test.ts │ │ ├── parser │ │ ├── array.ts │ │ ├── body.ts │ │ ├── content-type.ts │ │ ├── decorator.ts │ │ ├── deep.ts │ │ ├── default.get.ts │ │ ├── ignore.ts │ │ ├── not-action.ts │ │ ├── response.ts │ │ └── test.ts │ │ ├── tsconfig.json │ │ └── utils.test.ts ├── testing │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── middleware.ts │ │ ├── response.ts │ │ ├── runin.ts │ │ ├── service.ts │ │ └── startup.ts │ └── test │ │ ├── http.test.ts │ │ ├── middleware.test.ts │ │ ├── service.test.ts │ │ ├── shell.test.ts │ │ ├── startup.test.ts │ │ ├── test.proto │ │ └── tsconfig.json ├── tsconfig.base.json ├── tsconfig.cjs.json ├── tsconfig.code.json ├── tsconfig.mjs.json ├── typeorm │ ├── package.json │ ├── src │ │ ├── constant.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ └── options.ts │ └── test │ │ ├── .gitignore │ │ ├── entities │ │ └── TestEntity.ts │ │ ├── entity.test.ts │ │ ├── exec.test.ts │ │ ├── http-context.test.ts │ │ ├── identity.test.ts │ │ └── tsconfig.json ├── validator │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── cli-transformer.ts │ │ ├── constant.ts │ │ ├── create-decorator.ts │ │ ├── decorators.ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── validate.pipe.ts │ │ └── validator-lib.ts │ └── test │ │ ├── custom.test.ts │ │ ├── error.test.ts │ │ ├── name.test.ts │ │ ├── options.test.ts │ │ ├── parent.test.ts │ │ ├── rules.test.ts │ │ ├── transformer-test │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ │ ├── transformer.test.ts │ │ ├── tsconfig.json │ │ └── validate.test.ts ├── view │ ├── base.readme.md │ ├── package.json │ ├── src │ │ ├── cli-config.ts │ │ ├── index.ts │ │ ├── user-view.ts │ │ └── view-options.ts │ └── test │ │ ├── call.test.ts │ │ ├── cli-config.test.ts │ │ ├── engine.test.ts │ │ ├── error.test.ts │ │ ├── middleware.test.ts │ │ ├── path.test.ts │ │ ├── tsconfig.json │ │ └── views │ │ ├── ejs │ │ ├── index.ejs │ │ └── index.txt │ │ ├── empty │ │ └── index.ejs │ │ ├── error │ │ ├── engine │ │ │ ├── index.doc │ │ │ └── index.txt │ │ └── path.dir │ │ │ └── index │ │ ├── html │ │ ├── index.html │ │ └── index.txt │ │ └── pug │ │ └── test.pug └── ws │ ├── package.json │ ├── src │ ├── decorator.ts │ ├── index.ts │ ├── manager.ts │ └── options.ts │ └── test │ ├── error.test.ts │ ├── startup.test.ts │ └── tsconfig.json ├── scripts ├── ci-install.sh ├── clean-dist.sh ├── copy-package-file.ts ├── copy-package-files.sh ├── create-readme.ts ├── get-packages.js ├── init-cli-deps.sh ├── init-docker.sh └── sync-cnpm.ts ├── tsconfig.json └── yarn.lock /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | const { getPackages } = require("./scripts/get-packages"); 2 | 3 | module.exports = { 4 | extends: ["@commitlint/config-conventional"], 5 | rules: { 6 | "type-enum": [ 7 | 2, 8 | "always", 9 | [ 10 | "build", 11 | "chore", 12 | "ci", 13 | "docs", 14 | "feat", 15 | "fix", 16 | "perf", 17 | "refactor", 18 | "revert", 19 | "style", 20 | "test", 21 | "typo" 22 | ], 23 | ], 24 | "scope-enum": [2, "always", ["release", ...getPackages()]], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.d.ts -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | extends: [ 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:prettier/recommended", 6 | ], 7 | plugins: ["@typescript-eslint"], 8 | rules: { 9 | "@typescript-eslint/no-explicit-any": "off", 10 | "@typescript-eslint/no-non-null-assertion": "off", 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Document 4 | url: https://halsp.org 5 | about: Find the answer in the document first. 6 | - name: Questions & Discussions 7 | url: https://github.com/halsp/core/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | commit-message: 8 | prefix: "chore" -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Semantic Pull Request 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | runs-on: ubuntu-latest 13 | name: Semantic Pull Request 14 | steps: 15 | - name: Validate PR title 16 | uses: amannn/action-semantic-pull-request@v4 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node_version: [18, 20] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node_version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node_version }} 22 | - run: sh scripts/ci-install.sh 23 | - name: Run test 24 | env: 25 | NODE_OPTIONS: "--max-old-space-size=8192" 26 | run: npm run test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | *debug.log 5 | .husky/_ 6 | yarn-error.log 7 | temp-test -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: ["/packages/*/jest.config.js"], 3 | collectCoverage: true, 4 | collectCoverageFrom: ["src/**/*.ts"], 5 | verbose: true, 6 | }; 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "npmClient": "yarn", 4 | "version": "2.4.5", 5 | "ignoreChanges": ["packages/*/test/**", "**/*.md"] 6 | } 7 | -------------------------------------------------------------------------------- /packages/alifc/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/alifc` 是 `Halsp` 的阿里云云函数计算数托管环境 4 | 5 | 可以将 Halsp 托管到阿里云函数计算,有效提升云函数响应速度 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/alifc/src/ali-req.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | 3 | export type AliReq = { 4 | path: string; 5 | method: string; 6 | url: string; 7 | clientIP: string; 8 | headers: Record; 9 | queries: Record; 10 | } & http.IncomingMessage; 11 | -------------------------------------------------------------------------------- /packages/alifc/src/ali-res.ts: -------------------------------------------------------------------------------- 1 | export type AliRes = { 2 | statusCode: number; 3 | headers: Record; 4 | setStatusCode: (code: number) => void; 5 | setHeader: (key: string, val: string) => void; 6 | deleteHeader: (key: string) => void; 7 | hasHeader: (key: string) => boolean; 8 | send: (val: string | Buffer) => void; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/alifc/src/cli-config.ts: -------------------------------------------------------------------------------- 1 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any, { command }) => { 2 | if (!config.build) { 3 | config.build = {}; 4 | } 5 | if (typeof config.build.copyPackage == "undefined") { 6 | config.build.copyPackage = command == "build"; 7 | } 8 | if (typeof config.build.removeDevDeps == "undefined") { 9 | config.build.removeDevDeps = command == "build"; 10 | } 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/alifc/src/context.ts: -------------------------------------------------------------------------------- 1 | import { AliReq } from "./ali-req"; 2 | import { AliRes } from "./ali-res"; 3 | 4 | declare module "@halsp/core" { 5 | interface Context { 6 | get aliContext(): any; 7 | get aliReq(): AliReq; 8 | get aliRes(): AliRes; 9 | get reqStream(): AliReq; 10 | } 11 | interface Request { 12 | get aliReq(): AliReq; 13 | } 14 | interface Response { 15 | get aliRes(): AliRes; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/alifc/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./context"; 2 | import "./startup"; 3 | 4 | export { AliReq } from "./ali-req"; 5 | export { AliRes } from "./ali-res"; 6 | export { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "./cli-config"; 7 | -------------------------------------------------------------------------------- /packages/alifc/test/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | import { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "../src"; 2 | 3 | test("cli config hook with start command", async () => { 4 | expect(HALSP_CLI_PLUGIN_CONFIG_HOOK({}, { command: "start" })).toEqual({ 5 | build: { 6 | copyPackage: false, 7 | removeDevDeps: false, 8 | }, 9 | }); 10 | }); 11 | 12 | test("cli config hook with build command", async () => { 13 | expect( 14 | HALSP_CLI_PLUGIN_CONFIG_HOOK({ build: {} }, { command: "build" }), 15 | ).toEqual({ 16 | build: { 17 | copyPackage: true, 18 | removeDevDeps: true, 19 | }, 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/alifc/test/context.test.ts: -------------------------------------------------------------------------------- 1 | import "../src"; 2 | import { Response, Startup } from "@halsp/core"; 3 | 4 | const req: any = { 5 | queries: {}, 6 | path: "/", 7 | method: "get", 8 | url: "", 9 | clientIP: "", 10 | headers: {}, 11 | }; 12 | const res: any = new Response(); 13 | res.send = () => undefined; 14 | 15 | test("context", async () => { 16 | await new Startup() 17 | .useAlifc() 18 | .use((ctx) => { 19 | const ctxAliReq = ctx.aliReq; 20 | const ctxAliRes = ctx.aliRes; 21 | const reqAliReq = ctx.req.aliReq; 22 | const resAliRes = ctx.res.aliRes; 23 | const reqStream = ctx.reqStream; 24 | 25 | expect(ctx.aliContext).toBe({ 26 | c: 3, 27 | }); 28 | expect(ctxAliReq).toBe(reqAliReq); 29 | expect(ctxAliReq).toEqual(req); 30 | expect(ctxAliRes).toBe(resAliRes); 31 | expect(ctxAliRes).toEqual(res); 32 | expect(reqStream).toBe(ctxAliReq); 33 | }) 34 | .run(req, res, { 35 | c: 3, 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/alifc/test/error.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import { newAliReq, newAliRes } from "./utils"; 3 | 4 | test("without md", async function () { 5 | const aliContext: any = {}; 6 | const aliReq = newAliReq(); 7 | const aliRes = newAliRes(); 8 | 9 | await new Startup().useAlifc().register("").run(aliReq, aliRes, aliContext); 10 | 11 | expect(aliRes.statusCode).toBe(404); 12 | expect(aliRes._body).toBe(""); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/alifc/test/type/stream.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import { createReadStream } from "fs"; 3 | import { newAliRes, newAliReq } from "../utils"; 4 | 5 | test("stream body", async function () { 6 | const aliContext: any = {}; 7 | const aliReq = newAliReq(); 8 | const aliRes = newAliRes(); 9 | 10 | await new Startup() 11 | .useAlifc() 12 | .use((ctx) => { 13 | ctx.res.ok(createReadStream("./LICENSE")); 14 | }) 15 | .run(aliReq, aliRes, aliContext); 16 | 17 | expect(aliRes.statusCode).toBe(200); 18 | expect(aliRes.headers["content-type"]).toBe("application/octet-stream"); 19 | const buffer = aliRes._body as Buffer; 20 | expect(buffer.toString().startsWith("MIT License")).toBeTruthy(); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/base.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | node_modules 4 | coverage 5 | /LICENSE 6 | /README.md 7 | /jest.config.js 8 | /jest.setup.js 9 | /.eslintignore 10 | /.eslintrc.js 11 | /.eslintrc.cjs 12 | /.gitignore 13 | /tsconfig.* -------------------------------------------------------------------------------- /packages/body/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | 3 | export { MultipartBody } from "./multipart"; 4 | -------------------------------------------------------------------------------- /packages/body/src/multipart.ts: -------------------------------------------------------------------------------- 1 | import formidable from "formidable"; 2 | 3 | export type MultipartBody = 4 | | { fields: formidable.Fields; files: formidable.Files } 5 | | undefined; 6 | -------------------------------------------------------------------------------- /packages/body/test/urlencoded.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import request from "supertest"; 3 | import "./utils"; 4 | 5 | test("useHttpUrlencodedBody", async () => { 6 | let invoke = false; 7 | const server = new Startup() 8 | .use(async (ctx, next) => { 9 | await next(); 10 | 11 | expect(ctx.res.body).toEqual({ 12 | body: { name: "hal" }, 13 | type: "application/x-www-form-urlencoded", 14 | }); 15 | invoke = true; 16 | }) 17 | .useHttpUrlencodedBody() 18 | .use(async (ctx) => { 19 | ctx.res.ok({ 20 | body: ctx.req.body, 21 | type: ctx.req.getHeader("content-type"), 22 | }); 23 | }) 24 | .listenTest(); 25 | await request(server).post("").type("urlencoded").send("name=hal"); 26 | server.close(); 27 | expect(invoke).toBeTruthy(); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/cookie/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const REQUEST_HEADER_NAME = "cookie"; 2 | export const RESPONSE_HEADER_NAME = "Set-Cookie"; 3 | -------------------------------------------------------------------------------- /packages/cookie/src/options.ts: -------------------------------------------------------------------------------- 1 | import cookie from "cookie"; 2 | 3 | export interface Options { 4 | serialize?: cookie.CookieSerializeOptions; 5 | parse?: cookie.CookieParseOptions; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/exception.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from "./utils"; 2 | 3 | export type ExceptionMessage = object & { 4 | message: string; 5 | [key: string]: any; 6 | }; 7 | 8 | export function isExceptionMessage( 9 | error: any, 10 | ): error is string | ExceptionMessage { 11 | if (!error) return false; 12 | 13 | return isString(error) || (isObject(error) && !!error.message); 14 | } 15 | 16 | export class HalspException extends Error { 17 | constructor(public readonly error?: string | ExceptionMessage) { 18 | super(""); 19 | 20 | this.name = this.constructor.name; 21 | 22 | if (isString(error)) { 23 | this.message = error; 24 | } else if (error && isObject(error)) { 25 | this.message = error.message ?? ""; 26 | } 27 | } 28 | 29 | inner?: Error | any; 30 | 31 | public breakthrough = false; 32 | public setBreakthrough(breakthrough = true): this { 33 | this.breakthrough = breakthrough; 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/hook/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./hook.item"; 2 | -------------------------------------------------------------------------------- /packages/core/src/logger.ts: -------------------------------------------------------------------------------- 1 | export interface ILogger { 2 | debug(message: any, ...optionalParams: any[]): this; 3 | info(message: any, ...optionalParams: any[]): this; 4 | warn(message: any, ...optionalParams: any[]): this; 5 | error(message: any, ...optionalParams: any[]): this; 6 | } 7 | 8 | export class BaseLogger implements ILogger { 9 | debug(message: any, ...optionalParams: any[]): this { 10 | console.debug(message, ...optionalParams); 11 | return this; 12 | } 13 | info(message: any, ...optionalParams: any[]): this { 14 | console.info(message, ...optionalParams); 15 | return this; 16 | } 17 | warn(message: any, ...optionalParams: any[]): this { 18 | console.warn(message, ...optionalParams); 19 | return this; 20 | } 21 | error(message: any, ...optionalParams: any[]): this { 22 | console.error(message, ...optionalParams); 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./middleware"; 2 | export * from "./lambda.middleware"; 3 | export * from "./compose.middleware"; 4 | -------------------------------------------------------------------------------- /packages/core/src/middlewares/lambda.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../context"; 2 | import { Middleware } from "./middleware"; 3 | 4 | type builderType = 5 | | ((ctx: Context, next: () => Promise) => void) 6 | | ((ctx: Context, next: () => Promise) => Promise); 7 | 8 | export class LambdaMiddleware extends Middleware { 9 | constructor(builder: builderType) { 10 | super(); 11 | this.#builder = builder; 12 | } 13 | 14 | #builder: builderType; 15 | 16 | async invoke(): Promise { 17 | await this.#builder(this.ctx, this.next.bind(this)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/register.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./context"; 2 | 3 | export interface Register { 4 | pattern: string; 5 | handler?: (ctx: Context) => Promise | void; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/utils/cli-assets.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "./typeis"; 2 | 3 | export function isCliAssetExist( 4 | assets: any[], 5 | compare: (asset: string) => boolean, 6 | ): boolean { 7 | return assets.some((item) => { 8 | if (isString(item)) { 9 | return compare(item); 10 | } else { 11 | if (isString(item.include)) { 12 | return compare(item.include); 13 | } else { 14 | return item.include.some((item: string) => compare(item)); 15 | } 16 | } 17 | }); 18 | } 19 | 20 | export function tryAddCliAssets( 21 | config: any, 22 | compare: (asset: string) => boolean, 23 | ...addAssets: any[] 24 | ) { 25 | const assets = getCliAssets(config); 26 | 27 | if (!isCliAssetExist(assets, compare)) { 28 | assets.push(...addAssets); 29 | } 30 | return config; 31 | } 32 | 33 | export function getCliAssets(config: any) { 34 | if (!config.build) { 35 | config.build = {}; 36 | } 37 | if (!config.build.assets) { 38 | config.build.assets = []; 39 | } 40 | return config.build.assets as any[]; 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./typeis"; 2 | export * from "./types"; 3 | export * from "./cli-assets"; 4 | export * from "./net"; 5 | export * from "./module"; 6 | -------------------------------------------------------------------------------- /packages/core/src/utils/module.ts: -------------------------------------------------------------------------------- 1 | import type {} from "@halsp/cli"; 2 | import url from "url"; 3 | 4 | export async function safeImport(name: string) { 5 | try { 6 | try { 7 | return _require(name) as T; 8 | } catch { 9 | try { 10 | return (await dynamicImport(name)) as T; 11 | } catch { 12 | return (await dynamicImport(url.pathToFileURL(name).toString())) as T; 13 | } 14 | } 15 | } catch { 16 | return null; 17 | } 18 | } 19 | 20 | const dynamicImport = new Function( 21 | "specifier", 22 | `return import(specifier); 23 | `, 24 | ) as (specifier: string) => Promise; 25 | -------------------------------------------------------------------------------- /packages/core/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type Dict = { 2 | [key: string]: T; 3 | }; 4 | export type ReadonlyDict = { 5 | readonly [key: string]: T; 6 | }; 7 | export type ObjectConstructor = { 8 | new (...args: any[]): T; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/core/test/hook/begining.test.ts: -------------------------------------------------------------------------------- 1 | import { HookType, Startup } from "../../src"; 2 | import "../test-startup"; 3 | 4 | describe("begining hook", () => { 5 | it("shoud create Context by hook", async () => { 6 | const { ctx } = await new Startup() 7 | .hook(HookType.BeforeInvoke, (ctx) => { 8 | ctx.set("res", ctx.get("begining")); 9 | }) 10 | .hook(HookType.Begining, (ctx) => { 11 | ctx.set("begining", true); 12 | }) 13 | .use(() => {}) 14 | .run(); 15 | expect(ctx.get("res")).toBeTruthy(); 16 | }); 17 | 18 | it("shoud stop invoke when Begining hook return false", async () => { 19 | const { ctx } = await new Startup() 20 | .hook(HookType.Begining, () => { 21 | return false; 22 | }) 23 | .use((ctx) => { 24 | ctx.set("res", true); 25 | }) 26 | .run(); 27 | expect(ctx.get("res")).toBeUndefined(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/core/test/hook/context.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, HookType, Startup } from "../../src"; 2 | import "../test-startup"; 3 | 4 | describe("context hook", () => { 5 | it("shoud create Context by hook", async () => { 6 | const { ctx } = await new Startup() 7 | .hook(HookType.Context, (args) => { 8 | const ctx = new Context(); 9 | ctx.set("args", args); 10 | return ctx; 11 | }) 12 | .run(1, 2, 3); 13 | expect(ctx.get("args")).toEqual([1, 2, 3]); 14 | }); 15 | 16 | it("shoud create Context by default instance", async () => { 17 | const initCtx = new Context(); 18 | const { ctx } = await new Startup().run(initCtx); 19 | expect(initCtx).toBe(ctx); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/core/test/hook/initialization.test.ts: -------------------------------------------------------------------------------- 1 | import { HookType, Startup } from "../../src"; 2 | import "../test-startup"; 3 | 4 | describe("initialization hook", () => { 5 | it("shoud initializa before invoke", async () => { 6 | let testArgs: any; 7 | await new Startup() 8 | .hook(HookType.Initialization, (args) => { 9 | testArgs = args; 10 | }) 11 | .run(1, 2, 3); 12 | expect(testArgs).toEqual([1, 2, 3]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/core/test/middleware/singleton.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Startup } from "../../src"; 2 | import "../test-startup"; 3 | 4 | test("middleware pipeline", async () => { 5 | const startup = new Startup().add(new Md()); 6 | 7 | let res = await startup.run(); 8 | expect(res.ctx.get("num")).toBe(1); 9 | res = await startup.run(); 10 | expect(res.ctx.get("num")).toBe(2); 11 | res = await startup.run(); 12 | expect(res.ctx.get("num")).toBe(3); 13 | }); 14 | 15 | class Md extends Middleware { 16 | #number = 0; 17 | 18 | async invoke(): Promise { 19 | this.#number++; 20 | this.ctx.set("num", this.#number); 21 | await this.next(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/test/module.test.ts: -------------------------------------------------------------------------------- 1 | import { safeImport } from "../src"; 2 | 3 | describe("safeImport", () => { 4 | it("should import commonjs package", async () => { 5 | const dep = await safeImport("typescript"); 6 | expect(dep).not.toBeNull(); 7 | }); 8 | 9 | it("should import esm package", async () => { 10 | const dep = await safeImport("@halsp/core"); 11 | expect(dep).not.toBeNull(); 12 | }); 13 | 14 | it("should be null when package is not exist", async () => { 15 | const dep = await safeImport("not-exist"); 16 | expect(dep).toBeNull(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/core/test/test-startup.ts: -------------------------------------------------------------------------------- 1 | import { Response, Startup } from "../src"; 2 | 3 | declare module "../src" { 4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 5 | interface Startup { 6 | run(...args: any[]): Promise; 7 | } 8 | } 9 | 10 | const initMap = new WeakMap(); 11 | Startup.prototype.run = async function (...args: any[]) { 12 | if (!initMap.has(this)) { 13 | initMap.set(this, true); 14 | await this["initialize"](...args); 15 | } 16 | return await this["invoke"](...args); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/core/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/cors/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import { CorsMiddleware } from "./cors.middleware"; 3 | import { Options } from "./options"; 4 | 5 | declare module "@halsp/core" { 6 | interface Startup { 7 | useCors(options?: Options): this; 8 | } 9 | } 10 | 11 | Startup.prototype.useCors = function (options: Options = {}) { 12 | return this.add(() => new CorsMiddleware(options)); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/cors/src/options.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | 3 | export interface Options { 4 | allowMethods?: string | string[]; 5 | origin?: string | ((ctx: Context) => Promise | string); 6 | exposeHeaders?: string | string[]; 7 | allowHeaders?: string | string[]; 8 | credentials?: boolean | ((ctx: Context) => Promise | boolean); 9 | maxAge?: number; 10 | privateNetworkAccess?: boolean; 11 | secureContext?: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /packages/env/src/cli-config.ts: -------------------------------------------------------------------------------- 1 | import { getCliAssets, isCliAssetExist } from "@halsp/core"; 2 | 3 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any, { command }) => { 4 | const assets = getCliAssets(config); 5 | if (!isCliAssetExist(assets, (ass) => ass == ".env")) { 6 | assets.push(".env"); 7 | } 8 | if (!isCliAssetExist(assets, (ass) => ass.startsWith(".env."))) { 9 | if (command == "start") { 10 | assets.push(".env.*"); 11 | } else { 12 | assets.push({ 13 | include: ".env.*", 14 | exclude: ".env.local", 15 | }); 16 | } 17 | } 18 | return config; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/env/src/options.ts: -------------------------------------------------------------------------------- 1 | import { DotenvConfigOptions } from "dotenv"; 2 | 3 | export interface EnvOptions extends Omit { 4 | cwd?: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/env/src/version.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import * as fs from "fs"; 3 | 4 | export async function getVersion( 5 | cwd = process.cwd(), 6 | ): Promise { 7 | let pkgPath = "package.json"; 8 | while (true) { 9 | const absolutePath = path.join(cwd, pkgPath); 10 | if (fs.existsSync(absolutePath)) { 11 | const pkgStr = await fs.promises.readFile(absolutePath, "utf-8"); 12 | return JSON.parse(pkgStr).version; 13 | } else { 14 | pkgPath = path.join("..", pkgPath); 15 | if (path.resolve(absolutePath) == path.resolve(cwd, pkgPath)) { 16 | break; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/env/test/envs/.env: -------------------------------------------------------------------------------- 1 | BNAME=BASE -------------------------------------------------------------------------------- /packages/env/test/envs/.env.dev: -------------------------------------------------------------------------------- 1 | SNAME=DEV -------------------------------------------------------------------------------- /packages/env/test/envs/.env.development: -------------------------------------------------------------------------------- 1 | NAME=DEVELOPMENT 2 | BNAME=DEVELOPMENT -------------------------------------------------------------------------------- /packages/env/test/envs/.env.prod: -------------------------------------------------------------------------------- 1 | SNAME=PROD -------------------------------------------------------------------------------- /packages/env/test/envs/.env.production: -------------------------------------------------------------------------------- 1 | NAME=PRODUCTION 2 | BNAME=PRODUCTION -------------------------------------------------------------------------------- /packages/env/test/envs/.env.stage: -------------------------------------------------------------------------------- 1 | NAME=STAGE -------------------------------------------------------------------------------- /packages/env/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/env/test/utils.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/testing"; 2 | import { EnvOptions } from "../src"; 3 | import "../src"; 4 | import { Startup } from "@halsp/core"; 5 | 6 | export async function getEnv(mode?: string, options?: EnvOptions) { 7 | process.chdir("test/envs"); 8 | const startup = new Startup(); 9 | if (mode) { 10 | process.env.NODE_ENV = mode; 11 | } 12 | 13 | try { 14 | const { ctx } = await startup 15 | .useEnv(options as any) 16 | .use((ctx) => { 17 | ctx.set("env", process.env); 18 | }) 19 | .test(); 20 | return ctx.get("env"); 21 | } finally { 22 | process.chdir("../.."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/env/test/version/package.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /packages/filter/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/filter` 是 `Halsp` 的请求过滤器插件 4 | 5 | 请求过滤器可用于参数校验、身份验证、返回结果转换等 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/filter/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const USE_FILTER = "@halsp/filter/useFilter"; 2 | 3 | export const FILTERS_METADATA = "@halsp/filter/filtersMetadata"; 4 | 5 | export const GLOBAL_FILTERS_BAG = "@halsp/filter/globalFiltersBag"; 6 | export const FILTERS_ORDER_BAG = "@halsp/filter/filtersOrderBag"; 7 | -------------------------------------------------------------------------------- /packages/filter/src/filters/action.filter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Filter } from "./filter"; 3 | 4 | export interface ActionFilter extends Filter { 5 | onActionExecuted(ctx: Context): void | Promise; 6 | onActionExecuting( 7 | ctx: Context, 8 | ): boolean | Promise | void | Promise; 9 | } 10 | -------------------------------------------------------------------------------- /packages/filter/src/filters/authorization.filter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Filter } from "./filter"; 3 | 4 | export interface AuthorizationFilter extends Filter { 5 | onAuthorization(ctx: Context): boolean | Promise; 6 | } 7 | -------------------------------------------------------------------------------- /packages/filter/src/filters/exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Filter } from "./filter"; 3 | 4 | export interface ExceptionFilter extends Filter { 5 | onException(ctx: Context, error: T): boolean | Promise; 6 | } 7 | -------------------------------------------------------------------------------- /packages/filter/src/filters/filter.ts: -------------------------------------------------------------------------------- 1 | import { ObjectConstructor } from "@halsp/core"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface Filter {} 5 | 6 | export type FilterItem = T | ObjectConstructor; 7 | 8 | export interface OrderRecord { 9 | filter: ObjectConstructor; 10 | order: number; 11 | } 12 | -------------------------------------------------------------------------------- /packages/filter/src/filters/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./filter"; 2 | export * from "./action.filter"; 3 | export * from "./resource.filter"; 4 | export * from "./exception.filter"; 5 | export * from "./authorization.filter"; 6 | export * from "./exec-filter"; 7 | -------------------------------------------------------------------------------- /packages/filter/src/filters/resource.filter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Filter } from "./filter"; 3 | 4 | export interface ResourceFilter extends Filter { 5 | onResourceExecuted(ctx: Context): void | Promise; 6 | onResourceExecuting( 7 | ctx: Context, 8 | ): boolean | Promise | void | Promise; 9 | } 10 | -------------------------------------------------------------------------------- /packages/filter/src/use-filters.decorator.ts: -------------------------------------------------------------------------------- 1 | import { FilterItem } from "./filters/filter"; 2 | import "reflect-metadata"; 3 | import { FILTERS_METADATA } from "./constant"; 4 | 5 | export function UseFilters(...filters: FilterItem[]): ClassDecorator { 6 | return (target: any) => { 7 | const existFilters: FilterItem[] = 8 | Reflect.getMetadata(FILTERS_METADATA, target) ?? []; 9 | Reflect.defineMetadata( 10 | FILTERS_METADATA, 11 | [...existFilters, ...filters], 12 | target, 13 | ); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/filter/test/actions/auth.get.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Action, ActionMetadata } from "@halsp/router"; 3 | import { AuthorizationFilter, UseFilters } from "../../src"; 4 | 5 | const Admin = ActionMetadata("admin", "true"); 6 | 7 | class TestAuthorizationFilter implements AuthorizationFilter { 8 | onAuthorization(ctx: Context): boolean | Promise { 9 | ctx.res.set("admin", ctx.actionMetadata.admin); 10 | const executing: boolean = ctx.req.body["executing"]; 11 | if (!executing) { 12 | ctx.res.unauthorizedMsg(); 13 | } 14 | return executing; 15 | } 16 | } 17 | 18 | @Admin 19 | @UseFilters(TestAuthorizationFilter) 20 | export default class extends Action { 21 | async invoke(): Promise { 22 | this.ok(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/filter/test/exec-named.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, Startup } from "@halsp/core"; 2 | import { Action } from "@halsp/router"; 3 | import "@halsp/testing"; 4 | import "@halsp/http"; 5 | import { execFilters, Filter, UseFilters } from "../src"; 6 | 7 | class CustomFilter implements Filter { 8 | execute(ctx: Context) { 9 | ctx.res.set("custom", 1); 10 | } 11 | } 12 | 13 | @UseFilters(CustomFilter) 14 | class TestAction extends Action { 15 | invoke(): void | Promise { 16 | execFilters(this, true, "execute"); 17 | } 18 | } 19 | 20 | test(`execFilters`, async () => { 21 | const res = await new Startup() 22 | .useHttp() 23 | .use(async (ctx, next) => { 24 | ctx.res.body = 0; 25 | await next(); 26 | }) 27 | .useFilter() 28 | .add(TestAction) 29 | .test(); 30 | expect(res.getHeader("custom")).toBe("1"); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/filter/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/http/src/context/http-error-message.ts: -------------------------------------------------------------------------------- 1 | export interface HttpErrorMessage { 2 | message?: string; 3 | 4 | [key: string]: unknown; 5 | } 6 | -------------------------------------------------------------------------------- /packages/http/src/context/index.ts: -------------------------------------------------------------------------------- 1 | import "./request"; 2 | import "./response"; 3 | 4 | export { ResultHandler, initResultHandler } from "./result-handler"; 5 | export { HeaderHandler, initHeaderHandler } from "./header-handler"; 6 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/bad-gateway-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class BadGatewayException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.BAD_GATEWAY, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/bad-request-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class BadRequestException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.BAD_REQUEST, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/conflict-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class ConflictException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.CONFLICT, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/forbidden-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class ForbiddenException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.FORBIDDEN, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/gateway-timeout-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class GatewayTimeoutException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.GATEWAY_TIMEOUT, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/gone-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class GoneException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.GONE, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/http-version-not-supported-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class HttpVersionNotSupportedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.HTTP_VERSION_NOT_SUPPORTED, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/im-a-teapot-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class ImATeapotException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.IM_A_TEAPOT, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/internal-server-error-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class InternalServerErrorException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.INTERNAL_SERVER_ERROR, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/method-not-allowed-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class MethodNotAllowedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.METHOD_NOT_ALLOWED, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/misdirected-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class MisdirectedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.MISDIRECTED_REQUEST, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/not-acceptable-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class NotAcceptableException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.NOT_ACCEPTABLE, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/not-found-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class NotFoundException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.NOT_FOUND, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/not-implemented-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class NotImplementedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.NOT_IMPLEMENTED, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/precondition-failed-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class PreconditionFailedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.PRECONDITION_FAILED, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/request-timeout-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class RequestTimeoutException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.REQUEST_TIMEOUT, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/request-too-long-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class RequestTooLongException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.REQUEST_TOO_LONG, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/service-unavailable-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class ServiceUnavailableException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.SERVICE_UNAVAILABLE, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/unauthorized-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class UnauthorizedException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.UNAUTHORIZED, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/unprocessable-entity-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class UnprocessableEntityException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.UNPROCESSABLE_ENTITY, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/exceptions/unsupported-media-type-exception.ts: -------------------------------------------------------------------------------- 1 | import { StatusCodes } from "http-status-codes"; 2 | import { ExceptionMessage } from "@halsp/core"; 3 | import { HttpException } from "./http-exception"; 4 | 5 | export class UnsupportedMediaTypeException extends HttpException { 6 | constructor(error?: string | ExceptionMessage) { 7 | super(StatusCodes.UNSUPPORTED_MEDIA_TYPE, error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/http/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from "@halsp/core"; 2 | import { 3 | HeaderHandler, 4 | initHeaderHandler, 5 | initResultHandler, 6 | ResultHandler, 7 | } from "./context"; 8 | 9 | declare module "@halsp/core" { 10 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 11 | interface Middleware extends ResultHandler, HeaderHandler {} 12 | } 13 | 14 | initResultHandler(Middleware.prototype, function () { 15 | return this.res; 16 | }); 17 | initHeaderHandler( 18 | Middleware.prototype, 19 | function () { 20 | return this.req.headers; 21 | }, 22 | function () { 23 | return this.res.headers; 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /packages/http/src/options.ts: -------------------------------------------------------------------------------- 1 | export interface HttpOptions { 2 | customMethods?: string[]; 3 | } 4 | -------------------------------------------------------------------------------- /packages/http/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Dict, ReadonlyDict } from "@halsp/core"; 2 | 3 | export type HeaderValue = string | string[]; 4 | export type NumericalHeaderValue = string | number | (number | string)[]; 5 | export type HeadersDict = Dict; 6 | export type ReadonlyHeadersDict = ReadonlyDict; 7 | export type NumericalHeadersDict = ReadonlyDict; 8 | -------------------------------------------------------------------------------- /packages/http/test/request/body.test.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "@halsp/core"; 2 | import "../../src"; 3 | 4 | test("str body", async () => { 5 | const req = new Request().setBody("test body"); 6 | 7 | expect(typeof req.body).toBe("string"); 8 | expect(req.body).toBe("test body"); 9 | }); 10 | 11 | test("obj body", async () => { 12 | const req = new Request().setBody({ 13 | b1: 1, 14 | b2: "2", 15 | }); 16 | 17 | expect(req.body.b1).toBe(1); 18 | expect(req.body.b2).toBe("2"); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/http/test/request/query.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, ReadonlyDict } from "@halsp/core"; 2 | import "../../src"; 3 | 4 | test("request setQuery", async () => { 5 | const req = new Context().req 6 | .setQuery("p1", "1") 7 | .setQuery("p2", "2") 8 | .setQuery("p3", "3") 9 | .setQuery("p4", null as any); 10 | 11 | expectQuery(req.query); 12 | }); 13 | 14 | test("request setQuery", async () => { 15 | const req = new Context().req.setQuery({ 16 | p1: "1", 17 | p2: "2", 18 | p3: "3", 19 | p4: undefined as any, 20 | }); 21 | expectQuery(req.query); 22 | }); 23 | 24 | function expectQuery(query: ReadonlyDict) { 25 | expect(query.p1).toBe("1"); 26 | expect(query.p2).toBe("2"); 27 | expect(query.p3).toBe("3"); 28 | expect(query.p4).toBe(""); 29 | expect(query.p5).toBeUndefined(); 30 | } 31 | -------------------------------------------------------------------------------- /packages/http/test/response/custom-headers.test.ts: -------------------------------------------------------------------------------- 1 | import { HeadersDict, HeaderHandler, initHeaderHandler } from "../../src"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging 4 | class CustomHeader { 5 | constructor() { 6 | initHeaderHandler( 7 | this, 8 | () => this.headers, 9 | () => this.headers, 10 | ); 11 | } 12 | 13 | readonly headers: HeadersDict = {}; 14 | } 15 | // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unsafe-declaration-merging 16 | interface CustomHeader extends HeaderHandler {} 17 | 18 | test("custom halsp headers", async () => { 19 | const header = new CustomHeader() 20 | .setHeader("h1", "1") 21 | .setHeaders({ 22 | h2: "2", 23 | h3: 3, 24 | }) 25 | .removeHeader("h2"); 26 | 27 | expect(header.getHeader("h1")).toBe("1"); 28 | expect(header.getHeader("h2")).toBeUndefined; 29 | expect(header.getHeader("h3")).toBe("3"); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/http/test/response/response-error.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import { StatusCodes } from "../../src"; 3 | 4 | test("response error message", async () => { 5 | const result = await new Startup() 6 | .useHttp() 7 | .use(async (ctx) => { 8 | ctx.res 9 | .setStatus(StatusCodes.BAD_REQUEST) 10 | .setBody({ message: "error msg" }); 11 | }) 12 | ["invoke"](); 13 | 14 | expect(result.status).toBe(400); 15 | expect(result.isSuccess).toBeFalsy(); 16 | expect(result.body.message).toBe("error msg"); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/http/test/response/result-handler.test.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "@halsp/core"; 2 | import { initResultHandler, ResultHandler } from "../../src"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging 5 | class CustomResultHandler { 6 | constructor() { 7 | initResultHandler(this, () => this.res); 8 | } 9 | 10 | readonly res = new Response(); 11 | } 12 | // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unsafe-declaration-merging 13 | interface CustomResultHandler extends ResultHandler {} 14 | 15 | test("custom result handler", async () => { 16 | const result = new CustomResultHandler().ok({ 17 | msg: "ok", 18 | }); 19 | 20 | expect(result.res.body).toEqual({ 21 | msg: "ok", 22 | }); 23 | expect(result.res.status).toBe(200); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/http/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/inject/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/inject` 是 `Halsp` 的依赖注入插件 4 | 5 | 依赖注入能极大的提升代码可读性与安全性 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/inject/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const PROPERTY_METADATA = "@halsp/inject/propertyMetadata"; 2 | export const KEY_METADATA = "@halsp/inject/keyMetadata"; 3 | export const CUSTOM_METADATA = "@halsp/inject/customMetadata"; 4 | export const SCOPED_BAG = "@halsp/inject/scopedBag"; 5 | export const TRANSIENT_BAG = "@halsp/inject/transientBag"; 6 | export const SINGLETON_BAG = "@halsp/inject/singletonBag"; 7 | export const MAP_BAG = "@halsp/inject/mapBag"; 8 | export const INITIALIZING_EXECUTED = "@halsp/inject/initializingExecuted"; 9 | export const INITIALIZED_EXECUTED = "@halsp/inject/initializdExecuted"; 10 | -------------------------------------------------------------------------------- /packages/inject/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create-inject"; 2 | export * from "./inject.decorator"; 3 | -------------------------------------------------------------------------------- /packages/inject/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | 3 | export { 4 | Inject, 5 | createInject, 6 | getClassProptotype, 7 | getClassConstractor, 8 | } from "./decorators"; 9 | export { InjectType } from "./inject-type"; 10 | export { IService } from "./interfaces"; 11 | -------------------------------------------------------------------------------- /packages/inject/src/inject-type.ts: -------------------------------------------------------------------------------- 1 | export enum InjectType { 2 | Singleton, 3 | Scoped, 4 | Transient, 5 | } 6 | -------------------------------------------------------------------------------- /packages/inject/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./inject-custom"; 2 | export * from "./inject-key"; 3 | export * from "./inject-map"; 4 | export * from "./service"; 5 | -------------------------------------------------------------------------------- /packages/inject/src/interfaces/inject-custom.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { InjectType } from "../inject-type"; 3 | 4 | export type InjectCustom = 5 | | { 6 | readonly handler: (parent: any) => T | Promise; 7 | readonly property: string | symbol; 8 | readonly parameterIndex?: number; 9 | readonly type: InjectType.Singleton; 10 | } 11 | | { 12 | readonly handler: (ctx: Context, parent: any) => T | Promise; 13 | readonly property: string | symbol; 14 | readonly parameterIndex?: number; 15 | readonly type?: InjectType.Scoped | InjectType.Transient; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/inject/src/interfaces/inject-key.ts: -------------------------------------------------------------------------------- 1 | export interface InjectKey { 2 | readonly key: string; 3 | readonly property: string | symbol; 4 | readonly parameterIndex?: number; 5 | } 6 | 7 | export type KeyTargetType = 8 | | object 9 | | number 10 | | bigint 11 | | string 12 | | boolean 13 | | symbol; 14 | -------------------------------------------------------------------------------- /packages/inject/src/interfaces/inject-map.ts: -------------------------------------------------------------------------------- 1 | import { Context, ObjectConstructor } from "@halsp/core"; 2 | import { InjectType } from "../inject-type"; 3 | 4 | export interface InjectMap { 5 | readonly anestor: 6 | | ObjectConstructor 7 | | string 8 | | ((ctx: Context) => T | Promise); 9 | readonly target: ObjectConstructor | U; 10 | readonly type: InjectType; 11 | } 12 | -------------------------------------------------------------------------------- /packages/inject/src/interfaces/service.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | 3 | export interface IService { 4 | /** 5 | * Execute after request, Scoped/Transient 6 | */ 7 | dispose: (ctx: Context) => Promise | void; 8 | /** 9 | * Execute before injecting property 10 | */ 11 | initializing: (ctx: Context) => Promise | void; 12 | 13 | /** 14 | * Execute after injecting 15 | */ 16 | initialized: (ctx: Context) => Promise | void; 17 | } 18 | -------------------------------------------------------------------------------- /packages/inject/test/derive.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Startup } from "@halsp/core"; 2 | import "../src"; 3 | import { Service2, Service3 } from "./services"; 4 | import { Inject } from "../src"; 5 | import "@halsp/testing"; 6 | 7 | class TestMiddleware extends Middleware { 8 | @Inject 9 | private readonly service2!: Service2; 10 | @Inject 11 | private readonly service3!: Service3; 12 | 13 | async invoke(): Promise { 14 | this.ctx.set("result", { 15 | service2: this.service2.invoke(), 16 | service3: this.service3.invoke(), 17 | }); 18 | } 19 | } 20 | 21 | test(`derive`, async function () { 22 | const { ctx } = await new Startup() 23 | .useInject() 24 | .inject(Service2, Service3) 25 | .add(TestMiddleware) 26 | .test(); 27 | 28 | expect(ctx.get("result")).toEqual({ 29 | service2: "service3.service2.service1", 30 | service3: "service3.service2.service1", 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/inject/test/import.test.ts: -------------------------------------------------------------------------------- 1 | import "../src/interfaces"; 2 | 3 | it("import", async () => { 4 | // 5 | }); 6 | -------------------------------------------------------------------------------- /packages/inject/test/key-inject/chain.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import "@halsp/testing"; 3 | import { Inject } from "../../src"; 4 | 5 | it("key chain inject", async () => { 6 | class TestService1 {} 7 | 8 | class TestService2 { 9 | @Inject 10 | readonly service1!: TestService1; 11 | } 12 | 13 | class TestService3 { 14 | @Inject 15 | readonly service1!: TestService1; 16 | @Inject 17 | readonly service2!: TestService2; 18 | } 19 | 20 | await new Startup() 21 | .useInject() 22 | .inject("Test", TestService3) 23 | .use(async (ctx) => { 24 | const service3 = await ctx.getService("Test"); 25 | if (!service3) { 26 | throw new Error(); 27 | } 28 | expect(service3.constructor).toBe(TestService3); 29 | expect(service3.service2.constructor).toBe(TestService2); 30 | expect(service3.service1.constructor).toBe(TestService1); 31 | expect(service3.service2.service1.constructor).toBe(TestService1); 32 | }) 33 | .test(); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/inject/test/services.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from "../src"; 2 | 3 | export class Service1 extends Object { 4 | public invoke(): string { 5 | this.count++; 6 | return "service1"; 7 | } 8 | 9 | public count = 0; 10 | } 11 | 12 | export class Service2 extends Object { 13 | @Inject 14 | private readonly service1!: Service1; 15 | 16 | public invoke(): string { 17 | return "service2." + this.service1.invoke(); 18 | } 19 | } 20 | 21 | export class Service3 extends Service2 { 22 | public invoke(): string { 23 | return "service3." + super.invoke(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/inject/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/jest.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import url from "url"; 3 | 4 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 5 | const name = __dirname.replace(/\\/g, "/").replace(/^.*\//, ""); 6 | 7 | export default { 8 | roots: ["/test"], 9 | testRegex: "test/(.+)\\.test\\.(jsx?|tsx?)$", 10 | transform: { 11 | "^.+\\.tsx?$": [ 12 | "ts-jest", 13 | { 14 | useESM: true, 15 | }, 16 | ], 17 | }, 18 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 19 | displayName: name, 20 | verbose: true, 21 | setupFilesAfterEnv: ["/jest.setup.js"], 22 | collectCoverage: true, 23 | collectCoverageFrom: ["src/**/*.ts"], 24 | }; 25 | -------------------------------------------------------------------------------- /packages/jest.setup.js: -------------------------------------------------------------------------------- 1 | beforeEach(() => { 2 | process.chdir(__dirname); 3 | }); 4 | 5 | beforeAll(() => { 6 | global._require = require; 7 | }); 8 | -------------------------------------------------------------------------------- /packages/jwt/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/jwt` 是 `Halsp` 的 jwt 工具插件 4 | 5 | 基于 [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) 和 [@halsp/inject](https://github.com/halsp/core) 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/jwt/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS = "@halsp/jwt/options"; 2 | -------------------------------------------------------------------------------- /packages/jwt/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from "@halsp/inject"; 2 | import "reflect-metadata"; 3 | import { JwtService } from "./jwt.service"; 4 | 5 | export const JwtToken = Inject((ctx) => ctx.jwtToken); 6 | export const JwtObject = Inject(async (ctx) => { 7 | const jwtService = await ctx.getService(JwtService); 8 | return jwtService.decode({ 9 | complete: true, 10 | json: true, 11 | }); 12 | }); 13 | export const JwtPayload = Inject(async (ctx) => { 14 | const jwtService = await ctx.getService(JwtService); 15 | return jwtService.decode({ 16 | complete: false, 17 | json: true, 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/jwt/src/jwt-options.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import * as jwt from "jsonwebtoken"; 3 | 4 | export enum JwtSecretRequestType { 5 | SIGN, 6 | VERIFY, 7 | } 8 | 9 | export interface JwtOptions { 10 | signOptions?: jwt.SignOptions; 11 | secret?: string | Buffer; 12 | publicKey?: string | Buffer; 13 | privateKey?: jwt.Secret; 14 | secretOrKeyProvider?: ( 15 | requestType: JwtSecretRequestType, 16 | tokenOrPayload: string | object | Buffer, 17 | options?: jwt.VerifyOptions | jwt.SignOptions, 18 | ) => jwt.Secret; 19 | verifyOptions?: jwt.VerifyOptions; 20 | tokenProvider?: (ctx: Context) => string; 21 | prefix?: string; 22 | } 23 | 24 | export interface JwtSignOptions extends jwt.SignOptions { 25 | secret?: string | Buffer; 26 | privateKey?: string | Buffer; 27 | } 28 | 29 | export interface JwtVerifyOptions extends jwt.VerifyOptions { 30 | secret?: string | Buffer; 31 | publicKey?: string | Buffer; 32 | } 33 | -------------------------------------------------------------------------------- /packages/jwt/test/prefix.test.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/testing"; 2 | import { createTestContext } from "./utils"; 3 | import "../src"; 4 | import { Startup } from "@halsp/core"; 5 | 6 | test("prefix", async function () { 7 | process.env.HALSP_ENV = "http"; 8 | const { ctx } = await new Startup() 9 | .setContext( 10 | await createTestContext( 11 | { 12 | secret: "secret", 13 | }, 14 | undefined, 15 | "custom ", 16 | ), 17 | ) 18 | .useJwt({ 19 | secret: "secret", 20 | prefix: "custom", 21 | }) 22 | .useJwtVerify() 23 | .use((ctx) => ctx.set("token", ctx.jwtToken)) 24 | .test(); 25 | expect(!!ctx.get("token")).toBeTruthy(); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/jwt/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/knex/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_IDENTITY = "@halsp/knex/optionsIdentity"; 2 | -------------------------------------------------------------------------------- /packages/knex/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject, createInject } from "@halsp/inject"; 2 | import { OPTIONS_IDENTITY } from "./constant"; 3 | import * as knex from "knex"; 4 | 5 | export function Knex(identity?: string): PropertyDecorator & ParameterDecorator; 6 | export function Knex(target: any, propertyKey: string | symbol): void; 7 | export function Knex( 8 | target: any, 9 | propertyKey: string | symbol | undefined, 10 | parameterIndex: number, 11 | ): void; 12 | export function Knex(...args: any[]) { 13 | if (args.length == 0 || (args.length == 1 && typeof args[0] == "string")) { 14 | return Inject(OPTIONS_IDENTITY + (args[0] ?? "")); 15 | } else { 16 | createInject( 17 | (ctx) => ctx.getService(OPTIONS_IDENTITY), 18 | args[0], 19 | args[1], 20 | args[2], 21 | ); 22 | } 23 | } 24 | 25 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 26 | export interface Knex extends knex.Knex {} 27 | -------------------------------------------------------------------------------- /packages/knex/src/options.ts: -------------------------------------------------------------------------------- 1 | import { InjectType } from "@halsp/inject"; 2 | import * as knex from "knex"; 3 | 4 | export type Options = knex.Knex.Config & { 5 | injectType?: InjectType; 6 | identity?: string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/knex/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/koa/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const KOA_MIDDLEWARES_BAG = "@halsp/koa/koaMiddlewaresBag"; 2 | export const KOA_CTX = "@halsp/koa/koaCtx"; 3 | export const KOA_NEXT = "@halsp/koa/koaNext"; 4 | -------------------------------------------------------------------------------- /packages/koa/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./koa.middleware"; 2 | 3 | export { koaHalsp } from "./halsp.middleware"; 4 | -------------------------------------------------------------------------------- /packages/lambda/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/lambda` 是 `Halsp` 的 `serverless` 云函数托管环境 4 | 5 | 可以将 `Halsp` 项目托管到 `腾讯云事件云函数`、`阿里云事件云函数`、`aws lambda`、`azure functions` 等,提升云函数响应速度 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/lambda/src/cli-config.ts: -------------------------------------------------------------------------------- 1 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any, { command }) => { 2 | if (!config.build) { 3 | config.build = {}; 4 | } 5 | if (typeof config.build.copyPackage == "undefined") { 6 | config.build.copyPackage = command == "build"; 7 | } 8 | if (typeof config.build.removeDevDeps == "undefined") { 9 | config.build.removeDevDeps = command == "build"; 10 | } 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/lambda/src/context.ts: -------------------------------------------------------------------------------- 1 | import { Dict } from "@halsp/core"; 2 | 3 | declare module "@halsp/core" { 4 | interface Context { 5 | get lambdaContext(): Dict; 6 | get lambdaEvent(): Dict; 7 | } 8 | interface Request { 9 | get lambdaContext(): Dict; 10 | get lambdaEvent(): Dict; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/lambda/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import "./context"; 3 | 4 | export { ResponseStruct } from "./response-struct"; 5 | export { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "./cli-config"; 6 | -------------------------------------------------------------------------------- /packages/lambda/src/response-struct.ts: -------------------------------------------------------------------------------- 1 | import { HeadersDict } from "@halsp/http"; 2 | 3 | export interface ResponseStruct { 4 | readonly isBase64Encoded: boolean; 5 | readonly statusCode: number; 6 | readonly status: number; 7 | readonly headers: HeadersDict; 8 | readonly body: any; 9 | } 10 | -------------------------------------------------------------------------------- /packages/lambda/test/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | import { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "../src"; 2 | 3 | test("cli config hook with start command", async () => { 4 | expect(HALSP_CLI_PLUGIN_CONFIG_HOOK({}, { command: "start" })).toEqual({ 5 | build: { 6 | copyPackage: false, 7 | removeDevDeps: false, 8 | }, 9 | }); 10 | }); 11 | 12 | test("cli config hook with build command", async () => { 13 | expect( 14 | HALSP_CLI_PLUGIN_CONFIG_HOOK({ build: {} }, { command: "build" }), 15 | ).toEqual({ 16 | build: { 17 | copyPackage: true, 18 | removeDevDeps: true, 19 | }, 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/lambda/test/context.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import "../src"; 3 | 4 | test("context", async () => { 5 | await new Startup() 6 | .useLambda() 7 | .use((ctx) => { 8 | const context1 = ctx.lambdaContext; 9 | const context2 = ctx.req.lambdaContext; 10 | const event1 = ctx.lambdaEvent; 11 | const event2 = ctx.req.lambdaEvent; 12 | expect(context1).toEqual({ 13 | a: 1, 14 | }); 15 | expect(context1).toBe(context2); 16 | expect(event1).toEqual({ 17 | b: "2", 18 | }); 19 | expect(event1).toBe(event2); 20 | }) 21 | .run({ a: 1 }, { b: "2" }); 22 | }); 23 | 24 | test("request context method", async () => { 25 | await new Startup() 26 | .useLambda() 27 | .use((ctx) => { 28 | expect(ctx.req.method).toBe("POST"); 29 | }) 30 | .run( 31 | { 32 | requestContext: { 33 | http: { 34 | method: "post", 35 | }, 36 | }, 37 | }, 38 | {}, 39 | ); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/lambda/test/query.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import "../src"; 3 | 4 | test("query", async () => { 5 | const res = await new Startup() 6 | .useLambda() 7 | .use(async (ctx, next) => { 8 | ctx.res.ok(ctx.req.query); 9 | await next(); 10 | }) 11 | .run( 12 | { 13 | queryStringParameters: { 14 | a: "1", 15 | b: "2", 16 | }, 17 | }, 18 | {}, 19 | ); 20 | 21 | expect(res.statusCode).toBe(200); 22 | expect(res.body).toBe( 23 | JSON.stringify({ 24 | a: "1", 25 | b: "2", 26 | }), 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/logger/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/logger` 是 `Halsp` 的日志插件 4 | 5 | 基于 [winston](https://github.com/winstonjs/winston) 6 | 7 | 通过依赖注入 [inject](https://github.com/halsp/core) 的方式使用 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/logger/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_IDENTITY = "@halsp/logger/optionsIdentity"; 2 | -------------------------------------------------------------------------------- /packages/logger/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject, createInject } from "@halsp/inject"; 2 | import { OPTIONS_IDENTITY } from "./constant"; 3 | import winston from "winston"; 4 | 5 | export function Logger( 6 | identity?: string, 7 | ): PropertyDecorator & ParameterDecorator; 8 | export function Logger(target: any, propertyKey: string | symbol): void; 9 | export function Logger( 10 | target: any, 11 | propertyKey: string | symbol | undefined, 12 | parameterIndex: number, 13 | ): void; 14 | export function Logger(...args: any[]) { 15 | if (args.length == 0 || (args.length == 1 && typeof args[0] == "string")) { 16 | return Inject(OPTIONS_IDENTITY + (args[0] ?? "")); 17 | } else { 18 | createInject( 19 | (ctx) => ctx.getService(OPTIONS_IDENTITY), 20 | args[0], 21 | args[1], 22 | args[2], 23 | ); 24 | } 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 28 | export interface Logger extends winston.Logger {} 29 | -------------------------------------------------------------------------------- /packages/logger/src/options.ts: -------------------------------------------------------------------------------- 1 | import { InjectType } from "@halsp/inject"; 2 | import winston from "winston"; 3 | import { FileTransportOptions } from "winston/lib/winston/transports"; 4 | 5 | export interface Options extends winston.LoggerOptions { 6 | injectType?: InjectType; 7 | } 8 | 9 | export interface FileOptions extends Options { 10 | fileTransportOptions?: FileTransportOptions; 11 | } 12 | -------------------------------------------------------------------------------- /packages/logger/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/logger/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { Transport } from "../src"; 2 | 3 | export class CustomTransport extends Transport { 4 | constructor(private readonly data: any[]) { 5 | super(); 6 | } 7 | 8 | log(info: any, next: () => void) { 9 | this.data.push(info); 10 | next(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/cli-config.ts: -------------------------------------------------------------------------------- 1 | import { tryAddCliAssets } from "@halsp/core"; 2 | 3 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any) => { 4 | return tryAddCliAssets( 5 | config, 6 | (ass) => ass.startsWith("protos/"), 7 | { 8 | include: "protos/**/*.proto", 9 | root: "src", 10 | }, 11 | "protos/**/*.proto", 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/micro/client"; 2 | import { useMicroClient, InjectMicroClient } from "@halsp/micro/client"; 3 | import { MicroGrpcClientOptions } from "./options"; 4 | import { MicroGrpcClient } from "./client"; 5 | 6 | export { MicroGrpcClient } from "./client"; 7 | export { MicroGrpcClientOptions }; 8 | export { WriteIterator, ReadIterator } from "../stream"; 9 | 10 | declare module "@halsp/core" { 11 | interface Startup { 12 | useMicroGrpcClient( 13 | options?: MicroGrpcClientOptions & InjectMicroClient, 14 | ): this; 15 | } 16 | } 17 | 18 | useMicroClient("useMicroGrpcClient", MicroGrpcClient); 19 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/client/options.ts: -------------------------------------------------------------------------------- 1 | import type grpc from "@grpc/grpc-js"; 2 | import type grpcLoader from "@grpc/proto-loader"; 3 | 4 | export interface MicroGrpcClientOptions extends grpc.ChannelOptions { 5 | host?: string; 6 | port?: number; 7 | credentials?: grpc.ChannelCredentials; 8 | protoFiles?: string | string[]; 9 | loaderOptions?: grpcLoader.Options; 10 | } 11 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/index.ts: -------------------------------------------------------------------------------- 1 | export { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "./cli-config"; 2 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/load-packages.ts: -------------------------------------------------------------------------------- 1 | import * as grpcLoader from "@grpc/proto-loader"; 2 | import * as grpc from "@grpc/grpc-js"; 3 | import path from "path"; 4 | import { glob } from "glob"; 5 | 6 | export interface Options { 7 | loaderOptions?: grpcLoader.Options; 8 | protoFiles?: string | string[]; 9 | } 10 | 11 | export async function loadPackages( 12 | options: Options = {}, 13 | ): Promise { 14 | let protoFiles = options.protoFiles; 15 | if (!protoFiles || (Array.isArray(protoFiles) && !protoFiles.length)) { 16 | const proptosDir = path.join(process.cwd(), "protos"); 17 | protoFiles = glob 18 | .sync("*.proto", { 19 | cwd: proptosDir, 20 | }) 21 | .map((f: string) => path.join(proptosDir, f)); 22 | } 23 | 24 | const definition = await grpcLoader.load(protoFiles, options.loaderOptions); 25 | 26 | return grpc.loadPackageDefinition(definition); 27 | } 28 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import type grpc from "@grpc/grpc-js"; 3 | import { MicroGrpcOptions } from "./options"; 4 | import "@halsp/micro/server"; 5 | 6 | export { WriteIterator, ReadIterator } from "../stream"; 7 | export { MicroGrpcOptions }; 8 | 9 | declare module "@halsp/core" { 10 | interface Startup { 11 | useMicroGrpc(options?: MicroGrpcOptions): this; 12 | 13 | listen(): Promise; 14 | close(): Promise; 15 | } 16 | 17 | interface Request { 18 | get call(): grpc.ServerUnaryCall; 19 | get metadata(): grpc.Metadata; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/micro-grpc/src/server/options.ts: -------------------------------------------------------------------------------- 1 | import type grpc from "@grpc/grpc-js"; 2 | import type grpcLoader from "@grpc/proto-loader"; 3 | 4 | export interface MicroGrpcOptions extends grpc.ChannelOptions { 5 | host?: string; 6 | port?: number; 7 | credentials?: grpc.ServerCredentials; 8 | protoFiles?: string | string[]; 9 | loaderOptions?: grpcLoader.Options; 10 | } 11 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | import { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "../src"; 2 | 3 | test("cli config hook", async () => { 4 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({}); 5 | expect(config).toEqual({ 6 | build: { 7 | assets: [ 8 | { 9 | include: "protos/**/*.proto", 10 | root: "src", 11 | }, 12 | "protos/**/*.proto", 13 | ], 14 | }, 15 | }); 16 | }); 17 | 18 | test("cli config hook", async () => { 19 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({ 20 | build: { 21 | assets: ["protos/**/*.proto"], 22 | }, 23 | }); 24 | expect(config).toEqual({ 25 | build: { 26 | assets: ["protos/**/*.proto"], 27 | }, 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/load-packages.test.ts: -------------------------------------------------------------------------------- 1 | import { loadPackages } from "../src/load-packages"; 2 | import { runin } from "@halsp/testing"; 3 | 4 | describe("load packages", () => { 5 | it("should load default empty packages", async () => { 6 | const packages = await loadPackages(); 7 | expect(Object.keys(packages).length == 0).toBeTruthy(); 8 | }); 9 | 10 | it("should load default packages", async () => { 11 | await runin("test", async () => { 12 | const packages = await loadPackages(); 13 | expect(Object.keys(packages).length >= 0).toBeTruthy(); 14 | }); 15 | 16 | await runin("test", async () => { 17 | const packages = await loadPackages({ 18 | protoFiles: [], 19 | }); 20 | expect(Object.keys(packages).length >= 0).toBeTruthy(); 21 | }); 22 | 23 | await runin("test", async () => { 24 | const packages = await loadPackages({ 25 | protoFiles: "", 26 | }); 27 | expect(Object.keys(packages).length >= 0).toBeTruthy(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/protos/stream.client.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package clientStream; 4 | 5 | service ClientStreamService { 6 | rpc testMethod (stream TestRequest) returns (TestReply) {} 7 | } 8 | 9 | message TestRequest { 10 | string reqMessage = 1; 11 | } 12 | 13 | message TestReply { 14 | repeated string resMessage = 1; 15 | } 16 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/protos/stream.cs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package csStream; 4 | 5 | service CSStreamService { 6 | rpc testMethod (stream TestRequest) returns (stream TestReply) {} 7 | } 8 | 9 | message TestRequest { 10 | string reqMessage = 1; 11 | } 12 | 13 | message TestReply { 14 | string resMessage = 1; 15 | } 16 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/protos/stream.server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package serverStream; 4 | 5 | service ServerStreamService { 6 | rpc testMethod (TestRequest) returns (stream TestReply) {} 7 | } 8 | 9 | message TestRequest { 10 | string reqMessage = 1; 11 | } 12 | 13 | message TestReply { 14 | string resMessage = 1; 15 | } 16 | -------------------------------------------------------------------------------- /packages/micro-grpc/test/protos/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | service TestService { 6 | rpc testMethod (TestRequest) returns (TestReply) {} 7 | } 8 | 9 | service TestService2 { 10 | rpc testMethod (TestRequest) returns (TestReply) {} 11 | } 12 | 13 | message TestRequest { 14 | string reqMessage = 1; 15 | } 16 | 17 | message TestReply { 18 | string resMessage = 1; 19 | } 20 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/micro/client"; 2 | import { useMicroClient, InjectMicroClient } from "@halsp/micro/client"; 3 | import { MicroMqttClient } from "./client"; 4 | import { MicroMqttClientOptions } from "./options"; 5 | 6 | export { MicroMqttClientOptions }; 7 | export { MicroMqttClient }; 8 | 9 | declare module "@halsp/core" { 10 | interface Startup { 11 | useMicroMqttClient( 12 | options?: MicroMqttClientOptions & InjectMicroClient, 13 | ): this; 14 | } 15 | } 16 | 17 | useMicroClient("useMicroMqttClient", MicroMqttClient); 18 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/client/options.ts: -------------------------------------------------------------------------------- 1 | import type mqtt from "mqtt"; 2 | 3 | export interface MicroMqttClientOptions extends mqtt.IClientOptions { 4 | subscribeOptions?: mqtt.IClientSubscribeOptions; 5 | publishOptions?: mqtt.IClientPublishOptions; 6 | prefix?: string; 7 | sendTimeout?: number; 8 | } 9 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import type mqtt from "mqtt"; 2 | import { MicroMqttOptions } from "./options"; 3 | import "./startup"; 4 | import "@halsp/micro/server"; 5 | 6 | export { MicroMqttOptions }; 7 | 8 | declare module "@halsp/core" { 9 | interface Startup { 10 | useMicroMqtt(options?: MicroMqttOptions): this; 11 | 12 | listen(): Promise; 13 | close(): Promise; 14 | } 15 | 16 | interface Request { 17 | get packet(): mqtt.IPublishPacket; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/server/options.ts: -------------------------------------------------------------------------------- 1 | import type mqtt from "mqtt"; 2 | 3 | export interface MicroMqttOptions extends mqtt.IClientOptions { 4 | subscribeOptions?: mqtt.IClientSubscribeOptions; 5 | publishOptions?: mqtt.IClientPublishOptions; 6 | } 7 | -------------------------------------------------------------------------------- /packages/micro-mqtt/src/server/topic.ts: -------------------------------------------------------------------------------- 1 | export function matchTopic(pattern: string, topic: string) { 2 | const patternArray = pattern.split("/"); 3 | const topicArray = topic.split("/"); 4 | 5 | const length = patternArray.length; 6 | for (let i = 0; i < length; ++i) { 7 | const filterItem = patternArray[i]; 8 | const topicItem = topicArray[i]; 9 | if (filterItem === "#") { 10 | return topicArray.length >= length - 1; 11 | } 12 | if (filterItem !== "+" && filterItem !== topicItem) { 13 | return false; 14 | } 15 | } 16 | 17 | return length === topicArray.length; 18 | } 19 | -------------------------------------------------------------------------------- /packages/micro-mqtt/test/client/prefix.test.ts: -------------------------------------------------------------------------------- 1 | import { Startup } from "@halsp/core"; 2 | import "../../src/server"; 3 | import { MicroMqttClient } from "../../src/client"; 4 | 5 | describe("prefix", () => { 6 | it("should subscribe and publish pattern with prefix", async () => { 7 | const startup = new Startup() 8 | .useMicroMqtt({ 9 | port: 6002, 10 | }) 11 | .register("pt_test_pattern", (ctx) => { 12 | ctx.res.body = ctx.req.body; 13 | expect(!!ctx.req.packet).toBeTruthy(); 14 | }); 15 | await startup.listen(); 16 | 17 | const client = new MicroMqttClient({ 18 | port: 6002, 19 | prefix: "pt_", 20 | subscribeOptions: { qos: 1 }, 21 | publishOptions: {}, 22 | }); 23 | await client["connect"](); 24 | const result = await client.send("test_pattern", "test_body"); 25 | 26 | await client.dispose(true); 27 | await startup.close(); 28 | 29 | expect(result).toBe("test_body"); 30 | }, 10000); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/micro-mqtt/test/client/timeout.test.ts: -------------------------------------------------------------------------------- 1 | import { MicroMqttClient } from "../../src/client"; 2 | 3 | describe("timeout", () => { 4 | it("should return timeout without callback", async () => { 5 | const client = new MicroMqttClient(); 6 | (client as any).client = { 7 | connected: true, 8 | subscribe: () => undefined, 9 | unsubscribe: () => undefined, 10 | publish: () => undefined, 11 | }; 12 | 13 | let error: any; 14 | try { 15 | await client.send("", "", { 16 | timeout: 1000, 17 | }); 18 | } catch (err) { 19 | error = err; 20 | } 21 | expect(error.message).toBe("Send timeout"); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/micro-mqtt/test/topic.test.ts: -------------------------------------------------------------------------------- 1 | import { matchTopic } from "../src/server/topic"; 2 | 3 | describe("match topic", () => { 4 | it("should not match topic", async () => { 5 | expect(matchTopic("a/b", "a/c")).toBeFalsy(); 6 | }); 7 | 8 | it("should match topic with #", async () => { 9 | expect(matchTopic("a/#", "a/b")).toBeTruthy(); 10 | expect(matchTopic("a/#", "a/b/c")).toBeTruthy(); 11 | }); 12 | 13 | it("should match topic with +", async () => { 14 | expect(matchTopic("a/+", "a/b")).toBeTruthy(); 15 | expect(matchTopic("+/b", "a/b")).toBeTruthy(); 16 | }); 17 | 18 | it("should not match topic with +", async () => { 19 | expect(matchTopic("a/+", "a/b/c")).toBeFalsy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/micro-nats/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/micro/client"; 2 | import { useMicroClient, InjectMicroClient } from "@halsp/micro/client"; 3 | import { MicroNatsClientOptions } from "./options"; 4 | import { MicroNatsClient } from "./client"; 5 | 6 | export { MicroNatsClient }; 7 | export { MicroNatsClientOptions }; 8 | 9 | declare module "@halsp/core" { 10 | interface Startup { 11 | useMicroNatsClient( 12 | options?: MicroNatsClientOptions & InjectMicroClient, 13 | ): this; 14 | } 15 | } 16 | 17 | useMicroClient("useMicroNatsClient", MicroNatsClient); 18 | -------------------------------------------------------------------------------- /packages/micro-nats/src/client/options.ts: -------------------------------------------------------------------------------- 1 | import * as nats from "nats"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface MicroNatsOptions extends nats.ConnectionOptions {} 5 | 6 | export interface MicroNatsClientOptions extends nats.ConnectionOptions { 7 | prefix?: string; 8 | subscribeOptions?: Omit; 9 | sendTimeout?: number; 10 | } 11 | -------------------------------------------------------------------------------- /packages/micro-nats/src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/micro-nats/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import * as nats from "nats"; 2 | import { MicroNatsOptions } from "./options"; 3 | import "./startup"; 4 | import "@halsp/micro/server"; 5 | 6 | export { MicroNatsOptions }; 7 | 8 | declare module "@halsp/core" { 9 | interface Request { 10 | get headers(): nats.MsgHdrs; 11 | } 12 | interface Response { 13 | get headers(): nats.MsgHdrs; 14 | } 15 | 16 | interface Startup { 17 | useMicroNats(options?: MicroNatsOptions): this; 18 | 19 | listen(): Promise; 20 | close(): Promise; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/micro-nats/src/server/options.ts: -------------------------------------------------------------------------------- 1 | import * as nats from "nats"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface MicroNatsOptions extends nats.ConnectionOptions {} 5 | 6 | export interface MicroNatsClientOptions extends nats.ConnectionOptions { 7 | prefix?: string; 8 | subscribeOptions?: Omit; 9 | sendTimeout?: number; 10 | } 11 | -------------------------------------------------------------------------------- /packages/micro-nats/test/client/connect.test.ts: -------------------------------------------------------------------------------- 1 | import { MicroNatsClient } from "../../src/client"; 2 | 3 | describe("connect", () => { 4 | it("should connect with default host and port", async () => { 5 | const client = new MicroNatsClient(); 6 | 7 | let error: any; 8 | try { 9 | await client["connect"](); 10 | } catch (err) { 11 | error = err; 12 | } 13 | 14 | await client.dispose(); 15 | expect(error.message).toBe("CONNECTION_REFUSED"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/micro-nats/test/client/send.test.ts: -------------------------------------------------------------------------------- 1 | import { MicroNatsClient } from "../../src/client"; 2 | 3 | describe("send", () => { 4 | it("should not send message before connect", async () => { 5 | const client = new MicroNatsClient(); 6 | 7 | let error: any; 8 | try { 9 | await client.send("", ""); 10 | } catch (err) { 11 | error = err; 12 | } 13 | expect(error.message).toBe("The connection is not connected"); 14 | }); 15 | 16 | it("should not send message when connection is closed", async () => { 17 | const client = new MicroNatsClient(); 18 | (client as any).connection = { 19 | isClosed: () => true, 20 | disconnect: () => undefined, 21 | }; 22 | 23 | let error: any; 24 | try { 25 | await client.send("", ""); 26 | } catch (err) { 27 | error = err; 28 | } 29 | expect(error.message).toBe("The connection is not connected"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/micro-nats/test/client/timeout.test.ts: -------------------------------------------------------------------------------- 1 | import { MicroNatsClient } from "../../src/client"; 2 | 3 | describe("timeout", () => { 4 | it("should return timeout without callback", async () => { 5 | const client = new MicroNatsClient(); 6 | (client as any).connection = { 7 | isClosed: () => false, 8 | subscribe: () => { 9 | return { 10 | unsubscribe: () => undefined, 11 | }; 12 | }, 13 | unsubscribe: () => undefined, 14 | publish: () => undefined, 15 | }; 16 | 17 | let error: any; 18 | try { 19 | await client.send("", "", { 20 | timeout: 1000, 21 | }); 22 | } catch (err) { 23 | error = err; 24 | } 25 | expect(error.message).toBe("Send timeout"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/micro-redis/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/micro/client"; 2 | import { useMicroClient, InjectMicroClient } from "@halsp/micro/client"; 3 | import { MicroRedisClientOptions } from "./options"; 4 | import { MicroRedisClient } from "./client"; 5 | 6 | export { MicroRedisClient }; 7 | export { MicroRedisClientOptions }; 8 | 9 | declare module "@halsp/core" { 10 | interface Startup { 11 | useMicroRedisClient( 12 | options?: MicroRedisClientOptions & InjectMicroClient, 13 | ): this; 14 | } 15 | } 16 | 17 | useMicroClient("useMicroRedisClient", MicroRedisClient); 18 | -------------------------------------------------------------------------------- /packages/micro-redis/src/client/options.ts: -------------------------------------------------------------------------------- 1 | import type redis from "redis"; 2 | 3 | export interface MicroRedisClientOptions< 4 | M extends redis.RedisModules = redis.RedisModules, 5 | F extends redis.RedisFunctions = redis.RedisFunctions, 6 | S extends redis.RedisScripts = redis.RedisScripts, 7 | > extends redis.RedisClientOptions { 8 | prefix?: string; 9 | sendTimeout?: number; 10 | } 11 | -------------------------------------------------------------------------------- /packages/micro-redis/src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/micro-redis/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import * as redis from "redis"; 3 | import { MicroRedisOptions } from "./options"; 4 | import "@halsp/micro/server"; 5 | 6 | export { MicroRedisOptions }; 7 | 8 | declare module "@halsp/core" { 9 | interface Startup { 10 | useMicroRedis(options?: MicroRedisOptions): this; 11 | 12 | listen(): Promise<{ 13 | pub: redis.RedisClientType; 14 | sub: redis.RedisClientType; 15 | }>; 16 | close(): Promise; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/micro-redis/src/server/options.ts: -------------------------------------------------------------------------------- 1 | import type redis from "redis"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface MicroRedisOptions< 5 | M extends redis.RedisModules = redis.RedisModules, 6 | F extends redis.RedisFunctions = redis.RedisFunctions, 7 | S extends redis.RedisScripts = redis.RedisScripts, 8 | > extends redis.RedisClientOptions {} 9 | -------------------------------------------------------------------------------- /packages/micro-tcp/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/micro/client"; 2 | import { useMicroClient, InjectMicroClient } from "@halsp/micro/client"; 3 | import { MicroTcpClientOptions } from "./options"; 4 | import { MicroTcpClient } from "./client"; 5 | 6 | export { MicroTcpClientOptions }; 7 | export { MicroTcpClient }; 8 | 9 | declare module "@halsp/core" { 10 | interface Startup { 11 | useMicroTcpClient( 12 | options?: MicroTcpClientOptions & InjectMicroClient, 13 | ): this; 14 | } 15 | } 16 | 17 | useMicroClient("useMicroTcpClient", MicroTcpClient); 18 | -------------------------------------------------------------------------------- /packages/micro-tcp/src/client/options.ts: -------------------------------------------------------------------------------- 1 | export interface MicroTcpClientOptions { 2 | host?: string; 3 | port?: number; 4 | prefix?: string; 5 | sendTimeout?: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/micro-tcp/src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/micro-tcp/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import * as net from "net"; 3 | import { MicroTcpOptions } from "./options"; 4 | import "@halsp/micro/server"; 5 | 6 | export { MicroTcpOptions }; 7 | 8 | declare module "@halsp/core" { 9 | interface Startup { 10 | useMicroTcp(options?: MicroTcpOptions): this; 11 | 12 | listen(): Promise; 13 | close(): Promise; 14 | } 15 | 16 | interface Context { 17 | get socket(): net.Socket; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/micro-tcp/src/server/options.ts: -------------------------------------------------------------------------------- 1 | import * as net from "net"; 2 | 3 | export interface MicroTcpOptions extends net.ServerOpts, net.ListenOptions { 4 | handle?: any; 5 | } 6 | -------------------------------------------------------------------------------- /packages/micro/src/client/constant.ts: -------------------------------------------------------------------------------- 1 | export const MICRO_IDENTITY_KEY = "@halsp/micro/client-identity"; 2 | -------------------------------------------------------------------------------- /packages/micro/src/client/decorators.ts: -------------------------------------------------------------------------------- 1 | import { MICRO_IDENTITY_KEY } from "./constant"; 2 | import { Inject } from "@halsp/inject"; 3 | 4 | export const MicroClient = (identity?: string) => 5 | Inject(MICRO_IDENTITY_KEY + (identity ?? "")); 6 | -------------------------------------------------------------------------------- /packages/micro/src/client/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/inject"; 2 | import { Context } from "@halsp/core"; 3 | import { MICRO_IDENTITY_KEY } from "./constant"; 4 | 5 | declare module "@halsp/core" { 6 | interface Context { 7 | getMicroClient(identity?: string): Promise; 8 | } 9 | } 10 | 11 | Context.prototype.getMicroClient = async function ( 12 | identity?: string, 13 | ): Promise { 14 | const injectKey = MICRO_IDENTITY_KEY + (identity ?? ""); 15 | return await this.getService(injectKey); 16 | }; 17 | 18 | export { useMicroClient, InjectMicroClient } from "./use-client"; 19 | export { IMicroClient } from "./client"; 20 | export { MicroClient } from "./decorators"; 21 | -------------------------------------------------------------------------------- /packages/micro/src/common.internal/index.ts: -------------------------------------------------------------------------------- 1 | export { parseJsonBuffer } from "./json-buffer"; 2 | export { ClientPacket, ServerPacket } from "./packet"; 3 | -------------------------------------------------------------------------------- /packages/micro/src/common.internal/json-buffer.ts: -------------------------------------------------------------------------------- 1 | export function parseJsonBuffer(buffer: Buffer) { 2 | const str = buffer.toString("utf-8"); 3 | 4 | try { 5 | return JSON.parse(str); 6 | } catch (e) { 7 | return str; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/micro/src/common.internal/packet.ts: -------------------------------------------------------------------------------- 1 | export interface ServerPacket { 2 | pattern: string; 3 | data: T; 4 | id?: string; 5 | } 6 | 7 | export interface ClientPacket { 8 | id?: string; 9 | data?: T; 10 | error?: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/micro/src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/micro/src/server/exception.ts: -------------------------------------------------------------------------------- 1 | import { ExceptionMessage, HalspException } from "@halsp/core"; 2 | 3 | export class MicroException extends HalspException { 4 | constructor(error?: string | ExceptionMessage) { 5 | super(error); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/micro/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import "./context"; 3 | 4 | declare module "@halsp/core" { 5 | interface Request { 6 | id?: string; 7 | setId(id?: string): this; 8 | 9 | get pattern(): string; 10 | get payload(): any; 11 | setPayload(val: any): this; 12 | } 13 | 14 | interface Response { 15 | get error(): string | undefined; 16 | set error(err: string | undefined); 17 | setError(err: string | undefined): this; 18 | 19 | get payload(): any; 20 | set payload(val: any); 21 | setPayload(val: any): this; 22 | } 23 | } 24 | 25 | export { handleMessage } from "./startup"; 26 | export { MicroException } from "./exception"; 27 | -------------------------------------------------------------------------------- /packages/micro/test/json-buffer.test.ts: -------------------------------------------------------------------------------- 1 | import { parseJsonBuffer } from "../src/common.internal"; 2 | 3 | describe("parse json buffer", () => { 4 | it("should parse json buffer", async () => { 5 | expect(parseJsonBuffer(Buffer.from(`{"a":1}`))).toEqual({ 6 | a: 1, 7 | }); 8 | }); 9 | 10 | it("should parse empty json object", async () => { 11 | expect(parseJsonBuffer(Buffer.from("{}"))).toEqual({}); 12 | }); 13 | 14 | it("should not parse string value", async () => { 15 | expect(parseJsonBuffer(Buffer.from("abc"))).toBe("abc"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/micro/test/server/body.test.ts: -------------------------------------------------------------------------------- 1 | import { parseMicroBody } from "../../src/server/startup"; 2 | 3 | describe("parse json body", () => { 4 | it("should parse json str body", async () => { 5 | expect(parseMicroBody(`{"a":1}`)).toEqual({ a: 1 }); 6 | expect(parseMicroBody(`[1,2]`)).toEqual([1, 2]); 7 | }); 8 | 9 | it("should not parse body when body is not json string", async () => { 10 | expect(parseMicroBody(`abcd`)).toBe("abcd"); 11 | expect(parseMicroBody(`{abcd`)).toBe("{abcd"); 12 | expect(parseMicroBody(undefined)).toBe(undefined); 13 | expect(parseMicroBody(true)).toBe(true); 14 | expect(parseMicroBody(false)).toBe(false); 15 | expect(parseMicroBody(123)).toBe(123); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/micro/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/mongoose/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_IDENTITY = "@halsp/mongoose/optionsIdentity"; 2 | -------------------------------------------------------------------------------- /packages/mongoose/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject, createInject } from "@halsp/inject"; 2 | import { OPTIONS_IDENTITY } from "./constant"; 3 | import mongoose from "mongoose"; 4 | 5 | export function Mongoose( 6 | identity?: string, 7 | ): PropertyDecorator & ParameterDecorator; 8 | export function Mongoose(target: any, propertyKey: string | symbol): void; 9 | export function Mongoose( 10 | target: any, 11 | propertyKey: string | symbol | undefined, 12 | parameterIndex: number, 13 | ): void; 14 | export function Mongoose(...args: any[]) { 15 | if (args.length == 0 || (args.length == 1 && typeof args[0] == "string")) { 16 | return Inject(OPTIONS_IDENTITY + (args[0] ?? "")); 17 | } else { 18 | createInject( 19 | (ctx) => ctx.getService(OPTIONS_IDENTITY), 20 | args[0], 21 | args[1], 22 | args[2], 23 | ); 24 | } 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 28 | export interface Mongoose extends mongoose.Connection {} 29 | -------------------------------------------------------------------------------- /packages/mongoose/src/options.ts: -------------------------------------------------------------------------------- 1 | import { InjectType } from "@halsp/inject"; 2 | import mongoose from "mongoose"; 3 | 4 | export type Options = mongoose.ConnectOptions & { 5 | url: string; 6 | injectType?: InjectType; 7 | identity?: string; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/mongoose/test/http-context.test.ts: -------------------------------------------------------------------------------- 1 | import "../src"; 2 | import mongoose from "mongoose"; 3 | import "@halsp/testing"; 4 | import { Startup } from "@halsp/core"; 5 | 6 | it("should get mongoose by ctx", async () => { 7 | await new Startup() 8 | .use(async (ctx, next) => { 9 | (mongoose as any).createConnection = async () => { 10 | return { 11 | close: () => undefined, 12 | } as any; 13 | }; 14 | await next(); 15 | }) 16 | .useMongoose({ 17 | url: "test", 18 | identity: "abc", 19 | }) 20 | .useMongoose({ 21 | url: "test", 22 | }) 23 | .use(async (ctx, next) => { 24 | expect(!!(await ctx.getMongoose())).toBeTruthy(); 25 | expect(!!(await ctx.getMongoose("abc"))).toBeTruthy(); 26 | expect(!!(await ctx.getMongoose("def"))).toBeFalsy(); 27 | 28 | await next(); 29 | }) 30 | .test(); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/mongoose/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/mva/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/mva` 是 `Halsp` 的 MVA 框架 4 | 5 | - 支持 RESTful 规范 6 | - 根据文件系统映射访问路径,彻底解耦无关联功能 7 | - 轻量化,高可扩展性 8 | - 移除 controller 层,灵活性更高 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/mva/src/mva-options.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { RouterOptions } from "@halsp/router"; 3 | import { ViewOptions } from "@halsp/view"; 4 | 5 | export type CodeType = 6 | | { code: number; path?: string; replace?: number } 7 | | number; 8 | 9 | export interface MvaOptions { 10 | viewOptions?: ViewOptions; 11 | routerOptions?: RouterOptions; 12 | renderMethods?: string | string[]; 13 | randerEnable?: (ctx: Context) => Promise | boolean; 14 | } 15 | -------------------------------------------------------------------------------- /packages/mva/src/result.filter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { Filter } from "@halsp/filter"; 3 | 4 | export interface ResultFilter extends Filter { 5 | onResultExecuted(ctx: Context): void | Promise; 6 | onResultExecuting( 7 | ctx: Context, 8 | ): boolean | Promise | void | Promise; 9 | } 10 | -------------------------------------------------------------------------------- /packages/mva/test/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { Request, Startup } from "@halsp/core"; 2 | import "@halsp/testing"; 3 | import "../src"; 4 | import { runMva } from "./global"; 5 | import "@halsp/filter"; 6 | 7 | function runTest(executing: boolean) { 8 | test(`filter ${executing}`, async () => { 9 | await runMva(async () => { 10 | const res = await new Startup() 11 | .setContext( 12 | new Request().setPath("filter").setMethod("GET").setBody({ 13 | executing, 14 | }), 15 | ) 16 | .useFilter() 17 | .useMva() 18 | .test(); 19 | 20 | expect(res.status).toBe(200); 21 | expect(res.getHeader("result1")).toBe("1"); 22 | expect(res.getHeader("result2")).toBe(executing ? "2" : undefined); 23 | expect(res.body).toBe(executing ? `

filter

` : "OK"); 24 | }); 25 | }); 26 | } 27 | 28 | runTest(true); 29 | runTest(false); 30 | -------------------------------------------------------------------------------- /packages/mva/test/global.ts: -------------------------------------------------------------------------------- 1 | import { runin } from "@halsp/testing"; 2 | 3 | export async function runMva(test: () => Promise): Promise { 4 | await runin("test/mva", test); 5 | } 6 | -------------------------------------------------------------------------------- /packages/mva/test/mva.test.ts: -------------------------------------------------------------------------------- 1 | import { Request, Startup } from "@halsp/core"; 2 | import "@halsp/testing"; 3 | import "../src"; 4 | import { runMva } from "./global"; 5 | 6 | test("default", async function () { 7 | await runMva(async () => { 8 | const res = await new Startup() 9 | .setContext(new Request().setMethod("GET")) 10 | .useMva() 11 | .test(); 12 | 13 | expect(res.getHeader("content-type")).toBe("text/html"); 14 | expect(res.status).toBe(200); 15 | expect(res.body).toBe("

@halsp/mva

"); 16 | }); 17 | }); 18 | 19 | test("use again", async function () { 20 | await runMva(async () => { 21 | const res = await new Startup() 22 | .setContext(new Request().setMethod("GET")) 23 | .useMva() 24 | .useMva() 25 | .test(); 26 | 27 | expect(res.getHeader("content-type")).toBe("text/html"); 28 | expect(res.status).toBe(200); 29 | expect(res.body).toBe("

@halsp/mva

"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/mva/test/mva/actions/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.noContent(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mva/test/mva/actions/_.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | }); 8 | return; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/mva/test/mva/actions/filter.get.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { UseFilters } from "@halsp/filter"; 3 | import { Action } from "@halsp/router"; 4 | import { ResultFilter } from "../../../src"; 5 | 6 | class TestFilter implements ResultFilter { 7 | onResultExecuted(ctx: Context): void | Promise { 8 | ctx.res.setHeader("result2", 2); 9 | } 10 | onResultExecuting( 11 | ctx: Context, 12 | ): boolean | void | Promise | Promise { 13 | ctx.res.setHeader("result1", 1); 14 | return ctx.req.body["executing"]; 15 | } 16 | } 17 | 18 | @UseFilters(TestFilter) 19 | export default class extends Action { 20 | async invoke(): Promise { 21 | this.ok("OK"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mva/test/mva/actions/user/^email.get.ts: -------------------------------------------------------------------------------- 1 | import { Action, SetActionMetadata } from "@halsp/router"; 2 | import { users } from "../../mock"; 3 | 4 | @SetActionMetadata("roles", ["pl"]) 5 | export default class extends Action { 6 | async invoke(): Promise { 7 | const email = this.ctx.req.params.email; 8 | this.ok(users.filter((u) => u.email == email)[0]); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/mva/test/mva/actions/user/_.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | const email = this.ctx.req.query.email; 6 | const password = this.ctx.req.query.password; 7 | 8 | this.ok({ 9 | email, 10 | password, 11 | }); 12 | return; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/mva/test/mva/mock.ts: -------------------------------------------------------------------------------- 1 | import User from "./models/User"; 2 | 3 | const users = [ 4 | { 5 | email: "test1@hal.wang", 6 | password: "test1password", 7 | }, 8 | { 9 | email: "test2@hal.wang", 10 | password: "test2password", 11 | }, 12 | { 13 | email: "test3@hal.wang", 14 | password: "test3password", 15 | }, 16 | ]; 17 | 18 | export { users }; 19 | -------------------------------------------------------------------------------- /packages/mva/test/mva/models/User.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | email: string; 3 | password: string; 4 | } 5 | 6 | export default User; 7 | -------------------------------------------------------------------------------- /packages/mva/test/mva/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext" 5 | ], 6 | "module": "commonjs", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "target": "es2015", 10 | "strict": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | }, 14 | "exclude": [ 15 | "views" 16 | ] 17 | } -------------------------------------------------------------------------------- /packages/mva/test/mva/views/403.ejs: -------------------------------------------------------------------------------- 1 |

403

-------------------------------------------------------------------------------- /packages/mva/test/mva/views/404.ejs: -------------------------------------------------------------------------------- 1 |

404

-------------------------------------------------------------------------------- /packages/mva/test/mva/views/filter.ejs: -------------------------------------------------------------------------------- 1 |

filter

-------------------------------------------------------------------------------- /packages/mva/test/mva/views/index.ejs: -------------------------------------------------------------------------------- 1 |

@halsp/mva

-------------------------------------------------------------------------------- /packages/mva/test/mva/views/user/^email/index.ejs: -------------------------------------------------------------------------------- 1 |

email: <%= email %>

-------------------------------------------------------------------------------- /packages/mva/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/native/src/context.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | 3 | declare module "@halsp/core" { 4 | interface Context { 5 | get reqStream(): http.IncomingMessage; 6 | get resStream(): http.ServerResponse; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/native/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./startup"; 2 | import "./context"; 3 | import { NativeOptions } from "./options"; 4 | 5 | export { NativeOptions }; 6 | -------------------------------------------------------------------------------- /packages/native/src/options.ts: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | import net from "net"; 3 | import https from "https"; 4 | 5 | export interface NativeOptions extends net.ListenOptions, http.ServerOptions { 6 | https?: https.ServerOptions; 7 | } 8 | -------------------------------------------------------------------------------- /packages/native/test/body/empty.test.ts: -------------------------------------------------------------------------------- 1 | import "../../src"; 2 | import request from "supertest"; 3 | import { Startup } from "@halsp/core"; 4 | 5 | test("empty body", async () => { 6 | const server = await new Startup() 7 | .useNative({ 8 | port: 0, 9 | }) 10 | .use(async (ctx) => { 11 | ctx.res.ok(undefined); 12 | }) 13 | .listen(); 14 | const res = await request(server).get("").type("text"); 15 | server.close(); 16 | 17 | expect(res.status).toBe(200); 18 | expect(res.text).toEqual(""); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/pipe/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const PIPE_RECORDS_METADATA = "@halsp/pipe/recordsMetadata"; 2 | export const GLOBAL_PIPE_BAG = "@halsp/pipe/globalPipeBag"; 3 | -------------------------------------------------------------------------------- /packages/pipe/src/decorators/create-property-decorator.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { addPipeRecord } from "../pipe-req-record"; 3 | import { PipeReqType } from "../pipe-req-type"; 4 | 5 | export function createPropertyDecorator(type: PipeReqType, args: any[]) { 6 | if (typeof args[0] == "string") { 7 | // property params 8 | const pipes = args.slice(1, args.length); 9 | return function (target: any, propertyKey: string | symbol) { 10 | addPipeRecord(type, pipes, target, propertyKey, undefined, args[0]); 11 | }; 12 | } else if (typeof args[1] == "string") { 13 | const target = args[0]; 14 | addPipeRecord(type, [], target, args[1]); 15 | } else { 16 | const pipes = args; 17 | return function (target: any, propertyKey: string | symbol) { 18 | addPipeRecord(type, pipes, target, propertyKey, undefined, undefined); 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/pipe/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./req.decorator"; 2 | export * from "./create-req-decorator"; 3 | export * from "./create-property-decorator"; 4 | -------------------------------------------------------------------------------- /packages/pipe/src/global/global-pipe-item.ts: -------------------------------------------------------------------------------- 1 | import { PipeItem } from "../pipe-item"; 2 | import { GlobalPipeType } from "./global-pipe-type"; 3 | 4 | export type GlobalPipeItem = { 5 | pipe: PipeItem; 6 | type: GlobalPipeType; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/pipe/src/global/global-pipe-type.ts: -------------------------------------------------------------------------------- 1 | export enum GlobalPipeType { 2 | before = "before", 3 | after = "after", 4 | } 5 | -------------------------------------------------------------------------------- /packages/pipe/src/global/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./global-pipe-item"; 2 | export * from "./global-pipe-type"; 3 | -------------------------------------------------------------------------------- /packages/pipe/src/pipe-item.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, TransformArgs } from "./pipe-transform"; 2 | import { ObjectConstructor } from "@halsp/core"; 3 | 4 | export type PipeItem = 5 | | ((args: TransformArgs) => R | Promise) 6 | | PipeTransform 7 | | ObjectConstructor>; 8 | -------------------------------------------------------------------------------- /packages/pipe/src/pipe-req-type.ts: -------------------------------------------------------------------------------- 1 | export type PipeReqType = "query" | "param" | "header" | "body" | "property"; 2 | -------------------------------------------------------------------------------- /packages/pipe/src/pipe-transform.ts: -------------------------------------------------------------------------------- 1 | import { Context, ObjectConstructor } from "@halsp/core"; 2 | import { PipeItem } from "./pipe-item"; 3 | 4 | export interface TransformArgs { 5 | value: T; 6 | ctx: Context; 7 | propertyType: any; 8 | target: ObjectConstructor; 9 | parent: U; 10 | propertyKey: string | symbol; 11 | parameterIndex?: number; 12 | pipes: PipeItem[]; 13 | property?: string; 14 | } 15 | 16 | export interface PipeTransform { 17 | transform(args: TransformArgs): Promise | R; 18 | } 19 | -------------------------------------------------------------------------------- /packages/pipe/src/presets/error.ts: -------------------------------------------------------------------------------- 1 | import { safeImport } from "@halsp/core"; 2 | 3 | export async function createBadRequestError(message: string) { 4 | if (process.env.HALSP_ENV == "http") { 5 | const { BadRequestException } = await safeImport("@halsp/http"); 6 | return new BadRequestException(message); 7 | } else if (process.env.HALSP_ENV == "micro") { 8 | const { MicroException } = await safeImport("@halsp/micro/server"); 9 | return new MicroException(message); 10 | } else { 11 | return new Error(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/pipe/src/presets/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./parse-int.pipe"; 2 | export * from "./parse-float.pipe"; 3 | export * from "./parse-bool.pipe"; 4 | export * from "./lambda.pipe"; 5 | export * from "./trim.pipe"; 6 | export * from "./default-value.pipe"; 7 | -------------------------------------------------------------------------------- /packages/pipe/src/presets/lambda.pipe.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, TransformArgs } from "../pipe-transform"; 2 | 3 | export class LambdaPipe implements PipeTransform { 4 | constructor( 5 | private readonly handler: (args: TransformArgs) => R | Promise, 6 | ) {} 7 | 8 | async transform(args: TransformArgs) { 9 | return await this.handler(args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/pipe/src/presets/parse-float.pipe.ts: -------------------------------------------------------------------------------- 1 | import { createBadRequestError } from "./error"; 2 | import { PipeTransform } from "../pipe-transform"; 3 | 4 | export class ParseFloatPipe implements PipeTransform { 5 | async transform({ value }) { 6 | if (typeof value == "string") { 7 | value = parseFloat(value); 8 | } 9 | 10 | if (value == value) { 11 | return value; 12 | } 13 | 14 | throw await createBadRequestError( 15 | "Validation failed (numeric string is expected)", 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/pipe/src/presets/parse-int.pipe.ts: -------------------------------------------------------------------------------- 1 | import { createBadRequestError } from "./error"; 2 | import { PipeTransform } from "../pipe-transform"; 3 | 4 | export class ParseIntPipe implements PipeTransform { 5 | async transform({ value }) { 6 | if (typeof value == "string") { 7 | value = parseInt(value, 10); 8 | } 9 | 10 | if (value == value) { 11 | return Math.floor(value); 12 | } 13 | 14 | throw await createBadRequestError( 15 | "Validation failed (numeric string is expected)", 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/pipe/test/create-decorator.test.ts: -------------------------------------------------------------------------------- 1 | import { createReqDecorator } from "../src/decorators/create-req-decorator"; 2 | 3 | class TestClass { 4 | // 5 | } 6 | 7 | test("create decorator", async () => { 8 | const decorator = createReqDecorator("query", ["propertyKey"]); 9 | expect(typeof decorator).toBe("function"); 10 | }); 11 | 12 | test("without property", async () => { 13 | const decorator = createReqDecorator("query", [TestClass, undefined, 0]); 14 | expect(decorator).toBeUndefined(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/pipe/test/empty.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Request, Startup } from "@halsp/core"; 2 | import "@halsp/http"; 3 | import "@halsp/testing"; 4 | import { Body, Param } from "../src"; 5 | 6 | test("null body", async () => { 7 | class TestMiddleware extends Middleware { 8 | @Body 9 | readonly body!: any; 10 | @Body("field") 11 | readonly field!: any; 12 | @Param 13 | readonly param!: any; 14 | 15 | invoke(): void { 16 | this.ok({ 17 | body: this.body, 18 | field: this.field, 19 | param: this.param, 20 | }); 21 | } 22 | } 23 | 24 | const res = await new Startup() 25 | .useHttp() 26 | .setContext(new Request().setBody(null)) 27 | .useInject() 28 | .add(TestMiddleware) 29 | .test(); 30 | expect(res.body).toEqual({ 31 | body: null, 32 | field: undefined, 33 | param: undefined, 34 | }); 35 | expect(res.status).toBe(200); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/default-value.test.ts: -------------------------------------------------------------------------------- 1 | import { DefaultValuePipe, DefaultValuePipeOptions } from "../../src"; 2 | import { runSuccessPipeTest } from "./utils"; 3 | 4 | function runTest( 5 | source: any, 6 | defaultValue: any, 7 | target: any, 8 | options?: DefaultValuePipeOptions, 9 | ) { 10 | runSuccessPipeTest( 11 | [new DefaultValuePipe(defaultValue, options)], 12 | source, 13 | target, 14 | ); 15 | } 16 | 17 | runTest(null, 123, 123); 18 | runTest(undefined, 123, 123); 19 | runTest(null, null, null); 20 | runTest(undefined, undefined, undefined); 21 | 22 | runTest("", 123, "", { 23 | ignoreEmptyString: true, 24 | }); 25 | runTest("", 123, 123); 26 | 27 | runTest(null, 123, null, { 28 | ignoreNull: true, 29 | }); 30 | runTest(undefined, 123, undefined, { 31 | ignoreUndefined: true, 32 | }); 33 | 34 | runTest(parseInt("a"), 123, 123); 35 | runTest(parseInt("a"), 123, NaN, { 36 | ignoreNaN: true, 37 | }); 38 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/lambda-pipe.test.ts: -------------------------------------------------------------------------------- 1 | import { runSuccessPipeTest } from "./utils"; 2 | 3 | runSuccessPipeTest([({ value }) => Number(value)], "123", 123); 4 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/obj-pipe.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Request, Startup } from "@halsp/core"; 2 | import "@halsp/http"; 3 | import "@halsp/testing"; 4 | import { Body } from "../../src"; 5 | 6 | class TestMiddleware extends Middleware { 7 | @Body(({ value }) => JSON.stringify(value)) 8 | readonly body!: any; 9 | 10 | invoke(): void { 11 | this.ok(this.body); 12 | } 13 | } 14 | 15 | test("simple test", async () => { 16 | const res = await new Startup() 17 | .useHttp() 18 | .setContext( 19 | new Request().setBody({ 20 | b1: 1, 21 | }), 22 | ) 23 | .useInject() 24 | .add(new TestMiddleware()) 25 | .test(); 26 | expect(res.status).toBe(200); 27 | expect(res.body).toBe( 28 | JSON.stringify({ 29 | b1: 1, 30 | }), 31 | ); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/parse-float.test.ts: -------------------------------------------------------------------------------- 1 | import { ParseFloatPipe } from "../../src"; 2 | import { runFieldPipeTest, runSuccessPipeTest } from "./utils"; 3 | 4 | function runSuccessTest(source: any, target: any) { 5 | runSuccessPipeTest([ParseFloatPipe], source, target); 6 | } 7 | 8 | function runFieldTest(source: any) { 9 | runFieldPipeTest([ParseFloatPipe], source); 10 | } 11 | 12 | runSuccessTest("123", 123); 13 | runSuccessTest("0", 0); 14 | runSuccessTest("-345", -345); 15 | runFieldTest(""); 16 | runFieldTest("a"); 17 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/parse-int.test.ts: -------------------------------------------------------------------------------- 1 | import { ParseIntPipe } from "../../src"; 2 | import { runFieldPipeTest, runSuccessPipeTest } from "./utils"; 3 | 4 | function runSuccessTest(source: any, target: any) { 5 | runSuccessPipeTest([ParseIntPipe], source, target); 6 | } 7 | 8 | function runFieldTest(source: any) { 9 | runFieldPipeTest([ParseIntPipe], source); 10 | } 11 | 12 | runSuccessTest("123", 123); 13 | runSuccessTest("0", 0); 14 | runSuccessTest("-345", -345); 15 | runFieldTest(""); 16 | runFieldTest("a"); 17 | -------------------------------------------------------------------------------- /packages/pipe/test/pipe/trim.test.ts: -------------------------------------------------------------------------------- 1 | import { TrimPipe, TrimPipeOptions } from "../../src"; 2 | import { runFieldPipeTest, runSuccessPipeTest } from "./utils"; 3 | 4 | function runSuccessTest(source: any, target: any, options?: TrimPipeOptions) { 5 | runSuccessPipeTest([new TrimPipe(options)], source, target); 6 | } 7 | 8 | function runFieldTest(source: any, options?: TrimPipeOptions, target?: any) { 9 | runFieldPipeTest([new TrimPipe(options)], source, target); 10 | } 11 | 12 | runSuccessTest("", ""); 13 | runSuccessTest("123 ", "123"); 14 | runSuccessTest(" 123", "123"); 15 | runSuccessTest(" 123 ", "123"); 16 | runSuccessTest(" 123 ", "123 ", { end: false }); 17 | runSuccessTest(" 123 ", " 123", { start: false }); 18 | runSuccessTest(" 123 ", " 123 ", { end: false, start: false }); 19 | 20 | runFieldTest(123); 21 | runSuccessTest(123, "1234", { 22 | notString: (val) => String(val) + "4", 23 | }); 24 | 25 | runFieldTest(null); 26 | runFieldTest(undefined); 27 | -------------------------------------------------------------------------------- /packages/pipe/test/plain-to-class.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Request, Startup } from "@halsp/core"; 2 | import "@halsp/http"; 3 | import "@halsp/testing"; 4 | import { Body } from "../src"; 5 | 6 | test("plain to class", async () => { 7 | class TestDto { 8 | h1!: string; 9 | h2!: number; 10 | 11 | get h() { 12 | return this.h1 + this.h2; 13 | } 14 | } 15 | class TestMiddleware extends Middleware { 16 | @Body 17 | private readonly body!: TestDto; 18 | 19 | async invoke(): Promise { 20 | this.ok(this.body); 21 | } 22 | } 23 | 24 | const res = await new Startup() 25 | .useHttp() 26 | .setContext( 27 | new Request().setBody({ 28 | h1: "a", 29 | h2: 1, 30 | }), 31 | ) 32 | .useInject() 33 | .add(TestMiddleware) 34 | .test(); 35 | 36 | const body = res.body as TestDto; 37 | expect(body.h).toBe("a1"); 38 | expect(res.status).toBe(200); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/pipe/test/record.test.ts: -------------------------------------------------------------------------------- 1 | import { HookType, Startup } from "@halsp/core"; 2 | import "@halsp/http"; 3 | import "@halsp/testing"; 4 | import { getPipeRecords } from "../src"; 5 | import { expectBody, getTestRequest, TestMiddleware } from "./TestMiddleware"; 6 | 7 | test("record test", async () => { 8 | let done = false; 9 | const startup = new Startup() 10 | .useHttp() 11 | .setContext(getTestRequest()) 12 | .useInject(); 13 | startup.hook(HookType.BeforeInvoke, (ctx, md) => { 14 | const fn = (cls: any, empty: boolean) => { 15 | const metadata = getPipeRecords(cls); 16 | expect(Array.isArray(metadata)).toBeTruthy(); 17 | expect(metadata.length > 0).toBe(!empty); 18 | }; 19 | 20 | // fn(md, true); 21 | fn(md.constructor, false); 22 | fn(md.constructor.prototype, false); 23 | done = true; 24 | }); 25 | startup.add(TestMiddleware); 26 | 27 | const res = await startup.test(); 28 | expect(done).toBeTruthy(); 29 | expect(res.status).toBe(200); 30 | expect(res.body).toEqual(expectBody); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/pipe/test/simple.test.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/http"; 2 | import "@halsp/testing"; 3 | import "@halsp/inject"; 4 | import "../src"; 5 | import { expectBody, getTestRequest, TestMiddleware } from "./TestMiddleware"; 6 | import { Startup } from "@halsp/core"; 7 | 8 | function runTest(isConstructor: boolean) { 9 | test("simple test", async () => { 10 | const startup = new Startup() 11 | .useHttp() 12 | .setContext(getTestRequest()) 13 | .useInject(); 14 | if (isConstructor) { 15 | startup.add(TestMiddleware); 16 | } else { 17 | startup.add(new TestMiddleware()); 18 | } 19 | 20 | const res = await startup.test(); 21 | expect(res.status).toBe(200); 22 | expect(res.body).toEqual(expectBody); 23 | }); 24 | } 25 | 26 | runTest(false); 27 | runTest(true); 28 | -------------------------------------------------------------------------------- /packages/pipe/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/redis/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_IDENTITY = "@halsp/redis/optionsIdentity"; 2 | -------------------------------------------------------------------------------- /packages/redis/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject, createInject } from "@halsp/inject"; 2 | import { OPTIONS_IDENTITY } from "./constant"; 3 | import * as redis from "redis"; 4 | 5 | export function Redis( 6 | identity?: string, 7 | ): PropertyDecorator & ParameterDecorator; 8 | export function Redis(target: any, propertyKey: string | symbol): void; 9 | export function Redis( 10 | target: any, 11 | propertyKey: string | symbol | undefined, 12 | parameterIndex: number, 13 | ): void; 14 | export function Redis(...args: any[]) { 15 | if (args.length == 0 || (args.length == 1 && typeof args[0] == "string")) { 16 | return Inject(OPTIONS_IDENTITY + (args[0] ?? "")); 17 | } else { 18 | createInject( 19 | (ctx) => ctx.getService(OPTIONS_IDENTITY), 20 | args[0], 21 | args[1], 22 | args[2], 23 | ); 24 | } 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 28 | export interface Redis extends redis.RedisClientType {} 29 | -------------------------------------------------------------------------------- /packages/redis/src/options.ts: -------------------------------------------------------------------------------- 1 | import { InjectType } from "@halsp/inject"; 2 | import redis from "redis"; 3 | 4 | export type Options< 5 | M extends redis.RedisModules = redis.RedisModules, 6 | F extends redis.RedisFunctions = redis.RedisFunctions, 7 | S extends redis.RedisScripts = redis.RedisScripts, 8 | > = redis.RedisClientOptions & { 9 | injectType?: InjectType; 10 | identity?: string; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/redis/test/http-context.test.ts: -------------------------------------------------------------------------------- 1 | import "../src"; 2 | import "@halsp/testing"; 3 | import RedisClient from "@redis/client/dist/lib/client"; 4 | import { Startup } from "@halsp/core"; 5 | 6 | it("should get redis by ctx", async () => { 7 | const beforeConnect = RedisClient.prototype.connect; 8 | RedisClient.prototype.connect = async () => undefined as any; 9 | const beforeDisconnect = RedisClient.prototype.disconnect; 10 | RedisClient.prototype.disconnect = async () => undefined; 11 | 12 | await new Startup() 13 | .useRedis({ 14 | identity: "abc", 15 | }) 16 | .useRedis() 17 | .use(async (ctx, next) => { 18 | expect(!!(await ctx.getRedis())).toBeTruthy(); 19 | expect(!!(await ctx.getRedis("abc"))).toBeTruthy(); 20 | expect(!!(await ctx.getRedis("def"))).toBeFalsy(); 21 | 22 | await next(); 23 | }) 24 | .test(); 25 | 26 | RedisClient.prototype.connect = beforeConnect; 27 | RedisClient.prototype.disconnect = beforeDisconnect; 28 | }); 29 | -------------------------------------------------------------------------------- /packages/redis/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/router/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/router` 是 `Halsp` 的路由插件 4 | 5 | - 支持 RESTful 规范 6 | - 根据文件系统映射访问路径,彻底解耦无关联功能 7 | - 按需加载,提升请求速度 8 | - 轻量化,高可扩展性 9 | - 移除 controller 层,灵活性更高 10 | - 预编译路由,提升启动速度和响应速度 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/router/src/action/index.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from "@halsp/core"; 2 | 3 | export abstract class Action extends Middleware {} 4 | 5 | export * from "./action-metadata"; 6 | export * from "./http-method.decorator"; 7 | export * from "./pattern.decorator"; 8 | -------------------------------------------------------------------------------- /packages/router/src/action/method-item.ts: -------------------------------------------------------------------------------- 1 | export interface MethodItem { 2 | method: string; 3 | url: string; 4 | } 5 | -------------------------------------------------------------------------------- /packages/router/src/action/pattern.decorator.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { ACTION_PATTERN_METADATA } from "../constant"; 3 | 4 | export function MicroPattern(pattern: string): ClassDecorator { 5 | return function (target: any) { 6 | const patterns: string[] = 7 | Reflect.getMetadata(ACTION_PATTERN_METADATA, target.prototype) ?? []; 8 | Reflect.defineMetadata( 9 | ACTION_PATTERN_METADATA, 10 | [...patterns, pattern], 11 | target.prototype, 12 | ); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /packages/router/src/blank.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from "@halsp/core"; 2 | 3 | export class BlankMiddleware extends Middleware { 4 | async invoke(): Promise { 5 | await this.next(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_ACTION_DIR = "actions"; 2 | export const DEFAULT_MODULES_DIR = "modules"; 3 | export const CONFIG_FILE_NAME = "halsp-router.config"; 4 | export const HALSP_ROUTER_DIR = "@halsp/router/dir"; 5 | export const HALSP_ROUTER_IS_MODULE = "@halsp/router/isModule"; 6 | export const ACTION_METADATA = "@halsp/router/actionMetadata"; 7 | export const ACTION_METHOD_METADATA = "@halsp/router/actionMethodMetadata"; 8 | export const ACTION_PATTERN_METADATA = "@halsp/router/actionPatternMetadata"; 9 | export const ROUTER_INITED_OPTIONS_BAG = "@halsp.router/routerInitedOptionsBag"; 10 | -------------------------------------------------------------------------------- /packages/router/src/router-options.ts: -------------------------------------------------------------------------------- 1 | import MapItem from "./map/map-item"; 2 | 3 | export interface RouterOptions { 4 | prefix?: string; 5 | decorators?: ClassDecorator[] | ((mapItem: MapItem) => ClassDecorator[]); 6 | } 7 | 8 | export interface RouterInitedOptions extends RouterOptions { 9 | dir: string; 10 | } 11 | 12 | export interface RouterDistOptions { 13 | dir: string; 14 | map: MapItem[]; 15 | } 16 | 17 | export interface RouterOptionsMerged extends RouterInitedOptions { 18 | map?: MapItem[]; 19 | } 20 | -------------------------------------------------------------------------------- /packages/router/test/actions/NullBody.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/router/test/actions/NullBody.ts -------------------------------------------------------------------------------- /packages/router/test/actions/Router.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("ok"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "GET", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/decorator/method-base-path.ts: -------------------------------------------------------------------------------- 1 | import { Action, HttpGet } from "../../../src"; 2 | 3 | @HttpGet("//mup") 4 | export default class extends Action { 5 | async invoke(): Promise { 6 | this.ok("method"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/router/test/actions/decorator/method-custom.ts: -------------------------------------------------------------------------------- 1 | import { Action, HttpCustom } from "../../../src"; 2 | 3 | @HttpCustom("CUSTOM_DEC", "muc") 4 | export default class extends Action { 5 | async invoke(): Promise { 6 | this.ok("method"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/router/test/actions/decorator/method-url.ts: -------------------------------------------------------------------------------- 1 | import { Action, HttpPut } from "../../../src"; 2 | 3 | @HttpPut("/mu") 4 | export default class extends Action { 5 | async invoke(): Promise { 6 | this.ok("method"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/router/test/actions/decorator/method.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Action, 3 | HttpConnect, 4 | HttpCopy, 5 | HttpDelete, 6 | HttpGet, 7 | HttpHead, 8 | HttpLink, 9 | HttpMove, 10 | HttpOptions, 11 | HttpPatch, 12 | HttpPost, 13 | HttpTrace, 14 | HttpUnlink, 15 | HttpWrapped, 16 | } from "../../../src"; 17 | 18 | @HttpGet 19 | @HttpPost 20 | @HttpHead 21 | @HttpConnect 22 | @HttpOptions 23 | @HttpPatch 24 | @HttpTrace 25 | @HttpDelete 26 | @HttpMove 27 | @HttpCopy 28 | @HttpLink 29 | @HttpUnlink 30 | @HttpWrapped 31 | export default class extends Action { 32 | async invoke(): Promise { 33 | this.ok("method"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/router/test/actions/err/_.1post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/err/_.post1.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/metadata/custom.get.ts: -------------------------------------------------------------------------------- 1 | import { ObjectConstructor } from "@halsp/core"; 2 | import { Action, getActionMetadata, ActionMetadata } from "../../../src"; 3 | 4 | const Admin = ActionMetadata("admin", true); 5 | 6 | @ActionMetadata("custom", "11") 7 | @Admin 8 | export default class extends Action { 9 | async invoke(): Promise { 10 | this.ok({ 11 | get: getActionMetadata(this.constructor as ObjectConstructor), 12 | custom: this.ctx.actionMetadata.custom, 13 | admin: this.ctx.actionMetadata.admin, 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/router/test/actions/metadata/set-metadata.get.ts: -------------------------------------------------------------------------------- 1 | import { ObjectConstructor } from "@halsp/core"; 2 | import { 3 | Action, 4 | getActionMetadata, 5 | ActionMetadata, 6 | setActionMetadata, 7 | } from "../../../src"; 8 | 9 | @ActionMetadata("m1", 1) 10 | export default class extends Action { 11 | async invoke(): Promise { 12 | setActionMetadata(this, "m2", 2); 13 | setActionMetadata(this.constructor as ObjectConstructor, "m3", 3); 14 | this.ok({ 15 | constructor: getActionMetadata( 16 | this.constructor as ObjectConstructor, 17 | ), 18 | object: getActionMetadata(this), 19 | m1: getActionMetadata(this, "m1"), 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/router/test/actions/multiple.get.ts: -------------------------------------------------------------------------------- 1 | import { Action, HttpPost, HttpPut } from "../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("multiple-default"); 6 | } 7 | } 8 | 9 | export class test1 extends Action { 10 | async invoke(): Promise { 11 | this.ok("multiple-test"); 12 | } 13 | } 14 | 15 | @HttpPost 16 | export class test2 extends Action { 17 | async invoke(): Promise { 18 | this.ok("multiple-post"); 19 | } 20 | } 21 | 22 | @HttpPut("//path") 23 | export class test3 extends Action { 24 | async invoke(): Promise { 25 | this.ok("multiple-path"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/router/test/actions/otherClass.ts: -------------------------------------------------------------------------------- 1 | export default class OtherClass {} 2 | -------------------------------------------------------------------------------- /packages/router/test/actions/otherFile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/router/test/actions/otherFile.txt -------------------------------------------------------------------------------- /packages/router/test/actions/otherObject.ts: -------------------------------------------------------------------------------- 1 | const obj = {}; 2 | export default obj; 3 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "GET", 7 | id1: this.ctx.req.params.id, 8 | id2: (this.ctx.req as any).param.id, 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id/.delete.head.options.NO.patch.post.put.trace.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: this.ctx.req.method, 7 | id: this.ctx.req.params.id, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id/_.connect.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "CONNECT", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id/_.custom.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "CUSTOM", 7 | mapItem: this.ctx.actionMetadata, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id/animals.connect.delete.get.head.options.patch.post.trace.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: this.ctx.req.method, 7 | id: this.ctx.req.params.id, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/^id/animals.trace.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: this.ctx.req.method, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/_.any.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "ANY", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/_.connect.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "CONNECT", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/_.delete.head.options.patch.post.put.trace.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: this.ctx.req.method, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "GET", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/^params/_.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | action: "params", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/^params/miss/_.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | action: "params/miss", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/^params2/^nextParams.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | action: "params2/nextParams", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/miss/^query.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | action: "miss/params", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/miss/_.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "POST", 7 | action: "miss", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/method/simple.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | method: "ANY", 7 | action: "simple", 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/mostLike/^id1/act.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.noContent(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/mostLike/^id2/act.post.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.noContent(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/sortest/sort.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("outer"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/restful/sortest/sort/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("inner"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/simple/AdminAuth.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionMetadata } from "../../../src"; 2 | 3 | @ActionMetadata("roles", ["admin"]) 4 | export default class extends Action { 5 | async invoke(): Promise { 6 | const { account, password } = this.ctx.req.headers; 7 | 8 | this.ok({ 9 | msg: "admin auth", 10 | account, 11 | password, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/router/test/actions/simple/LoginAuth.ts: -------------------------------------------------------------------------------- 1 | import { Action, SetActionMetadata } from "../../../src"; 2 | 3 | @SetActionMetadata("roles", ["login"]) 4 | export default class extends Action { 5 | async invoke(): Promise { 6 | const { account, password } = this.ctx.req.headers; 7 | 8 | this.ok({ 9 | msg: "login auth", 10 | account, 11 | password, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/router/test/actions/simple/Router.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("ok"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/simple/deepActions/Router.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok("ok"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/actions/test.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/router/test/actions/test.d.ts -------------------------------------------------------------------------------- /packages/router/test/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | it("show be error", async () => { 2 | expect(() => require("../src/cli-config")).toThrow(); 3 | }); 4 | -------------------------------------------------------------------------------- /packages/router/test/def-actions/actions/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok({ 6 | defaultActions: true, 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/micro/path.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../src"; 2 | 3 | export default class extends Action { 4 | invoke() { 5 | this.res.setBody("pattern-path-test"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/micro/path2.message.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../src"; 2 | 3 | export default class extends Action { 4 | invoke() { 5 | this.res.setBody("pattern-message-path-test"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/micro/pattern.ts: -------------------------------------------------------------------------------- 1 | import { Action, MicroPattern } from "../../src"; 2 | 3 | @MicroPattern("event:123") 4 | export class EventPatternAction extends Action { 5 | invoke() { 6 | this.res.setBody("event-pattern-test"); 7 | } 8 | } 9 | 10 | @MicroPattern("message:123") 11 | export class MessagePatternAction extends Action { 12 | invoke() { 13 | this.res.setBody("message-pattern-test"); 14 | } 15 | } 16 | 17 | @MicroPattern("multi:123") 18 | @MicroPattern("multi:456") 19 | export class MultiPatternAction extends Action { 20 | invoke() { 21 | this.res.setBody("multi-pattern-test"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/router/test/modules/decorators/actions/deco.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ctx.set("module", true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/modules/decorators/module.ts: -------------------------------------------------------------------------------- 1 | import { defineModule } from "../../../src"; 2 | 3 | function Deco(target: any) { 4 | target.prototype.moduleTest = true; 5 | } 6 | 7 | export default defineModule(() => ({ 8 | decorators: [Deco], 9 | deepDir: "actions", 10 | })); 11 | -------------------------------------------------------------------------------- /packages/router/test/modules/def/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ctx.set("module", true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/modules/def/error.get.ts: -------------------------------------------------------------------------------- 1 | throw new Error(); 2 | -------------------------------------------------------------------------------- /packages/router/test/modules/ignore/ignore.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ctx.set("module", true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/modules/ignore/module.ts: -------------------------------------------------------------------------------- 1 | import { defineModule } from "../../../src"; 2 | 3 | export default defineModule(() => ({ 4 | deepDir: "actions", 5 | })); 6 | -------------------------------------------------------------------------------- /packages/router/test/modules/prefix/actions/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ctx.set("module", true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/modules/prefix/actions/deep/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ctx.set("module", true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/router/test/modules/prefix/actions/deep/deco.ts: -------------------------------------------------------------------------------- 1 | import { Action, HttpPost } from "../../../../../src"; 2 | 3 | @HttpPost("deep-deco") 4 | export class DecoAction extends Action { 5 | async invoke() { 6 | this.ctx.set("module", true); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/router/test/modules/prefix/module.js: -------------------------------------------------------------------------------- 1 | const { defineModule } = require("../../../src"); 2 | 3 | module.exports = defineModule({ 4 | prefix: "pre", 5 | deepDir: "actions", 6 | }); 7 | -------------------------------------------------------------------------------- /packages/router/test/post-build/.gitignore: -------------------------------------------------------------------------------- 1 | halsp-router.config -------------------------------------------------------------------------------- /packages/router/test/post-build/actions/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../../../src"; 2 | 3 | export default class extends Action { 4 | async invoke() { 5 | this.ok({ 6 | method: "GET", 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/router/test/post-build/modules/_.get.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionMetadata } from "../../../src"; 2 | 3 | @ActionMetadata("modules-first", 1) 4 | export default class extends Action { 5 | async invoke() { 6 | this.ok({ 7 | modules: true, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/router/test/prefix.test.ts: -------------------------------------------------------------------------------- 1 | import { Request, Startup } from "@halsp/core"; 2 | import "@halsp/testing"; 3 | import "@halsp/http"; 4 | import "../src"; 5 | import "./utils"; 6 | 7 | test("prefix", async () => { 8 | const result = await new Startup() 9 | .useHttp() 10 | .setContext(new Request().setPath("/api2/simple/router").setMethod("POST")) 11 | .useTestRouter({ 12 | prefix: "api2/", 13 | }) 14 | .test(); 15 | expect(result.status).toBe(200); 16 | }); 17 | 18 | test("error prefix", async () => { 19 | const result = await new Startup() 20 | .useHttp() 21 | .setContext(new Request().setPath("/api2/simple/router").setMethod("POST")) 22 | .useTestRouter({ 23 | prefix: "error/", 24 | }) 25 | .test(); 26 | expect(result.status).toBe(404); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/router/test/restful/any.test.ts: -------------------------------------------------------------------------------- 1 | import "../../src"; 2 | import { HttpMethods } from "@halsp/http"; 3 | import "@halsp/testing"; 4 | import "../utils"; 5 | import { Request, Startup } from "@halsp/core"; 6 | 7 | const methods = ["test", "aaa", "NO"]; 8 | 9 | methods.forEach((method) => { 10 | test(`${method} -> any restful test`, async () => { 11 | const result = await new Startup() 12 | .useHttp() 13 | .setContext(new Request().setPath("/restful").setMethod(method)) 14 | .useTestRouter() 15 | .test(); 16 | 17 | expect(result.status).toBe(200); 18 | expect(!!result.body.method).toBe(true); 19 | expect(result.body.method).toBe(HttpMethods.any); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/router/test/restful/err.test.ts: -------------------------------------------------------------------------------- 1 | import "../../src"; 2 | import { HttpMethods } from "@halsp/http"; 3 | import { Request, Startup } from "@halsp/core"; 4 | import "@halsp/testing"; 5 | import "../utils"; 6 | 7 | test(`action name error`, async () => { 8 | const result = await new Startup() 9 | .useHttp() 10 | .setContext(new Request().setPath("/err").setMethod(HttpMethods.post)) 11 | .useTestRouter() 12 | .test(); 13 | 14 | expect(result.status).toBe(405); 15 | }); 16 | 17 | test(`without method`, async () => { 18 | const result = await new Startup() 19 | .useHttp() 20 | .setContext(new Request().setPath("/restful").setMethod("")) 21 | .useTestRouter() 22 | .test(); 23 | 24 | expect(result.status).toBe(200); 25 | expect(result.body).toEqual({ 26 | method: "ANY", 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/router/test/restful/not-allowed.test.ts: -------------------------------------------------------------------------------- 1 | import "../../src"; 2 | import { Request, Startup } from "@halsp/core"; 3 | import "@halsp/testing"; 4 | import "@halsp/http"; 5 | import "../utils"; 6 | 7 | test(`method not allowed`, async () => { 8 | const result = await new Startup() 9 | .useHttp() 10 | .setContext(new Request().setPath("/restful/1").setMethod("NO")) 11 | .useTestRouter() 12 | .test(); 13 | expect(result.status).toBe(405); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/router/test/restful/restful-method.test.ts: -------------------------------------------------------------------------------- 1 | import { HttpMethods } from "@halsp/http"; 2 | import { Request, Startup } from "@halsp/core"; 3 | import "@halsp/testing"; 4 | import "../../src"; 5 | import "../utils"; 6 | 7 | const methods = [ 8 | HttpMethods.get, 9 | HttpMethods.connect, 10 | HttpMethods.delete, 11 | HttpMethods.post, 12 | HttpMethods.head, 13 | HttpMethods.options, 14 | HttpMethods.patch, 15 | HttpMethods.put, 16 | HttpMethods.trace, 17 | ]; 18 | 19 | methods.forEach((method) => { 20 | test(`${method} restful test`, async () => { 21 | const result = await new Startup() 22 | .useHttp() 23 | .setContext(new Request().setPath("/restful").setMethod(method)) 24 | .useTestRouter() 25 | .test(); 26 | expect(result.status).toBe(200); 27 | expect(result.body.method).toBe(method); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/router/test/restful/root.test.ts: -------------------------------------------------------------------------------- 1 | import { HttpMethods } from "@halsp/http"; 2 | import { Request, Startup } from "@halsp/core"; 3 | import "@halsp/testing"; 4 | import "../../src"; 5 | import "../utils"; 6 | 7 | test("restful root get", async () => { 8 | const result = await new Startup() 9 | .useHttp() 10 | .setContext( 11 | new Request().setPath("/").setMethod(HttpMethods.get.toUpperCase()), 12 | ) 13 | .useTestRouter() 14 | .test(); 15 | expect(result.status).toBe(200); 16 | expect(result.body.method).toBe(HttpMethods.get); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/router/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/router/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { RouterOptions } from "../src"; 2 | import "@halsp/testing"; 3 | import "../src"; 4 | import { Startup } from "@halsp/core"; 5 | import { HALSP_ROUTER_DIR, HALSP_ROUTER_IS_MODULE } from "../src/constant"; 6 | 7 | export const testDir = () => 8 | process.env.HALSP_ENV == "micro" ? "test/micro" : "test/actions"; 9 | 10 | declare module "@halsp/core" { 11 | interface Startup { 12 | useTestRouter( 13 | config?: RouterOptions & { dir?: string; isModule?: boolean }, 14 | ): this; 15 | } 16 | } 17 | 18 | Startup.prototype.useTestRouter = function (config = {}) { 19 | process.env[HALSP_ROUTER_DIR] = config?.dir ?? testDir(); 20 | process.env[HALSP_ROUTER_IS_MODULE] = String(config?.isModule ?? false); 21 | 22 | this.useRouter({ 23 | prefix: config?.prefix, 24 | decorators: config?.decorators, 25 | }); 26 | return this; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/static/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/static` 是 `Halsp` 的静态资源插件 4 | 5 | - 能够返回静态资源,如图片、html、css、js 等文件 6 | - 能够匹配单个文件或整个文件夹 7 | - 可以托管静态网站 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/static/src/cli.config.ts: -------------------------------------------------------------------------------- 1 | import { tryAddCliAssets } from "@halsp/core"; 2 | 3 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any) => { 4 | return tryAddCliAssets( 5 | config, 6 | (ass) => ass.startsWith("static/"), 7 | { 8 | include: "static/**/*", 9 | root: "src", 10 | }, 11 | "static/**/*", 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/static/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const FILE_BAG = "@halsp/static/fileBag"; 2 | export const FILE_ERROR_STATUS_BAG = "@halsp/static/fileErrorStatusBag"; 3 | export const DIR_RESULT_BAG = "@halsp/static/dirResultBag"; 4 | export const MATCH_RESULT_BAG = "@halsp/static/matchResultBag"; 5 | export const IS_METHOD_VALID_BAG = "@halsp/static/isMethodValidBag"; 6 | -------------------------------------------------------------------------------- /packages/static/src/options.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | encoding?: BufferEncoding; 3 | method?: string | string[]; 4 | strictMethod?: boolean; 5 | use404?: string | true; 6 | use405?: string | true; 7 | } 8 | 9 | export interface DirectoryOptions extends Options { 10 | dir: string; 11 | prefix?: string; 12 | exclude?: string | string[]; 13 | listDir?: boolean; 14 | useIndex?: string | string[] | true; 15 | useExt?: string | string[] | true; 16 | } 17 | 18 | export interface FileOptions extends Options { 19 | file: string; 20 | reqPath?: string | string[]; 21 | } 22 | -------------------------------------------------------------------------------- /packages/static/test/cli.config.test.ts: -------------------------------------------------------------------------------- 1 | import { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "../src"; 2 | 3 | test("cli config hook", async () => { 4 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({}); 5 | expect(config).toEqual({ 6 | build: { 7 | assets: [ 8 | { 9 | include: "static/**/*", 10 | root: "src", 11 | }, 12 | "static/**/*", 13 | ], 14 | }, 15 | }); 16 | }); 17 | 18 | test("cli config hook", async () => { 19 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({ 20 | build: { 21 | assets: ["static/*"], 22 | }, 23 | }); 24 | expect(config).toEqual({ 25 | build: { 26 | assets: ["static/*"], 27 | }, 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/static/test/serve/dir/file.txt: -------------------------------------------------------------------------------- 1 | txt-test -------------------------------------------------------------------------------- /packages/static/test/serve/index.html: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /packages/static/test/static/404.html: -------------------------------------------------------------------------------- 1 | 404 page -------------------------------------------------------------------------------- /packages/static/test/static/405.html: -------------------------------------------------------------------------------- 1 | 405 page -------------------------------------------------------------------------------- /packages/static/test/static/dir/index.html: -------------------------------------------------------------------------------- 1 | TEST -------------------------------------------------------------------------------- /packages/static/test/static/index.html: -------------------------------------------------------------------------------- 1 | TEST -------------------------------------------------------------------------------- /packages/static/test/static/index.un: -------------------------------------------------------------------------------- 1 | unknown -------------------------------------------------------------------------------- /packages/static/test/static/中文.html: -------------------------------------------------------------------------------- 1 | Chinese -------------------------------------------------------------------------------- /packages/static/test/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | 3 | export async function readStream( 4 | stream: fs.ReadStream, 5 | output?: BufferEncoding, 6 | ) { 7 | const chunks: Buffer[] = []; 8 | await new Promise((resolve, reject) => { 9 | stream.on("data", (chunk) => { 10 | const encoding = stream.readableEncoding ?? undefined; 11 | if (Buffer.isBuffer(chunk)) { 12 | chunks.push(chunk); 13 | } else { 14 | chunks.push(Buffer.from(chunk, encoding)); 15 | } 16 | }); 17 | stream.on("end", () => { 18 | resolve(); 19 | }); 20 | stream.on("error", (err) => { 21 | reject(err); 22 | }); 23 | }); 24 | return Buffer.concat(chunks).toString(output); 25 | } 26 | -------------------------------------------------------------------------------- /packages/swagger/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/swagger` 是 `Halsp` 的 swagger 文档插件 4 | 5 | 基于 `@halsp/validator`,使用装饰器注释文档,可以解析部分 `@halsp/validator` 的装饰器 6 | 7 | 在浏览器中使用 [swagger-ui](https://github.com/swagger-api/swagger-ui) 渲染文档 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/swagger/src/options.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "@halsp/core"; 2 | import { OpenApiBuilder } from "openapi3-ts-remove-yaml"; 3 | import type swaggerUi from "swagger-ui-dist"; 4 | 5 | type SwaggerBuilder = ( 6 | builder: OpenApiBuilder, 7 | ctx: Context, 8 | ) => void | Promise | OpenApiBuilder | Promise; 9 | 10 | export interface SwaggerHtmlOptions { 11 | lang?: string; 12 | title?: string; 13 | removeDefaultStyle?: boolean; 14 | favicon?: string | string[]; 15 | css?: string | string[]; 16 | style?: string | string[]; 17 | js?: string | string[]; 18 | script?: string | string[]; 19 | } 20 | 21 | export type SwaggerUIBundleConfig = Omit< 22 | Omit, 23 | "dom_id" 24 | >; 25 | 26 | export interface SwaggerOptions { 27 | basePath?: string; 28 | path?: string; 29 | builder?: SwaggerBuilder; 30 | html?: SwaggerHtmlOptions; 31 | initOAuth?: any; 32 | uiBundleOptions?: SwaggerUIBundleConfig; 33 | } 34 | -------------------------------------------------------------------------------- /packages/swagger/test-compiler/src/test.post.ts: -------------------------------------------------------------------------------- 1 | import { Header } from "@halsp/pipe"; 2 | import { Action } from "@halsp/router"; 3 | 4 | function Property(target: any, propertyKey: string) { 5 | // 6 | } 7 | 8 | class HeaderDto { 9 | @Property 10 | h1?: string; 11 | h2?: number; 12 | } 13 | 14 | export default class extends Action { 15 | // @Header("h") 16 | // private readonly body!: any; 17 | @Header 18 | private readonly h111!: HeaderDto; 19 | 20 | async invoke(): Promise { 21 | this.ok(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/swagger/test-compiler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext" 5 | ], 6 | "module": "commonjs", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "target": "es2015", 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "noImplicitAny": false, 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "sourceMap": true 17 | }, 18 | "include": [ 19 | "src/**/*.ts" 20 | ] 21 | } -------------------------------------------------------------------------------- /packages/swagger/test-http/actions/multi.get.ts: -------------------------------------------------------------------------------- 1 | import { V } from "@halsp/validator"; 2 | import "../../src"; 3 | import { Action } from "@halsp/router"; 4 | import { Body } from "@halsp/pipe"; 5 | 6 | @V().Description("obj dto") 7 | class ObjDto { 8 | @V().Required() 9 | id!: number; 10 | } 11 | 12 | @V().Description("content1 dto") 13 | class Content1Dto { 14 | @V().Required() 15 | id1!: number; 16 | } 17 | 18 | @V().Description("content2 dto") 19 | class Content2Dto { 20 | @V().Required() 21 | id2!: number; 22 | } 23 | 24 | @V().Description("obj2 dto") 25 | class Obj2Dto extends ObjDto { 26 | @V().Required() 27 | content2!: Content1Dto; 28 | } 29 | 30 | @V().Description("obj3 dto") 31 | class Obj3Dto extends ObjDto { 32 | @V().Required() 33 | content3!: Content2Dto; 34 | } 35 | 36 | @V().Summary("multi test") 37 | @V().Tags("test").Response(Obj2Dto) 38 | export default class extends Action { 39 | @Body 40 | private readonly b!: Obj3Dto; 41 | 42 | async invoke(): Promise { 43 | this.ok(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/swagger/test-http/actions/test.get.put.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | 3 | export default class extends Action { 4 | async invoke(): Promise { 5 | this.ok(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/swagger/test-http/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/native"; 2 | import "@halsp/body"; 3 | import chalk from "chalk"; 4 | import "../src"; 5 | import "@halsp/router"; 6 | import { HALSP_ROUTER_DIR } from "@halsp/router/constant"; 7 | import { Startup } from "@halsp/core"; 8 | 9 | async function bootstrap() { 10 | // const startup = new NativeStartup() 11 | // .useHttpJsonBody() 12 | // .useSwagger({}) 13 | // .useRouter({ 14 | // dir: "test-http/actions", 15 | // }); 16 | 17 | const startup = new Startup() 18 | .useNative({ 19 | port: 2333, 20 | }) 21 | .useSwagger({ 22 | path: "", 23 | }) 24 | .useRouter(); 25 | process.env[HALSP_ROUTER_DIR] = "test/parser"; 26 | 27 | await startup.listen(); 28 | console.log(chalk.blue(`start: http://localhost:${2333}`)); 29 | } 30 | 31 | bootstrap(); 32 | -------------------------------------------------------------------------------- /packages/swagger/test-http/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext" 5 | ], 6 | "module": "commonjs", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "target": "es2015", 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "noImplicitAny": false, 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "sourceMap": true 17 | }, 18 | } -------------------------------------------------------------------------------- /packages/swagger/test/decorator.test.ts: -------------------------------------------------------------------------------- 1 | import { MapItem } from "@halsp/router"; 2 | import { OpenApiBuilder } from "openapi3-ts-remove-yaml"; 3 | import { Parser } from "../src/parser"; 4 | import { runin } from "@halsp/testing"; 5 | 6 | test("decorators", async () => { 7 | const builder = new OpenApiBuilder(); 8 | await runin("test/parser", async () => { 9 | await new Parser( 10 | [ 11 | new MapItem({ 12 | path: "decorator.ts", 13 | actionName: "TestDecorator", 14 | url: "test", 15 | methods: ["post"], 16 | }), 17 | ], 18 | builder, 19 | ).parse(); 20 | }); 21 | const doc = builder.getSpec(); 22 | expect("post" in doc["paths"]["/test"]).toBeTruthy(); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/swagger/test/empty-version/package.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /packages/swagger/test/parser/deep.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | import { V } from "../../src"; 3 | import { Body, Property } from "@halsp/pipe"; 4 | 5 | class TestDto { 6 | @V().Required() 7 | @Property("b") 8 | testProperty!: number; 9 | } 10 | 11 | @V().Tags("test") 12 | export class TestDeep extends Action { 13 | @Body private readonly dto!: TestDto; 14 | 15 | @Property private readonly b2!: string; 16 | 17 | async invoke(): Promise { 18 | this.ok(this.dto); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/swagger/test/parser/default.get.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "@halsp/router"; 2 | 3 | export default class extends Action { 4 | invoke() { 5 | this.ok(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/swagger/test/parser/not-action.ts: -------------------------------------------------------------------------------- 1 | import { Header } from "@halsp/pipe"; 2 | import "reflect-metadata"; 3 | 4 | export function func() { 5 | return 0; 6 | } 7 | 8 | export const num = 0; 9 | 10 | export class TestClass { 11 | @Header() 12 | testProp: any; 13 | } 14 | -------------------------------------------------------------------------------- /packages/swagger/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/swagger/test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { pipeTypeToDocType } from "../src/parser/utils"; 2 | 3 | describe("pipeTypeToDocType", () => { 4 | test("should throw when error parameters", async () => { 5 | expect(() => pipeTypeToDocType("body")).toThrow(); 6 | }); 7 | 8 | test("should translate header", async () => { 9 | expect(pipeTypeToDocType("header")).toBe("header"); 10 | }); 11 | 12 | test("should translate query", async () => { 13 | expect(pipeTypeToDocType("query")).toBe("query"); 14 | }); 15 | 16 | test("should translate param", async () => { 17 | expect(pipeTypeToDocType("param")).toBe("path"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/testing/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./middleware"; 2 | import "./service"; 3 | import "./response"; 4 | import "./startup"; 5 | 6 | export { runin } from "./runin"; 7 | 8 | export { TestMiddlewareFn, ExpectMiddlewareType } from "./middleware"; 9 | -------------------------------------------------------------------------------- /packages/testing/src/response.ts: -------------------------------------------------------------------------------- 1 | import { isFunction, isNumber, Response } from "@halsp/core"; 2 | 3 | declare module "@halsp/core" { 4 | interface Response { 5 | expect(status: number): this; 6 | expect(status: number, body: any): this; 7 | expect(checker: (res: Response) => void): this; 8 | expect(body: any): this; 9 | } 10 | } 11 | 12 | Response.prototype.expect = function (...args: any[]) { 13 | const arg0 = args[0]; 14 | const arg1 = args[1]; 15 | if (isFunction(arg0)) { 16 | arg0(this); 17 | } else if (isNumber(arg0)) { 18 | expect(this["status"]).toBe(arg0); 19 | if (args.length >= 2) { 20 | expect(this.body).toEqual(arg1); 21 | } 22 | } else { 23 | expect(this.body).toEqual(arg0); 24 | } 25 | return this; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/testing/src/runin.ts: -------------------------------------------------------------------------------- 1 | export async function runin(path: string, func: () => Promise | void) { 2 | const root = process.cwd(); 3 | process.chdir(path); 4 | try { 5 | await func(); 6 | } finally { 7 | process.chdir(root); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/testing/src/service.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/core"; 2 | import { Context, ObjectConstructor, Startup } from "@halsp/core"; 3 | import type {} from "@halsp/inject"; 4 | 5 | declare module "@halsp/core" { 6 | interface Startup { 7 | expectInject( 8 | key: string, 9 | fn: (service: T, ctx: Context) => void | Promise, 10 | ): this; 11 | expectInject( 12 | service: ObjectConstructor, 13 | fn: (service: T, ctx: Context) => void | Promise, 14 | ): this; 15 | } 16 | } 17 | 18 | Startup.prototype.expectInject = function ( 19 | service: ObjectConstructor | string, 20 | fn: (service: T, ctx: Context) => void | Promise, 21 | ) { 22 | return this.useInject().use(async (ctx, next) => { 23 | await next(); 24 | 25 | const sv = await ctx["getService"](service as any); 26 | if (!sv) throw new Error("Create service failed"); 27 | 28 | await fn(sv, ctx); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/testing/test/shell.test.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs"; 2 | import { runin } from "../src"; 3 | 4 | test("runin", async () => { 5 | let run = false; 6 | await runin(__dirname, () => { 7 | run = true; 8 | expect(existsSync("./shell.test.ts")).toBeTruthy(); 9 | }); 10 | expect(run).toBeTruthy(); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/testing/test/startup.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, HookType, Startup } from "@halsp/core"; 2 | import "../src"; 3 | 4 | describe("startup", () => { 5 | it("should remove error when expectError success", async () => { 6 | await new Startup() 7 | .keepThrow() 8 | .expectError((err) => { 9 | expect(err.message).toBe("err"); 10 | }) 11 | .hook(HookType.Unhandled, (ctx, md, err) => { 12 | ctx.set("err", err); 13 | }) 14 | .setContext(new Context()) 15 | .use(() => { 16 | throw new Error("err"); 17 | }) 18 | .test(); 19 | }); 20 | 21 | it("error shound be throw", async () => { 22 | let errMsg: string | undefined; 23 | try { 24 | await new Startup() 25 | .keepThrow() 26 | .keepThrow() 27 | .use(() => { 28 | throw new Error("err"); 29 | }) 30 | .test(); 31 | } catch (err) { 32 | errMsg = (err as Error).message; 33 | } 34 | expect(errMsg).toBe("err"); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/testing/test/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | service TestService { 6 | rpc testMethod (TestRequest) returns (TestReply) {} 7 | } 8 | 9 | message TestRequest { 10 | string reqMessage = 1; 11 | } 12 | 13 | message TestReply { 14 | string resMessage = 1; 15 | } 16 | -------------------------------------------------------------------------------- /packages/testing/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "Bundler", 6 | "declaration": true, 7 | "resolveJsonModule": true, 8 | "outDir": "./dist", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "noImplicitAny": false, 13 | "experimentalDecorators": true, 14 | "emitDecoratorMetadata": true, 15 | "preserveWatchOutput": true, 16 | "sourceMap": true, 17 | "types": [ 18 | "@types/node", 19 | "@halsp/cli", 20 | "@types/jest" 21 | ] 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "dist/**/*", 28 | "coverage/**/*", 29 | "node_modules/**/*", 30 | ] 31 | } -------------------------------------------------------------------------------- /packages/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "module": "CommonJS", 6 | "moduleResolution": "Node", 7 | "outDir": "./dist-cjs", 8 | "declaration": false 9 | } 10 | } -------------------------------------------------------------------------------- /packages/tsconfig.code.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json" 3 | } -------------------------------------------------------------------------------- /packages/tsconfig.mjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist-mjs", 5 | } 6 | } -------------------------------------------------------------------------------- /packages/typeorm/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_IDENTITY = "@halsp/typeorm/optionsIdentity"; 2 | -------------------------------------------------------------------------------- /packages/typeorm/src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject, createInject } from "@halsp/inject"; 2 | import { OPTIONS_IDENTITY } from "./constant"; 3 | import * as typeorm from "typeorm"; 4 | 5 | export function Typeorm( 6 | identity?: string, 7 | ): PropertyDecorator & ParameterDecorator; 8 | export function Typeorm(target: any, propertyKey: string | symbol): void; 9 | export function Typeorm( 10 | target: any, 11 | propertyKey: string | symbol | undefined, 12 | parameterIndex: number, 13 | ): void; 14 | export function Typeorm(...args: any[]) { 15 | if (args.length == 0 || (args.length == 1 && typeof args[0] == "string")) { 16 | return Inject(OPTIONS_IDENTITY + (args[0] ?? "")); 17 | } else { 18 | createInject( 19 | (ctx) => ctx.getService(OPTIONS_IDENTITY), 20 | args[0], 21 | args[1], 22 | args[2], 23 | ); 24 | } 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 28 | export interface Typeorm extends typeorm.DataSource {} 29 | -------------------------------------------------------------------------------- /packages/typeorm/src/options.ts: -------------------------------------------------------------------------------- 1 | import { InjectType } from "@halsp/inject"; 2 | import typeorm from "typeorm"; 3 | 4 | export type Options = typeorm.DataSourceOptions & { 5 | injectType?: InjectType; 6 | identity?: string; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/typeorm/test/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite.db -------------------------------------------------------------------------------- /packages/typeorm/test/entities/TestEntity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import "reflect-metadata"; 3 | 4 | @Entity() 5 | export class TestEntity { 6 | @PrimaryGeneratedColumn() 7 | id!: number; 8 | 9 | @Column() 10 | name!: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/typeorm/test/exec.test.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/testing"; 2 | import "../src"; 3 | import { Typeorm } from "../src"; 4 | import { OPTIONS_IDENTITY } from "../src/constant"; 5 | import { TestEntity } from "./entities/TestEntity"; 6 | import { Startup } from "@halsp/core"; 7 | 8 | it("should insert entity to sqlite", async () => { 9 | await new Startup() 10 | .useTypeorm({ 11 | type: "sqlite", 12 | database: "test/sqlite.db", 13 | synchronize: true, 14 | entities: [TestEntity], 15 | }) 16 | .use(async (ctx) => { 17 | const connection = await ctx.getService(OPTIONS_IDENTITY); 18 | if (!connection) throw new Error(); 19 | 20 | const testDto = new TestEntity(); 21 | testDto.name = "test"; 22 | await connection.manager.save(testDto); 23 | const findResult = await connection.getRepository(TestEntity).findOne({ 24 | where: { 25 | name: "test", 26 | }, 27 | }); 28 | expect(!!findResult).toBeTruthy(); 29 | }) 30 | .test(); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/typeorm/test/http-context.test.ts: -------------------------------------------------------------------------------- 1 | import "../src"; 2 | import "@halsp/testing"; 3 | import { Startup } from "@halsp/core"; 4 | 5 | it("should get typeorm by ctx", async () => { 6 | await new Startup() 7 | .useTypeorm({ 8 | identity: "abc", 9 | type: "sqlite", 10 | database: "test/sqlite.db", 11 | }) 12 | .useTypeorm({ 13 | type: "sqlite", 14 | database: "test/sqlite.db", 15 | }) 16 | .use(async (ctx, next) => { 17 | expect(!!(await ctx.getTypeorm())).toBeTruthy(); 18 | expect(!!(await ctx.getTypeorm("abc"))).toBeTruthy(); 19 | expect(!!(await ctx.getTypeorm("def"))).toBeFalsy(); 20 | 21 | await next(); 22 | }) 23 | .test(); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/typeorm/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/validator/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/validator` 是 `Halsp` 的参数校验插件 4 | 5 | 基于 [class-validator](https://github.com/typestack/class-validator) 和 [@halsp/pipe](https://github.com/halsp/pipe) 校验请求参数 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/validator/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const OPTIONS_METADATA = "@halsp/validator/optionsMetadata"; 2 | export const ENABLE_METADATA = "@halsp/validator/enableMetadata"; 3 | export const METADATA = "@halsp/validator/metadata"; 4 | -------------------------------------------------------------------------------- /packages/validator/src/error.ts: -------------------------------------------------------------------------------- 1 | import { safeImport } from "@halsp/core"; 2 | 3 | export async function createBadRequestError(message: string | string[]) { 4 | if (process.env.HALSP_ENV == "http") { 5 | const { BadRequestException } = await safeImport("@halsp/http"); 6 | return new BadRequestException(message); 7 | } else if (process.env.HALSP_ENV == "micro") { 8 | const { MicroException } = await safeImport("@halsp/micro/server"); 9 | return new MicroException(message); 10 | } else { 11 | return new Error(Array.isArray(message) ? message.join(", ") : message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/validator/test/name.test.ts: -------------------------------------------------------------------------------- 1 | import { addCustomValidator, V, ValidatorDecoratorReturnType } from "../src"; 2 | 3 | declare module "../src" { 4 | interface ValidatorLib { 5 | CustomDecorator1: () => ValidatorDecoratorReturnType; 6 | CustomDecorator2: () => ValidatorDecoratorReturnType; 7 | } 8 | } 9 | 10 | describe("name", () => { 11 | addCustomValidator({ 12 | validate: () => false, 13 | errorMessage: "", 14 | name: "CustomDecorator1", 15 | }); 16 | 17 | const lib = V(); 18 | 19 | it("should set decorator name", async () => { 20 | expect(lib.Contains.name).toBe("Contains"); 21 | }); 22 | 23 | it("should set extend decorator name", async () => { 24 | expect(lib.Required.name).toBe("Required"); 25 | }); 26 | 27 | it("should set custom decorator name", async () => { 28 | expect(lib.CustomDecorator1.name).toBe("CustomDecorator1"); 29 | }); 30 | 31 | it("should set not-existed decorator name", async () => { 32 | expect(lib.CustomDecorator2.name).toBe("CustomDecorator2"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/validator/test/transformer-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@halsp/validator": "*" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/validator/test/transformer-test/src/index.ts: -------------------------------------------------------------------------------- 1 | import { V } from "@halsp/validator"; 2 | 3 | export class TestDto { 4 | @V 5 | a!: string; 6 | 7 | @V() 8 | b!: string; 9 | 10 | @V.IsInt().IsInt().IsInt() 11 | c!: string; 12 | 13 | @V().IsInt() 14 | d!: string; 15 | } 16 | -------------------------------------------------------------------------------- /packages/validator/test/transformer-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "Bundler", 6 | "declaration": true, 7 | "resolveJsonModule": true, 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "noImplicitAny": false, 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "preserveWatchOutput": true, 17 | "sourceMap": true, 18 | "types": [ 19 | "@types/node", 20 | "@halsp/cli", 21 | "@types/jest" 22 | ] 23 | }, 24 | "include": [ 25 | "src/**/*" 26 | ], 27 | } -------------------------------------------------------------------------------- /packages/validator/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "types": [ 14 | "@types/node", 15 | "@halsp/cli", 16 | "@types/jest" 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /packages/view/base.readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `@halsp/view` 是 `Halsp` 的视图渲染插件 4 | 5 | - 基于 `consolidate`, 支持多种视图模板,参考 [consolidate](https://github.com/tj/consolidate.js) 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/view/src/cli-config.ts: -------------------------------------------------------------------------------- 1 | import { tryAddCliAssets } from "@halsp/core"; 2 | 3 | export const HALSP_CLI_PLUGIN_CONFIG_HOOK = (config: any) => { 4 | return tryAddCliAssets( 5 | config, 6 | (ass) => ass.startsWith("views/"), 7 | { 8 | include: "views/**/*", 9 | root: "src", 10 | }, 11 | "views/**/*", 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/view/src/index.ts: -------------------------------------------------------------------------------- 1 | import "@halsp/core"; 2 | import { Startup } from "@halsp/core"; 3 | import { ViewOptions } from "./view-options"; 4 | import { useView } from "./user-view"; 5 | 6 | declare module "@halsp/core" { 7 | interface Startup { 8 | useView(options?: ViewOptions): this; 9 | } 10 | 11 | interface Context { 12 | state: Record; 13 | view( 14 | tmpPath: string, 15 | locals?: Record, 16 | ): Promise; 17 | } 18 | 19 | interface Response { 20 | view(tmpPath: string, locals?: Record): Promise; 21 | } 22 | } 23 | 24 | Startup.prototype.useView = function (options: ViewOptions = {}): Startup { 25 | return useView(this, options); 26 | }; 27 | 28 | export { 29 | consolidate, 30 | Engine, 31 | RendererInterface, 32 | ViewOptions, 33 | } from "./view-options"; 34 | export { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "./cli-config"; 35 | -------------------------------------------------------------------------------- /packages/view/src/view-options.ts: -------------------------------------------------------------------------------- 1 | import * as consolidate from "consolidate"; 2 | 3 | type RendererInterface = typeof consolidate.ejs; 4 | type Engine = { ext: string; render: RendererInterface | string }; 5 | 6 | export interface ViewOptions { 7 | dir?: string; 8 | options?: Record; 9 | engines?: Engine[] | Engine; 10 | } 11 | 12 | export { RendererInterface, Engine, consolidate }; 13 | -------------------------------------------------------------------------------- /packages/view/test/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | import { HALSP_CLI_PLUGIN_CONFIG_HOOK } from "../src"; 2 | 3 | test("cli config hook", async () => { 4 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({}); 5 | expect(config).toEqual({ 6 | build: { 7 | assets: [ 8 | { 9 | include: "views/**/*", 10 | root: "src", 11 | }, 12 | "views/**/*", 13 | ], 14 | }, 15 | }); 16 | }); 17 | 18 | test("cli config hook with custom assets", async () => { 19 | const config = HALSP_CLI_PLUGIN_CONFIG_HOOK({ 20 | build: { 21 | assets: ["views/*"], 22 | }, 23 | }); 24 | expect(config).toEqual({ 25 | build: { 26 | assets: ["views/*"], 27 | }, 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/view/test/middleware.test.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Startup } from "@halsp/core"; 2 | import "@halsp/testing"; 3 | import "../src"; 4 | 5 | describe("middleware", () => { 6 | it("should not render empty string", async () => { 7 | class Md extends Middleware { 8 | async invoke(): Promise { 9 | this.ctx.set("view", await this.ctx.view("")); 10 | } 11 | } 12 | 13 | const { ctx } = await new Startup() 14 | .useView() 15 | .add(() => new Md()) 16 | .test(); 17 | 18 | expect(ctx.get("view")).toBeUndefined(); 19 | }); 20 | 21 | it("should set views dir", async () => { 22 | class Md extends Middleware { 23 | async invoke(): Promise { 24 | this.ctx.set("view", await this.ctx.view("")); 25 | } 26 | } 27 | 28 | const { ctx } = await new Startup() 29 | .useView({ 30 | dir: "test/views", 31 | }) 32 | .add(() => new Md()) 33 | .test(); 34 | 35 | expect(ctx.get("view")).toBeUndefined(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/view/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /packages/view/test/views/ejs/index.ejs: -------------------------------------------------------------------------------- 1 |

<%= name %>

-------------------------------------------------------------------------------- /packages/view/test/views/ejs/index.txt: -------------------------------------------------------------------------------- 1 | txt -------------------------------------------------------------------------------- /packages/view/test/views/empty/index.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/view/test/views/empty/index.ejs -------------------------------------------------------------------------------- /packages/view/test/views/error/engine/index.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/view/test/views/error/engine/index.doc -------------------------------------------------------------------------------- /packages/view/test/views/error/engine/index.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/view/test/views/error/engine/index.txt -------------------------------------------------------------------------------- /packages/view/test/views/error/path.dir/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/halsp/core/5d1fa8c3abf4a1c5305cea1acb53d876855a29ee/packages/view/test/views/error/path.dir/index -------------------------------------------------------------------------------- /packages/view/test/views/html/index.html: -------------------------------------------------------------------------------- 1 | html content -------------------------------------------------------------------------------- /packages/view/test/views/html/index.txt: -------------------------------------------------------------------------------- 1 | txt -------------------------------------------------------------------------------- /packages/view/test/views/pug/test.pug: -------------------------------------------------------------------------------- 1 | p #{name} -------------------------------------------------------------------------------- /packages/ws/src/decorator.ts: -------------------------------------------------------------------------------- 1 | import { createInject, Inject } from "@halsp/inject"; 2 | import ws from "ws"; 3 | 4 | export function WebSocket( 5 | target: any, 6 | propertyKey: string | symbol | undefined, 7 | parameterIndex?: number, 8 | ): void; 9 | export function WebSocket(): PropertyDecorator & ParameterDecorator; 10 | export function WebSocket(...args: any[]): any { 11 | if (args.length > 0) { 12 | return createInject( 13 | (ctx) => ctx.acceptWebSocket(), 14 | args[0], 15 | args[1], 16 | args[2], 17 | ); 18 | } else { 19 | return Inject((ctx) => ctx.acceptWebSocket()); 20 | } 21 | } 22 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 23 | export interface WebSocket extends ws.WebSocket {} 24 | -------------------------------------------------------------------------------- /packages/ws/src/options.ts: -------------------------------------------------------------------------------- 1 | import type ws from "ws"; 2 | import type http from "http"; 3 | 4 | export interface WsOptions { 5 | keepAliveTimeout?: number; 6 | allowedOrigins?: string[]; 7 | 8 | backlog?: number | undefined; 9 | verifyClient?: 10 | | ws.VerifyClientCallbackAsync 11 | | ws.VerifyClientCallbackSync 12 | | undefined; 13 | handleProtocols?: ( 14 | protocols: Set, 15 | request: http.IncomingMessage, 16 | ) => string | false; 17 | clientTracking?: boolean | undefined; 18 | perMessageDeflate?: boolean | ws.PerMessageDeflateOptions | undefined; 19 | maxPayload?: number | undefined; 20 | skipUTF8Validation?: boolean | undefined; 21 | WebSocket?: typeof ws.WebSocket; 22 | } 23 | -------------------------------------------------------------------------------- /packages/ws/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true 13 | } 14 | } -------------------------------------------------------------------------------- /scripts/ci-install.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | npm install -g @halsp/cli 4 | npm install -g yarn 5 | yarn config set registry https://registry.npmjs.org 6 | 7 | yarn install 8 | -------------------------------------------------------------------------------- /scripts/clean-dist.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./dist/* -------------------------------------------------------------------------------- /scripts/copy-package-file.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import path from "path"; 3 | import { getPackages } from "./get-packages"; 4 | 5 | const sourceFileName = process.argv[2]; 6 | const targetFileName = process.argv[3] ?? sourceFileName; 7 | 8 | getPackages().forEach((item: string) => { 9 | fs.copyFileSync( 10 | path.join("packages", sourceFileName), 11 | path.join("packages", item, targetFileName), 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /scripts/copy-package-files.sh: -------------------------------------------------------------------------------- 1 | ts-node scripts/copy-package-file.ts base.gitignore .gitignore 2 | ts-node scripts/copy-package-file.ts jest.config.js 3 | ts-node scripts/copy-package-file.ts jest.setup.js 4 | ts-node scripts/copy-package-file.ts tsconfig.base.json 5 | ts-node scripts/copy-package-file.ts tsconfig.code.json tsconfig.json 6 | ts-node scripts/copy-package-file.ts tsconfig.cjs.json 7 | ts-node scripts/copy-package-file.ts tsconfig.mjs.json 8 | ts-node scripts/copy-package-file.ts ../LICENSE LICENSE 9 | ts-node scripts/copy-package-file.ts ../.eslintignore .eslintignore 10 | ts-node scripts/copy-package-file.ts ../.eslintrc.cjs .eslintrc.cjs 11 | 12 | ts-node scripts/create-readme.ts 13 | 14 | cp README.md packages/core/README.md -------------------------------------------------------------------------------- /scripts/get-packages.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const getPackages = (cwd = process.cwd()) => { 5 | return fs 6 | .readdirSync(path.join(cwd, "packages")) 7 | .filter((item) => 8 | fs.statSync(path.join(cwd, "packages", item)).isDirectory() 9 | ) 10 | .filter((item) => 11 | fs.existsSync(path.join(cwd, "packages", item, "package.json")) 12 | ); 13 | }; 14 | 15 | module.exports = { getPackages }; 16 | -------------------------------------------------------------------------------- /scripts/init-cli-deps.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd ./node_modules/@halsp/cli 4 | if [ ! -d "core-deps-temp" ]; then 5 | mkdir "core-deps-temp" 6 | fi 7 | cd "core-deps-temp" 8 | npm init -y 9 | npm install @halsp/core 10 | npm install @halsp/inject 11 | 12 | cd .. 13 | 14 | if [ ! -d "node_modules/@halsp" ];then 15 | mkdir "node_modules/@halsp" 16 | fi 17 | cp -r -f core-deps-temp/node_modules/@halsp/core node_modules/@halsp/core 18 | cp -r -f core-deps-temp/node_modules/@halsp/inject node_modules/@halsp/inject 19 | -------------------------------------------------------------------------------- /scripts/init-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $(docker ps -a -q -f "name=^halsp-test-nats$") ]; 4 | then 5 | docker run -itd --name halsp-test-nats -p 6001:4222 nats:2-linux 6 | else 7 | docker start halsp-test-nats 8 | fi 9 | 10 | if [ -z $(docker ps -a -q -f "name=^halsp-test-mqtt$") ]; 11 | then 12 | docker run -itd --name halsp-test-mqtt -p 6002:1883 emqx:5 13 | else 14 | docker start halsp-test-mqtt 15 | fi 16 | 17 | if [ -z $(docker ps -a -q -f "name=^halsp-test-redis$") ]; 18 | then 19 | docker run -itd --name halsp-test-redis -p 6003:6379 redis:6 20 | else 21 | docker start halsp-test-redis 22 | fi -------------------------------------------------------------------------------- /scripts/sync-cnpm.ts: -------------------------------------------------------------------------------- 1 | import { getPackages } from "./get-packages"; 2 | 3 | const dynamicImport = new Function( 4 | "specifier", 5 | `return import(specifier); 6 | `, 7 | ) as (specifier: string) => Promise; 8 | 9 | async function dynamicImportDefault(specifier: string) { 10 | const module = await dynamicImport(specifier); 11 | return module.default as T; 12 | } 13 | 14 | type Fetch = typeof import("node-fetch").default; 15 | 16 | async function sync(fetch: Fetch, name: string) { 17 | const url = `https://registry-direct.npmmirror.com/-/package/@halsp/${name}/syncs`; 18 | const res = await fetch(url, { 19 | method: "PUT", 20 | }); 21 | const json: any = await res.json(); 22 | console.log(`sync ${name} ${json.ok ? "success" : "failed"}`); 23 | } 24 | 25 | (async () => { 26 | const fetch: Fetch = await dynamicImportDefault("node-fetch"); 27 | const packages: string[] = getPackages(); 28 | for (const pkg of packages) { 29 | await sync(fetch, pkg); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "noImplicitAny": false, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true 12 | } 13 | } --------------------------------------------------------------------------------