@slopware/sloppy-darwin-x64 0.1.0-alpha.0
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/LICENSE +201 -0
- package/README.md +5 -0
- package/bin/sloppy +0 -0
- package/bin/sloppyc +0 -0
- package/docs/KNOWN_LIMITATIONS.md +16 -0
- package/docs/LICENSES.md +6 -0
- package/docs/NOTICE.md +8 -0
- package/examples/README.md +140 -0
- package/examples/auth-api/README.md +20 -0
- package/examples/auth-api/app.js +61 -0
- package/examples/auth-api/appsettings.json +7 -0
- package/examples/auth-api/sloppy.json +5 -0
- package/examples/cache-basic/README.md +9 -0
- package/examples/cache-basic/app.js +32 -0
- package/examples/cache-hybrid-postgres/README.md +10 -0
- package/examples/cache-hybrid-postgres/app.js +27 -0
- package/examples/cache-output-api/README.md +10 -0
- package/examples/cache-output-api/app.js +35 -0
- package/examples/codec-base64-hex/README.md +14 -0
- package/examples/codec-base64-hex/app.js +15 -0
- package/examples/codec-checksums/README.md +15 -0
- package/examples/codec-checksums/app.js +8 -0
- package/examples/codec-compression/README.md +13 -0
- package/examples/codec-compression/app.js +9 -0
- package/examples/codec-streaming-compression/README.md +19 -0
- package/examples/codec-streaming-compression/app.js +16 -0
- package/examples/codec-text-binary/README.md +16 -0
- package/examples/codec-text-binary/app.js +17 -0
- package/examples/compiler-hello/README.md +71 -0
- package/examples/compiler-hello/app.js +7 -0
- package/examples/compiler-hello/expected/app.js +8 -0
- package/examples/compiler-hello/expected/app.js.map +53 -0
- package/examples/compiler-hello/expected/app.plan.json +229 -0
- package/examples/compiler-hello/expected/routes.slrt +0 -0
- package/examples/config-basic/README.md +13 -0
- package/examples/config-basic/app.js +13 -0
- package/examples/config-basic/appsettings.json +7 -0
- package/examples/config-secrets-redaction/README.md +9 -0
- package/examples/config-secrets-redaction/app.js +9 -0
- package/examples/config-secrets-redaction/appsettings.json +5 -0
- package/examples/config-strict-mode/README.md +7 -0
- package/examples/config-strict-mode/app.js +10 -0
- package/examples/config-strict-mode/appsettings.json +7 -0
- package/examples/configured-api/README.md +38 -0
- package/examples/configured-api/app.js +12 -0
- package/examples/configured-api/appsettings.Development.json +5 -0
- package/examples/configured-api/appsettings.json +6 -0
- package/examples/configured-api/sloppy.json +5 -0
- package/examples/core-config-secrets/README.md +10 -0
- package/examples/core-config-secrets/app.js +15 -0
- package/examples/core-fs-time-codec/README.md +9 -0
- package/examples/core-fs-time-codec/app.js +8 -0
- package/examples/core-network-time-codec/README.md +11 -0
- package/examples/core-network-time-codec/app.js +20 -0
- package/examples/core-policy-audit/README.md +7 -0
- package/examples/core-policy-audit/app.js +22 -0
- package/examples/core-process-time-codec/README.md +8 -0
- package/examples/core-process-time-codec/app.js +28 -0
- package/examples/core-worker-time/README.md +8 -0
- package/examples/core-worker-time/app.js +17 -0
- package/examples/crypto-hash-hmac/README.md +17 -0
- package/examples/crypto-hash-hmac/app.js +29 -0
- package/examples/crypto-password/README.md +21 -0
- package/examples/crypto-password/app.js +12 -0
- package/examples/crypto-random-token/README.md +16 -0
- package/examples/crypto-random-token/app.js +12 -0
- package/examples/crypto-secret-constant-time/README.md +21 -0
- package/examples/crypto-secret-constant-time/app.js +15 -0
- package/examples/data-foundation/README.md +39 -0
- package/examples/data-foundation/app.js +63 -0
- package/examples/dependency-graph/README.md +19 -0
- package/examples/dependency-graph/fixtures/graph-helper/index.js +3 -0
- package/examples/dependency-graph/fixtures/graph-helper/package.json +6 -0
- package/examples/dependency-graph/package.json +7 -0
- package/examples/dependency-graph/public/message.txt +1 -0
- package/examples/dependency-graph/sloppy.json +9 -0
- package/examples/dependency-graph/src/main.ts +8 -0
- package/examples/dogfood/README.md +23 -0
- package/examples/dogfood/dogfood.json +136 -0
- package/examples/dynamic-module-include/README.md +20 -0
- package/examples/dynamic-module-include/public/readme.txt +1 -0
- package/examples/dynamic-module-include/sloppy.json +12 -0
- package/examples/dynamic-module-include/src/main.ts +6 -0
- package/examples/dynamic-module-include/src/plugins/alpha.js +3 -0
- package/examples/dynamic-module-include/src/plugins/beta.js +3 -0
- package/examples/ergonomics/README.md +42 -0
- package/examples/ergonomics/app.js +38 -0
- package/examples/framework-controller/README.md +12 -0
- package/examples/framework-controller/app.js +31 -0
- package/examples/framework-di-services/README.md +17 -0
- package/examples/framework-di-services/app.ts +40 -0
- package/examples/framework-explicit-binding/README.md +12 -0
- package/examples/framework-explicit-binding/app.ts +34 -0
- package/examples/framework-hello/README.md +16 -0
- package/examples/framework-hello/app.ts +16 -0
- package/examples/framework-postgres-crud/README.md +73 -0
- package/examples/framework-postgres-crud/app.ts +64 -0
- package/examples/framework-sqlite-crud/README.md +52 -0
- package/examples/framework-sqlite-crud/app.ts +90 -0
- package/examples/framework-sqlite-crud/appsettings.json +11 -0
- package/examples/framework-sqlserver-crud/README.md +73 -0
- package/examples/framework-sqlserver-crud/app.ts +64 -0
- package/examples/framework-validation-errors/README.md +12 -0
- package/examples/framework-validation-errors/app.ts +16 -0
- package/examples/fs-basic/README.md +24 -0
- package/examples/fs-basic/app.js +12 -0
- package/examples/fs-roots-policy/README.md +14 -0
- package/examples/fs-roots-policy/app.js +4 -0
- package/examples/fs-streams/README.md +18 -0
- package/examples/fs-streams/app.js +11 -0
- package/examples/fs-watch/README.md +19 -0
- package/examples/fs-watch/app.js +11 -0
- package/examples/hello/README.md +63 -0
- package/examples/hello/app.js +19 -0
- package/examples/hello-minimal/README.md +51 -0
- package/examples/hello-minimal/sloppy.json +5 -0
- package/examples/hello-minimal/src/main.ts +9 -0
- package/examples/http-client-basic/README.md +11 -0
- package/examples/http-client-basic/app.js +46 -0
- package/examples/http-client-generated/README.md +22 -0
- package/examples/http-client-generated/openapi.json +45 -0
- package/examples/http-client-resilience/README.md +4 -0
- package/examples/http-client-resilience/app.js +38 -0
- package/examples/http-client-runtime-loopback/README.md +24 -0
- package/examples/http-client-testhost/README.md +4 -0
- package/examples/http-client-testhost/app.js +27 -0
- package/examples/http-client-testhost-package-mock/README.md +26 -0
- package/examples/http-client-typed/README.md +5 -0
- package/examples/http-client-typed/app.js +33 -0
- package/examples/modules-api/README.md +30 -0
- package/examples/modules-api/app.js +9 -0
- package/examples/modules-api/modules/routes.js +16 -0
- package/examples/modules-api/sloppy.json +5 -0
- package/examples/modules-basic/README.md +32 -0
- package/examples/modules-basic/app.js +41 -0
- package/examples/net-deadline-cancel/README.md +13 -0
- package/examples/net-deadline-cancel/app.js +34 -0
- package/examples/net-local-ipc/README.md +12 -0
- package/examples/net-local-ipc/app.js +46 -0
- package/examples/net-policy-strict/README.md +12 -0
- package/examples/net-policy-strict/app.js +34 -0
- package/examples/net-tcp-client/README.md +10 -0
- package/examples/net-tcp-client/app.js +23 -0
- package/examples/net-tcp-echo/README.md +11 -0
- package/examples/net-tcp-echo/app.js +45 -0
- package/examples/net-tcp-server/README.md +10 -0
- package/examples/net-tcp-server/app.js +28 -0
- package/examples/node-compat-path-events/README.md +15 -0
- package/examples/node-compat-path-events/sloppy.json +6 -0
- package/examples/node-compat-path-events/src/main.ts +15 -0
- package/examples/ops-compiler/README.md +9 -0
- package/examples/ops-compiler/app.js +26 -0
- package/examples/ops-health-metrics-management/README.md +14 -0
- package/examples/ops-health-metrics-management/app.js +24 -0
- package/examples/orm-basic/README.md +17 -0
- package/examples/orm-basic/app.js +82 -0
- package/examples/orm-cursor-export/README.md +16 -0
- package/examples/orm-cursor-export/app.js +28 -0
- package/examples/orm-migrations/README.md +14 -0
- package/examples/orm-migrations/migrations/.gitkeep +1 -0
- package/examples/orm-migrations/sloppy.json +9 -0
- package/examples/orm-migrations/src/app.ts +34 -0
- package/examples/orm-relations-includes/README.md +10 -0
- package/examples/orm-relations-includes/app.js +47 -0
- package/examples/orm-testservices/README.md +37 -0
- package/examples/orm-testservices/test.mjs +32 -0
- package/examples/os-runtime-api/README.md +11 -0
- package/examples/os-runtime-api/app.js +44 -0
- package/examples/package-zod-like/README.md +28 -0
- package/examples/package-zod-like/fixtures/zod-like/index.js +48 -0
- package/examples/package-zod-like/fixtures/zod-like/package.json +12 -0
- package/examples/package-zod-like/package.json +7 -0
- package/examples/package-zod-like/sloppy.json +6 -0
- package/examples/package-zod-like/src/main.ts +16 -0
- package/examples/postgres-basic/README.md +31 -0
- package/examples/postgres-basic/app.js +50 -0
- package/examples/prealpha-control-plane/README.md +50 -0
- package/examples/prealpha-control-plane/appsettings.Development.json +11 -0
- package/examples/prealpha-control-plane/appsettings.json +15 -0
- package/examples/prealpha-control-plane/sloppy.json +5 -0
- package/examples/prealpha-control-plane/src/db/schema.js +7 -0
- package/examples/prealpha-control-plane/src/db/seed.js +6 -0
- package/examples/prealpha-control-plane/src/main.js +21 -0
- package/examples/prealpha-control-plane/src/routes/apps.js +34 -0
- package/examples/prealpha-control-plane/src/routes/builds.js +25 -0
- package/examples/prealpha-control-plane/src/routes/deployments.js +19 -0
- package/examples/prealpha-control-plane/src/routes/diagnostics.js +11 -0
- package/examples/prealpha-control-plane/src/routes/health.js +27 -0
- package/examples/prealpha-control-plane/src/routes/projects.js +38 -0
- package/examples/prealpha-control-plane/src/services/diagnosticsSink.js +11 -0
- package/examples/prealpha-control-plane/src/services/repositories.js +9 -0
- package/examples/prealpha-control-plane/src/validation/schemas.js +6 -0
- package/examples/program-fs-process/README.md +31 -0
- package/examples/program-fs-process/sloppy.json +9 -0
- package/examples/program-fs-process/src/main.ts +27 -0
- package/examples/program-hello/README.md +32 -0
- package/examples/program-hello/main.ts +8 -0
- package/examples/program-hello/message.ts +1 -0
- package/examples/program-hello/sloppy.json +5 -0
- package/examples/rate-limit-auth/README.md +3 -0
- package/examples/rate-limit-auth/app.js +14 -0
- package/examples/rate-limit-basic/README.md +3 -0
- package/examples/rate-limit-basic/app.js +13 -0
- package/examples/rate-limit-redis/README.md +5 -0
- package/examples/rate-limit-redis/app.js +20 -0
- package/examples/rate-limit-testhost/README.md +4 -0
- package/examples/rate-limit-testhost/app.js +13 -0
- package/examples/rate-limit-websocket/README.md +3 -0
- package/examples/rate-limit-websocket/app.js +16 -0
- package/examples/realtime-auth/README.md +8 -0
- package/examples/realtime-auth/app.js +25 -0
- package/examples/realtime-auth/test.mjs +43 -0
- package/examples/realtime-chat/README.md +8 -0
- package/examples/realtime-chat/app.js +32 -0
- package/examples/realtime-chat/test.mjs +52 -0
- package/examples/realtime-dashboard/README.md +20 -0
- package/examples/realtime-dashboard/app.js +37 -0
- package/examples/realtime-presence/README.md +8 -0
- package/examples/realtime-presence/app.js +32 -0
- package/examples/realtime-presence/test.mjs +50 -0
- package/examples/realtime-testhost/README.md +8 -0
- package/examples/realtime-testhost/test.mjs +31 -0
- package/examples/redis-basic/README.md +17 -0
- package/examples/redis-basic/app.js +39 -0
- package/examples/redis-cache/README.md +14 -0
- package/examples/redis-cache/app.js +36 -0
- package/examples/redis-locks/README.md +13 -0
- package/examples/redis-locks/app.js +49 -0
- package/examples/request-context/README.md +32 -0
- package/examples/request-context/app.js +15 -0
- package/examples/sqlite-basic/README.md +52 -0
- package/examples/sqlite-basic/app.js +56 -0
- package/examples/sqlserver-basic/README.md +36 -0
- package/examples/sqlserver-basic/app.js +59 -0
- package/examples/static-files-basic/README.md +11 -0
- package/examples/static-files-basic/app.js +12 -0
- package/examples/static-files-basic/public/app.js +1 -0
- package/examples/static-files-basic/public/site.css +3 -0
- package/examples/static-files-package/README.md +12 -0
- package/examples/static-files-package/app.js +10 -0
- package/examples/static-files-package/public/index.html +2 -0
- package/examples/static-files-precompressed/README.md +12 -0
- package/examples/static-files-precompressed/app.js +11 -0
- package/examples/static-files-precompressed/public/app.js +1 -0
- package/examples/static-files-precompressed/public/app.js.br +0 -0
- package/examples/static-files-precompressed/public/app.js.gz +0 -0
- package/examples/static-files-spa/README.md +12 -0
- package/examples/static-files-spa/app.js +16 -0
- package/examples/static-files-spa/dist/assets/app.js +1 -0
- package/examples/static-files-spa/dist/index.html +4 -0
- package/examples/static-files-testhost/README.md +8 -0
- package/examples/static-files-testhost/app.js +13 -0
- package/examples/static-files-testhost/public/app.js +1 -0
- package/examples/static-files-testhost/public/app.js.gz +0 -0
- package/examples/static-files-testhost/test.mjs +38 -0
- package/examples/testhost-basic/README.md +26 -0
- package/examples/testhost-db/README.md +31 -0
- package/examples/testservices-postgres/README.md +68 -0
- package/examples/testservices-redis/README.md +71 -0
- package/examples/testservices-sqlserver/README.md +75 -0
- package/examples/time-basic/README.md +18 -0
- package/examples/time-basic/app.js +12 -0
- package/examples/time-deadline-cancellation/README.md +11 -0
- package/examples/time-deadline-cancellation/app.js +27 -0
- package/examples/time-fake-clock/README.md +14 -0
- package/examples/time-fake-clock/app.js +25 -0
- package/examples/time-interval-schedule/README.md +13 -0
- package/examples/time-interval-schedule/app.js +60 -0
- package/examples/users-api-sqlite/README.md +74 -0
- package/examples/users-api-sqlite/app.js +11 -0
- package/examples/users-api-sqlite/appsettings.Development.json +11 -0
- package/examples/users-api-sqlite/appsettings.json +11 -0
- package/examples/users-api-sqlite/modules/users.js +40 -0
- package/examples/users-api-sqlite/sloppy.json +5 -0
- package/examples/validation-errors/README.md +36 -0
- package/examples/validation-errors/app.js +14 -0
- package/examples/validation-errors/invalid-user.http +6 -0
- package/examples/validation-errors/sloppy.json +5 -0
- package/examples/web-dynamic-routes/README.md +17 -0
- package/examples/web-dynamic-routes/app.ts +27 -0
- package/examples/webhooks-basic/README.md +11 -0
- package/examples/webhooks-basic/app.js +48 -0
- package/examples/websocket-auth/README.md +8 -0
- package/examples/websocket-auth/app.js +16 -0
- package/examples/websocket-echo/README.md +9 -0
- package/examples/websocket-echo/app.js +36 -0
- package/examples/websocket-json-schema/README.md +5 -0
- package/examples/websocket-json-schema/app.js +25 -0
- package/examples/websocket-testhost/README.md +11 -0
- package/examples/websocket-testhost/test.mjs +49 -0
- package/examples/workers-background-service/README.md +7 -0
- package/examples/workers-background-service/app.js +16 -0
- package/examples/workers-js-isolate/README.md +8 -0
- package/examples/workers-js-isolate/app.js +19 -0
- package/examples/workers-js-isolate/workers/parser.ts +11 -0
- package/examples/workers-shutdown/README.md +6 -0
- package/examples/workers-shutdown/app.js +26 -0
- package/examples/workers-workerpool/README.md +6 -0
- package/examples/workers-workerpool/app.js +23 -0
- package/examples/workers-workqueue/README.md +8 -0
- package/examples/workers-workqueue/app.js +24 -0
- package/manifest.json +59 -0
- package/package.json +31 -0
- package/stdlib/sloppy/README.md +177 -0
- package/stdlib/sloppy/app.js +2142 -0
- package/stdlib/sloppy/auth.js +1813 -0
- package/stdlib/sloppy/bootstrap.manifest.json +83 -0
- package/stdlib/sloppy/cache.js +1542 -0
- package/stdlib/sloppy/codec.js +1153 -0
- package/stdlib/sloppy/config.js +61 -0
- package/stdlib/sloppy/crypto.js +312 -0
- package/stdlib/sloppy/data.js +2945 -0
- package/stdlib/sloppy/ffi.js +185 -0
- package/stdlib/sloppy/fs.js +795 -0
- package/stdlib/sloppy/health.js +603 -0
- package/stdlib/sloppy/http.js +1595 -0
- package/stdlib/sloppy/index.js +59 -0
- package/stdlib/sloppy/internal/bytes.js +31 -0
- package/stdlib/sloppy/internal/capabilities.js +155 -0
- package/stdlib/sloppy/internal/config.js +640 -0
- package/stdlib/sloppy/internal/disposable.js +31 -0
- package/stdlib/sloppy/internal/headers.js +63 -0
- package/stdlib/sloppy/internal/intrinsics.js +2 -0
- package/stdlib/sloppy/internal/json.js +20 -0
- package/stdlib/sloppy/internal/logging.js +278 -0
- package/stdlib/sloppy/internal/modules.js +405 -0
- package/stdlib/sloppy/internal/redaction.js +87 -0
- package/stdlib/sloppy/internal/routes.js +2279 -0
- package/stdlib/sloppy/internal/runtime-classic.js +19837 -0
- package/stdlib/sloppy/internal/services.js +690 -0
- package/stdlib/sloppy/internal/shared.js +32 -0
- package/stdlib/sloppy/internal/testhost-diagnostics.js +88 -0
- package/stdlib/sloppy/internal/testhost-http-server.js +238 -0
- package/stdlib/sloppy/internal/testhost-http.js +118 -0
- package/stdlib/sloppy/internal/testhost-loopback.js +50 -0
- package/stdlib/sloppy/internal/testservices-docker.js +154 -0
- package/stdlib/sloppy/internal/validation.js +117 -0
- package/stdlib/sloppy/metrics.js +427 -0
- package/stdlib/sloppy/net.js +5208 -0
- package/stdlib/sloppy/node/assert/strict.js +39 -0
- package/stdlib/sloppy/node/assert.js +228 -0
- package/stdlib/sloppy/node/buffer.js +247 -0
- package/stdlib/sloppy/node/console.js +33 -0
- package/stdlib/sloppy/node/constants.js +9 -0
- package/stdlib/sloppy/node/crypto.js +89 -0
- package/stdlib/sloppy/node/diagnostics_channel.js +41 -0
- package/stdlib/sloppy/node/events.js +113 -0
- package/stdlib/sloppy/node/fs/promises.js +27 -0
- package/stdlib/sloppy/node/fs.js +280 -0
- package/stdlib/sloppy/node/http.js +11 -0
- package/stdlib/sloppy/node/https.js +11 -0
- package/stdlib/sloppy/node/module.js +40 -0
- package/stdlib/sloppy/node/os.js +22 -0
- package/stdlib/sloppy/node/path.js +78 -0
- package/stdlib/sloppy/node/perf_hooks.js +12 -0
- package/stdlib/sloppy/node/process.js +129 -0
- package/stdlib/sloppy/node/querystring.js +21 -0
- package/stdlib/sloppy/node/stream/promises.js +3 -0
- package/stdlib/sloppy/node/stream.js +132 -0
- package/stdlib/sloppy/node/string_decoder.js +23 -0
- package/stdlib/sloppy/node/timers.js +26 -0
- package/stdlib/sloppy/node/tty.js +18 -0
- package/stdlib/sloppy/node/url.js +17 -0
- package/stdlib/sloppy/node/util.js +95 -0
- package/stdlib/sloppy/node/zlib.js +72 -0
- package/stdlib/sloppy/orm.js +2188 -0
- package/stdlib/sloppy/os.js +580 -0
- package/stdlib/sloppy/problem-details.js +29 -0
- package/stdlib/sloppy/providers/sqlite.js +26 -0
- package/stdlib/sloppy/rate-limit.js +856 -0
- package/stdlib/sloppy/realtime.js +1508 -0
- package/stdlib/sloppy/redis.js +1272 -0
- package/stdlib/sloppy/request-id.js +184 -0
- package/stdlib/sloppy/request-logging.js +101 -0
- package/stdlib/sloppy/results.js +933 -0
- package/stdlib/sloppy/schema.js +546 -0
- package/stdlib/sloppy/testing.js +4081 -0
- package/stdlib/sloppy/testservices.js +1041 -0
- package/stdlib/sloppy/time.js +894 -0
- package/stdlib/sloppy/webhooks.js +1330 -0
- package/stdlib/sloppy/workers.js +986 -0
- package/templates/api/README.md +82 -0
- package/templates/api/appsettings.Development.json +14 -0
- package/templates/api/appsettings.json +13 -0
- package/templates/api/data/.gitkeep +1 -0
- package/templates/api/gitignore +4 -0
- package/templates/api/migrations/0001_create_users.sql +1 -0
- package/templates/api/package.json +16 -0
- package/templates/api/public/hello.txt +1 -0
- package/templates/api/sloppy.json +14 -0
- package/templates/api/src/config.ts +1 -0
- package/templates/api/src/db/migrate.ts +14 -0
- package/templates/api/src/db/schema.ts +4 -0
- package/templates/api/src/db/usersRepository.ts +23 -0
- package/templates/api/src/main.ts +18 -0
- package/templates/api/src/models/user.ts +7 -0
- package/templates/api/src/routes/health.ts +20 -0
- package/templates/api/src/routes/users.ts +40 -0
- package/templates/api/src/services/usersService.ts +21 -0
- package/templates/api/tsconfig.json +15 -0
- package/templates/cli/README.md +16 -0
- package/templates/cli/gitignore +2 -0
- package/templates/cli/package.json +13 -0
- package/templates/cli/sloppy.json +6 -0
- package/templates/cli/src/commands/echo.ts +9 -0
- package/templates/cli/src/commands/inspect.ts +20 -0
- package/templates/cli/src/main.ts +50 -0
- package/templates/cli/tsconfig.json +15 -0
- package/templates/minimal-api/README.md +14 -0
- package/templates/minimal-api/gitignore +3 -0
- package/templates/minimal-api/package.json +14 -0
- package/templates/minimal-api/sloppy.json +5 -0
- package/templates/minimal-api/src/main.ts +9 -0
- package/templates/minimal-api/tsconfig.json +15 -0
- package/templates/node-compat/README.md +40 -0
- package/templates/node-compat/gitignore +2 -0
- package/templates/node-compat/package.json +11 -0
- package/templates/node-compat/sloppy.json +6 -0
- package/templates/node-compat/src/main.ts +40 -0
- package/templates/package-api/README.md +44 -0
- package/templates/package-api/fixtures/validator-lite/index.js +7 -0
- package/templates/package-api/fixtures/validator-lite/package.json +6 -0
- package/templates/package-api/gitignore +3 -0
- package/templates/package-api/package.json +17 -0
- package/templates/package-api/sloppy.json +5 -0
- package/templates/package-api/src/main.ts +10 -0
- package/templates/package-api/src/routes/health.ts +5 -0
- package/templates/package-api/src/routes/users.ts +12 -0
- package/templates/package-api/tsconfig.json +15 -0
- package/templates/program/README.md +12 -0
- package/templates/program/gitignore +1 -0
- package/templates/program/package.json +10 -0
- package/templates/program/sloppy.json +6 -0
- package/templates/program/src/main.ts +9 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
import { isPlainObject } from "./internal/validation.js";
|
|
2
|
+
import { copyBinaryLike } from "./internal/bytes.js";
|
|
3
|
+
import { Deadline } from "./time.js";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_QUEUE_CAPACITY = 1024;
|
|
6
|
+
const DEFAULT_WORKER_POOL_CAPACITY = 64;
|
|
7
|
+
const MAX_RESOURCE_NAME_LENGTH = 128;
|
|
8
|
+
const WORKER_SERIALIZATION_MARKER = "__sloppyWorkerSerialized";
|
|
9
|
+
|
|
10
|
+
class SloppyWorkerError extends Error {
|
|
11
|
+
constructor(code, message, options = undefined) {
|
|
12
|
+
super(`${code}: ${message}`, options);
|
|
13
|
+
this.name = "SloppyWorkerError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function workerError(code, message, cause = undefined) {
|
|
19
|
+
return new SloppyWorkerError(code, message, cause === undefined ? undefined : { cause });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toWorkerError(error, fallbackCode, fallbackMessage) {
|
|
23
|
+
if (error instanceof SloppyWorkerError) {
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
const code = typeof error?.code === "string" && error.code.startsWith("SLOPPY_E_")
|
|
27
|
+
? error.code
|
|
28
|
+
: fallbackCode;
|
|
29
|
+
const message = typeof error?.message === "string" && error.message.length > 0
|
|
30
|
+
? error.message
|
|
31
|
+
: fallbackMessage;
|
|
32
|
+
return workerError(code, message, error);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateName(name, subject) {
|
|
36
|
+
if (
|
|
37
|
+
typeof name !== "string" ||
|
|
38
|
+
name.length === 0 ||
|
|
39
|
+
name.length > MAX_RESOURCE_NAME_LENGTH ||
|
|
40
|
+
name.trim() !== name ||
|
|
41
|
+
/[\0\r\n]/u.test(name)
|
|
42
|
+
) {
|
|
43
|
+
throw new TypeError(`${subject} name must be a non-empty stable string.`);
|
|
44
|
+
}
|
|
45
|
+
return name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function positiveInteger(value, field, fallback = undefined) {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
if (fallback !== undefined) {
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
throw new TypeError(`${field} is required.`);
|
|
54
|
+
}
|
|
55
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
56
|
+
throw new TypeError(`${field} must be a positive integer.`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeDeadline(deadline) {
|
|
62
|
+
if (deadline === undefined || deadline === null) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (typeof deadline.remainingMs === "function") {
|
|
66
|
+
return deadline;
|
|
67
|
+
}
|
|
68
|
+
if (typeof deadline === "number") {
|
|
69
|
+
return Deadline.after(deadline);
|
|
70
|
+
}
|
|
71
|
+
throw new TypeError("worker deadline must expose remainingMs().");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function deadlineRemainingMs(deadline) {
|
|
75
|
+
if (deadline === undefined) {
|
|
76
|
+
return Infinity;
|
|
77
|
+
}
|
|
78
|
+
const remaining = Number(deadline.remainingMs());
|
|
79
|
+
if (remaining === Infinity) {
|
|
80
|
+
return Infinity;
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(remaining)) {
|
|
83
|
+
throw new TypeError("worker deadline remainingMs() must be finite or Infinity.");
|
|
84
|
+
}
|
|
85
|
+
return Math.max(0, remaining);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isSignal(value) {
|
|
89
|
+
return value !== null && typeof value === "object" &&
|
|
90
|
+
(typeof value.cancelled === "boolean" || typeof value.aborted === "boolean");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function signalCancelled(signal) {
|
|
94
|
+
return isSignal(signal) && (signal.cancelled === true || signal.aborted === true);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function signalReason(signal) {
|
|
98
|
+
return isSignal(signal) ? signal.reason : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function subscribeSignal(signal, listener) {
|
|
102
|
+
if (!isSignal(signal) || typeof listener !== "function") {
|
|
103
|
+
return () => {};
|
|
104
|
+
}
|
|
105
|
+
if (signalCancelled(signal)) {
|
|
106
|
+
listener(signalReason(signal));
|
|
107
|
+
return () => {};
|
|
108
|
+
}
|
|
109
|
+
if (typeof signal._subscribe === "function") {
|
|
110
|
+
return signal._subscribe(listener);
|
|
111
|
+
}
|
|
112
|
+
if (typeof signal.addEventListener === "function") {
|
|
113
|
+
const wrapped = () => listener(signalReason(signal));
|
|
114
|
+
signal.addEventListener("abort", wrapped);
|
|
115
|
+
return () => signal.removeEventListener?.("abort", wrapped);
|
|
116
|
+
}
|
|
117
|
+
return () => {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function raceSignalCancellation(promise, signal, timeoutCode = "SLOPPY_E_WORK_JOB_TIMEOUT") {
|
|
121
|
+
if (!isSignal(signal)) {
|
|
122
|
+
return Promise.resolve(promise);
|
|
123
|
+
}
|
|
124
|
+
if (signalCancelled(signal)) {
|
|
125
|
+
return Promise.reject(rejectForCancellation(signalReason(signal), timeoutCode));
|
|
126
|
+
}
|
|
127
|
+
let cleanup = () => {};
|
|
128
|
+
const cancellation = new Promise((_, reject) => {
|
|
129
|
+
cleanup = subscribeSignal(signal, (reason) => reject(rejectForCancellation(reason, timeoutCode)));
|
|
130
|
+
});
|
|
131
|
+
return Promise.race([promise, cancellation]).finally(cleanup);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class WorkerCancellationSignal {
|
|
135
|
+
constructor() {
|
|
136
|
+
this.cancelled = false;
|
|
137
|
+
this.aborted = false;
|
|
138
|
+
this.reason = undefined;
|
|
139
|
+
this._listeners = new Set();
|
|
140
|
+
Object.seal(this);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throwIfCancelled() {
|
|
144
|
+
if (this.cancelled) {
|
|
145
|
+
throw workerError("SLOPPY_E_WORK_JOB_CANCELLED", "worker operation was cancelled");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
addEventListener(type, listener) {
|
|
150
|
+
if (type === "abort" && typeof listener === "function") {
|
|
151
|
+
this._listeners.add(listener);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
removeEventListener(type, listener) {
|
|
156
|
+
if (type === "abort") {
|
|
157
|
+
this._listeners.delete(listener);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_subscribe(listener) {
|
|
162
|
+
if (this.cancelled) {
|
|
163
|
+
listener(this.reason);
|
|
164
|
+
return () => {};
|
|
165
|
+
}
|
|
166
|
+
this._listeners.add(listener);
|
|
167
|
+
return () => {
|
|
168
|
+
this._listeners.delete(listener);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_cancel(reason) {
|
|
173
|
+
if (this.cancelled) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
this.cancelled = true;
|
|
177
|
+
this.aborted = true;
|
|
178
|
+
this.reason = reason;
|
|
179
|
+
const listeners = Array.from(this._listeners);
|
|
180
|
+
this._listeners.clear();
|
|
181
|
+
for (const listener of listeners) {
|
|
182
|
+
try {
|
|
183
|
+
listener(reason);
|
|
184
|
+
} catch {
|
|
185
|
+
// Cancellation fan-out must stay deterministic even if user code throws.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
class WorkerCancellationController {
|
|
193
|
+
constructor() {
|
|
194
|
+
this.signal = new WorkerCancellationSignal();
|
|
195
|
+
Object.freeze(this);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
cancel(reason = "cancelled") {
|
|
199
|
+
return this.signal._cancel(reason);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeOperationOptions(options = undefined) {
|
|
204
|
+
if (options === undefined || options === null) {
|
|
205
|
+
return { deadline: undefined, signal: undefined };
|
|
206
|
+
}
|
|
207
|
+
if (!isPlainObject(options)) {
|
|
208
|
+
throw new TypeError("worker operation options must be a plain object.");
|
|
209
|
+
}
|
|
210
|
+
if (options.signal !== undefined && !isSignal(options.signal)) {
|
|
211
|
+
throw new TypeError("worker signal must be a Sloppy cancellation signal or AbortSignal-like object.");
|
|
212
|
+
}
|
|
213
|
+
let timeoutDeadline = undefined;
|
|
214
|
+
if (options.timeoutMs !== undefined) {
|
|
215
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0) {
|
|
216
|
+
throw new TypeError("worker timeoutMs must be finite and non-negative.");
|
|
217
|
+
}
|
|
218
|
+
timeoutDeadline = Deadline.after(Math.ceil(options.timeoutMs));
|
|
219
|
+
}
|
|
220
|
+
const explicitDeadline = normalizeDeadline(options.deadline);
|
|
221
|
+
return {
|
|
222
|
+
deadline: combineDeadlines(explicitDeadline, timeoutDeadline),
|
|
223
|
+
signal: options.signal,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function combineDeadlines(first, second) {
|
|
228
|
+
if (first === undefined) {
|
|
229
|
+
return second;
|
|
230
|
+
}
|
|
231
|
+
if (second === undefined) {
|
|
232
|
+
return first;
|
|
233
|
+
}
|
|
234
|
+
return Object.freeze({
|
|
235
|
+
remainingMs() {
|
|
236
|
+
return Math.min(deadlineRemainingMs(first), deadlineRemainingMs(second));
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function timeoutMsFromOptions(options) {
|
|
242
|
+
const deadlineMs = deadlineRemainingMs(options.deadline);
|
|
243
|
+
return deadlineMs === Infinity ? Infinity : Math.ceil(deadlineMs);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function delay(ms) {
|
|
247
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function yieldTurn() {
|
|
251
|
+
return Promise.resolve();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function copyBytes(bytes) {
|
|
255
|
+
return copyBinaryLike(bytes);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function validateWorkerOptions(options, operation) {
|
|
259
|
+
if (options === undefined || options === null) {
|
|
260
|
+
return Object.freeze({});
|
|
261
|
+
}
|
|
262
|
+
if (!isPlainObject(options)) {
|
|
263
|
+
throw new TypeError(`${operation} options must be a plain object.`);
|
|
264
|
+
}
|
|
265
|
+
return Object.freeze({ ...options });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function serializePayload(value, seen = new Set()) {
|
|
269
|
+
if (value === undefined) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
if (typeof value === "number") {
|
|
276
|
+
if (!Number.isFinite(value)) {
|
|
277
|
+
throw workerError("SLOPPY_E_WORKER_UNSUPPORTED_PAYLOAD", "worker payload is not serializable");
|
|
278
|
+
}
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
const bytes = copyBytes(value);
|
|
282
|
+
if (bytes !== undefined) {
|
|
283
|
+
return bytes;
|
|
284
|
+
}
|
|
285
|
+
if (Array.isArray(value)) {
|
|
286
|
+
if (seen.has(value)) {
|
|
287
|
+
throw workerError("SLOPPY_E_WORKER_MESSAGE_SERIALIZATION_FAILED", "worker payload contains a cycle");
|
|
288
|
+
}
|
|
289
|
+
seen.add(value);
|
|
290
|
+
const copy = value.map((item) => serializePayload(item, seen));
|
|
291
|
+
seen.delete(value);
|
|
292
|
+
return copy;
|
|
293
|
+
}
|
|
294
|
+
if (isPlainObject(value)) {
|
|
295
|
+
if (seen.has(value)) {
|
|
296
|
+
throw workerError("SLOPPY_E_WORKER_MESSAGE_SERIALIZATION_FAILED", "worker payload contains a cycle");
|
|
297
|
+
}
|
|
298
|
+
if (Object.prototype.hasOwnProperty.call(value, WORKER_SERIALIZATION_MARKER)) {
|
|
299
|
+
throw workerError("SLOPPY_E_WORKER_UNSUPPORTED_PAYLOAD", "worker payload uses a reserved serialization marker");
|
|
300
|
+
}
|
|
301
|
+
seen.add(value);
|
|
302
|
+
const copy = {};
|
|
303
|
+
for (const [key, item] of Object.entries(value)) {
|
|
304
|
+
if (item !== undefined) {
|
|
305
|
+
copy[key] = serializePayload(item, seen);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
seen.delete(value);
|
|
309
|
+
return copy;
|
|
310
|
+
}
|
|
311
|
+
throw workerError("SLOPPY_E_WORKER_UNSUPPORTED_PAYLOAD", "worker payload type is unsupported");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function makeContext(options, extra = {}) {
|
|
315
|
+
const controller = new WorkerCancellationController();
|
|
316
|
+
const parentCleanup = subscribeSignal(options.signal, (reason) => controller.cancel(reason));
|
|
317
|
+
const timeoutMs = timeoutMsFromOptions(options);
|
|
318
|
+
let timer = undefined;
|
|
319
|
+
if (timeoutMs === 0) {
|
|
320
|
+
controller.cancel("deadline");
|
|
321
|
+
} else if (timeoutMs !== Infinity) {
|
|
322
|
+
timer = setTimeout(() => controller.cancel("deadline"), timeoutMs);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
ctx: Object.freeze({
|
|
326
|
+
signal: controller.signal,
|
|
327
|
+
deadline: options.deadline,
|
|
328
|
+
...extra,
|
|
329
|
+
}),
|
|
330
|
+
controller,
|
|
331
|
+
cleanup() {
|
|
332
|
+
parentCleanup();
|
|
333
|
+
if (timer !== undefined) {
|
|
334
|
+
clearTimeout(timer);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function isDeadlineReason(reason) {
|
|
341
|
+
return reason === "deadline" || reason === "timeout";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function rejectForCancellation(reason, timeoutCode = "SLOPPY_E_WORK_JOB_TIMEOUT") {
|
|
345
|
+
if (isDeadlineReason(reason)) {
|
|
346
|
+
return workerError(timeoutCode, "worker operation timed out");
|
|
347
|
+
}
|
|
348
|
+
return workerError("SLOPPY_E_WORK_JOB_CANCELLED", "worker operation was cancelled");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function normalizeRetry(options = undefined) {
|
|
352
|
+
const retry = options?.retry ?? {};
|
|
353
|
+
const attempts = positiveInteger(retry.maxAttempts ?? options?.maxAttempts, "retry.maxAttempts", 1);
|
|
354
|
+
const backoff = retry.backoffMs ?? options?.backoffMs ?? 0;
|
|
355
|
+
if (typeof backoff !== "function" && (!Number.isFinite(backoff) || backoff < 0)) {
|
|
356
|
+
throw new TypeError("retry backoff must be a non-negative millisecond value or function.");
|
|
357
|
+
}
|
|
358
|
+
return { attempts, backoff };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function retryDelayMs(backoff, attempt, error) {
|
|
362
|
+
const value = typeof backoff === "function" ? backoff(attempt, error) : backoff;
|
|
363
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
364
|
+
throw new TypeError("retry backoff must return a non-negative millisecond value.");
|
|
365
|
+
}
|
|
366
|
+
return Math.ceil(value);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
class WorkQueueHandle {
|
|
370
|
+
constructor(name, options = undefined) {
|
|
371
|
+
options = validateWorkerOptions(options, "WorkQueue.create");
|
|
372
|
+
this.name = validateName(name, "WorkQueue");
|
|
373
|
+
this.maxQueued = positiveInteger(options?.maxQueued, "WorkQueue.maxQueued", DEFAULT_QUEUE_CAPACITY);
|
|
374
|
+
this.concurrency = positiveInteger(options?.concurrency, "WorkQueue.concurrency", 1);
|
|
375
|
+
this.overflow = options?.overflow ?? "reject";
|
|
376
|
+
if (!["reject", "backpressure"].includes(this.overflow)) {
|
|
377
|
+
throw new TypeError("WorkQueue overflow must be \"reject\" or \"backpressure\".");
|
|
378
|
+
}
|
|
379
|
+
this.maxBackpressureWaiters = positiveInteger(
|
|
380
|
+
options?.maxBackpressureWaiters,
|
|
381
|
+
"WorkQueue.maxBackpressureWaiters",
|
|
382
|
+
this.maxQueued,
|
|
383
|
+
);
|
|
384
|
+
this.retry = normalizeRetry(options);
|
|
385
|
+
this._handler = undefined;
|
|
386
|
+
this._queue = [];
|
|
387
|
+
this._waiters = [];
|
|
388
|
+
this._active = 0;
|
|
389
|
+
this._stopped = false;
|
|
390
|
+
this._nextJobId = 1;
|
|
391
|
+
this._stopPromise = undefined;
|
|
392
|
+
this._resolveStop = undefined;
|
|
393
|
+
this._stats = {
|
|
394
|
+
enqueued: 0,
|
|
395
|
+
completed: 0,
|
|
396
|
+
failed: 0,
|
|
397
|
+
cancelled: 0,
|
|
398
|
+
timedOut: 0,
|
|
399
|
+
retryExhausted: 0,
|
|
400
|
+
overflow: 0,
|
|
401
|
+
};
|
|
402
|
+
Object.seal(this);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
get state() {
|
|
406
|
+
return Object.freeze({
|
|
407
|
+
name: this.name,
|
|
408
|
+
stopped: this._stopped,
|
|
409
|
+
queued: this._queue.length,
|
|
410
|
+
active: this._active,
|
|
411
|
+
maxQueued: this.maxQueued,
|
|
412
|
+
concurrency: this.concurrency,
|
|
413
|
+
overflow: this.overflow,
|
|
414
|
+
stats: Object.freeze({ ...this._stats }),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
process(handler) {
|
|
419
|
+
if (typeof handler !== "function") {
|
|
420
|
+
throw new TypeError("WorkQueue.process requires a job handler.");
|
|
421
|
+
}
|
|
422
|
+
if (this._handler !== undefined) {
|
|
423
|
+
throw new TypeError("WorkQueue.process may only be called once.");
|
|
424
|
+
}
|
|
425
|
+
this._handler = handler;
|
|
426
|
+
this._pump();
|
|
427
|
+
return this;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
enqueue(data, options = undefined) {
|
|
431
|
+
if (this._stopped) {
|
|
432
|
+
return Promise.reject(workerError("SLOPPY_E_WORK_QUEUE_STOPPED", "work queue is stopped"));
|
|
433
|
+
}
|
|
434
|
+
if (this._handler === undefined) {
|
|
435
|
+
return Promise.reject(workerError("SLOPPY_E_WORK_QUEUE_STOPPED", "work queue has no processor"));
|
|
436
|
+
}
|
|
437
|
+
let payload;
|
|
438
|
+
try {
|
|
439
|
+
payload = serializePayload(data);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return Promise.reject(error);
|
|
442
|
+
}
|
|
443
|
+
return this._enqueuePayload(payload, options);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
_enqueuePayload(payload, options = undefined) {
|
|
447
|
+
if (this._stopped) {
|
|
448
|
+
return Promise.reject(workerError("SLOPPY_E_WORK_QUEUE_STOPPED", "work queue is stopped"));
|
|
449
|
+
}
|
|
450
|
+
let normalizedOptions;
|
|
451
|
+
try {
|
|
452
|
+
normalizedOptions = normalizeOperationOptions(options);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
return Promise.reject(error);
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
if (signalCancelled(normalizedOptions.signal)) {
|
|
458
|
+
return Promise.reject(rejectForCancellation(signalReason(normalizedOptions.signal)));
|
|
459
|
+
}
|
|
460
|
+
if (deadlineRemainingMs(normalizedOptions.deadline) <= 0) {
|
|
461
|
+
return Promise.reject(rejectForCancellation("deadline"));
|
|
462
|
+
}
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return Promise.reject(error);
|
|
465
|
+
}
|
|
466
|
+
const submit = () => new Promise((resolve, reject) => {
|
|
467
|
+
if (this._stopped) {
|
|
468
|
+
reject(workerError("SLOPPY_E_WORK_QUEUE_STOPPED", "work queue is stopped"));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const job = {
|
|
472
|
+
id: this._nextJobId++,
|
|
473
|
+
data: payload,
|
|
474
|
+
attempt: 0,
|
|
475
|
+
options: normalizedOptions,
|
|
476
|
+
resolve,
|
|
477
|
+
reject,
|
|
478
|
+
};
|
|
479
|
+
this._queue.push(job);
|
|
480
|
+
this._stats.enqueued += 1;
|
|
481
|
+
this._pump();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (this._queue.length >= this.maxQueued && this._active >= this.concurrency) {
|
|
485
|
+
this._stats.overflow += 1;
|
|
486
|
+
if (this.overflow === "reject") {
|
|
487
|
+
return Promise.reject(workerError("SLOPPY_E_WORK_QUEUE_FULL", "work queue is full"));
|
|
488
|
+
}
|
|
489
|
+
if (this._waiters.length >= this.maxBackpressureWaiters) {
|
|
490
|
+
return Promise.reject(workerError("SLOPPY_E_WORK_QUEUE_FULL", "work queue backpressure waiters are full"));
|
|
491
|
+
}
|
|
492
|
+
return new Promise((resolve, reject) => {
|
|
493
|
+
const waiter = this._createBackpressureWaiter(normalizedOptions, resolve, reject, submit);
|
|
494
|
+
this._waiters.push(waiter);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return submit();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
_createBackpressureWaiter(options, resolve, reject, submit) {
|
|
501
|
+
let finished = false;
|
|
502
|
+
let timeoutId = undefined;
|
|
503
|
+
let cleanupSignal = () => {};
|
|
504
|
+
const remove = (waiter) => {
|
|
505
|
+
const index = this._waiters.indexOf(waiter);
|
|
506
|
+
if (index >= 0) {
|
|
507
|
+
this._waiters.splice(index, 1);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const finish = (waiter, callback) => {
|
|
511
|
+
if (finished) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
finished = true;
|
|
515
|
+
cleanupSignal();
|
|
516
|
+
if (timeoutId !== undefined) {
|
|
517
|
+
clearTimeout(timeoutId);
|
|
518
|
+
}
|
|
519
|
+
callback();
|
|
520
|
+
};
|
|
521
|
+
const waiter = {
|
|
522
|
+
cleanup: () => {
|
|
523
|
+
cleanupSignal();
|
|
524
|
+
if (timeoutId !== undefined) {
|
|
525
|
+
clearTimeout(timeoutId);
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
reject: (error) => finish(waiter, () => reject(error)),
|
|
529
|
+
resume: () => finish(waiter, () => {
|
|
530
|
+
try {
|
|
531
|
+
if (signalCancelled(options.signal)) {
|
|
532
|
+
this._stats.cancelled += 1;
|
|
533
|
+
reject(rejectForCancellation(signalReason(options.signal)));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (deadlineRemainingMs(options.deadline) <= 0) {
|
|
537
|
+
this._stats.timedOut += 1;
|
|
538
|
+
reject(rejectForCancellation("deadline"));
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
reject(error);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
submit().then(resolve, reject);
|
|
546
|
+
}),
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
cleanupSignal = subscribeSignal(options.signal, (reason) => {
|
|
550
|
+
remove(waiter);
|
|
551
|
+
this._stats.cancelled += 1;
|
|
552
|
+
waiter.reject(rejectForCancellation(reason));
|
|
553
|
+
});
|
|
554
|
+
const remaining = deadlineRemainingMs(options.deadline);
|
|
555
|
+
if (remaining !== Infinity) {
|
|
556
|
+
timeoutId = setTimeout(() => {
|
|
557
|
+
remove(waiter);
|
|
558
|
+
this._stats.timedOut += 1;
|
|
559
|
+
waiter.reject(rejectForCancellation("deadline"));
|
|
560
|
+
}, Math.ceil(remaining));
|
|
561
|
+
}
|
|
562
|
+
return waiter;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_rejectWaiters(error, options = undefined) {
|
|
566
|
+
const countCancelled = options?.countCancelled !== false;
|
|
567
|
+
while (this._waiters.length > 0) {
|
|
568
|
+
const waiter = this._waiters.shift();
|
|
569
|
+
waiter.cleanup?.();
|
|
570
|
+
waiter.reject(error);
|
|
571
|
+
if (countCancelled) {
|
|
572
|
+
this._stats.cancelled += 1;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async drain() {
|
|
578
|
+
if (this._queue.length === 0 && this._active === 0) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (this._stopPromise === undefined) {
|
|
582
|
+
this._stopPromise = new Promise((resolve) => {
|
|
583
|
+
this._resolveStop = resolve;
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
await this._stopPromise;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async stop(options = undefined) {
|
|
590
|
+
const drain = options?.drain !== false;
|
|
591
|
+
this._stopped = true;
|
|
592
|
+
if (!drain) {
|
|
593
|
+
while (this._queue.length > 0) {
|
|
594
|
+
const job = this._queue.shift();
|
|
595
|
+
job.reject(workerError("SLOPPY_E_WORKER_SHUTDOWN_CANCELLED", "queued job was cancelled by shutdown"));
|
|
596
|
+
this._stats.cancelled += 1;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (drain) {
|
|
600
|
+
this._rejectWaiters(workerError("SLOPPY_E_WORK_QUEUE_STOPPED", "work queue is stopped"), {
|
|
601
|
+
countCancelled: false,
|
|
602
|
+
});
|
|
603
|
+
} else {
|
|
604
|
+
this._rejectWaiters(workerError("SLOPPY_E_WORKER_SHUTDOWN_CANCELLED", "queued job was cancelled by shutdown"));
|
|
605
|
+
}
|
|
606
|
+
await this.drain();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
_pump() {
|
|
610
|
+
while (!this._stopped && this._waiters.length > 0 && this._queue.length < this.maxQueued) {
|
|
611
|
+
this._waiters.shift().resume();
|
|
612
|
+
}
|
|
613
|
+
while (this._handler !== undefined && this._active < this.concurrency && this._queue.length > 0) {
|
|
614
|
+
const job = this._queue.shift();
|
|
615
|
+
this._active += 1;
|
|
616
|
+
this._runJob(job);
|
|
617
|
+
}
|
|
618
|
+
if (this._queue.length === 0 && this._active === 0 && this._resolveStop !== undefined) {
|
|
619
|
+
this._resolveStop();
|
|
620
|
+
this._resolveStop = undefined;
|
|
621
|
+
this._stopPromise = undefined;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async _runJob(job) {
|
|
626
|
+
let settled = false;
|
|
627
|
+
const attemptLimit = this.retry.attempts;
|
|
628
|
+
try {
|
|
629
|
+
for (;;) {
|
|
630
|
+
job.attempt += 1;
|
|
631
|
+
const owned = makeContext(job.options, { attempt: job.attempt });
|
|
632
|
+
try {
|
|
633
|
+
if (signalCancelled(owned.ctx.signal)) {
|
|
634
|
+
throw rejectForCancellation(signalReason(owned.ctx.signal));
|
|
635
|
+
}
|
|
636
|
+
const result = await raceSignalCancellation(
|
|
637
|
+
this._handler(Object.freeze({ id: job.id, data: job.data, attempt: job.attempt }), owned.ctx),
|
|
638
|
+
owned.ctx.signal,
|
|
639
|
+
);
|
|
640
|
+
if (!settled) {
|
|
641
|
+
settled = true;
|
|
642
|
+
this._stats.completed += 1;
|
|
643
|
+
job.resolve(result);
|
|
644
|
+
}
|
|
645
|
+
break;
|
|
646
|
+
} catch (error) {
|
|
647
|
+
if (error instanceof SloppyWorkerError &&
|
|
648
|
+
(error.code === "SLOPPY_E_WORK_JOB_CANCELLED" || error.code === "SLOPPY_E_WORK_JOB_TIMEOUT"))
|
|
649
|
+
{
|
|
650
|
+
settled = true;
|
|
651
|
+
if (error.code === "SLOPPY_E_WORK_JOB_TIMEOUT") {
|
|
652
|
+
this._stats.timedOut += 1;
|
|
653
|
+
} else {
|
|
654
|
+
this._stats.cancelled += 1;
|
|
655
|
+
}
|
|
656
|
+
job.reject(error);
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
if (job.attempt >= attemptLimit) {
|
|
660
|
+
settled = true;
|
|
661
|
+
this._stats.failed += 1;
|
|
662
|
+
this._stats.retryExhausted += attemptLimit > 1 ? 1 : 0;
|
|
663
|
+
job.reject(attemptLimit === 1 && error instanceof SloppyWorkerError
|
|
664
|
+
? error
|
|
665
|
+
: workerError("SLOPPY_E_WORK_RETRY_EXHAUSTED", "work queue retry attempts were exhausted", error));
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
const ms = retryDelayMs(this.retry.backoff, job.attempt, error);
|
|
669
|
+
if (ms > 0) {
|
|
670
|
+
await delay(ms);
|
|
671
|
+
} else {
|
|
672
|
+
await yieldTurn();
|
|
673
|
+
}
|
|
674
|
+
} finally {
|
|
675
|
+
owned.cleanup();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} finally {
|
|
679
|
+
this._active -= 1;
|
|
680
|
+
this._pump();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
__sloppyPlanMetadata() {
|
|
685
|
+
return Object.freeze({
|
|
686
|
+
kind: "workQueue",
|
|
687
|
+
name: this.name,
|
|
688
|
+
maxQueued: this.maxQueued,
|
|
689
|
+
concurrency: this.concurrency,
|
|
690
|
+
overflow: this.overflow,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
class BackgroundServiceHandle {
|
|
696
|
+
constructor(name, handler, options = undefined) {
|
|
697
|
+
options = validateWorkerOptions(options, "BackgroundService.create");
|
|
698
|
+
this.name = validateName(name, "BackgroundService");
|
|
699
|
+
if (typeof handler !== "function") {
|
|
700
|
+
throw new TypeError("BackgroundService.create requires a function.");
|
|
701
|
+
}
|
|
702
|
+
this._handler = handler;
|
|
703
|
+
this._options = options ?? {};
|
|
704
|
+
this._controller = undefined;
|
|
705
|
+
this._promise = undefined;
|
|
706
|
+
this._state = "created";
|
|
707
|
+
this._failure = undefined;
|
|
708
|
+
this.__sloppyWorkerResource = "backgroundService";
|
|
709
|
+
Object.seal(this);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
get state() {
|
|
713
|
+
return this._state;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
get failure() {
|
|
717
|
+
return this._failure;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
start() {
|
|
721
|
+
if (this._state === "running") {
|
|
722
|
+
return this;
|
|
723
|
+
}
|
|
724
|
+
if (this._state === "stopped") {
|
|
725
|
+
throw workerError("SLOPPY_E_WORKER_STALE_HANDLE", "background service has been stopped");
|
|
726
|
+
}
|
|
727
|
+
this._controller = new WorkerCancellationController();
|
|
728
|
+
this._state = "running";
|
|
729
|
+
const ctx = Object.freeze({ name: this.name, signal: this._controller.signal });
|
|
730
|
+
this._promise = Promise.resolve()
|
|
731
|
+
.then(() => this._handler(ctx))
|
|
732
|
+
.then(
|
|
733
|
+
() => {
|
|
734
|
+
if (this._state === "running") {
|
|
735
|
+
this._state = "completed";
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
(error) => {
|
|
739
|
+
if (this._state === "running") {
|
|
740
|
+
this._state = "failed";
|
|
741
|
+
this._failure = workerError("SLOPPY_E_BACKGROUND_SERVICE_FAILED", "background service failed", error);
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
);
|
|
745
|
+
return this;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async stop(reason = "app shutdown") {
|
|
749
|
+
if (this._state === "created") {
|
|
750
|
+
this._state = "stopped";
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (this._state === "stopped") {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
this._controller?.cancel(reason);
|
|
757
|
+
await this._promise;
|
|
758
|
+
this._state = "stopped";
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
__sloppyStartForApp() {
|
|
762
|
+
return this.start();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
__sloppyStopForApp(reason) {
|
|
766
|
+
return this.stop(reason);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
__sloppyPlanMetadata() {
|
|
770
|
+
return Object.freeze({
|
|
771
|
+
kind: "backgroundService",
|
|
772
|
+
name: this.name,
|
|
773
|
+
start: this._options.start ?? "app",
|
|
774
|
+
shutdown: this._options.shutdown ?? "cancel-and-drain",
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
class WorkerPoolHandle {
|
|
780
|
+
constructor(name, options = undefined) {
|
|
781
|
+
options = validateWorkerOptions(options, "WorkerPool.create");
|
|
782
|
+
this.name = validateName(name, "WorkerPool");
|
|
783
|
+
this.workers = positiveInteger(options?.workers, "WorkerPool.workers", 1);
|
|
784
|
+
this.maxQueued = positiveInteger(options?.maxQueued, "WorkerPool.maxQueued", DEFAULT_WORKER_POOL_CAPACITY);
|
|
785
|
+
this._queue = new WorkQueueHandle(`${name}:pool`, {
|
|
786
|
+
maxQueued: this.maxQueued,
|
|
787
|
+
concurrency: this.workers,
|
|
788
|
+
overflow: options?.overflow ?? "reject",
|
|
789
|
+
});
|
|
790
|
+
this._queue.process(async (job, ctx) => {
|
|
791
|
+
const bridge = globalThis.__sloppy?.workers;
|
|
792
|
+
if (bridge === undefined || typeof bridge.runPool !== "function") {
|
|
793
|
+
throw workerError(
|
|
794
|
+
"SLOPPY_E_WORKER_BRIDGE_UNAVAILABLE",
|
|
795
|
+
"worker pool bridge is unavailable",
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
return await bridge.runPool(this.name, job.data.fn, job.data.input, ctx);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
throw toWorkerError(error, "SLOPPY_E_WORKER_CRASHED", "worker pool operation failed");
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
this.__sloppyWorkerResource = "workerPool";
|
|
805
|
+
Object.seal(this);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
get state() {
|
|
809
|
+
return Object.freeze({
|
|
810
|
+
name: this.name,
|
|
811
|
+
workers: this.workers,
|
|
812
|
+
maxQueued: this.maxQueued,
|
|
813
|
+
queue: this._queue.state,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
run(fn, options = undefined) {
|
|
818
|
+
if (typeof fn !== "function") {
|
|
819
|
+
return Promise.reject(new TypeError("WorkerPool.run requires a function."));
|
|
820
|
+
}
|
|
821
|
+
let input;
|
|
822
|
+
try {
|
|
823
|
+
input = serializePayload(options?.input);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
return Promise.reject(error);
|
|
826
|
+
}
|
|
827
|
+
return this._queue._enqueuePayload({ fn, input }, options);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
drain() {
|
|
831
|
+
return this._queue.drain();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
stop(options = undefined) {
|
|
835
|
+
return this._queue.stop(options);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
__sloppyPlanMetadata() {
|
|
839
|
+
return Object.freeze({
|
|
840
|
+
kind: "workerPool",
|
|
841
|
+
name: this.name,
|
|
842
|
+
workers: this.workers,
|
|
843
|
+
maxQueued: this.maxQueued,
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
class NativeJsWorkerHandle {
|
|
849
|
+
constructor(modulePath, nativeHandle, options) {
|
|
850
|
+
this.modulePath = modulePath;
|
|
851
|
+
this._native = nativeHandle;
|
|
852
|
+
this._stopped = false;
|
|
853
|
+
this._memoryLimitMb = options.memoryLimitMb;
|
|
854
|
+
this.__sloppyWorkerResource = "jsWorker";
|
|
855
|
+
Object.seal(this);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
async invoke(exportName, payload = undefined, options = undefined) {
|
|
859
|
+
if (this._stopped) {
|
|
860
|
+
throw workerError("SLOPPY_E_WORKER_STALE_HANDLE", "worker handle has been stopped");
|
|
861
|
+
}
|
|
862
|
+
if (typeof exportName !== "string" || exportName.length === 0) {
|
|
863
|
+
throw new TypeError("Worker.invoke export name must be a non-empty string.");
|
|
864
|
+
}
|
|
865
|
+
const input = serializePayload(payload);
|
|
866
|
+
const owned = makeContext(normalizeOperationOptions(options));
|
|
867
|
+
try {
|
|
868
|
+
return await raceSignalCancellation(
|
|
869
|
+
this._native.invoke(exportName, input, options),
|
|
870
|
+
owned.ctx.signal,
|
|
871
|
+
"SLOPPY_E_WORK_JOB_TIMEOUT",
|
|
872
|
+
);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
throw toWorkerError(error, "SLOPPY_E_WORKER_CRASHED", "worker crashed");
|
|
875
|
+
} finally {
|
|
876
|
+
owned.cleanup();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async post(message, options = undefined) {
|
|
881
|
+
if (this._stopped) {
|
|
882
|
+
throw workerError("SLOPPY_E_WORKER_STALE_HANDLE", "worker handle has been stopped");
|
|
883
|
+
}
|
|
884
|
+
const input = serializePayload(message);
|
|
885
|
+
const owned = makeContext(normalizeOperationOptions(options));
|
|
886
|
+
try {
|
|
887
|
+
return await raceSignalCancellation(
|
|
888
|
+
this._native.post(input, options),
|
|
889
|
+
owned.ctx.signal,
|
|
890
|
+
"SLOPPY_E_WORK_JOB_TIMEOUT",
|
|
891
|
+
);
|
|
892
|
+
} catch (error) {
|
|
893
|
+
throw toWorkerError(error, "SLOPPY_E_WORKER_CRASHED", "worker crashed");
|
|
894
|
+
} finally {
|
|
895
|
+
owned.cleanup();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
onMessage(callback) {
|
|
900
|
+
if (this._stopped) {
|
|
901
|
+
throw workerError("SLOPPY_E_WORKER_STALE_HANDLE", "worker handle has been stopped");
|
|
902
|
+
}
|
|
903
|
+
if (typeof callback !== "function") {
|
|
904
|
+
throw new TypeError("Worker.onMessage requires a callback.");
|
|
905
|
+
}
|
|
906
|
+
const subscribe = this._native.onMessage ?? this._native.addMessageListener;
|
|
907
|
+
if (typeof subscribe !== "function") {
|
|
908
|
+
throw workerError(
|
|
909
|
+
"SLOPPY_E_WORKER_BRIDGE_UNAVAILABLE",
|
|
910
|
+
"worker receive bridge is unavailable",
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
return subscribe.call(this._native, callback);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async stop() {
|
|
917
|
+
if (this._stopped) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
this._stopped = true;
|
|
921
|
+
try {
|
|
922
|
+
await this._native.stop();
|
|
923
|
+
} catch (error) {
|
|
924
|
+
throw toWorkerError(error, "SLOPPY_E_WORKER_STALE_HANDLE", "worker handle has been stopped");
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
__sloppyPlanMetadata() {
|
|
929
|
+
return Object.freeze({
|
|
930
|
+
kind: "jsWorker",
|
|
931
|
+
path: this.modulePath,
|
|
932
|
+
memoryLimitMb: this._memoryLimitMb,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const BackgroundService = Object.freeze({
|
|
938
|
+
create(name, handler, options = undefined) {
|
|
939
|
+
return new BackgroundServiceHandle(name, handler, options);
|
|
940
|
+
},
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
const WorkQueue = Object.freeze({
|
|
944
|
+
create(name, options = undefined) {
|
|
945
|
+
return new WorkQueueHandle(name, options);
|
|
946
|
+
},
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
const WorkerPool = Object.freeze({
|
|
950
|
+
create(name, options = undefined) {
|
|
951
|
+
return new WorkerPoolHandle(name, options);
|
|
952
|
+
},
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
const Worker = Object.freeze({
|
|
956
|
+
async start(modulePath, options = undefined) {
|
|
957
|
+
options = validateWorkerOptions(options, "Worker.start");
|
|
958
|
+
if (typeof modulePath !== "string" || modulePath.length === 0 || modulePath.includes("\0")) {
|
|
959
|
+
throw new TypeError("Worker.start module path must be a non-empty string without NUL.");
|
|
960
|
+
}
|
|
961
|
+
const memoryLimitMb = positiveInteger(options?.memoryLimitMb, "Worker.memoryLimitMb", 128);
|
|
962
|
+
const bridge = globalThis.__sloppy?.workers;
|
|
963
|
+
if (bridge !== undefined && typeof bridge.startWorker === "function") {
|
|
964
|
+
try {
|
|
965
|
+
const nativeHandle = await bridge.startWorker(modulePath, { memoryLimitMb });
|
|
966
|
+
return new NativeJsWorkerHandle(modulePath, nativeHandle, { memoryLimitMb });
|
|
967
|
+
} catch (error) {
|
|
968
|
+
throw toWorkerError(error, "SLOPPY_E_WORKER_ISOLATE_STARTUP_FAILED", "worker isolate startup failed");
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
throw workerError(
|
|
972
|
+
"SLOPPY_E_WORKER_BRIDGE_UNAVAILABLE",
|
|
973
|
+
"worker bridge is unavailable",
|
|
974
|
+
);
|
|
975
|
+
},
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
export {
|
|
979
|
+
BackgroundService,
|
|
980
|
+
SloppyWorkerError,
|
|
981
|
+
WorkQueue,
|
|
982
|
+
Worker,
|
|
983
|
+
WorkerCancellationController,
|
|
984
|
+
WorkerCancellationSignal,
|
|
985
|
+
WorkerPool,
|
|
986
|
+
};
|