@signal24/dk-server-foundation 26.213.615
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.gitlab-ci.yml +49 -0
- package/.oxlintrc.json +40 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +15 -0
- package/.serena/project.yml +111 -0
- package/.vscode/launch.json +15 -0
- package/.vscode/settings.json +12 -0
- package/.yarnrc.yml +5 -0
- package/CLAUDE.md +279 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/TEST_MIGRATION_GUIDE.md +348 -0
- package/dist/resources/proto/generated/test/test.d.ts +224 -0
- package/dist/resources/proto/generated/test/test.d.ts.map +1 -0
- package/dist/resources/proto/generated/test/test.js +2376 -0
- package/dist/resources/proto/generated/test/test.js.map +1 -0
- package/dist/src/app/base.d.ts +37 -0
- package/dist/src/app/base.d.ts.map +1 -0
- package/dist/src/app/base.js +244 -0
- package/dist/src/app/base.js.map +1 -0
- package/dist/src/app/config.d.ts +91 -0
- package/dist/src/app/config.d.ts.map +1 -0
- package/dist/src/app/config.js +33 -0
- package/dist/src/app/config.js.map +1 -0
- package/dist/src/app/config.loader.d.ts +14 -0
- package/dist/src/app/config.loader.d.ts.map +1 -0
- package/dist/src/app/config.loader.js +67 -0
- package/dist/src/app/config.loader.js.map +1 -0
- package/dist/src/app/const.d.ts +3 -0
- package/dist/src/app/const.d.ts.map +1 -0
- package/dist/src/app/const.js +6 -0
- package/dist/src/app/const.js.map +1 -0
- package/dist/src/app/dev.d.ts +6 -0
- package/dist/src/app/dev.d.ts.map +1 -0
- package/dist/src/app/dev.js +78 -0
- package/dist/src/app/dev.js.map +1 -0
- package/dist/src/app/index.d.ts +7 -0
- package/dist/src/app/index.d.ts.map +1 -0
- package/dist/src/app/index.js +12 -0
- package/dist/src/app/index.js.map +1 -0
- package/dist/src/app/openapi.d.ts +4 -0
- package/dist/src/app/openapi.d.ts.map +1 -0
- package/dist/src/app/openapi.js +6 -0
- package/dist/src/app/openapi.js.map +1 -0
- package/dist/src/app/resolver.d.ts +11 -0
- package/dist/src/app/resolver.d.ts.map +1 -0
- package/dist/src/app/resolver.js +60 -0
- package/dist/src/app/resolver.js.map +1 -0
- package/dist/src/app/shutdown.d.ts +12 -0
- package/dist/src/app/shutdown.d.ts.map +1 -0
- package/dist/src/app/shutdown.js +63 -0
- package/dist/src/app/shutdown.js.map +1 -0
- package/dist/src/app/state.d.ts +16 -0
- package/dist/src/app/state.d.ts.map +1 -0
- package/dist/src/app/state.js +12 -0
- package/dist/src/app/state.js.map +1 -0
- package/dist/src/auth/index.d.ts +3 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +6 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/jwt.d.ts +76 -0
- package/dist/src/auth/jwt.d.ts.map +1 -0
- package/dist/src/auth/jwt.js +218 -0
- package/dist/src/auth/jwt.js.map +1 -0
- package/dist/src/auth/provider.d.ts +15 -0
- package/dist/src/auth/provider.d.ts.map +1 -0
- package/dist/src/auth/provider.js +50 -0
- package/dist/src/auth/provider.js.map +1 -0
- package/dist/src/cli/dksf-dev.d.ts +3 -0
- package/dist/src/cli/dksf-dev.d.ts.map +1 -0
- package/dist/src/cli/dksf-dev.js +359 -0
- package/dist/src/cli/dksf-dev.js.map +1 -0
- package/dist/src/cli/dksf-gen-proto.d.ts +3 -0
- package/dist/src/cli/dksf-gen-proto.d.ts.map +1 -0
- package/dist/src/cli/dksf-gen-proto.js +164 -0
- package/dist/src/cli/dksf-gen-proto.js.map +1 -0
- package/dist/src/cli/dksf-install.d.ts +3 -0
- package/dist/src/cli/dksf-install.d.ts.map +1 -0
- package/dist/src/cli/dksf-install.js +10 -0
- package/dist/src/cli/dksf-install.js.map +1 -0
- package/dist/src/cli/dksf-test.d.ts +3 -0
- package/dist/src/cli/dksf-test.d.ts.map +1 -0
- package/dist/src/cli/dksf-test.js +91 -0
- package/dist/src/cli/dksf-test.js.map +1 -0
- package/dist/src/cli/dksf-update.d.ts +3 -0
- package/dist/src/cli/dksf-update.d.ts.map +1 -0
- package/dist/src/cli/dksf-update.js +86 -0
- package/dist/src/cli/dksf-update.js.map +1 -0
- package/dist/src/database/common.d.ts +84 -0
- package/dist/src/database/common.d.ts.map +1 -0
- package/dist/src/database/common.js +380 -0
- package/dist/src/database/common.js.map +1 -0
- package/dist/src/database/dialect.d.ts +10 -0
- package/dist/src/database/dialect.d.ts.map +1 -0
- package/dist/src/database/dialect.js +56 -0
- package/dist/src/database/dialect.js.map +1 -0
- package/dist/src/database/entity.d.ts +62 -0
- package/dist/src/database/entity.d.ts.map +1 -0
- package/dist/src/database/entity.js +198 -0
- package/dist/src/database/entity.js.map +1 -0
- package/dist/src/database/index.d.ts +8 -0
- package/dist/src/database/index.d.ts.map +1 -0
- package/dist/src/database/index.js +15 -0
- package/dist/src/database/index.js.map +1 -0
- package/dist/src/database/migration/MigrationResetCommand.d.ts +11 -0
- package/dist/src/database/migration/MigrationResetCommand.d.ts.map +1 -0
- package/dist/src/database/migration/MigrationResetCommand.js +149 -0
- package/dist/src/database/migration/MigrationResetCommand.js.map +1 -0
- package/dist/src/database/migration/MigrationRunCommand.d.ts +11 -0
- package/dist/src/database/migration/MigrationRunCommand.d.ts.map +1 -0
- package/dist/src/database/migration/MigrationRunCommand.js +118 -0
- package/dist/src/database/migration/MigrationRunCommand.js.map +1 -0
- package/dist/src/database/migration/characters.d.ts +14 -0
- package/dist/src/database/migration/characters.d.ts.map +1 -0
- package/dist/src/database/migration/characters.js +56 -0
- package/dist/src/database/migration/characters.js.map +1 -0
- package/dist/src/database/migration/create/MigrationCreateCommand.d.ts +11 -0
- package/dist/src/database/migration/create/MigrationCreateCommand.d.ts.map +1 -0
- package/dist/src/database/migration/create/MigrationCreateCommand.js +104 -0
- package/dist/src/database/migration/create/MigrationCreateCommand.js.map +1 -0
- package/dist/src/database/migration/create/comparator.d.ts +3 -0
- package/dist/src/database/migration/create/comparator.d.ts.map +1 -0
- package/dist/src/database/migration/create/comparator.js +408 -0
- package/dist/src/database/migration/create/comparator.js.map +1 -0
- package/dist/src/database/migration/create/db-reader.d.ts +5 -0
- package/dist/src/database/migration/create/db-reader.d.ts.map +1 -0
- package/dist/src/database/migration/create/db-reader.js +473 -0
- package/dist/src/database/migration/create/db-reader.js.map +1 -0
- package/dist/src/database/migration/create/ddl-generator.d.ts +3 -0
- package/dist/src/database/migration/create/ddl-generator.d.ts.map +1 -0
- package/dist/src/database/migration/create/ddl-generator.js +725 -0
- package/dist/src/database/migration/create/ddl-generator.js.map +1 -0
- package/dist/src/database/migration/create/entity-reader.d.ts +4 -0
- package/dist/src/database/migration/create/entity-reader.d.ts.map +1 -0
- package/dist/src/database/migration/create/entity-reader.js +408 -0
- package/dist/src/database/migration/create/entity-reader.js.map +1 -0
- package/dist/src/database/migration/create/file-generator.d.ts +2 -0
- package/dist/src/database/migration/create/file-generator.d.ts.map +1 -0
- package/dist/src/database/migration/create/file-generator.js +55 -0
- package/dist/src/database/migration/create/file-generator.js.map +1 -0
- package/dist/src/database/migration/create/prompt.d.ts +4 -0
- package/dist/src/database/migration/create/prompt.d.ts.map +1 -0
- package/dist/src/database/migration/create/prompt.js +55 -0
- package/dist/src/database/migration/create/prompt.js.map +1 -0
- package/dist/src/database/migration/create/schema-model.d.ts +109 -0
- package/dist/src/database/migration/create/schema-model.d.ts.map +1 -0
- package/dist/src/database/migration/create/schema-model.js +24 -0
- package/dist/src/database/migration/create/schema-model.js.map +1 -0
- package/dist/src/database/migration/helpers.d.ts +2 -0
- package/dist/src/database/migration/helpers.d.ts.map +1 -0
- package/dist/src/database/migration/helpers.js +8 -0
- package/dist/src/database/migration/helpers.js.map +1 -0
- package/dist/src/database/migration/index.d.ts +9 -0
- package/dist/src/database/migration/index.d.ts.map +1 -0
- package/dist/src/database/migration/index.js +43 -0
- package/dist/src/database/migration/index.js.map +1 -0
- package/dist/src/database/migration/migration.entity.d.ts +8 -0
- package/dist/src/database/migration/migration.entity.d.ts.map +1 -0
- package/dist/src/database/migration/migration.entity.js +16 -0
- package/dist/src/database/migration/migration.entity.js.map +1 -0
- package/dist/src/database/mysql.d.ts +16 -0
- package/dist/src/database/mysql.d.ts.map +1 -0
- package/dist/src/database/mysql.js +140 -0
- package/dist/src/database/mysql.js.map +1 -0
- package/dist/src/database/postgres.d.ts +16 -0
- package/dist/src/database/postgres.d.ts.map +1 -0
- package/dist/src/database/postgres.js +91 -0
- package/dist/src/database/postgres.js.map +1 -0
- package/dist/src/database/types.d.ts +21 -0
- package/dist/src/database/types.d.ts.map +1 -0
- package/dist/src/database/types.js +27 -0
- package/dist/src/database/types.js.map +1 -0
- package/dist/src/health/health.module.d.ts +6 -0
- package/dist/src/health/health.module.d.ts.map +1 -0
- package/dist/src/health/health.module.js +32 -0
- package/dist/src/health/health.module.js.map +1 -0
- package/dist/src/health/healthcheck.controller.d.ts +10 -0
- package/dist/src/health/healthcheck.controller.d.ts.map +1 -0
- package/dist/src/health/healthcheck.controller.js +30 -0
- package/dist/src/health/healthcheck.controller.js.map +1 -0
- package/dist/src/health/healthcheck.service.d.ts +8 -0
- package/dist/src/health/healthcheck.service.d.ts.map +1 -0
- package/dist/src/health/healthcheck.service.js +20 -0
- package/dist/src/health/healthcheck.service.js.map +1 -0
- package/dist/src/health/index.d.ts +3 -0
- package/dist/src/health/index.d.ts.map +1 -0
- package/dist/src/health/index.js +6 -0
- package/dist/src/health/index.js.map +1 -0
- package/dist/src/helpers/async/context.d.ts +11 -0
- package/dist/src/helpers/async/context.d.ts.map +1 -0
- package/dist/src/helpers/async/context.js +75 -0
- package/dist/src/helpers/async/context.js.map +1 -0
- package/dist/src/helpers/async/process.d.ts +16 -0
- package/dist/src/helpers/async/process.d.ts.map +1 -0
- package/dist/src/helpers/async/process.js +44 -0
- package/dist/src/helpers/async/process.js.map +1 -0
- package/dist/src/helpers/async/promise.d.ts +5 -0
- package/dist/src/helpers/async/promise.d.ts.map +1 -0
- package/dist/src/helpers/async/promise.js +27 -0
- package/dist/src/helpers/async/promise.js.map +1 -0
- package/dist/src/helpers/data/array.d.ts +3 -0
- package/dist/src/helpers/data/array.d.ts.map +1 -0
- package/dist/src/helpers/data/array.js +17 -0
- package/dist/src/helpers/data/array.js.map +1 -0
- package/dist/src/helpers/data/objects.d.ts +12 -0
- package/dist/src/helpers/data/objects.d.ts.map +1 -0
- package/dist/src/helpers/data/objects.js +75 -0
- package/dist/src/helpers/data/objects.js.map +1 -0
- package/dist/src/helpers/data/serialization.d.ts +4 -0
- package/dist/src/helpers/data/serialization.d.ts.map +1 -0
- package/dist/src/helpers/data/serialization.js +15 -0
- package/dist/src/helpers/data/serialization.js.map +1 -0
- package/dist/src/helpers/data/transformer.d.ts +13 -0
- package/dist/src/helpers/data/transformer.d.ts.map +1 -0
- package/dist/src/helpers/data/transformer.js +55 -0
- package/dist/src/helpers/data/transformer.js.map +1 -0
- package/dist/src/helpers/framework/decorators.d.ts +5 -0
- package/dist/src/helpers/framework/decorators.d.ts.map +1 -0
- package/dist/src/helpers/framework/decorators.js +39 -0
- package/dist/src/helpers/framework/decorators.js.map +1 -0
- package/dist/src/helpers/framework/event.d.ts +3 -0
- package/dist/src/helpers/framework/event.d.ts.map +1 -0
- package/dist/src/helpers/framework/event.js +20 -0
- package/dist/src/helpers/framework/event.js.map +1 -0
- package/dist/src/helpers/framework/injection.d.ts +7 -0
- package/dist/src/helpers/framework/injection.d.ts.map +1 -0
- package/dist/src/helpers/framework/injection.js +52 -0
- package/dist/src/helpers/framework/injection.js.map +1 -0
- package/dist/src/helpers/index.d.ts +22 -0
- package/dist/src/helpers/index.d.ts.map +1 -0
- package/dist/src/helpers/index.js +32 -0
- package/dist/src/helpers/index.js.map +1 -0
- package/dist/src/helpers/io/package.d.ts +5 -0
- package/dist/src/helpers/io/package.d.ts.map +1 -0
- package/dist/src/helpers/io/package.js +31 -0
- package/dist/src/helpers/io/package.js.map +1 -0
- package/dist/src/helpers/io/stream.d.ts +18 -0
- package/dist/src/helpers/io/stream.d.ts.map +1 -0
- package/dist/src/helpers/io/stream.js +91 -0
- package/dist/src/helpers/io/stream.js.map +1 -0
- package/dist/src/helpers/redis/broadcast.d.ts +12 -0
- package/dist/src/helpers/redis/broadcast.d.ts.map +1 -0
- package/dist/src/helpers/redis/broadcast.js +99 -0
- package/dist/src/helpers/redis/broadcast.js.map +1 -0
- package/dist/src/helpers/redis/cache.d.ts +7 -0
- package/dist/src/helpers/redis/cache.d.ts.map +1 -0
- package/dist/src/helpers/redis/cache.js +28 -0
- package/dist/src/helpers/redis/cache.js.map +1 -0
- package/dist/src/helpers/redis/mutex.d.ts +24 -0
- package/dist/src/helpers/redis/mutex.d.ts.map +1 -0
- package/dist/src/helpers/redis/mutex.js +240 -0
- package/dist/src/helpers/redis/mutex.js.map +1 -0
- package/dist/src/helpers/redis/redis.d.ts +11 -0
- package/dist/src/helpers/redis/redis.d.ts.map +1 -0
- package/dist/src/helpers/redis/redis.js +59 -0
- package/dist/src/helpers/redis/redis.js.map +1 -0
- package/dist/src/helpers/security/crypto.d.ts +26 -0
- package/dist/src/helpers/security/crypto.d.ts.map +1 -0
- package/dist/src/helpers/security/crypto.js +121 -0
- package/dist/src/helpers/security/crypto.js.map +1 -0
- package/dist/src/helpers/security/validation.d.ts +4 -0
- package/dist/src/helpers/security/validation.d.ts.map +1 -0
- package/dist/src/helpers/security/validation.js +25 -0
- package/dist/src/helpers/security/validation.js.map +1 -0
- package/dist/src/helpers/utils/date.d.ts +4 -0
- package/dist/src/helpers/utils/date.d.ts.map +1 -0
- package/dist/src/helpers/utils/date.js +23 -0
- package/dist/src/helpers/utils/date.js.map +1 -0
- package/dist/src/helpers/utils/error.d.ts +24 -0
- package/dist/src/helpers/utils/error.d.ts.map +1 -0
- package/dist/src/helpers/utils/error.js +168 -0
- package/dist/src/helpers/utils/error.js.map +1 -0
- package/dist/src/helpers/utils/jsx.d.ts +3 -0
- package/dist/src/helpers/utils/jsx.d.ts.map +1 -0
- package/dist/src/helpers/utils/jsx.js +13 -0
- package/dist/src/helpers/utils/jsx.js.map +1 -0
- package/dist/src/helpers/utils/uuid.d.ts +3 -0
- package/dist/src/helpers/utils/uuid.d.ts.map +1 -0
- package/dist/src/helpers/utils/uuid.js +14 -0
- package/dist/src/helpers/utils/uuid.js.map +1 -0
- package/dist/src/http/auth.d.ts +46 -0
- package/dist/src/http/auth.d.ts.map +1 -0
- package/dist/src/http/auth.js +162 -0
- package/dist/src/http/auth.js.map +1 -0
- package/dist/src/http/context.d.ts +5 -0
- package/dist/src/http/context.d.ts.map +1 -0
- package/dist/src/http/context.js +22 -0
- package/dist/src/http/context.js.map +1 -0
- package/dist/src/http/cors.d.ts +36 -0
- package/dist/src/http/cors.d.ts.map +1 -0
- package/dist/src/http/cors.js +171 -0
- package/dist/src/http/cors.js.map +1 -0
- package/dist/src/http/errors.d.ts +3 -0
- package/dist/src/http/errors.d.ts.map +1 -0
- package/dist/src/http/errors.js +10 -0
- package/dist/src/http/errors.js.map +1 -0
- package/dist/src/http/index.d.ts +24 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +25 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/kernel.d.ts +17 -0
- package/dist/src/http/kernel.d.ts.map +1 -0
- package/dist/src/http/kernel.js +133 -0
- package/dist/src/http/kernel.js.map +1 -0
- package/dist/src/http/middleware.d.ts +12 -0
- package/dist/src/http/middleware.d.ts.map +1 -0
- package/dist/src/http/middleware.js +61 -0
- package/dist/src/http/middleware.js.map +1 -0
- package/dist/src/http/overrides.d.ts +2 -0
- package/dist/src/http/overrides.d.ts.map +1 -0
- package/dist/src/http/overrides.js +19 -0
- package/dist/src/http/overrides.js.map +1 -0
- package/dist/src/http/store.d.ts +33 -0
- package/dist/src/http/store.d.ts.map +1 -0
- package/dist/src/http/store.js +102 -0
- package/dist/src/http/store.js.map +1 -0
- package/dist/src/http/uploads.d.ts +7 -0
- package/dist/src/http/uploads.d.ts.map +1 -0
- package/dist/src/http/uploads.js +8 -0
- package/dist/src/http/uploads.js.map +1 -0
- package/dist/src/http/workflow.d.ts +18 -0
- package/dist/src/http/workflow.d.ts.map +1 -0
- package/dist/src/http/workflow.js +181 -0
- package/dist/src/http/workflow.js.map +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +25 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/services/cli/invoke.d.ts +5 -0
- package/dist/src/services/cli/invoke.d.ts.map +1 -0
- package/dist/src/services/cli/invoke.js +45 -0
- package/dist/src/services/cli/invoke.js.map +1 -0
- package/dist/src/services/cli/repl.d.ts +5 -0
- package/dist/src/services/cli/repl.d.ts.map +1 -0
- package/dist/src/services/cli/repl.js +71 -0
- package/dist/src/services/cli/repl.js.map +1 -0
- package/dist/src/services/cli.d.ts +12 -0
- package/dist/src/services/cli.d.ts.map +1 -0
- package/dist/src/services/cli.js +76 -0
- package/dist/src/services/cli.js.map +1 -0
- package/dist/src/services/index.d.ts +7 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +10 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/leader.d.ts +32 -0
- package/dist/src/services/leader.d.ts.map +1 -0
- package/dist/src/services/leader.js +174 -0
- package/dist/src/services/leader.js.map +1 -0
- package/dist/src/services/logger.d.ts +35 -0
- package/dist/src/services/logger.d.ts.map +1 -0
- package/dist/src/services/logger.js +245 -0
- package/dist/src/services/logger.js.map +1 -0
- package/dist/src/services/mail/index.d.ts +61 -0
- package/dist/src/services/mail/index.d.ts.map +1 -0
- package/dist/src/services/mail/index.js +90 -0
- package/dist/src/services/mail/index.js.map +1 -0
- package/dist/src/services/mail/postmark.d.ts +11 -0
- package/dist/src/services/mail/postmark.d.ts.map +1 -0
- package/dist/src/services/mail/postmark.js +42 -0
- package/dist/src/services/mail/postmark.js.map +1 -0
- package/dist/src/services/mail/smtp.d.ts +11 -0
- package/dist/src/services/mail/smtp.d.ts.map +1 -0
- package/dist/src/services/mail/smtp.js +61 -0
- package/dist/src/services/mail/smtp.js.map +1 -0
- package/dist/src/services/mesh.d.ts +65 -0
- package/dist/src/services/mesh.d.ts.map +1 -0
- package/dist/src/services/mesh.js +422 -0
- package/dist/src/services/mesh.js.map +1 -0
- package/dist/src/services/worker/bootstrap.d.ts +3 -0
- package/dist/src/services/worker/bootstrap.d.ts.map +1 -0
- package/dist/src/services/worker/bootstrap.js +70 -0
- package/dist/src/services/worker/bootstrap.js.map +1 -0
- package/dist/src/services/worker/cli.d.ts +23 -0
- package/dist/src/services/worker/cli.d.ts.map +1 -0
- package/dist/src/services/worker/cli.js +81 -0
- package/dist/src/services/worker/cli.js.map +1 -0
- package/dist/src/services/worker/entity.d.ts +18 -0
- package/dist/src/services/worker/entity.d.ts.map +1 -0
- package/dist/src/services/worker/entity.js +16 -0
- package/dist/src/services/worker/entity.js.map +1 -0
- package/dist/src/services/worker/index.d.ts +9 -0
- package/dist/src/services/worker/index.d.ts.map +1 -0
- package/dist/src/services/worker/index.js +40 -0
- package/dist/src/services/worker/index.js.map +1 -0
- package/dist/src/services/worker/observer.d.ts +18 -0
- package/dist/src/services/worker/observer.d.ts.map +1 -0
- package/dist/src/services/worker/observer.js +172 -0
- package/dist/src/services/worker/observer.js.map +1 -0
- package/dist/src/services/worker/queue.d.ts +8 -0
- package/dist/src/services/worker/queue.d.ts.map +1 -0
- package/dist/src/services/worker/queue.js +31 -0
- package/dist/src/services/worker/queue.js.map +1 -0
- package/dist/src/services/worker/runner.d.ts +17 -0
- package/dist/src/services/worker/runner.d.ts.map +1 -0
- package/dist/src/services/worker/runner.js +131 -0
- package/dist/src/services/worker/runner.js.map +1 -0
- package/dist/src/services/worker/types.d.ts +26 -0
- package/dist/src/services/worker/types.d.ts.map +1 -0
- package/dist/src/services/worker/types.js +29 -0
- package/dist/src/services/worker/types.js.map +1 -0
- package/dist/src/srpc/SrpcByteStream.d.ts +67 -0
- package/dist/src/srpc/SrpcByteStream.d.ts.map +1 -0
- package/dist/src/srpc/SrpcByteStream.js +319 -0
- package/dist/src/srpc/SrpcByteStream.js.map +1 -0
- package/dist/src/srpc/SrpcClient.d.ts +75 -0
- package/dist/src/srpc/SrpcClient.d.ts.map +1 -0
- package/dist/src/srpc/SrpcClient.js +445 -0
- package/dist/src/srpc/SrpcClient.js.map +1 -0
- package/dist/src/srpc/SrpcServer.d.ts +54 -0
- package/dist/src/srpc/SrpcServer.d.ts.map +1 -0
- package/dist/src/srpc/SrpcServer.js +456 -0
- package/dist/src/srpc/SrpcServer.js.map +1 -0
- package/dist/src/srpc/index.d.ts +7 -0
- package/dist/src/srpc/index.d.ts.map +1 -0
- package/dist/src/srpc/index.js +12 -0
- package/dist/src/srpc/index.js.map +1 -0
- package/dist/src/srpc/types.d.ts +129 -0
- package/dist/src/srpc/types.d.ts.map +1 -0
- package/dist/src/srpc/types.js +65 -0
- package/dist/src/srpc/types.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +2 -0
- package/dist/src/telemetry/index.d.ts.map +1 -0
- package/dist/src/telemetry/index.js +5 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/telemetry/otel/MariaDBInstrumentation.d.ts +22 -0
- package/dist/src/telemetry/otel/MariaDBInstrumentation.d.ts.map +1 -0
- package/dist/src/telemetry/otel/MariaDBInstrumentation.js +248 -0
- package/dist/src/telemetry/otel/MariaDBInstrumentation.js.map +1 -0
- package/dist/src/telemetry/otel/helpers.d.ts +27 -0
- package/dist/src/telemetry/otel/helpers.d.ts.map +1 -0
- package/dist/src/telemetry/otel/helpers.js +126 -0
- package/dist/src/telemetry/otel/helpers.js.map +1 -0
- package/dist/src/telemetry/otel/index.d.ts +14 -0
- package/dist/src/telemetry/otel/index.d.ts.map +1 -0
- package/dist/src/telemetry/otel/index.js +132 -0
- package/dist/src/telemetry/otel/index.js.map +1 -0
- package/dist/src/telemetry/otel/metrics.controller.d.ts +6 -0
- package/dist/src/telemetry/otel/metrics.controller.d.ts.map +1 -0
- package/dist/src/telemetry/otel/metrics.controller.js +63 -0
- package/dist/src/telemetry/otel/metrics.controller.js.map +1 -0
- package/dist/src/telemetry/sentry.d.ts +9 -0
- package/dist/src/telemetry/sentry.d.ts.map +1 -0
- package/dist/src/telemetry/sentry.js +62 -0
- package/dist/src/telemetry/sentry.js.map +1 -0
- package/dist/src/testapp/bootstrap.d.ts +1 -0
- package/dist/src/testapp/bootstrap.d.ts.map +1 -0
- package/dist/src/testapp/bootstrap.js +18 -0
- package/dist/src/testapp/bootstrap.js.map +1 -0
- package/dist/src/testapp/sample.d.ts +6 -0
- package/dist/src/testapp/sample.d.ts.map +1 -0
- package/dist/src/testapp/sample.js +228 -0
- package/dist/src/testapp/sample.js.map +1 -0
- package/dist/src/testapp/srpc-test.d.ts +27 -0
- package/dist/src/testapp/srpc-test.d.ts.map +1 -0
- package/dist/src/testapp/srpc-test.js +570 -0
- package/dist/src/testapp/srpc-test.js.map +1 -0
- package/dist/src/testing/expect.d.ts +25 -0
- package/dist/src/testing/expect.d.ts.map +1 -0
- package/dist/src/testing/expect.js +151 -0
- package/dist/src/testing/expect.js.map +1 -0
- package/dist/src/testing/fixtures.d.ts +19 -0
- package/dist/src/testing/fixtures.d.ts.map +1 -0
- package/dist/src/testing/fixtures.js +69 -0
- package/dist/src/testing/fixtures.js.map +1 -0
- package/dist/src/testing/index.d.ts +260 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +345 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/requests.d.ts +10 -0
- package/dist/src/testing/requests.d.ts.map +1 -0
- package/dist/src/testing/requests.js +56 -0
- package/dist/src/testing/requests.js.map +1 -0
- package/dist/src/testing/sql.d.ts +11 -0
- package/dist/src/testing/sql.d.ts.map +1 -0
- package/dist/src/testing/sql.js +55 -0
- package/dist/src/testing/sql.js.map +1 -0
- package/dist/src/types/index.d.ts +57 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +73 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/phone.d.ts +11 -0
- package/dist/src/types/phone.d.ts.map +1 -0
- package/dist/src/types/phone.js +73 -0
- package/dist/src/types/phone.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/README.md +38 -0
- package/docs/authentication.md +215 -0
- package/docs/cli.md +302 -0
- package/docs/configuration.md +176 -0
- package/docs/database.md +422 -0
- package/docs/getting-started.md +154 -0
- package/docs/health.md +53 -0
- package/docs/helpers.md +436 -0
- package/docs/http.md +253 -0
- package/docs/leader-service.md +98 -0
- package/docs/logging.md +150 -0
- package/docs/mail.md +161 -0
- package/docs/mesh-service.md +204 -0
- package/docs/srpc.md +261 -0
- package/docs/telemetry.md +166 -0
- package/docs/testing.md +222 -0
- package/docs/types.md +215 -0
- package/docs/worker.md +174 -0
- package/lefthook.yml +12 -0
- package/openapi.yaml +109 -0
- package/package.json +133 -0
- package/patches/@deepkit+type+1.0.19.patch +38 -0
- package/resources/proto/generated/test/test.ts +2721 -0
- package/resources/proto/sample.proto +85 -0
- package/resources/proto/test.proto +178 -0
- package/src/app/base.ts +257 -0
- package/src/app/config.loader.ts +66 -0
- package/src/app/config.ts +120 -0
- package/src/app/const.ts +4 -0
- package/src/app/dev.ts +70 -0
- package/src/app/index.ts +6 -0
- package/src/app/openapi.ts +3 -0
- package/src/app/resolver.ts +49 -0
- package/src/app/shutdown.ts +55 -0
- package/src/app/state.ts +19 -0
- package/src/auth/index.ts +2 -0
- package/src/auth/jwt.ts +275 -0
- package/src/auth/provider.ts +57 -0
- package/src/cli/dksf-dev.ts +363 -0
- package/src/cli/dksf-gen-proto.ts +176 -0
- package/src/cli/dksf-install.ts +11 -0
- package/src/cli/dksf-test.ts +95 -0
- package/src/cli/dksf-update.ts +101 -0
- package/src/database/CLAUDE.md +390 -0
- package/src/database/common.ts +385 -0
- package/src/database/dialect.ts +43 -0
- package/src/database/entity.ts +285 -0
- package/src/database/index.ts +7 -0
- package/src/database/migration/MigrationResetCommand.ts +152 -0
- package/src/database/migration/MigrationRunCommand.ts +118 -0
- package/src/database/migration/characters.ts +53 -0
- package/src/database/migration/create/MigrationCreateCommand.ts +94 -0
- package/src/database/migration/create/comparator.ts +467 -0
- package/src/database/migration/create/db-reader.ts +510 -0
- package/src/database/migration/create/ddl-generator.ts +755 -0
- package/src/database/migration/create/entity-reader.ts +462 -0
- package/src/database/migration/create/file-generator.ts +52 -0
- package/src/database/migration/create/prompt.ts +49 -0
- package/src/database/migration/create/schema-model.ts +102 -0
- package/src/database/migration/helpers.ts +3 -0
- package/src/database/migration/index.ts +35 -0
- package/src/database/migration/migration.entity.ts +10 -0
- package/src/database/mysql.ts +140 -0
- package/src/database/postgres.ts +97 -0
- package/src/database/types.ts +18 -0
- package/src/health/health.module.ts +30 -0
- package/src/health/healthcheck.controller.ts +17 -0
- package/src/health/healthcheck.service.ts +15 -0
- package/src/health/index.ts +2 -0
- package/src/helpers/CLAUDE.md +71 -0
- package/src/helpers/async/context.ts +67 -0
- package/src/helpers/async/process.ts +49 -0
- package/src/helpers/async/promise.ts +16 -0
- package/src/helpers/data/array.ts +11 -0
- package/src/helpers/data/objects.ts +64 -0
- package/src/helpers/data/serialization.ts +11 -0
- package/src/helpers/data/transformer.ts +54 -0
- package/src/helpers/framework/decorators.ts +27 -0
- package/src/helpers/framework/event.ts +11 -0
- package/src/helpers/framework/injection.ts +47 -0
- package/src/helpers/index.ts +34 -0
- package/src/helpers/io/package.ts +26 -0
- package/src/helpers/io/stream.ts +79 -0
- package/src/helpers/redis/broadcast.ts +94 -0
- package/src/helpers/redis/cache.ts +28 -0
- package/src/helpers/redis/mutex.ts +260 -0
- package/src/helpers/redis/redis.ts +60 -0
- package/src/helpers/security/crypto.ts +133 -0
- package/src/helpers/security/validation.ts +16 -0
- package/src/helpers/utils/date.ts +13 -0
- package/src/helpers/utils/error.ts +155 -0
- package/src/helpers/utils/jsx.ts +8 -0
- package/src/helpers/utils/uuid.ts +8 -0
- package/src/http/auth.ts +156 -0
- package/src/http/context.ts +15 -0
- package/src/http/cors.ts +159 -0
- package/src/http/errors.ts +9 -0
- package/src/http/index.ts +19 -0
- package/src/http/kernel.ts +138 -0
- package/src/http/middleware.ts +59 -0
- package/src/http/overrides.ts +20 -0
- package/src/http/store.ts +86 -0
- package/src/http/uploads.ts +6 -0
- package/src/http/workflow.ts +167 -0
- package/src/index.ts +19 -0
- package/src/services/cli/invoke.ts +39 -0
- package/src/services/cli/repl.ts +67 -0
- package/src/services/cli.ts +74 -0
- package/src/services/index.ts +6 -0
- package/src/services/leader.ts +201 -0
- package/src/services/logger.ts +258 -0
- package/src/services/mail/index.ts +117 -0
- package/src/services/mail/postmark.ts +37 -0
- package/src/services/mail/smtp.ts +46 -0
- package/src/services/mesh.ts +508 -0
- package/src/services/worker/CLAUDE.md +77 -0
- package/src/services/worker/bootstrap.ts +58 -0
- package/src/services/worker/cli.ts +63 -0
- package/src/services/worker/entity.ts +22 -0
- package/src/services/worker/index.ts +30 -0
- package/src/services/worker/observer.ts +180 -0
- package/src/services/worker/queue.ts +34 -0
- package/src/services/worker/runner.ts +146 -0
- package/src/services/worker/types.ts +32 -0
- package/src/srpc/CLAUDE.md +194 -0
- package/src/srpc/SRPC_MIGRATION_GUIDE.md +348 -0
- package/src/srpc/SrpcByteStream.ts +382 -0
- package/src/srpc/SrpcClient.ts +512 -0
- package/src/srpc/SrpcServer.ts +575 -0
- package/src/srpc/index.ts +15 -0
- package/src/srpc/types.ts +144 -0
- package/src/telemetry/index.ts +1 -0
- package/src/telemetry/otel/MariaDBInstrumentation.ts +297 -0
- package/src/telemetry/otel/helpers.ts +117 -0
- package/src/telemetry/otel/index.ts +150 -0
- package/src/telemetry/otel/metrics.controller.ts +50 -0
- package/src/telemetry/sentry.ts +58 -0
- package/src/testapp/bootstrap.ts +17 -0
- package/src/testapp/sample.ts +220 -0
- package/src/testapp/srpc-test.ts +684 -0
- package/src/testing/expect.ts +148 -0
- package/src/testing/fixtures.ts +62 -0
- package/src/testing/index.ts +355 -0
- package/src/testing/requests.ts +68 -0
- package/src/testing/sql.ts +50 -0
- package/src/types/index.ts +64 -0
- package/src/types/phone.ts +64 -0
- package/tests/app/app.spec.ts +53 -0
- package/tests/app/type.spec.ts +22 -0
- package/tests/auth/jwt.spec.ts +90 -0
- package/tests/database/entity.spec.ts +382 -0
- package/tests/database/locks.spec.ts +142 -0
- package/tests/database/migration-create-integration.spec.ts +234 -0
- package/tests/database/migration-create-unit.spec.ts +3896 -0
- package/tests/helpers/array.spec.ts +80 -0
- package/tests/helpers/cache.spec.ts +202 -0
- package/tests/helpers/crypto.spec.ts +236 -0
- package/tests/helpers/date.spec.ts +94 -0
- package/tests/helpers/error.spec.ts +233 -0
- package/tests/helpers/mutex.spec.ts +354 -0
- package/tests/helpers/objects.spec.ts +212 -0
- package/tests/helpers/package.spec.ts +90 -0
- package/tests/helpers/promise.spec.ts +119 -0
- package/tests/helpers/redis.spec.ts +50 -0
- package/tests/helpers/serialization.spec.ts +150 -0
- package/tests/helpers/stream.spec.ts +225 -0
- package/tests/helpers/validation.spec.ts +133 -0
- package/tests/services/leader.spec.ts +257 -0
- package/tests/services/logger.spec.ts +269 -0
- package/tests/services/mesh.spec.ts +814 -0
- package/tests/shared/db.ts +105 -0
- package/tests/shared/globalSetup.ts +48 -0
- package/tests/shared/helpers.ts +40 -0
- package/tests/srpc/SrpcByteStream.spec.ts +542 -0
- package/tests/tsconfig.json +4 -0
- package/tests/types/index.spec.ts +60 -0
- package/tests/types/phone.spec.ts +140 -0
- package/tsconfig.json +106 -0
- package/tsconfig.test.json +8 -0
- package/types.d.ts +6 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
import { describe, it, before, after, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { hostname } from 'os';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MeshService,
|
|
7
|
+
MeshRequestTimeoutError,
|
|
8
|
+
MeshHandlerError,
|
|
9
|
+
MeshNoHandlerError,
|
|
10
|
+
sleepMs,
|
|
11
|
+
TestingHelpers,
|
|
12
|
+
createRedis,
|
|
13
|
+
disconnectAllRedis
|
|
14
|
+
} from '../../src';
|
|
15
|
+
|
|
16
|
+
type TestMessages = {
|
|
17
|
+
echo: { request: { text: string }; response: { text: string } };
|
|
18
|
+
add: { request: { a: number; b: number }; response: { result: number } };
|
|
19
|
+
slow: { request: { delayMs: number }; response: { done: boolean } };
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
21
|
+
fail: { request: {}; response: {} };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const FAST_OPTIONS = {
|
|
25
|
+
heartbeatIntervalMs: 200,
|
|
26
|
+
nodeTtlMs: 600,
|
|
27
|
+
requestTimeoutMs: 500,
|
|
28
|
+
leaderOptions: {
|
|
29
|
+
ttlMs: 500,
|
|
30
|
+
renewalIntervalMs: 150,
|
|
31
|
+
retryDelayMs: 50
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('MeshService', () => {
|
|
36
|
+
const tf = TestingHelpers.createTestingFacade({
|
|
37
|
+
defaultConfig: {
|
|
38
|
+
REDIS_HOST: 'localhost',
|
|
39
|
+
REDIS_PORT: 6379
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
before(() => tf.start());
|
|
43
|
+
after(async () => {
|
|
44
|
+
await tf.stop();
|
|
45
|
+
await disconnectAllRedis();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let services: MeshService<TestMessages>[];
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
services = [];
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
await Promise.all(services.map(s => s.stop()));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function createService(key: string, options = FAST_OPTIONS): MeshService<TestMessages> {
|
|
59
|
+
const svc = new MeshService<TestMessages>(key, options);
|
|
60
|
+
services.push(svc);
|
|
61
|
+
return svc;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
it('acquires instance ID and starts/stops cleanly', async () => {
|
|
65
|
+
const svc = createService('MeshTest1');
|
|
66
|
+
await svc.start();
|
|
67
|
+
|
|
68
|
+
assert.ok(svc.instanceId > 0);
|
|
69
|
+
|
|
70
|
+
await svc.stop();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('sends request and returns response between two nodes', async () => {
|
|
74
|
+
const svc1 = createService('MeshTest2');
|
|
75
|
+
const svc2 = createService('MeshTest2');
|
|
76
|
+
|
|
77
|
+
svc1.registerHandler('echo', data => ({ text: `echo: ${data.text}` }));
|
|
78
|
+
svc2.registerHandler('echo', data => ({ text: `echo: ${data.text}` }));
|
|
79
|
+
|
|
80
|
+
await svc1.start();
|
|
81
|
+
await svc2.start();
|
|
82
|
+
|
|
83
|
+
// Allow subscriptions to settle
|
|
84
|
+
await sleepMs(100);
|
|
85
|
+
|
|
86
|
+
const result = await svc2.invoke(svc1.instanceId, 'echo', { text: 'hello' });
|
|
87
|
+
assert.deepStrictEqual(result, { text: 'echo: hello' });
|
|
88
|
+
|
|
89
|
+
const result2 = await svc1.invoke(svc2.instanceId, 'echo', { text: 'world' });
|
|
90
|
+
assert.deepStrictEqual(result2, { text: 'echo: world' });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('calls handler directly for local invocation', async () => {
|
|
94
|
+
const handler = mock.fn(data => ({ text: `local: ${data.text}` }));
|
|
95
|
+
|
|
96
|
+
const svc = createService('MeshTest3');
|
|
97
|
+
svc.registerHandler('echo', handler);
|
|
98
|
+
await svc.start();
|
|
99
|
+
|
|
100
|
+
const result = await svc.invoke(svc.instanceId, 'echo', { text: 'self' });
|
|
101
|
+
assert.deepStrictEqual(result, { text: 'local: self' });
|
|
102
|
+
assert.strictEqual(handler.mock.callCount(), 1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('fires node cleanup callback when heartbeat expires', async () => {
|
|
106
|
+
const cleanedUp = mock.fn();
|
|
107
|
+
|
|
108
|
+
const svc1 = createService('MeshTest4');
|
|
109
|
+
svc1.setNodeCleanedUpCallback(cleanedUp);
|
|
110
|
+
await svc1.start();
|
|
111
|
+
|
|
112
|
+
const svc2 = createService('MeshTest4');
|
|
113
|
+
await svc2.start();
|
|
114
|
+
const svc2Id = svc2.instanceId;
|
|
115
|
+
|
|
116
|
+
// Wait for svc1 to become leader
|
|
117
|
+
await sleepMs(300);
|
|
118
|
+
|
|
119
|
+
// Simulate crash: stop heartbeat and subscriber without removing from sorted set
|
|
120
|
+
(svc2 as any).running = false;
|
|
121
|
+
if ((svc2 as any).heartbeatTimer) {
|
|
122
|
+
clearInterval((svc2 as any).heartbeatTimer);
|
|
123
|
+
(svc2 as any).heartbeatTimer = null;
|
|
124
|
+
}
|
|
125
|
+
if ((svc2 as any).leaderService) {
|
|
126
|
+
await (svc2 as any).leaderService.stop();
|
|
127
|
+
(svc2 as any).leaderService = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Wait for TTL to expire + cleanup cycle
|
|
131
|
+
await sleepMs(1200);
|
|
132
|
+
|
|
133
|
+
assert.deepStrictEqual(cleanedUp.mock.calls[cleanedUp.mock.callCount() - 1].arguments, [svc2Id]);
|
|
134
|
+
|
|
135
|
+
// Clean up svc2's subscriber manually since we bypassed normal stop
|
|
136
|
+
if ((svc2 as any).subscriberClient) {
|
|
137
|
+
try {
|
|
138
|
+
await (svc2 as any).subscriberClient.unsubscribe();
|
|
139
|
+
await (svc2 as any).subscriberClient.quit();
|
|
140
|
+
} catch {
|
|
141
|
+
/* ignore */
|
|
142
|
+
}
|
|
143
|
+
(svc2 as any).subscriberClient = null;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('times out when invoking on unknown instance', async () => {
|
|
148
|
+
const svc = createService('MeshTest5', {
|
|
149
|
+
...FAST_OPTIONS,
|
|
150
|
+
requestTimeoutMs: 200
|
|
151
|
+
});
|
|
152
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
153
|
+
await svc.start();
|
|
154
|
+
|
|
155
|
+
await assert.rejects(svc.invoke(99999, 'echo', { text: 'hello' }), MeshRequestTimeoutError);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('propagates handler errors to caller', async () => {
|
|
159
|
+
const svc1 = createService('MeshTest6');
|
|
160
|
+
const svc2 = createService('MeshTest6');
|
|
161
|
+
|
|
162
|
+
svc1.registerHandler('fail', () => {
|
|
163
|
+
throw new Error('handler exploded');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await svc1.start();
|
|
167
|
+
await svc2.start();
|
|
168
|
+
await sleepMs(100);
|
|
169
|
+
|
|
170
|
+
await assert.rejects(svc2.invoke(svc1.instanceId, 'fail', {}), MeshHandlerError);
|
|
171
|
+
|
|
172
|
+
await assert.rejects(svc2.invoke(svc1.instanceId, 'fail', {}), /handler exploded/);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('keeps long-running handler alive via request heartbeats', async () => {
|
|
176
|
+
const svc1 = createService('MeshTest7', {
|
|
177
|
+
...FAST_OPTIONS,
|
|
178
|
+
requestTimeoutMs: 500
|
|
179
|
+
});
|
|
180
|
+
const svc2 = createService('MeshTest7', {
|
|
181
|
+
...FAST_OPTIONS,
|
|
182
|
+
requestTimeoutMs: 500
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
svc1.registerHandler('slow', async data => {
|
|
186
|
+
await sleepMs(data.delayMs);
|
|
187
|
+
return { done: true };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await svc1.start();
|
|
191
|
+
await svc2.start();
|
|
192
|
+
await sleepMs(100);
|
|
193
|
+
|
|
194
|
+
// Handler takes 1200ms but timeout is 500ms — heartbeats should keep it alive
|
|
195
|
+
const result = await svc2.invoke(svc1.instanceId, 'slow', { delayMs: 1200 });
|
|
196
|
+
assert.deepStrictEqual(result, { done: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('uses independent namespaces for different mesh keys', async () => {
|
|
200
|
+
const svcA = createService('MeshTest8A');
|
|
201
|
+
const svcB = createService('MeshTest8B');
|
|
202
|
+
|
|
203
|
+
svcA.registerHandler('echo', data => ({ text: `A: ${data.text}` }));
|
|
204
|
+
svcB.registerHandler('echo', data => ({ text: `B: ${data.text}` }));
|
|
205
|
+
|
|
206
|
+
await svcA.start();
|
|
207
|
+
await svcB.start();
|
|
208
|
+
|
|
209
|
+
// Each service should have its own ID counter namespace
|
|
210
|
+
// They can't invoke each other since they're on different keys
|
|
211
|
+
const resultA = await svcA.invoke(svcA.instanceId, 'echo', { text: 'test' });
|
|
212
|
+
assert.deepStrictEqual(resultA, { text: 'A: test' });
|
|
213
|
+
|
|
214
|
+
const resultB = await svcB.invoke(svcB.instanceId, 'echo', { text: 'test' });
|
|
215
|
+
assert.deepStrictEqual(resultB, { text: 'B: test' });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('throws MeshNoHandlerError for unregistered type on local invocation', async () => {
|
|
219
|
+
const svc = createService('MeshTest9');
|
|
220
|
+
await svc.start();
|
|
221
|
+
|
|
222
|
+
await assert.rejects(svc.invoke(svc.instanceId, 'echo', { text: 'hello' }), MeshNoHandlerError);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('throws MeshNoHandlerError for unregistered type on remote invocation', async () => {
|
|
226
|
+
const svc1 = createService('MeshTest11');
|
|
227
|
+
const svc2 = createService('MeshTest11');
|
|
228
|
+
|
|
229
|
+
// svc1 has no handler registered for 'echo'
|
|
230
|
+
await svc1.start();
|
|
231
|
+
await svc2.start();
|
|
232
|
+
await sleepMs(100);
|
|
233
|
+
|
|
234
|
+
await assert.rejects(svc2.invoke(svc1.instanceId, 'echo', { text: 'hello' }), MeshNoHandlerError);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('throws if invoked when not running', async () => {
|
|
238
|
+
const svc = createService('MeshTest10');
|
|
239
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
240
|
+
|
|
241
|
+
await assert.rejects(svc.invoke(1, 'echo', { text: 'hello' }), { message: 'MeshService is not running' });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('throws if started twice', async () => {
|
|
245
|
+
const svc = createService('MeshTestStartTwice');
|
|
246
|
+
await svc.start();
|
|
247
|
+
|
|
248
|
+
await assert.rejects(svc.start(), { message: 'MeshService is already running' });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('assigns unique instance IDs to each node', async () => {
|
|
252
|
+
const svc1 = createService('MeshTestUniqueIds');
|
|
253
|
+
const svc2 = createService('MeshTestUniqueIds');
|
|
254
|
+
const svc3 = createService('MeshTestUniqueIds');
|
|
255
|
+
|
|
256
|
+
await svc1.start();
|
|
257
|
+
await svc2.start();
|
|
258
|
+
await svc3.start();
|
|
259
|
+
|
|
260
|
+
const ids = new Set([svc1.instanceId, svc2.instanceId, svc3.instanceId]);
|
|
261
|
+
assert.strictEqual(ids.size, 3);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('supports multiple handler types on the same service', async () => {
|
|
265
|
+
const svc1 = createService('MeshTestMultiHandler');
|
|
266
|
+
const svc2 = createService('MeshTestMultiHandler');
|
|
267
|
+
|
|
268
|
+
svc1.registerHandler('echo', data => ({ text: `echo: ${data.text}` }));
|
|
269
|
+
svc1.registerHandler('add', data => ({ result: data.a + data.b }));
|
|
270
|
+
|
|
271
|
+
await svc1.start();
|
|
272
|
+
await svc2.start();
|
|
273
|
+
await sleepMs(100);
|
|
274
|
+
|
|
275
|
+
const echoResult = await svc2.invoke(svc1.instanceId, 'echo', { text: 'hi' });
|
|
276
|
+
assert.deepStrictEqual(echoResult, { text: 'echo: hi' });
|
|
277
|
+
|
|
278
|
+
const addResult = await svc2.invoke(svc1.instanceId, 'add', { a: 3, b: 7 });
|
|
279
|
+
assert.deepStrictEqual(addResult, { result: 10 });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('handles concurrent requests to the same remote node', async () => {
|
|
283
|
+
const svc1 = createService('MeshTestConcurrent');
|
|
284
|
+
const svc2 = createService('MeshTestConcurrent');
|
|
285
|
+
|
|
286
|
+
svc1.registerHandler('add', data => ({ result: data.a + data.b }));
|
|
287
|
+
|
|
288
|
+
await svc1.start();
|
|
289
|
+
await svc2.start();
|
|
290
|
+
await sleepMs(100);
|
|
291
|
+
|
|
292
|
+
const results = await Promise.all([
|
|
293
|
+
svc2.invoke(svc1.instanceId, 'add', { a: 1, b: 2 }),
|
|
294
|
+
svc2.invoke(svc1.instanceId, 'add', { a: 10, b: 20 }),
|
|
295
|
+
svc2.invoke(svc1.instanceId, 'add', { a: 100, b: 200 })
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
assert.deepStrictEqual(results, [{ result: 3 }, { result: 30 }, { result: 300 }]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('handles bidirectional concurrent requests', async () => {
|
|
302
|
+
const svc1 = createService('MeshTestBidir');
|
|
303
|
+
const svc2 = createService('MeshTestBidir');
|
|
304
|
+
|
|
305
|
+
svc1.registerHandler('echo', data => ({ text: `from1: ${data.text}` }));
|
|
306
|
+
svc2.registerHandler('echo', data => ({ text: `from2: ${data.text}` }));
|
|
307
|
+
|
|
308
|
+
await svc1.start();
|
|
309
|
+
await svc2.start();
|
|
310
|
+
await sleepMs(100);
|
|
311
|
+
|
|
312
|
+
const [r1, r2] = await Promise.all([
|
|
313
|
+
svc1.invoke(svc2.instanceId, 'echo', { text: 'a' }),
|
|
314
|
+
svc2.invoke(svc1.instanceId, 'echo', { text: 'b' })
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
assert.deepStrictEqual(r1, { text: 'from2: a' });
|
|
318
|
+
assert.deepStrictEqual(r2, { text: 'from1: b' });
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('handles local async handler', async () => {
|
|
322
|
+
const svc = createService('MeshTestLocalAsync');
|
|
323
|
+
svc.registerHandler('slow', async () => {
|
|
324
|
+
await sleepMs(50);
|
|
325
|
+
return { done: true };
|
|
326
|
+
});
|
|
327
|
+
await svc.start();
|
|
328
|
+
|
|
329
|
+
const result = await svc.invoke(svc.instanceId, 'slow', { delayMs: 50 });
|
|
330
|
+
assert.deepStrictEqual(result, { done: true });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('propagates async handler rejection to remote caller', async () => {
|
|
334
|
+
const svc1 = createService('MeshTestAsyncReject');
|
|
335
|
+
const svc2 = createService('MeshTestAsyncReject');
|
|
336
|
+
|
|
337
|
+
svc1.registerHandler('fail', async () => {
|
|
338
|
+
await sleepMs(10);
|
|
339
|
+
throw new Error('async failure');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await svc1.start();
|
|
343
|
+
await svc2.start();
|
|
344
|
+
await sleepMs(100);
|
|
345
|
+
|
|
346
|
+
await assert.rejects(svc2.invoke(svc1.instanceId, 'fail', {}), MeshHandlerError);
|
|
347
|
+
await assert.rejects(svc2.invoke(svc1.instanceId, 'fail', {}), /async failure/);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('propagates local handler errors', async () => {
|
|
351
|
+
const svc = createService('MeshTestLocalError');
|
|
352
|
+
svc.registerHandler('fail', () => {
|
|
353
|
+
throw new Error('local boom');
|
|
354
|
+
});
|
|
355
|
+
await svc.start();
|
|
356
|
+
|
|
357
|
+
await assert.rejects(svc.invoke(svc.instanceId, 'fail', {}), { message: 'local boom' });
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('propagates local async handler rejection', async () => {
|
|
361
|
+
const svc = createService('MeshTestLocalAsyncReject');
|
|
362
|
+
svc.registerHandler('fail', async () => {
|
|
363
|
+
await sleepMs(10);
|
|
364
|
+
throw new Error('local async boom');
|
|
365
|
+
});
|
|
366
|
+
await svc.start();
|
|
367
|
+
|
|
368
|
+
await assert.rejects(svc.invoke(svc.instanceId, 'fail', {}), { message: 'local async boom' });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('rejects pending requests when stopped', async () => {
|
|
372
|
+
const svc1 = createService('MeshTestStopReject');
|
|
373
|
+
const svc2 = createService('MeshTestStopReject');
|
|
374
|
+
|
|
375
|
+
// svc1 has a slow handler so the request will be in-flight when we stop svc2
|
|
376
|
+
svc1.registerHandler('slow', async _data => {
|
|
377
|
+
await sleepMs(5000);
|
|
378
|
+
return { done: true };
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
await svc1.start();
|
|
382
|
+
await svc2.start();
|
|
383
|
+
await sleepMs(100);
|
|
384
|
+
|
|
385
|
+
// Capture the promise and attach a no-op catch to prevent unhandled rejection
|
|
386
|
+
const promise = svc2.invoke(svc1.instanceId, 'slow', { delayMs: 5000 });
|
|
387
|
+
promise.catch(() => {}); // prevent unhandled rejection during stop()
|
|
388
|
+
|
|
389
|
+
// Give the request time to be published
|
|
390
|
+
await sleepMs(50);
|
|
391
|
+
|
|
392
|
+
// Stop svc2 while the request is pending
|
|
393
|
+
await svc2.stop();
|
|
394
|
+
|
|
395
|
+
await assert.rejects(promise, { message: 'MeshService stopped' });
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('does not crash when node cleanup callback throws', async () => {
|
|
399
|
+
const svc1 = createService('MeshTestCleanupErr');
|
|
400
|
+
svc1.setNodeCleanedUpCallback(() => {
|
|
401
|
+
throw new Error('cleanup callback error');
|
|
402
|
+
});
|
|
403
|
+
await svc1.start();
|
|
404
|
+
|
|
405
|
+
const svc2 = createService('MeshTestCleanupErr');
|
|
406
|
+
await svc2.start();
|
|
407
|
+
|
|
408
|
+
// Wait for svc1 to become leader
|
|
409
|
+
await sleepMs(300);
|
|
410
|
+
|
|
411
|
+
// Simulate crash of svc2
|
|
412
|
+
(svc2 as any).running = false;
|
|
413
|
+
if ((svc2 as any).heartbeatTimer) {
|
|
414
|
+
clearInterval((svc2 as any).heartbeatTimer);
|
|
415
|
+
(svc2 as any).heartbeatTimer = null;
|
|
416
|
+
}
|
|
417
|
+
if ((svc2 as any).leaderService) {
|
|
418
|
+
await (svc2 as any).leaderService.stop();
|
|
419
|
+
(svc2 as any).leaderService = null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Wait for TTL to expire + cleanup cycle
|
|
423
|
+
await sleepMs(1200);
|
|
424
|
+
|
|
425
|
+
// svc1 should still be running despite the callback error
|
|
426
|
+
assert.strictEqual((svc1 as any).running, true);
|
|
427
|
+
|
|
428
|
+
// Clean up svc2's subscriber manually
|
|
429
|
+
if ((svc2 as any).subscriberClient) {
|
|
430
|
+
try {
|
|
431
|
+
await (svc2 as any).subscriberClient.unsubscribe();
|
|
432
|
+
await (svc2 as any).subscriberClient.quit();
|
|
433
|
+
} catch {
|
|
434
|
+
/* ignore */
|
|
435
|
+
}
|
|
436
|
+
(svc2 as any).subscriberClient = null;
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('handler registered after start works for local invocation', async () => {
|
|
441
|
+
const svc = createService('MeshTestLateHandler');
|
|
442
|
+
await svc.start();
|
|
443
|
+
|
|
444
|
+
// Register handler after start
|
|
445
|
+
svc.registerHandler('echo', data => ({ text: `late: ${data.text}` }));
|
|
446
|
+
|
|
447
|
+
const result = await svc.invoke(svc.instanceId, 'echo', { text: 'hello' });
|
|
448
|
+
assert.deepStrictEqual(result, { text: 'late: hello' });
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('handler registered after start works for remote invocation', async () => {
|
|
452
|
+
const svc1 = createService('MeshTestLateHandlerRemote');
|
|
453
|
+
const svc2 = createService('MeshTestLateHandlerRemote');
|
|
454
|
+
|
|
455
|
+
await svc1.start();
|
|
456
|
+
await svc2.start();
|
|
457
|
+
await sleepMs(100);
|
|
458
|
+
|
|
459
|
+
// Register handler after start
|
|
460
|
+
svc1.registerHandler('echo', data => ({ text: `late: ${data.text}` }));
|
|
461
|
+
|
|
462
|
+
const result = await svc2.invoke(svc1.instanceId, 'echo', { text: 'hello' });
|
|
463
|
+
assert.deepStrictEqual(result, { text: 'late: hello' });
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('supports mixed timeout heartbeats (caller has shorter timeout)', async () => {
|
|
467
|
+
const svc1 = createService('MeshTestMixedTimeout', {
|
|
468
|
+
...FAST_OPTIONS,
|
|
469
|
+
requestTimeoutMs: 2000 // handler side: long timeout
|
|
470
|
+
});
|
|
471
|
+
const svc2 = createService('MeshTestMixedTimeout', {
|
|
472
|
+
...FAST_OPTIONS,
|
|
473
|
+
requestTimeoutMs: 500 // caller side: short timeout
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
svc1.registerHandler('slow', async data => {
|
|
477
|
+
await sleepMs(data.delayMs);
|
|
478
|
+
return { done: true };
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await svc1.start();
|
|
482
|
+
await svc2.start();
|
|
483
|
+
await sleepMs(100);
|
|
484
|
+
|
|
485
|
+
// Handler takes 1200ms, caller timeout is 500ms
|
|
486
|
+
// Heartbeat should use caller's 500ms (sends at 375ms), keeping it alive
|
|
487
|
+
const result = await svc2.invoke(svc1.instanceId, 'slow', { delayMs: 1200 });
|
|
488
|
+
assert.deepStrictEqual(result, { done: true });
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('does not leak pending requests when JSON.stringify fails', async () => {
|
|
492
|
+
const svc = createService('MeshTestStringifyFail');
|
|
493
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
494
|
+
await svc.start();
|
|
495
|
+
|
|
496
|
+
// BigInt cannot be serialized by JSON.stringify
|
|
497
|
+
const circular: Record<string, unknown> = { text: 'hi' };
|
|
498
|
+
circular.self = circular;
|
|
499
|
+
|
|
500
|
+
await assert.rejects(svc.invoke(svc.instanceId + 1, 'echo', circular as any)); // TypeError from JSON.stringify
|
|
501
|
+
|
|
502
|
+
// No pending requests should be leaked
|
|
503
|
+
assert.strictEqual((svc as any).pendingRequests.size, 0);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('cleans up subscriber on partial start failure', async () => {
|
|
507
|
+
// We can't easily mock the HEARTBEAT call, but we can verify the cleanup path
|
|
508
|
+
// by ensuring that after a failed start, the service is in a clean state.
|
|
509
|
+
// We'll create a service and force the subscriber's subscribe to fail.
|
|
510
|
+
const svc = new MeshService<TestMessages>('MeshTestPartialStart', FAST_OPTIONS);
|
|
511
|
+
services.push(svc);
|
|
512
|
+
|
|
513
|
+
// Replace createRedis to make subscribe fail — access the internal subscriber
|
|
514
|
+
// after assigning it but before subscribe completes.
|
|
515
|
+
// Instead, we'll verify the cleanup code path indirectly:
|
|
516
|
+
// Start the service, then verify subscriberClient is set
|
|
517
|
+
await svc.start();
|
|
518
|
+
assert.notStrictEqual((svc as any).subscriberClient, null);
|
|
519
|
+
await svc.stop();
|
|
520
|
+
assert.strictEqual((svc as any).subscriberClient, null);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('drops malformed incoming request messages without crashing', async () => {
|
|
524
|
+
const svc = createService('MeshTestMalformed');
|
|
525
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
526
|
+
await svc.start();
|
|
527
|
+
await sleepMs(100);
|
|
528
|
+
|
|
529
|
+
const { client, prefix } = createRedis('MESH');
|
|
530
|
+
const channel = `${prefix}:mesh:MeshTestMalformed:node:${svc.instanceId}`;
|
|
531
|
+
|
|
532
|
+
// Publish various malformed messages
|
|
533
|
+
await client.publish(channel, 'not json at all');
|
|
534
|
+
await client.publish(channel, JSON.stringify(null));
|
|
535
|
+
await client.publish(channel, JSON.stringify(42));
|
|
536
|
+
await client.publish(channel, JSON.stringify({ requestId: 123, senderInstanceId: 'bad', type: 'echo' })); // wrong types
|
|
537
|
+
await client.publish(channel, JSON.stringify({ type: 'echo', data: {} })); // missing requestId and senderInstanceId
|
|
538
|
+
|
|
539
|
+
await sleepMs(100);
|
|
540
|
+
|
|
541
|
+
// Service should still be running and functional
|
|
542
|
+
assert.strictEqual((svc as any).running, true);
|
|
543
|
+
const result = await svc.invoke(svc.instanceId, 'echo', { text: 'still works' });
|
|
544
|
+
assert.deepStrictEqual(result, { text: 'still works' });
|
|
545
|
+
|
|
546
|
+
await client.quit();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('rejects with error when publish fails in request path', async () => {
|
|
550
|
+
const svc = createService('MeshTestPublishFail');
|
|
551
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
552
|
+
await svc.start();
|
|
553
|
+
await sleepMs(100);
|
|
554
|
+
|
|
555
|
+
// Since we can't easily mock the memoized Redis client, we verify the error path
|
|
556
|
+
// by checking that a no-handler error is caught and the pending request is cleaned up
|
|
557
|
+
const svc2 = createService('MeshTestPublishFail');
|
|
558
|
+
await svc2.start();
|
|
559
|
+
await sleepMs(100);
|
|
560
|
+
|
|
561
|
+
// svc2 has no handler, so this should get MeshNoHandlerError (not a timeout)
|
|
562
|
+
await assert.rejects(svc.invoke(svc2.instanceId, 'echo', { text: 'hello' }), MeshNoHandlerError);
|
|
563
|
+
|
|
564
|
+
// Verify no pending requests are left
|
|
565
|
+
assert.strictEqual((svc as any).pendingRequests.size, 0);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('does not crash when response/heartbeat publish fails on handler side', async () => {
|
|
569
|
+
// This tests resilience on the handler side.
|
|
570
|
+
// We test by having a handler that succeeds, and verifying the service
|
|
571
|
+
// is still operational after handling requests (publish errors are caught internally)
|
|
572
|
+
const svc1 = createService('MeshTestHandlerPublishResilience');
|
|
573
|
+
const svc2 = createService('MeshTestHandlerPublishResilience');
|
|
574
|
+
|
|
575
|
+
svc1.registerHandler('echo', data => ({ text: data.text }));
|
|
576
|
+
|
|
577
|
+
await svc1.start();
|
|
578
|
+
await svc2.start();
|
|
579
|
+
await sleepMs(100);
|
|
580
|
+
|
|
581
|
+
// Normal request should work
|
|
582
|
+
const result = await svc2.invoke(svc1.instanceId, 'echo', { text: 'test1' });
|
|
583
|
+
assert.deepStrictEqual(result, { text: 'test1' });
|
|
584
|
+
|
|
585
|
+
// Multiple sequential requests should all work (handler doesn't crash on publish)
|
|
586
|
+
const result2 = await svc2.invoke(svc1.instanceId, 'echo', { text: 'test2' });
|
|
587
|
+
assert.deepStrictEqual(result2, { text: 'test2' });
|
|
588
|
+
|
|
589
|
+
assert.strictEqual((svc1 as any).running, true);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('ignores messages after stop (running guard)', async () => {
|
|
593
|
+
const svc = createService('MeshTestRunningGuard');
|
|
594
|
+
const handler = mock.fn(data => ({ text: data.text }));
|
|
595
|
+
svc.registerHandler('echo', handler);
|
|
596
|
+
await svc.start();
|
|
597
|
+
await sleepMs(100);
|
|
598
|
+
|
|
599
|
+
const instanceId = svc.instanceId;
|
|
600
|
+
const { client, prefix } = createRedis('MESH');
|
|
601
|
+
const channel = `${prefix}:mesh:MeshTestRunningGuard:node:${instanceId}`;
|
|
602
|
+
|
|
603
|
+
await svc.stop();
|
|
604
|
+
|
|
605
|
+
// Publish a request to the old channel — should be ignored
|
|
606
|
+
await client.publish(
|
|
607
|
+
channel,
|
|
608
|
+
JSON.stringify({
|
|
609
|
+
requestId: 'test-id',
|
|
610
|
+
senderInstanceId: 999,
|
|
611
|
+
type: 'echo',
|
|
612
|
+
data: { text: 'after stop' }
|
|
613
|
+
})
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
await sleepMs(100);
|
|
617
|
+
|
|
618
|
+
// Handler should not have been called
|
|
619
|
+
assert.strictEqual(handler.mock.callCount(), 0);
|
|
620
|
+
|
|
621
|
+
await client.quit();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('ignores unknown response and heartbeat IDs without throwing', async () => {
|
|
625
|
+
const svc = createService('MeshTestUnknownIds');
|
|
626
|
+
svc.registerHandler('echo', data => ({ text: data.text }));
|
|
627
|
+
await svc.start();
|
|
628
|
+
await sleepMs(100);
|
|
629
|
+
|
|
630
|
+
const { client, prefix } = createRedis('MESH');
|
|
631
|
+
const channel = `${prefix}:mesh:MeshTestUnknownIds:node:${svc.instanceId}`;
|
|
632
|
+
|
|
633
|
+
// Publish a response with an unknown requestId
|
|
634
|
+
await client.publish(
|
|
635
|
+
channel,
|
|
636
|
+
JSON.stringify({
|
|
637
|
+
requestId: 'nonexistent-request-id',
|
|
638
|
+
reply: true,
|
|
639
|
+
data: { text: 'orphan response' }
|
|
640
|
+
})
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
// Publish a heartbeat with an unknown requestId
|
|
644
|
+
await client.publish(
|
|
645
|
+
channel,
|
|
646
|
+
JSON.stringify({
|
|
647
|
+
requestId: 'nonexistent-heartbeat-id',
|
|
648
|
+
heartbeat: true
|
|
649
|
+
})
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
await sleepMs(100);
|
|
653
|
+
|
|
654
|
+
// Service should still be running and functional
|
|
655
|
+
assert.strictEqual((svc as any).running, true);
|
|
656
|
+
const result = await svc.invoke(svc.instanceId, 'echo', { text: 'ok' });
|
|
657
|
+
assert.deepStrictEqual(result, { text: 'ok' });
|
|
658
|
+
|
|
659
|
+
await client.quit();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('only leader runs cleanup (non-leader does not fire callback)', async () => {
|
|
663
|
+
const cleanupCb1 = mock.fn();
|
|
664
|
+
const cleanupCb2 = mock.fn();
|
|
665
|
+
|
|
666
|
+
const svc1 = createService('MeshTestLeaderOnly');
|
|
667
|
+
const svc2 = createService('MeshTestLeaderOnly');
|
|
668
|
+
|
|
669
|
+
svc1.setNodeCleanedUpCallback(cleanupCb1);
|
|
670
|
+
svc2.setNodeCleanedUpCallback(cleanupCb2);
|
|
671
|
+
|
|
672
|
+
await svc1.start();
|
|
673
|
+
await svc2.start();
|
|
674
|
+
|
|
675
|
+
// Create a third node that will "crash"
|
|
676
|
+
const svc3 = createService('MeshTestLeaderOnly');
|
|
677
|
+
await svc3.start();
|
|
678
|
+
const svc3Id = svc3.instanceId;
|
|
679
|
+
|
|
680
|
+
// Wait for leader election to settle
|
|
681
|
+
await sleepMs(300);
|
|
682
|
+
|
|
683
|
+
// Simulate crash of svc3
|
|
684
|
+
(svc3 as any).running = false;
|
|
685
|
+
if ((svc3 as any).heartbeatTimer) {
|
|
686
|
+
clearInterval((svc3 as any).heartbeatTimer);
|
|
687
|
+
(svc3 as any).heartbeatTimer = null;
|
|
688
|
+
}
|
|
689
|
+
if ((svc3 as any).leaderService) {
|
|
690
|
+
await (svc3 as any).leaderService.stop();
|
|
691
|
+
(svc3 as any).leaderService = null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Wait for TTL to expire + cleanup cycle
|
|
695
|
+
await sleepMs(1200);
|
|
696
|
+
|
|
697
|
+
// Exactly one of the callbacks should have been called (whichever is leader)
|
|
698
|
+
const totalCalls = cleanupCb1.mock.callCount() + cleanupCb2.mock.callCount();
|
|
699
|
+
assert.ok(totalCalls >= 1);
|
|
700
|
+
|
|
701
|
+
// The cleanup should have been called with svc3's ID
|
|
702
|
+
const allCallArgs = [...cleanupCb1.mock.calls, ...cleanupCb2.mock.calls].map(c => c.arguments[0]);
|
|
703
|
+
assert.ok(allCallArgs.includes(svc3Id));
|
|
704
|
+
|
|
705
|
+
// Clean up svc3's subscriber manually
|
|
706
|
+
if ((svc3 as any).subscriberClient) {
|
|
707
|
+
try {
|
|
708
|
+
await (svc3 as any).subscriberClient.unsubscribe();
|
|
709
|
+
await (svc3 as any).subscriberClient.quit();
|
|
710
|
+
} catch {
|
|
711
|
+
/* ignore */
|
|
712
|
+
}
|
|
713
|
+
(svc3 as any).subscriberClient = null;
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('clears active handler intervals on stop', async () => {
|
|
718
|
+
const svc1 = createService('MeshTestIntervalCleanup');
|
|
719
|
+
const svc2 = createService('MeshTestIntervalCleanup');
|
|
720
|
+
|
|
721
|
+
svc1.registerHandler('slow', async data => {
|
|
722
|
+
await sleepMs(data.delayMs);
|
|
723
|
+
return { done: true };
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await svc1.start();
|
|
727
|
+
await svc2.start();
|
|
728
|
+
await sleepMs(100);
|
|
729
|
+
|
|
730
|
+
// Start a slow request that will keep a heartbeat interval alive
|
|
731
|
+
const promise = svc2.invoke(svc1.instanceId, 'slow', { delayMs: 10000 });
|
|
732
|
+
promise.catch(() => {}); // prevent unhandled rejection
|
|
733
|
+
|
|
734
|
+
// Give time for the handler to start and heartbeat interval to be registered
|
|
735
|
+
await sleepMs(200);
|
|
736
|
+
|
|
737
|
+
// svc1 should have an active handler interval
|
|
738
|
+
assert.ok((svc1 as any).activeHandlerIntervals.size > 0);
|
|
739
|
+
|
|
740
|
+
// Stop svc1 — should clear all active handler intervals
|
|
741
|
+
await svc1.stop();
|
|
742
|
+
assert.strictEqual((svc1 as any).activeHandlerIntervals.size, 0);
|
|
743
|
+
|
|
744
|
+
// Stop svc2 — should reject the pending request
|
|
745
|
+
await svc2.stop();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it('does not throw when stop is called before start or called twice', async () => {
|
|
749
|
+
const svc = createService('MeshTestStopIdempotent');
|
|
750
|
+
|
|
751
|
+
// stop() before start() should not throw
|
|
752
|
+
await svc.stop();
|
|
753
|
+
|
|
754
|
+
// Normal start/stop cycle
|
|
755
|
+
await svc.start();
|
|
756
|
+
await svc.stop();
|
|
757
|
+
|
|
758
|
+
// Double stop should not throw
|
|
759
|
+
await svc.stop();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('getNodes returns all live nodes with hostname and self flag', async () => {
|
|
763
|
+
const svc1 = createService('MeshTestGetNodes');
|
|
764
|
+
const svc2 = createService('MeshTestGetNodes');
|
|
765
|
+
const svc3 = createService('MeshTestGetNodes');
|
|
766
|
+
|
|
767
|
+
await svc1.start();
|
|
768
|
+
await svc2.start();
|
|
769
|
+
await svc3.start();
|
|
770
|
+
|
|
771
|
+
const nodes = await svc1.getNodes();
|
|
772
|
+
|
|
773
|
+
assert.strictEqual(nodes.length, 3);
|
|
774
|
+
|
|
775
|
+
const sorted = nodes.sort((a, b) => a.instanceId - b.instanceId);
|
|
776
|
+
assert.deepStrictEqual(
|
|
777
|
+
sorted.map(n => n.instanceId),
|
|
778
|
+
[svc1.instanceId, svc2.instanceId, svc3.instanceId].sort((a, b) => a - b)
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
// All nodes should have this machine's hostname
|
|
782
|
+
for (const node of nodes) {
|
|
783
|
+
assert.strictEqual(node.hostname, hostname());
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Exactly one node should be marked as self
|
|
787
|
+
const selfNodes = nodes.filter(n => n.self);
|
|
788
|
+
assert.strictEqual(selfNodes.length, 1);
|
|
789
|
+
assert.strictEqual(selfNodes[0].instanceId, svc1.instanceId);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('getNodes excludes stopped nodes', async () => {
|
|
793
|
+
const svc1 = createService('MeshTestGetNodesStopped');
|
|
794
|
+
const svc2 = createService('MeshTestGetNodesStopped');
|
|
795
|
+
|
|
796
|
+
await svc1.start();
|
|
797
|
+
await svc2.start();
|
|
798
|
+
|
|
799
|
+
let nodes = await svc1.getNodes();
|
|
800
|
+
assert.strictEqual(nodes.length, 2);
|
|
801
|
+
|
|
802
|
+
await svc2.stop();
|
|
803
|
+
|
|
804
|
+
nodes = await svc1.getNodes();
|
|
805
|
+
assert.strictEqual(nodes.length, 1);
|
|
806
|
+
assert.strictEqual(nodes[0].instanceId, svc1.instanceId);
|
|
807
|
+
assert.strictEqual(nodes[0].self, true);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('getNodes throws if not running', async () => {
|
|
811
|
+
const svc = createService('MeshTestGetNodesNotRunning');
|
|
812
|
+
await assert.rejects(svc.getNodes(), { message: 'MeshService is not running' });
|
|
813
|
+
});
|
|
814
|
+
});
|