@capybara-hq/kernel 0.1.0-rc.0 → 0.1.0-rc.2
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/bootstrap.json +10 -6
- package/dist/admin/admin.controller.d.ts +17 -17
- package/dist/admin/admin.controller.js +43 -26
- package/dist/admin/admin.module.d.ts +3 -0
- package/dist/admin/admin.module.js +22 -0
- package/dist/admin/extensions.controller.d.ts +14 -14
- package/dist/admin/extensions.controller.js +30 -17
- package/dist/admin/setup-lock.d.ts +16 -0
- package/dist/admin/setup-lock.js +47 -0
- package/dist/admin/setup.controller.d.ts +8 -8
- package/dist/admin/setup.controller.js +27 -25
- package/dist/admin/setup.service.d.ts +19 -27
- package/dist/admin/setup.service.js +61 -61
- package/dist/agents/agent-lookup.d.ts +53 -0
- package/dist/agents/agent-lookup.js +179 -0
- package/dist/agents/agents.controller.d.ts +1 -1
- package/dist/agents/agents.controller.js +14 -8
- package/dist/agents/agents.module.d.ts +7 -0
- package/dist/agents/agents.module.js +41 -0
- package/dist/agents/agents.service.d.ts +39 -58
- package/dist/agents/agents.service.js +261 -329
- package/dist/agents/definition.d.ts +8 -0
- package/dist/agents/definition.js +8 -0
- package/dist/agents/models.controller.d.ts +1 -1
- package/dist/agents/models.controller.js +2 -2
- package/dist/agents/templates.controller.d.ts +1 -1
- package/dist/agents/templates.controller.js +2 -2
- package/dist/agents/templates.d.ts +6 -8
- package/dist/agents/templates.js +20 -4
- package/dist/app.d.ts +93 -0
- package/dist/app.js +150 -0
- package/dist/catalog/admin-catalog.controller.d.ts +3 -3
- package/dist/catalog/admin-catalog.controller.js +27 -13
- package/dist/catalog/catalog-content.d.ts +185 -0
- package/dist/catalog/catalog-content.js +323 -0
- package/dist/catalog/catalog-items.d.ts +186 -0
- package/dist/catalog/catalog-items.js +410 -0
- package/dist/catalog/catalog.module.d.ts +14 -0
- package/dist/catalog/catalog.module.js +71 -0
- package/dist/catalog/catalog.service.d.ts +34 -188
- package/dist/catalog/catalog.service.js +155 -489
- package/dist/catalog/cli.d.ts +3 -2
- package/dist/catalog/cli.js +13 -31
- package/dist/catalog/job-ids.d.ts +15 -0
- package/dist/catalog/job-ids.js +15 -0
- package/dist/catalog/jobs.d.ts +27 -0
- package/dist/catalog/jobs.js +70 -0
- package/dist/catalog/locks.d.ts +24 -0
- package/dist/catalog/locks.js +63 -0
- package/dist/catalog/marketplaces.controller.d.ts +4 -4
- package/dist/catalog/marketplaces.controller.js +22 -11
- package/dist/catalog/marketplaces.service.d.ts +30 -13
- package/dist/catalog/marketplaces.service.js +330 -256
- package/dist/catalog/org-catalog.controller.d.ts +4 -3
- package/dist/catalog/org-catalog.controller.js +29 -14
- package/dist/catalog/org-catalog.service.d.ts +32 -198
- package/dist/catalog/org-catalog.service.js +159 -400
- package/dist/catalog/plugin-import.d.ts +73 -34
- package/dist/catalog/plugin-import.js +356 -217
- package/dist/catalog/plugin-install.d.ts +83 -41
- package/dist/catalog/plugin-install.js +421 -269
- package/dist/catalog/plugin-upgrade.d.ts +59 -4
- package/dist/catalog/plugin-upgrade.js +407 -282
- package/dist/catalog/requests.service.d.ts +27 -15
- package/dist/catalog/requests.service.js +201 -145
- package/dist/catalog/upgrade-targets.d.ts +4 -7
- package/dist/channels/actions.d.ts +6 -4
- package/dist/channels/actions.js +1 -1
- package/dist/channels/cards.d.ts +2 -2
- package/dist/channels/channels.module.d.ts +40 -0
- package/dist/channels/channels.module.js +128 -0
- package/dist/channels/connections.controller.d.ts +1 -1
- package/dist/channels/connections.controller.js +11 -6
- package/dist/channels/connections.service.d.ts +43 -21
- package/dist/channels/connections.service.js +236 -131
- package/dist/channels/inbound.d.ts +34 -38
- package/dist/channels/inbound.js +107 -41
- package/dist/channels/job-ids.d.ts +10 -0
- package/dist/channels/job-ids.js +10 -0
- package/dist/channels/jobs.d.ts +85 -0
- package/dist/channels/jobs.js +157 -0
- package/dist/channels/outbound.d.ts +60 -82
- package/dist/channels/outbound.js +165 -193
- package/dist/channels/supervisor.d.ts +88 -46
- package/dist/channels/supervisor.js +230 -132
- package/dist/channels/threads.d.ts +29 -8
- package/dist/channels/threads.js +71 -13
- package/dist/channels/webhook.d.ts +20 -14
- package/dist/channels/webhook.js +51 -20
- package/dist/config/load.d.ts +3 -2
- package/dist/config/load.js +3 -2
- package/dist/config/nacos-source.js +11 -2
- package/dist/config/schema.d.ts +16 -1
- package/dist/config/schema.js +54 -7
- package/dist/conversations/access.d.ts +29 -0
- package/dist/conversations/access.js +86 -0
- package/dist/conversations/artifacts.d.ts +10 -10
- package/dist/conversations/artifacts.js +35 -9
- package/dist/conversations/attachments.controller.d.ts +1 -1
- package/dist/conversations/attachments.controller.js +8 -6
- package/dist/conversations/attachments.module.d.ts +7 -0
- package/dist/conversations/attachments.module.js +25 -0
- package/dist/conversations/attachments.service.d.ts +30 -22
- package/dist/conversations/attachments.service.js +119 -46
- package/dist/conversations/conversations.controller.d.ts +8 -7
- package/dist/conversations/conversations.controller.js +59 -29
- package/dist/conversations/conversations.module.d.ts +8 -0
- package/dist/conversations/conversations.module.js +69 -0
- package/dist/conversations/conversations.service.d.ts +40 -38
- package/dist/conversations/conversations.service.js +130 -140
- package/dist/conversations/event-log.d.ts +28 -18
- package/dist/conversations/event-log.js +74 -27
- package/dist/conversations/files.d.ts +10 -10
- package/dist/conversations/files.js +37 -11
- package/dist/conversations/idem-keys.d.ts +5 -0
- package/dist/conversations/idem-keys.js +5 -0
- package/dist/conversations/job-ids.d.ts +6 -0
- package/dist/conversations/job-ids.js +6 -0
- package/dist/conversations/outputs-read.d.ts +11 -11
- package/dist/conversations/outputs-read.js +31 -7
- package/dist/conversations/queue.d.ts +48 -56
- package/dist/conversations/queue.js +13 -289
- package/dist/conversations/queue.service.d.ts +60 -0
- package/dist/conversations/queue.service.js +343 -0
- package/dist/conversations/sandbox-status.d.ts +13 -10
- package/dist/conversations/sandbox-status.js +36 -8
- package/dist/conversations/titles.d.ts +71 -24
- package/dist/conversations/titles.js +159 -62
- package/dist/ctx/bus.js +9 -3
- package/dist/ctx/create-ctx.d.ts +6 -0
- package/dist/ctx/create-ctx.js +7 -9
- package/dist/ctx/lock.d.ts +7 -3
- package/dist/ctx/lock.js +9 -20
- package/dist/ctx/queue.js +3 -2
- package/dist/ctx/secrets.d.ts +10 -2
- package/dist/ctx/secrets.js +7 -3
- package/dist/db/kernel-schema.d.ts +2 -2
- package/dist/db/pool.d.ts +5 -0
- package/dist/db/pool.js +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/evals/budget.d.ts +42 -21
- package/dist/evals/budget.js +114 -62
- package/dist/evals/evals.module.d.ts +31 -0
- package/dist/evals/evals.module.js +114 -0
- package/dist/evals/jobs.d.ts +20 -0
- package/dist/evals/jobs.js +45 -0
- package/dist/evals/run-replay.d.ts +56 -8
- package/dist/evals/run-replay.js +140 -80
- package/dist/evals/runs.controller.d.ts +1 -1
- package/dist/evals/runs.controller.js +17 -8
- package/dist/evals/runs.service.d.ts +23 -45
- package/dist/evals/runs.service.js +245 -234
- package/dist/evals/sets.controller.d.ts +1 -1
- package/dist/evals/sets.controller.js +17 -8
- package/dist/evals/sets.service.d.ts +22 -6
- package/dist/evals/sets.service.js +248 -194
- package/dist/evals/snapshot.d.ts +2 -2
- package/dist/evals/step-replay.d.ts +2 -2
- package/dist/evals/step-replay.js +1 -1
- package/dist/evals/trajectory.controller.d.ts +1 -1
- package/dist/evals/trajectory.controller.js +11 -7
- package/dist/evals/trajectory.service.d.ts +5 -4
- package/dist/evals/trajectory.service.js +30 -7
- package/dist/evals/worker.d.ts +44 -27
- package/dist/evals/worker.js +220 -120
- package/dist/extensions/admin-ops.d.ts +61 -33
- package/dist/extensions/admin-ops.js +248 -154
- package/dist/extensions/admin-view.d.ts +38 -27
- package/dist/extensions/admin-view.js +126 -55
- package/dist/extensions/approvals.d.ts +12 -8
- package/dist/extensions/approvals.js +73 -46
- package/dist/extensions/blobs.d.ts +27 -10
- package/dist/extensions/blobs.js +111 -45
- package/dist/extensions/bootstrap.d.ts +96 -42
- package/dist/extensions/bootstrap.js +303 -155
- package/dist/extensions/cli.d.ts +20 -10
- package/dist/extensions/cli.js +41 -96
- package/dist/extensions/config-issues.d.ts +7 -0
- package/dist/extensions/config-issues.js +31 -0
- package/dist/extensions/deployment-config-store.d.ts +49 -0
- package/dist/extensions/deployment-config-store.js +133 -0
- package/dist/extensions/deployment-config.d.ts +58 -67
- package/dist/extensions/deployment-config.js +202 -230
- package/dist/extensions/enabled.d.ts +5 -5
- package/dist/extensions/extensions.module.d.ts +8 -0
- package/dist/extensions/extensions.module.js +77 -0
- package/dist/extensions/hooks.d.ts +3 -2
- package/dist/extensions/hooks.js +1 -1
- package/dist/extensions/host.d.ts +72 -68
- package/dist/extensions/host.js +191 -152
- package/dist/extensions/installs.d.ts +49 -16
- package/dist/extensions/installs.js +128 -32
- package/dist/extensions/job-ids.d.ts +6 -0
- package/dist/extensions/job-ids.js +6 -0
- package/dist/extensions/lifecycle-topics.d.ts +18 -0
- package/dist/extensions/lifecycle-topics.js +18 -0
- package/dist/extensions/lifecycle.d.ts +35 -47
- package/dist/extensions/lifecycle.js +103 -39
- package/dist/extensions/locks.d.ts +33 -0
- package/dist/extensions/locks.js +86 -0
- package/dist/extensions/migrate-lock.d.ts +3 -4
- package/dist/extensions/org-config.controller.d.ts +1 -1
- package/dist/extensions/org-config.controller.js +8 -5
- package/dist/extensions/org-config.d.ts +48 -46
- package/dist/extensions/org-config.js +284 -226
- package/dist/extensions/registry.d.ts +50 -2
- package/dist/extensions/registry.js +89 -4
- package/dist/extensions/sources.d.ts +12 -8
- package/dist/extensions/sources.js +34 -13
- package/dist/files/files.module.d.ts +3 -0
- package/dist/files/files.module.js +22 -0
- package/dist/files/previewers.controller.d.ts +7 -2
- package/dist/files/previewers.controller.js +17 -9
- package/dist/files/previewers.d.ts +13 -18
- package/dist/files/previewers.js +83 -36
- package/dist/http/adapter.d.ts +3 -0
- package/dist/http/adapter.js +25 -0
- package/dist/http/api.module.d.ts +3 -110
- package/dist/http/api.module.js +47 -355
- package/dist/http/app.d.ts +11 -15
- package/dist/http/app.js +52 -47
- package/dist/http/error-filter.module.d.ts +5 -0
- package/dist/http/error-filter.module.js +18 -0
- package/dist/http/error.filter.js +2 -2
- package/dist/identity/accounts.d.ts +82 -29
- package/dist/identity/accounts.js +294 -175
- package/dist/identity/api-tokens.d.ts +11 -7
- package/dist/identity/api-tokens.js +71 -44
- package/dist/identity/audit.d.ts +7 -4
- package/dist/identity/audit.js +5 -1
- package/dist/identity/auth.controller.d.ts +11 -10
- package/dist/identity/auth.controller.js +31 -25
- package/dist/identity/auth.guard.d.ts +3 -3
- package/dist/identity/auth.guard.js +6 -4
- package/dist/identity/binding.d.ts +9 -11
- package/dist/identity/binding.js +41 -13
- package/dist/identity/enterprise-admin.d.ts +18 -12
- package/dist/identity/enterprise-admin.js +67 -13
- package/dist/identity/external-identity-lock.d.ts +17 -0
- package/dist/identity/external-identity-lock.js +39 -0
- package/dist/identity/external.d.ts +19 -15
- package/dist/identity/external.js +163 -125
- package/dist/identity/identity.module.d.ts +6 -0
- package/dist/identity/identity.module.js +56 -0
- package/dist/identity/me.controller.d.ts +16 -15
- package/dist/identity/me.controller.js +37 -31
- package/dist/identity/sessions.d.ts +12 -10
- package/dist/identity/sessions.js +74 -42
- package/dist/identity/settings.d.ts +20 -14
- package/dist/identity/settings.js +85 -57
- package/dist/identity/sso.controller.d.ts +7 -7
- package/dist/identity/sso.controller.js +13 -9
- package/dist/identity/sso.d.ts +9 -12
- package/dist/identity/sso.js +54 -24
- package/dist/inbox/inbox.module.d.ts +3 -0
- package/dist/inbox/inbox.module.js +22 -0
- package/dist/inbox/inbox.service.d.ts +7 -9
- package/dist/inbox/inbox.service.js +32 -11
- package/dist/index.d.ts +19 -12
- package/dist/index.js +13 -8
- package/dist/infra/cache/cache.module.d.ts +3 -0
- package/dist/infra/cache/cache.module.js +16 -0
- package/dist/infra/cache/cache.service.d.ts +21 -0
- package/dist/infra/cache/cache.service.js +80 -0
- package/dist/infra/cache/cacheable.decorator.d.ts +32 -0
- package/dist/infra/cache/cacheable.decorator.js +83 -0
- package/dist/infra/cli/kernel-cli.module.d.ts +66 -0
- package/dist/infra/cli/kernel-cli.module.js +165 -0
- package/dist/infra/close-within.d.ts +15 -0
- package/dist/infra/close-within.js +36 -0
- package/dist/infra/config/config.module.d.ts +13 -0
- package/dist/infra/config/config.module.js +39 -0
- package/dist/infra/config/kernel-config.service.d.ts +25 -0
- package/dist/infra/config/kernel-config.service.js +126 -0
- package/dist/infra/database/after-commit.d.ts +6 -0
- package/dist/infra/database/after-commit.js +9 -0
- package/dist/infra/database/database.module.d.ts +25 -0
- package/dist/infra/database/database.module.js +90 -0
- package/dist/infra/database/kernel-db.d.ts +22 -0
- package/dist/infra/database/kernel-db.js +65 -0
- package/dist/infra/database/transaction-host.d.ts +50 -0
- package/dist/infra/database/transaction-host.js +125 -0
- package/dist/infra/database/transactional.decorator.d.ts +15 -0
- package/dist/infra/database/transactional.decorator.js +27 -0
- package/dist/infra/database/xact-lock.d.ts +17 -0
- package/dist/infra/database/xact-lock.js +43 -0
- package/dist/infra/events/kernel-events.d.ts +96 -0
- package/dist/infra/events/kernel-events.js +65 -0
- package/dist/infra/health/health.controller.d.ts +26 -0
- package/dist/infra/health/health.controller.js +104 -0
- package/dist/infra/health/health.module.d.ts +9 -0
- package/dist/infra/health/health.module.js +24 -0
- package/dist/infra/health/health.options.d.ts +10 -0
- package/dist/infra/health/health.options.js +15 -0
- package/dist/infra/health/shutdown-state.d.ts +6 -0
- package/dist/infra/health/shutdown-state.js +2 -0
- package/dist/infra/lifecycle/graceful-shutdown.module.d.ts +6 -0
- package/dist/infra/lifecycle/graceful-shutdown.module.js +23 -0
- package/dist/infra/lifecycle/graceful-shutdown.service.d.ts +70 -0
- package/dist/infra/lifecycle/graceful-shutdown.service.js +135 -0
- package/dist/infra/lifecycle/process-guards.d.ts +52 -0
- package/dist/infra/lifecycle/process-guards.js +86 -0
- package/dist/infra/lifecycle/shutdown-signals.d.ts +30 -0
- package/dist/infra/lifecycle/shutdown-signals.js +48 -0
- package/dist/infra/lock/key-template.d.ts +13 -0
- package/dist/infra/lock/key-template.js +46 -0
- package/dist/infra/lock/lock.module.d.ts +3 -0
- package/dist/infra/lock/lock.module.js +16 -0
- package/dist/infra/lock/lock.service.d.ts +120 -0
- package/dist/infra/lock/lock.service.js +343 -0
- package/dist/infra/lock/shared-lease.d.ts +44 -0
- package/dist/infra/lock/shared-lease.js +121 -0
- package/dist/infra/lock/with-lock.decorator.d.ts +28 -0
- package/dist/infra/lock/with-lock.decorator.js +49 -0
- package/dist/infra/logging/logging.module.d.ts +16 -0
- package/dist/infra/logging/logging.module.js +34 -0
- package/dist/infra/queue/configured-processor.d.ts +18 -0
- package/dist/infra/queue/configured-processor.js +23 -0
- package/dist/infra/queue/job-failure.d.ts +18 -0
- package/dist/infra/queue/job-failure.js +26 -0
- package/dist/infra/queue/queue-shutdown.d.ts +27 -0
- package/dist/infra/queue/queue-shutdown.js +81 -0
- package/dist/infra/queue/queue.module.d.ts +16 -0
- package/dist/infra/queue/queue.module.js +61 -0
- package/dist/infra/queue/redis-connection.d.ts +7 -0
- package/dist/infra/queue/redis-connection.js +18 -0
- package/dist/infra/queue/retired-schedulers.d.ts +7 -0
- package/dist/infra/queue/retired-schedulers.js +9 -0
- package/dist/infra/queue/run-job.d.ts +16 -0
- package/dist/infra/queue/run-job.js +19 -0
- package/dist/infra/redis/redis.module.d.ts +24 -0
- package/dist/infra/redis/redis.module.js +87 -0
- package/dist/infra/schedule/cron-tick.d.ts +12 -0
- package/dist/infra/schedule/cron-tick.js +25 -0
- package/dist/infra/schedule/locked-cron.decorator.d.ts +27 -0
- package/dist/infra/schedule/locked-cron.decorator.js +53 -0
- package/dist/infra/schedule/locked-interval.d.ts +69 -0
- package/dist/infra/schedule/locked-interval.js +190 -0
- package/dist/infra/schedule/locked-tick.d.ts +90 -0
- package/dist/infra/schedule/locked-tick.js +137 -0
- package/dist/infra/schedule/schedule.module.d.ts +8 -0
- package/dist/infra/schedule/schedule.module.js +27 -0
- package/dist/infra/schedule/scheduled-runs.d.ts +69 -0
- package/dist/infra/schedule/scheduled-runs.js +163 -0
- package/dist/infra/swagger/swagger.d.ts +16 -0
- package/dist/infra/swagger/swagger.js +31 -0
- package/dist/infra/throttler/credential-throttler.d.ts +35 -0
- package/dist/infra/throttler/credential-throttler.js +77 -0
- package/dist/infra/throttler/named-throttlers.d.ts +31 -0
- package/dist/infra/throttler/named-throttlers.js +119 -0
- package/dist/infra/throttler/redis-throttler-storage.d.ts +38 -0
- package/dist/infra/throttler/redis-throttler-storage.js +130 -0
- package/dist/infra/throttler/throttler.guard.d.ts +23 -0
- package/dist/infra/throttler/throttler.guard.js +68 -0
- package/dist/infra/throttler/throttler.module.d.ts +15 -0
- package/dist/infra/throttler/throttler.module.js +53 -0
- package/dist/infra/tokens.d.ts +15 -0
- package/dist/infra/tokens.js +15 -0
- package/dist/infra/validation/create-zod-dto.d.ts +23 -0
- package/dist/infra/validation/create-zod-dto.js +35 -0
- package/dist/infra/validation/parse-with-zod.d.ts +7 -0
- package/dist/infra/validation/parse-with-zod.js +12 -0
- package/dist/infra/validation/validation.module.d.ts +3 -0
- package/dist/infra/validation/validation.module.js +16 -0
- package/dist/infra/validation/zod-params.d.ts +7 -0
- package/dist/infra/validation/zod-params.js +42 -0
- package/dist/infra/validation/zod-validation.pipe.d.ts +11 -0
- package/dist/infra/validation/zod-validation.pipe.js +31 -0
- package/dist/kernel.d.ts +9 -241
- package/dist/kernel.js +8 -1305
- package/dist/kernel.module.d.ts +17 -0
- package/dist/kernel.module.js +136 -0
- package/dist/lifecycle/kernel-lifecycle.d.ts +140 -0
- package/dist/lifecycle/kernel-lifecycle.js +451 -0
- package/dist/lifecycle/tokens.d.ts +3 -0
- package/dist/lifecycle/tokens.js +3 -0
- package/dist/log-config.d.ts +9 -0
- package/dist/log-config.js +27 -0
- package/dist/log-context.d.ts +34 -0
- package/dist/log-context.js +44 -0
- package/dist/log-loki.d.ts +34 -0
- package/dist/log-loki.js +145 -0
- package/dist/log.d.ts +41 -10
- package/dist/log.js +91 -17
- package/dist/models/models.module.d.ts +3 -0
- package/dist/models/models.module.js +24 -0
- package/dist/models/prices.d.ts +8 -2
- package/dist/models/prices.js +65 -37
- package/dist/models/purposes.controller.d.ts +1 -1
- package/dist/models/purposes.controller.js +5 -6
- package/dist/models/purposes.d.ts +35 -13
- package/dist/models/purposes.js +101 -51
- package/dist/net/public-address.js +3 -2
- package/dist/orgs/directory-sync.controller.d.ts +1 -1
- package/dist/orgs/directory-sync.controller.js +2 -2
- package/dist/orgs/directory-sync.d.ts +61 -31
- package/dist/orgs/directory-sync.js +212 -125
- package/dist/orgs/invitations.controller.d.ts +8 -8
- package/dist/orgs/invitations.controller.js +27 -26
- package/dist/orgs/invitations.service.d.ts +12 -9
- package/dist/orgs/invitations.service.js +130 -92
- package/dist/orgs/job-ids.d.ts +8 -0
- package/dist/orgs/job-ids.js +8 -0
- package/dist/orgs/members.service.d.ts +19 -12
- package/dist/orgs/members.service.js +161 -117
- package/dist/orgs/offboarding.d.ts +54 -57
- package/dist/orgs/offboarding.js +161 -71
- package/dist/orgs/org-lock.d.ts +28 -19
- package/dist/orgs/org-lock.js +81 -35
- package/dist/orgs/org.guard.d.ts +1 -1
- package/dist/orgs/org.guard.js +2 -2
- package/dist/orgs/orgs.controller.d.ts +13 -13
- package/dist/orgs/orgs.controller.js +29 -17
- package/dist/orgs/orgs.module.d.ts +6 -0
- package/dist/orgs/orgs.module.js +68 -0
- package/dist/orgs/orgs.service.d.ts +30 -19
- package/dist/orgs/orgs.service.js +139 -86
- package/dist/orgs/settings.controller.d.ts +1 -1
- package/dist/orgs/settings.controller.js +8 -6
- package/dist/orgs/settings.service.d.ts +14 -8
- package/dist/orgs/settings.service.js +75 -46
- package/dist/overrides.d.ts +56 -0
- package/dist/overrides.js +30 -0
- package/dist/roles/gateway-role.d.ts +43 -0
- package/dist/roles/gateway-role.js +98 -0
- package/dist/roles/gateway.module.d.ts +8 -0
- package/dist/roles/gateway.module.js +28 -0
- package/dist/roles/worker-role.d.ts +63 -0
- package/dist/roles/worker-role.js +144 -0
- package/dist/roles/worker.module.d.ts +14 -0
- package/dist/roles/worker.module.js +90 -0
- package/dist/runtime/abort.js +19 -2
- package/dist/runtime/checkpointer.d.ts +8 -3
- package/dist/runtime/checkpointer.js +87 -55
- package/dist/runtime/conversation-lock.d.ts +20 -11
- package/dist/runtime/conversation-lock.js +62 -72
- package/dist/runtime/hook-calls.d.ts +3 -3
- package/dist/runtime/hook-calls.js +1 -6
- package/dist/runtime/job-ids.d.ts +10 -0
- package/dist/runtime/job-ids.js +10 -0
- package/dist/runtime/langgraph-loop.js +1 -1
- package/dist/runtime/loop.d.ts +7 -0
- package/dist/runtime/loop.js +14 -0
- package/dist/runtime/maintenance-queue.d.ts +10 -0
- package/dist/runtime/maintenance-queue.js +10 -0
- package/dist/runtime/maintenance.d.ts +45 -74
- package/dist/runtime/maintenance.js +139 -136
- package/dist/runtime/model-call.d.ts +7 -4
- package/dist/runtime/model-call.js +24 -57
- package/dist/runtime/providers.d.ts +17 -6
- package/dist/runtime/providers.js +66 -25
- package/dist/runtime/quota.d.ts +17 -3
- package/dist/runtime/quota.js +27 -2
- package/dist/runtime/retired-schedulers.d.ts +7 -0
- package/dist/runtime/retired-schedulers.js +31 -0
- package/dist/runtime/run-control.d.ts +33 -0
- package/dist/runtime/run-control.js +77 -0
- package/dist/runtime/run-finish.d.ts +26 -17
- package/dist/runtime/run-finish.js +98 -51
- package/dist/runtime/run-jobs.d.ts +48 -0
- package/dist/runtime/run-jobs.js +95 -0
- package/dist/runtime/run-processors.d.ts +60 -0
- package/dist/runtime/run-processors.js +185 -0
- package/dist/runtime/run-queue.d.ts +32 -57
- package/dist/runtime/run-queue.js +291 -295
- package/dist/runtime/run-steps.d.ts +39 -0
- package/dist/runtime/run-steps.js +96 -0
- package/dist/runtime/run-worker.d.ts +121 -75
- package/dist/runtime/run-worker.js +340 -225
- package/dist/runtime/runtime.module.d.ts +10 -0
- package/dist/runtime/runtime.module.js +112 -0
- package/dist/runtime/step-log.d.ts +40 -0
- package/dist/runtime/step-log.js +94 -0
- package/dist/runtime/stream.controller.d.ts +5 -8
- package/dist/runtime/stream.controller.js +14 -12
- package/dist/runtime/stream.d.ts +24 -6
- package/dist/runtime/stream.js +78 -7
- package/dist/runtime/subagents/child.d.ts +3 -44
- package/dist/runtime/subagents/child.js +0 -79
- package/dist/runtime/subagents/definition.d.ts +4 -4
- package/dist/runtime/subagents/definition.js +2 -2
- package/dist/runtime/subagents/dispatch.d.ts +87 -6
- package/dist/runtime/subagents/dispatch.js +135 -140
- package/dist/runtime/subagents/limits.d.ts +9 -4
- package/dist/runtime/subagents/limits.js +4 -2
- package/dist/runtime/subagents/org-agents.d.ts +4 -4
- package/dist/runtime/subagents/org-agents.js +1 -1
- package/dist/runtime/subagents/progress.d.ts +2 -2
- package/dist/runtime/subagents/roster.d.ts +3 -3
- package/dist/runtime/subagents/roster.js +1 -1
- package/dist/runtime/subagents/skill-fork.d.ts +6 -7
- package/dist/runtime/subagents/skill-fork.js +16 -5
- package/dist/runtime/subagents/subagents.service.d.ts +40 -0
- package/dist/runtime/subagents/subagents.service.js +101 -0
- package/dist/runtime/usage-report.d.ts +94 -52
- package/dist/runtime/usage-report.js +200 -99
- package/dist/runtime/waits.d.ts +125 -57
- package/dist/runtime/waits.js +431 -208
- package/dist/sandbox/job-ids.d.ts +10 -0
- package/dist/sandbox/job-ids.js +10 -0
- package/dist/sandbox/jobs.d.ts +41 -0
- package/dist/sandbox/jobs.js +113 -0
- package/dist/sandbox/leases.d.ts +39 -31
- package/dist/sandbox/leases.js +117 -257
- package/dist/sandbox/providers.d.ts +19 -0
- package/dist/sandbox/providers.js +38 -0
- package/dist/sandbox/sandbox.module.d.ts +8 -0
- package/dist/sandbox/sandbox.module.js +30 -0
- package/dist/sandbox/sandbox.service.d.ts +24 -29
- package/dist/sandbox/sandbox.service.js +106 -75
- package/dist/skills/catalog.d.ts +33 -20
- package/dist/skills/catalog.js +96 -53
- package/dist/skills/changes.controller.d.ts +4 -4
- package/dist/skills/changes.controller.js +15 -9
- package/dist/skills/changes.service.d.ts +28 -18
- package/dist/skills/changes.service.js +421 -364
- package/dist/skills/invocable.controller.d.ts +1 -1
- package/dist/skills/invocable.controller.js +8 -5
- package/dist/skills/invoke.d.ts +16 -15
- package/dist/skills/invoke.js +48 -17
- package/dist/skills/skills.controller.d.ts +1 -1
- package/dist/skills/skills.controller.js +11 -6
- package/dist/skills/skills.module.d.ts +6 -0
- package/dist/skills/skills.module.js +30 -0
- package/dist/skills/skills.service.d.ts +47 -30
- package/dist/skills/skills.service.js +221 -158
- package/dist/storage/job-ids.d.ts +6 -0
- package/dist/storage/job-ids.js +6 -0
- package/dist/storage/purge.d.ts +22 -7
- package/dist/storage/purge.js +55 -3
- package/dist/storage/storage.module.d.ts +6 -0
- package/dist/storage/storage.module.js +23 -0
- package/dist/storage/storage.service.d.ts +18 -4
- package/dist/storage/storage.service.js +44 -3
- package/dist/tools/approvals.controller.d.ts +1 -1
- package/dist/tools/approvals.controller.js +12 -8
- package/dist/tools/approvals.service.d.ts +27 -25
- package/dist/tools/approvals.service.js +324 -258
- package/dist/tools/breaker.d.ts +2 -2
- package/dist/tools/breaker.js +21 -3
- package/dist/tools/builtins/ask-user.d.ts +2 -2
- package/dist/tools/builtins/ask-user.js +1 -1
- package/dist/tools/builtins/builtin-tools.d.ts +21 -0
- package/dist/tools/builtins/builtin-tools.js +80 -0
- package/dist/tools/builtins/read-tool-output.d.ts +2 -2
- package/dist/tools/builtins/read-tool-output.js +1 -1
- package/dist/tools/builtins/sandbox/bash.js +2 -2
- package/dist/tools/builtins/sandbox/common.d.ts +7 -4
- package/dist/tools/builtins/sandbox/common.js +5 -3
- package/dist/tools/builtins/sandbox/deliver.js +2 -2
- package/dist/tools/builtins/sandbox/edit.js +1 -1
- package/dist/tools/builtins/sandbox/glob.js +2 -2
- package/dist/tools/builtins/sandbox/grep.js +2 -2
- package/dist/tools/builtins/sandbox/read.js +1 -1
- package/dist/tools/builtins/sandbox/write.js +1 -1
- package/dist/tools/builtins/skill-load.d.ts +3 -3
- package/dist/tools/builtins/skill-load.js +1 -1
- package/dist/tools/canonical-json.d.ts +6 -0
- package/dist/tools/canonical-json.js +16 -0
- package/dist/tools/connector-content.controller.d.ts +1 -1
- package/dist/tools/connector-content.controller.js +11 -6
- package/dist/tools/connector-content.d.ts +12 -15
- package/dist/tools/connector-content.js +53 -18
- package/dist/tools/connectors.controller.d.ts +1 -1
- package/dist/tools/connectors.controller.js +21 -10
- package/dist/tools/connectors.service.d.ts +31 -13
- package/dist/tools/connectors.service.js +434 -344
- package/dist/tools/credentials.d.ts +17 -20
- package/dist/tools/credentials.js +55 -34
- package/dist/tools/drift.d.ts +29 -41
- package/dist/tools/drift.js +111 -79
- package/dist/tools/info.d.ts +12 -9
- package/dist/tools/info.js +33 -7
- package/dist/tools/job-ids.d.ts +13 -0
- package/dist/tools/job-ids.js +15 -0
- package/dist/tools/jobs.d.ts +24 -0
- package/dist/tools/jobs.js +69 -0
- package/dist/tools/oauth/flow-paths.d.ts +16 -0
- package/dist/tools/oauth/flow-paths.js +38 -0
- package/dist/tools/oauth/flow.d.ts +30 -30
- package/dist/tools/oauth/flow.js +109 -81
- package/dist/tools/oauth/oauth.controller.d.ts +3 -3
- package/dist/tools/oauth/oauth.controller.js +12 -9
- package/dist/tools/oauth/tokens.d.ts +11 -3
- package/dist/tools/oauth/tokens.js +15 -14
- package/dist/tools/outputs.d.ts +13 -4
- package/dist/tools/outputs.js +47 -3
- package/dist/tools/pipeline.d.ts +62 -46
- package/dist/tools/pipeline.js +145 -64
- package/dist/tools/policy.d.ts +2 -2
- package/dist/tools/questions.controller.d.ts +1 -1
- package/dist/tools/questions.controller.js +8 -5
- package/dist/tools/questions.service.d.ts +25 -24
- package/dist/tools/questions.service.js +201 -142
- package/dist/tools/registry.d.ts +30 -20
- package/dist/tools/registry.js +73 -37
- package/dist/tools/specs.d.ts +5 -4
- package/dist/tools/specs.js +3 -1
- package/dist/tools/tools.module.d.ts +10 -0
- package/dist/tools/tools.module.js +91 -0
- package/dist/ui/call-extension.d.ts +16 -0
- package/dist/ui/call-extension.js +38 -0
- package/dist/ui/embed-token.d.ts +9 -5
- package/dist/ui/embed-token.js +34 -6
- package/dist/ui/ui-stream.d.ts +22 -17
- package/dist/ui/ui-stream.js +72 -12
- package/dist/ui/ui.module.d.ts +7 -0
- package/dist/ui/ui.module.js +28 -0
- package/dist/ui/views.controller.d.ts +14 -12
- package/dist/ui/views.controller.js +32 -25
- package/dist/ui/views.d.ts +9 -57
- package/dist/ui/views.js +43 -45
- package/package.json +11 -2
- package/dist/http/health.controller.d.ts +0 -26
- package/dist/http/health.controller.js +0 -79
- package/dist/http/tokens.d.ts +0 -131
- package/dist/http/tokens.js +0 -131
- package/dist/http/zod.pipe.d.ts +0 -8
- package/dist/http/zod.pipe.js +0 -14
- package/dist/identity/rate-limit.d.ts +0 -55
- package/dist/identity/rate-limit.js +0 -121
- package/dist/sandbox/lifecycle.d.ts +0 -21
- package/dist/sandbox/lifecycle.js +0 -68
- /package/dist/http/{gateway.module.d.ts → gateway-routes.d.ts} +0 -0
- /package/dist/http/{gateway.module.js → gateway-routes.js} +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Inject, Injectable, } from "@nestjs/common";
|
|
11
|
+
import { DiscoveryService, MetadataScanner } from "@nestjs/core";
|
|
12
|
+
import { SchedulerRegistry } from "@nestjs/schedule";
|
|
13
|
+
import { waitWithin } from "../close-within.js";
|
|
14
|
+
import { runOutsideTransaction } from "../database/transaction-host.js";
|
|
15
|
+
import { GracefulShutdown } from "../lifecycle/graceful-shutdown.service.js";
|
|
16
|
+
import { KERNEL_LOGGER } from "../tokens.js";
|
|
17
|
+
import { LockedIntervalScheduler } from "./locked-interval.js";
|
|
18
|
+
import { LOCKED_JOB, LOCKED_JOB_BODY, SCHEDULED_RUNS, } from "./locked-tick.js";
|
|
19
|
+
/**
|
|
20
|
+
* The runs of the `@LockedCron` / `@LockedInterval` jobs (ticks and `runOnStart` runs), their
|
|
21
|
+
* start gate ({@link open}) and their stop. At module init it attaches itself to every provider instance with a locked job
|
|
22
|
+
* (`SCHEDULED_RUNS`), so each run is tracked and gets {@link signal}. {@link stop} (idempotent)
|
|
23
|
+
* stops the cron and interval timers, aborts the signal, refuses new runs and waits for the runs in
|
|
24
|
+
* progress within its budget, capped by what is left of the shared stop deadline
|
|
25
|
+
* (`GracefulShutdown`): the runs end (and release their locks) before Redis and the pool close.
|
|
26
|
+
* Runs over the budget are abandoned with a WARN; the job's own failures after the stop began are
|
|
27
|
+
* logged at WARN / INFO, never ERROR. The kernel's stop phases call it through the worker role
|
|
28
|
+
* (`WorkerRole.stopSchedules`, awaited in the runs phase, before the extension host closes: jobs may
|
|
29
|
+
* call extensions); `beforeApplicationShutdown` covers an application without them.
|
|
30
|
+
*/
|
|
31
|
+
let ScheduledRuns = class ScheduledRuns {
|
|
32
|
+
discovery;
|
|
33
|
+
scanner;
|
|
34
|
+
crons;
|
|
35
|
+
intervals;
|
|
36
|
+
shutdown;
|
|
37
|
+
log;
|
|
38
|
+
abort = new AbortController();
|
|
39
|
+
inFlight = new Set();
|
|
40
|
+
stopping;
|
|
41
|
+
gateOpen = false;
|
|
42
|
+
/** Each locked job's body by job name, bound to its instance (for {@link runNow}). */
|
|
43
|
+
bodies = new Map();
|
|
44
|
+
constructor(discovery, scanner, crons, intervals, shutdown, log) {
|
|
45
|
+
this.discovery = discovery;
|
|
46
|
+
this.scanner = scanner;
|
|
47
|
+
this.crons = crons;
|
|
48
|
+
this.intervals = intervals;
|
|
49
|
+
this.shutdown = shutdown;
|
|
50
|
+
this.log = log;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Attaches itself to every instance with a locked job in `onModuleInit`, which runs before any
|
|
54
|
+
* `onApplicationBootstrap` hook, so the start gate already guards the jobs when the cron
|
|
55
|
+
* orchestrator and the interval scheduler arm their timers there.
|
|
56
|
+
*/
|
|
57
|
+
onModuleInit() {
|
|
58
|
+
for (const wrapper of this.discovery.getProviders()) {
|
|
59
|
+
const instance = wrapper.instance;
|
|
60
|
+
if (!instance || typeof instance !== "object" || !wrapper.isDependencyTreeStatic())
|
|
61
|
+
continue;
|
|
62
|
+
const proto = Object.getPrototypeOf(instance);
|
|
63
|
+
if (!proto)
|
|
64
|
+
continue;
|
|
65
|
+
let hasJob = false;
|
|
66
|
+
for (const m of this.scanner.getAllMethodNames(proto)) {
|
|
67
|
+
const fn = instance[m];
|
|
68
|
+
if (typeof fn !== "function" || Reflect.getMetadata(LOCKED_JOB, fn) !== true)
|
|
69
|
+
continue;
|
|
70
|
+
hasJob = true;
|
|
71
|
+
const job = Reflect.getMetadata(LOCKED_JOB_BODY, fn);
|
|
72
|
+
if (job)
|
|
73
|
+
this.bodies.set(job.name, (signal) => job.body.call(instance, signal));
|
|
74
|
+
}
|
|
75
|
+
if (hasJob) {
|
|
76
|
+
Object.defineProperty(instance, SCHEDULED_RUNS, { value: this, configurable: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Runs the body of the locked job named `name` once, now, outside its schedule: no tick marker
|
|
82
|
+
* and no run lock, in a fresh async context (no transaction), with the stop signal. A job's name
|
|
83
|
+
* enqueued on the maintenance queue runs this way, as the maintenance handlers of the periodic
|
|
84
|
+
* jobs did before they became locked jobs. Resolves false when no job has that name; the body's
|
|
85
|
+
* error is thrown.
|
|
86
|
+
*/
|
|
87
|
+
async runNow(name) {
|
|
88
|
+
const body = this.bodies.get(name);
|
|
89
|
+
if (!body)
|
|
90
|
+
return false;
|
|
91
|
+
await runOutsideTransaction(() => Promise.resolve(body(this.abort.signal)));
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/** The start gate is open: ticks and start runs may run. */
|
|
95
|
+
get opened() {
|
|
96
|
+
return this.gateOpen && this.stopping === undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Opens the start gate. Until this is called every tick and start run skips at INFO: the
|
|
100
|
+
* cron and interval timers are armed at application bootstrap, which runs before the kernel's
|
|
101
|
+
* start sequence (migrations, bootstrap, extension load). The kernel's worker role opens it once
|
|
102
|
+
* its queue consumers started (then starts the `runOnStart` runs); a stop closes it again.
|
|
103
|
+
*/
|
|
104
|
+
open() {
|
|
105
|
+
if (this.stopping === undefined)
|
|
106
|
+
this.gateOpen = true;
|
|
107
|
+
}
|
|
108
|
+
get stopped() {
|
|
109
|
+
return this.stopping !== undefined;
|
|
110
|
+
}
|
|
111
|
+
get signal() {
|
|
112
|
+
return this.abort.signal;
|
|
113
|
+
}
|
|
114
|
+
/** The number of runs in progress. */
|
|
115
|
+
get inFlightCount() {
|
|
116
|
+
return this.inFlight.size;
|
|
117
|
+
}
|
|
118
|
+
track(p) {
|
|
119
|
+
this.inFlight.add(p);
|
|
120
|
+
const forget = () => {
|
|
121
|
+
this.inFlight.delete(p);
|
|
122
|
+
};
|
|
123
|
+
p.then(forget, forget);
|
|
124
|
+
return p;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Stops the schedulers and waits for the runs in progress, at most `budgetMs` (capped by the
|
|
128
|
+
* shared stop deadline). Idempotent: later calls return the first call's promise.
|
|
129
|
+
*/
|
|
130
|
+
stop(budgetMs) {
|
|
131
|
+
this.stopping ??= this.runStop(budgetMs);
|
|
132
|
+
return this.stopping;
|
|
133
|
+
}
|
|
134
|
+
async beforeApplicationShutdown() {
|
|
135
|
+
await this.stop(this.shutdown.remainingMs());
|
|
136
|
+
}
|
|
137
|
+
async runStop(budgetMs) {
|
|
138
|
+
this.gateOpen = false;
|
|
139
|
+
for (const job of this.crons.getCronJobs().values())
|
|
140
|
+
void job.stop();
|
|
141
|
+
this.intervals.stopAll();
|
|
142
|
+
this.abort.abort(new Error("the scheduler is stopping"));
|
|
143
|
+
if (this.inFlight.size === 0)
|
|
144
|
+
return;
|
|
145
|
+
const waitMs = Math.max(0, Math.min(budgetMs, this.shutdown.remainingMs()));
|
|
146
|
+
if (!(await waitWithin(Promise.allSettled([...this.inFlight]), waitMs))) {
|
|
147
|
+
this.log.warn("shutdown: scheduled jobs did not stop in time; abandoned", {
|
|
148
|
+
inFlight: this.inFlight.size,
|
|
149
|
+
budgetMs: waitMs,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
ScheduledRuns = __decorate([
|
|
155
|
+
Injectable(),
|
|
156
|
+
__param(0, Inject(DiscoveryService)),
|
|
157
|
+
__param(1, Inject(MetadataScanner)),
|
|
158
|
+
__param(2, Inject(SchedulerRegistry)),
|
|
159
|
+
__param(3, Inject(LockedIntervalScheduler)),
|
|
160
|
+
__param(4, Inject(GracefulShutdown)),
|
|
161
|
+
__param(5, Inject(KERNEL_LOGGER))
|
|
162
|
+
], ScheduledRuns);
|
|
163
|
+
export { ScheduledRuns };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { INestApplication } from "@nestjs/common";
|
|
2
|
+
import { type OpenAPIObject } from "@nestjs/swagger";
|
|
3
|
+
/** A `$ref` to a named DTO's schema, for `@ApiBody({ schema: dtoRef(Dto) })` and friends. */
|
|
4
|
+
export declare function dtoRef(dto: {
|
|
5
|
+
readonly dtoName: string | undefined;
|
|
6
|
+
}): {
|
|
7
|
+
$ref: string;
|
|
8
|
+
};
|
|
9
|
+
/** Builds the OpenAPI document: the routes Nest found plus every named zod DTO's JSON Schema. */
|
|
10
|
+
export declare function buildSwaggerDocument(app: INestApplication, version: string): OpenAPIObject;
|
|
11
|
+
/**
|
|
12
|
+
* Serves the swagger UI at `/docs` (JSON at `/docs-json`) when `enabled`; otherwise does nothing
|
|
13
|
+
* and builds no document. The routes sit outside Nest's guards, so they are unauthenticated: the
|
|
14
|
+
* option is for development and trials (`http.swagger`, default false).
|
|
15
|
+
*/
|
|
16
|
+
export declare function setupSwagger(app: INestApplication, enabled: boolean, version?: string): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
|
2
|
+
import { registeredZodDtos } from "../validation/create-zod-dto.js";
|
|
3
|
+
/** A `$ref` to a named DTO's schema, for `@ApiBody({ schema: dtoRef(Dto) })` and friends. */
|
|
4
|
+
export function dtoRef(dto) {
|
|
5
|
+
if (dto.dtoName === undefined)
|
|
6
|
+
throw new Error("dtoRef: the DTO has no name");
|
|
7
|
+
return { $ref: `#/components/schemas/${dto.dtoName}` };
|
|
8
|
+
}
|
|
9
|
+
/** Builds the OpenAPI document: the routes Nest found plus every named zod DTO's JSON Schema. */
|
|
10
|
+
export function buildSwaggerDocument(app, version) {
|
|
11
|
+
const doc = SwaggerModule.createDocument(app, new DocumentBuilder().setTitle("Capybara kernel API").setVersion(version).build());
|
|
12
|
+
const schemas = { ...(doc.components?.schemas ?? {}) };
|
|
13
|
+
for (const dto of registeredZodDtos()) {
|
|
14
|
+
if (dto.dtoName !== undefined)
|
|
15
|
+
schemas[dto.dtoName] = dto.jsonSchema();
|
|
16
|
+
}
|
|
17
|
+
doc.components = { ...doc.components, schemas: schemas };
|
|
18
|
+
return doc;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Serves the swagger UI at `/docs` (JSON at `/docs-json`) when `enabled`; otherwise does nothing
|
|
22
|
+
* and builds no document. The routes sit outside Nest's guards, so they are unauthenticated: the
|
|
23
|
+
* option is for development and trials (`http.swagger`, default false).
|
|
24
|
+
*/
|
|
25
|
+
export function setupSwagger(app, enabled, version = "0") {
|
|
26
|
+
if (!enabled)
|
|
27
|
+
return;
|
|
28
|
+
SwaggerModule.setup("docs", app, buildSwaggerDocument(app, version), {
|
|
29
|
+
jsonDocumentUrl: "docs-json",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ThrottlerStorage } from "@nestjs/throttler";
|
|
2
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
3
|
+
import { type NamedThrottler } from "./named-throttlers.js";
|
|
4
|
+
/**
|
|
5
|
+
* The Redis key of one counter. All counters of a credential group share the hash tag
|
|
6
|
+
* `{cred:<group>}`, so the keys of one reservation live in one Redis Cluster slot (one EVAL, no
|
|
7
|
+
* CROSSSLOT).
|
|
8
|
+
*/
|
|
9
|
+
export declare const credentialRedisKey: (t: NamedThrottler, tracker: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Credential-attempt limiting for handlers (login, change password, setup token, invitation
|
|
12
|
+
* accept), on the named-throttler table and the Redis storage.
|
|
13
|
+
* Call it inside the handler after pipes and preconditions, as the limiter is called today:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* await this.credentials.reserve(["login", "login-ip", "login-account"], req, reply);
|
|
17
|
+
* ... check the credentials ...
|
|
18
|
+
* await this.credentials.succeeded(["login", "login-ip", "login-account"], req);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* `reserve` is one EVAL across the keys, all or nothing: a request rejected on one dimension is
|
|
22
|
+
* counted on none, and `retry-after` is the largest remaining TTL among the keys at their limit.
|
|
23
|
+
* `succeeded` is one EVAL: `clear` throttlers lose their counter, `refund` ones one attempt.
|
|
24
|
+
* FAIL-CLOSED: a Redis error propagates (a 500).
|
|
25
|
+
*/
|
|
26
|
+
export declare class CredentialThrottler {
|
|
27
|
+
private readonly storage;
|
|
28
|
+
constructor(storage: ThrottlerStorage);
|
|
29
|
+
/** Reserves one attempt on every named throttler or throws 429 `auth.rate_limited` (header set on `reply`). */
|
|
30
|
+
reserve(names: readonly string[], req: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
31
|
+
/** A successful attempt: clears the `clear` counters and refunds one on the `refund` ones. */
|
|
32
|
+
succeeded(names: readonly string[], req: FastifyRequest): Promise<void>;
|
|
33
|
+
private resolve;
|
|
34
|
+
private redisStorage;
|
|
35
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
11
|
+
import { getStorageToken } from "@nestjs/throttler";
|
|
12
|
+
import { ApiError } from "../../errors.js";
|
|
13
|
+
import { NAMED_THROTTLERS, throttlerKey } from "./named-throttlers.js";
|
|
14
|
+
/**
|
|
15
|
+
* The Redis key of one counter. All counters of a credential group share the hash tag
|
|
16
|
+
* `{cred:<group>}`, so the keys of one reservation live in one Redis Cluster slot (one EVAL, no
|
|
17
|
+
* CROSSSLOT).
|
|
18
|
+
*/
|
|
19
|
+
export const credentialRedisKey = (t, tracker) => `throttler:{cred:${t.group}}:${t.name}:${throttlerKey(t.name, tracker)}`;
|
|
20
|
+
/**
|
|
21
|
+
* Credential-attempt limiting for handlers (login, change password, setup token, invitation
|
|
22
|
+
* accept), on the named-throttler table and the Redis storage.
|
|
23
|
+
* Call it inside the handler after pipes and preconditions, as the limiter is called today:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* await this.credentials.reserve(["login", "login-ip", "login-account"], req, reply);
|
|
27
|
+
* ... check the credentials ...
|
|
28
|
+
* await this.credentials.succeeded(["login", "login-ip", "login-account"], req);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* `reserve` is one EVAL across the keys, all or nothing: a request rejected on one dimension is
|
|
32
|
+
* counted on none, and `retry-after` is the largest remaining TTL among the keys at their limit.
|
|
33
|
+
* `succeeded` is one EVAL: `clear` throttlers lose their counter, `refund` ones one attempt.
|
|
34
|
+
* FAIL-CLOSED: a Redis error propagates (a 500).
|
|
35
|
+
*/
|
|
36
|
+
let CredentialThrottler = class CredentialThrottler {
|
|
37
|
+
storage;
|
|
38
|
+
constructor(storage) {
|
|
39
|
+
this.storage = storage;
|
|
40
|
+
}
|
|
41
|
+
/** Reserves one attempt on every named throttler or throws 429 `auth.rate_limited` (header set on `reply`). */
|
|
42
|
+
async reserve(names, req, reply) {
|
|
43
|
+
const ts = this.resolve(names);
|
|
44
|
+
const r = await this.redisStorage().reserve(ts.map((t) => credentialRedisKey(t, t.getTracker(req))), ts.map((t) => t.limit), ts[0]?.ttl ?? 0);
|
|
45
|
+
if (r.allowed)
|
|
46
|
+
return;
|
|
47
|
+
reply.header("retry-after", String(r.retryAfterSeconds));
|
|
48
|
+
throw new ApiError("auth.rate_limited", ts[0]?.message ?? "");
|
|
49
|
+
}
|
|
50
|
+
/** A successful attempt: clears the `clear` counters and refunds one on the `refund` ones. */
|
|
51
|
+
async succeeded(names, req) {
|
|
52
|
+
const ts = this.resolve(names);
|
|
53
|
+
const key = (t) => credentialRedisKey(t, t.getTracker(req));
|
|
54
|
+
await this.redisStorage().succeeded(ts.filter((t) => t.onSuccess === "clear").map(key), ts.filter((t) => t.onSuccess === "refund").map(key));
|
|
55
|
+
}
|
|
56
|
+
resolve(names) {
|
|
57
|
+
const ts = names.map((n) => {
|
|
58
|
+
const t = NAMED_THROTTLERS.find((x) => x.name === n);
|
|
59
|
+
if (!t)
|
|
60
|
+
throw new Error(`unknown throttler "${n}"`);
|
|
61
|
+
return t;
|
|
62
|
+
});
|
|
63
|
+
const group = ts[0]?.group;
|
|
64
|
+
if (!group || ts.some((t) => t.group !== group || t.ttl !== ts[0]?.ttl)) {
|
|
65
|
+
throw new Error("credential throttlers of one call must share one group and window");
|
|
66
|
+
}
|
|
67
|
+
return ts;
|
|
68
|
+
}
|
|
69
|
+
redisStorage() {
|
|
70
|
+
return this.storage;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
CredentialThrottler = __decorate([
|
|
74
|
+
Injectable(),
|
|
75
|
+
__param(0, Inject(getStorageToken()))
|
|
76
|
+
], CredentialThrottler);
|
|
77
|
+
export { CredentialThrottler };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FastifyRequest } from "fastify";
|
|
2
|
+
/** The counting window of every credential throttler, ms (first attempt starts it). */
|
|
3
|
+
export declare const CREDENTIAL_WINDOW_MS = 900000;
|
|
4
|
+
/** A named throttler: a limit over a tracker, answering 429 `auth.rate_limited` with `message`. */
|
|
5
|
+
export interface NamedThrottler {
|
|
6
|
+
name: string;
|
|
7
|
+
limit: number;
|
|
8
|
+
ttl: number;
|
|
9
|
+
/** The 429 message (today's text of the route that used the limiter). */
|
|
10
|
+
message: string;
|
|
11
|
+
/** The credential group: throttlers of one group are reserved together (one Redis hash tag). */
|
|
12
|
+
group: string;
|
|
13
|
+
/** What a successful attempt does to this counter: delete it, or give one attempt back. */
|
|
14
|
+
onSuccess: "clear" | "refund";
|
|
15
|
+
getTracker: (req: FastifyRequest) => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Every rate limit of the kernel today, as named throttlers (spec §2.4). The credential routes
|
|
19
|
+
* reserve theirs through `CredentialThrottler` inside the handler (one group per route, all or
|
|
20
|
+
* nothing); a plain per-route limit would opt in with `@Throttled(...names)`.
|
|
21
|
+
*
|
|
22
|
+
* | route | names (reserved via `CredentialThrottler`) |
|
|
23
|
+
* |---|---|
|
|
24
|
+
* | `POST /api/v1/auth/login` | `login`, `login-ip`, `login-account` |
|
|
25
|
+
* | `PUT /api/v1/me/password` | `change-password`, `change-password-ip` |
|
|
26
|
+
* | `POST /api/v1/setup` | `setup-token`, `setup-ip` |
|
|
27
|
+
* | `POST /api/v1/invitations/:token/accept` | `invitation`, `invitation-ip` |
|
|
28
|
+
*/
|
|
29
|
+
export declare const NAMED_THROTTLERS: readonly NamedThrottler[];
|
|
30
|
+
/** The storage key of a named throttler's counter for a tracker (shared by the guard and success paths). */
|
|
31
|
+
export declare const throttlerKey: (name: string, tracker: string) => string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** The counting window of every credential throttler, ms (first attempt starts it). */
|
|
3
|
+
export const CREDENTIAL_WINDOW_MS = 900_000;
|
|
4
|
+
const sha256 = (s) => createHash("sha256").update(s).digest("hex");
|
|
5
|
+
/** Logins are normalized the way local accounts are (no format check): case / space variants share a count. */
|
|
6
|
+
const loginHash = (login) => sha256(login.normalize("NFKC").trim().toLowerCase());
|
|
7
|
+
const bodyLogin = (req) => {
|
|
8
|
+
const b = req.body;
|
|
9
|
+
const login = typeof b === "object" && b !== null ? b.login : undefined;
|
|
10
|
+
return typeof login === "string" ? login : "";
|
|
11
|
+
};
|
|
12
|
+
/** The client address: Fastify's `request.ip`, which already honors `http.trustProxy`. */
|
|
13
|
+
const ip = (req) => req.ip;
|
|
14
|
+
const principalAccountId = (req) => {
|
|
15
|
+
const p = req.principal;
|
|
16
|
+
return p?.account?.id ?? "";
|
|
17
|
+
};
|
|
18
|
+
const routeToken = (req) => String(req.params?.token ?? "");
|
|
19
|
+
const LOGIN_MSG = "Too many failed login attempts; try again later";
|
|
20
|
+
/**
|
|
21
|
+
* Every rate limit of the kernel today, as named throttlers (spec §2.4). The credential routes
|
|
22
|
+
* reserve theirs through `CredentialThrottler` inside the handler (one group per route, all or
|
|
23
|
+
* nothing); a plain per-route limit would opt in with `@Throttled(...names)`.
|
|
24
|
+
*
|
|
25
|
+
* | route | names (reserved via `CredentialThrottler`) |
|
|
26
|
+
* |---|---|
|
|
27
|
+
* | `POST /api/v1/auth/login` | `login`, `login-ip`, `login-account` |
|
|
28
|
+
* | `PUT /api/v1/me/password` | `change-password`, `change-password-ip` |
|
|
29
|
+
* | `POST /api/v1/setup` | `setup-token`, `setup-ip` |
|
|
30
|
+
* | `POST /api/v1/invitations/:token/accept` | `invitation`, `invitation-ip` |
|
|
31
|
+
*/
|
|
32
|
+
export const NAMED_THROTTLERS = [
|
|
33
|
+
// (login, IP): 10; an attacker filling one login from one IP leaves the owner's other IPs alone (R25)
|
|
34
|
+
{
|
|
35
|
+
name: "login",
|
|
36
|
+
group: "login",
|
|
37
|
+
onSuccess: "clear",
|
|
38
|
+
limit: 10,
|
|
39
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
40
|
+
message: LOGIN_MSG,
|
|
41
|
+
getTracker: (req) => `${loginHash(bodyLogin(req))}:${sha256(ip(req))}`,
|
|
42
|
+
},
|
|
43
|
+
// client IP across logins (credential stuffing): 50
|
|
44
|
+
{
|
|
45
|
+
name: "login-ip",
|
|
46
|
+
group: "login",
|
|
47
|
+
onSuccess: "refund",
|
|
48
|
+
limit: 50,
|
|
49
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
50
|
+
message: LOGIN_MSG,
|
|
51
|
+
getTracker: ip,
|
|
52
|
+
},
|
|
53
|
+
// one login across IPs (distributed guessing): 100
|
|
54
|
+
{
|
|
55
|
+
name: "login-account",
|
|
56
|
+
group: "login",
|
|
57
|
+
onSuccess: "clear",
|
|
58
|
+
limit: 100,
|
|
59
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
60
|
+
message: LOGIN_MSG,
|
|
61
|
+
getTracker: (req) => loginHash(bodyLogin(req)),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "change-password",
|
|
65
|
+
group: "change-password",
|
|
66
|
+
onSuccess: "clear",
|
|
67
|
+
limit: 10,
|
|
68
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
69
|
+
message: "Too many failed password attempts; try again later",
|
|
70
|
+
getTracker: (req) => sha256(principalAccountId(req)),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "change-password-ip",
|
|
74
|
+
group: "change-password",
|
|
75
|
+
onSuccess: "refund",
|
|
76
|
+
limit: 50,
|
|
77
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
78
|
+
message: "Too many failed password attempts; try again later",
|
|
79
|
+
getTracker: ip,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "setup-token",
|
|
83
|
+
group: "setup",
|
|
84
|
+
onSuccess: "clear",
|
|
85
|
+
limit: 100,
|
|
86
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
87
|
+
message: "Too many setup attempts; try again later",
|
|
88
|
+
getTracker: () => sha256("token"),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "setup-ip",
|
|
92
|
+
group: "setup",
|
|
93
|
+
onSuccess: "refund",
|
|
94
|
+
limit: 10,
|
|
95
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
96
|
+
message: "Too many setup attempts; try again later",
|
|
97
|
+
getTracker: ip,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "invitation",
|
|
101
|
+
group: "invitation",
|
|
102
|
+
onSuccess: "clear",
|
|
103
|
+
limit: 10,
|
|
104
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
105
|
+
message: "Too many invitation attempts; try again later",
|
|
106
|
+
getTracker: (req) => sha256(routeToken(req)),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "invitation-ip",
|
|
110
|
+
group: "invitation",
|
|
111
|
+
onSuccess: "refund",
|
|
112
|
+
limit: 20,
|
|
113
|
+
ttl: CREDENTIAL_WINDOW_MS,
|
|
114
|
+
message: "Too many invitation attempts; try again later",
|
|
115
|
+
getTracker: ip,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
/** The storage key of a named throttler's counter for a tracker (shared by the guard and success paths). */
|
|
119
|
+
export const throttlerKey = (name, tracker) => sha256(`${name}:${tracker}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ThrottlerStorage } from "@nestjs/throttler";
|
|
2
|
+
import type { Redis } from "ioredis";
|
|
3
|
+
import type { KernelLogger } from "../../log.js";
|
|
4
|
+
interface ThrottlerStorageRecord {
|
|
5
|
+
totalHits: number;
|
|
6
|
+
timeToExpire: number;
|
|
7
|
+
isBlocked: boolean;
|
|
8
|
+
timeToBlockExpire: number;
|
|
9
|
+
}
|
|
10
|
+
export type ReserveResult = {
|
|
11
|
+
allowed: true;
|
|
12
|
+
} | {
|
|
13
|
+
allowed: false;
|
|
14
|
+
retryAfterSeconds: number;
|
|
15
|
+
};
|
|
16
|
+
/** The Redis key of a counter. The `{key}` hash tag keeps it in one Redis Cluster slot. */
|
|
17
|
+
export declare const throttlerRedisKey: (key: string, throttlerName: string) => string;
|
|
18
|
+
/**
|
|
19
|
+
* The `@nestjs/throttler` storage on the shared Redis connection: every replica counts into the
|
|
20
|
+
* same keys (the default in-memory storage would give each replica its own quota). Fail-open: a
|
|
21
|
+
* Redis error lets the request through and logs a warning, so a Redis blip never turns every
|
|
22
|
+
* throttled route into a 5xx (availability over limit precision).
|
|
23
|
+
*/
|
|
24
|
+
export declare class RedisThrottlerStorage implements ThrottlerStorage {
|
|
25
|
+
private readonly redis;
|
|
26
|
+
private readonly log;
|
|
27
|
+
constructor(redis: Redis, log: KernelLogger);
|
|
28
|
+
/** `ttl` is in milliseconds (the throttler convention); `blockDuration` is unused. */
|
|
29
|
+
increment(key: string, ttl: number, limit: number, _blockDuration: number, throttlerName: string): Promise<ThrottlerStorageRecord>;
|
|
30
|
+
/**
|
|
31
|
+
* Reserves one attempt on every key (which must share one hash tag), all or nothing. Unlike
|
|
32
|
+
* `increment` it does NOT swallow Redis errors: the caller fails closed.
|
|
33
|
+
*/
|
|
34
|
+
reserve(keys: string[], limits: number[], ttlMs: number): Promise<ReserveResult>;
|
|
35
|
+
/** A successful attempt: deletes the `clear` counters and refunds one on the `refund` ones. Fails closed. */
|
|
36
|
+
succeeded(clear: string[], refund: string[]): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One atomic EVAL: when the counter already reached the limit the attempt is rejected and
|
|
3
|
+
* NOT counted (a rejected attempt must not push the count further, so the window ends on
|
|
4
|
+
* schedule); otherwise the counter is incremented and always carries a TTL. Returns
|
|
5
|
+
* `{hits, pttl, blocked}`. `now` is not needed: no TIME command, no clock in the script.
|
|
6
|
+
*/
|
|
7
|
+
const INCREMENT_LUA = `
|
|
8
|
+
local key = KEYS[1]
|
|
9
|
+
local ttlMs = tonumber(ARGV[1])
|
|
10
|
+
local limit = tonumber(ARGV[2])
|
|
11
|
+
local hits = tonumber(redis.call('GET', key) or '0')
|
|
12
|
+
local blocked = 0
|
|
13
|
+
if hits >= limit then
|
|
14
|
+
blocked = 1
|
|
15
|
+
else
|
|
16
|
+
hits = redis.call('INCR', key)
|
|
17
|
+
end
|
|
18
|
+
local pttl = redis.call('PTTL', key)
|
|
19
|
+
if pttl < 0 then
|
|
20
|
+
redis.call('PEXPIRE', key, ttlMs)
|
|
21
|
+
pttl = ttlMs
|
|
22
|
+
end
|
|
23
|
+
return {hits, pttl, blocked}
|
|
24
|
+
`;
|
|
25
|
+
/**
|
|
26
|
+
* All-or-nothing reservation across KEYS (one hash tag): ARGV[1] is the window in ms, ARGV[i + 1]
|
|
27
|
+
* the limit of KEYS[i]. Any key at its limit rejects the attempt and NOTHING is incremented;
|
|
28
|
+
* otherwise every key is incremented and carries a TTL. Returns `{1}` or `{0, retryPttl}` (the
|
|
29
|
+
* largest remaining TTL among the keys at their limit).
|
|
30
|
+
*/
|
|
31
|
+
const RESERVE_LUA = `
|
|
32
|
+
local retry = -1
|
|
33
|
+
for i, key in ipairs(KEYS) do
|
|
34
|
+
local n = tonumber(redis.call('GET', key) or '0')
|
|
35
|
+
if n >= tonumber(ARGV[i + 1]) then
|
|
36
|
+
local pttl = redis.call('PTTL', key)
|
|
37
|
+
if pttl > retry then retry = pttl end
|
|
38
|
+
if retry < 1 then retry = 1 end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
if retry > 0 then
|
|
42
|
+
return {0, retry}
|
|
43
|
+
end
|
|
44
|
+
for i, key in ipairs(KEYS) do
|
|
45
|
+
local n = redis.call('INCR', key)
|
|
46
|
+
if n == 1 or redis.call('PTTL', key) < 0 then
|
|
47
|
+
redis.call('PEXPIRE', key, ARGV[1])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
return {1}
|
|
51
|
+
`;
|
|
52
|
+
/**
|
|
53
|
+
* Success: the first ARGV[1] keys are deleted, the rest lose one count (deleted at zero, never
|
|
54
|
+
* created).
|
|
55
|
+
*/
|
|
56
|
+
const SUCCEEDED_LUA = `
|
|
57
|
+
local nClear = tonumber(ARGV[1])
|
|
58
|
+
for i = 1, nClear do
|
|
59
|
+
redis.call('DEL', KEYS[i])
|
|
60
|
+
end
|
|
61
|
+
for i = nClear + 1, #KEYS do
|
|
62
|
+
if redis.call('EXISTS', KEYS[i]) == 1 then
|
|
63
|
+
if redis.call('DECR', KEYS[i]) <= 0 then
|
|
64
|
+
redis.call('DEL', KEYS[i])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
return 0
|
|
69
|
+
`;
|
|
70
|
+
const NOT_BLOCKED = {
|
|
71
|
+
totalHits: 0,
|
|
72
|
+
timeToExpire: 0,
|
|
73
|
+
isBlocked: false,
|
|
74
|
+
timeToBlockExpire: 0,
|
|
75
|
+
};
|
|
76
|
+
/** The Redis key of a counter. The `{key}` hash tag keeps it in one Redis Cluster slot. */
|
|
77
|
+
export const throttlerRedisKey = (key, throttlerName) => `throttler:{${key}}:${throttlerName}`;
|
|
78
|
+
/**
|
|
79
|
+
* The `@nestjs/throttler` storage on the shared Redis connection: every replica counts into the
|
|
80
|
+
* same keys (the default in-memory storage would give each replica its own quota). Fail-open: a
|
|
81
|
+
* Redis error lets the request through and logs a warning, so a Redis blip never turns every
|
|
82
|
+
* throttled route into a 5xx (availability over limit precision).
|
|
83
|
+
*/
|
|
84
|
+
export class RedisThrottlerStorage {
|
|
85
|
+
redis;
|
|
86
|
+
log;
|
|
87
|
+
constructor(redis, log) {
|
|
88
|
+
this.redis = redis;
|
|
89
|
+
this.log = log;
|
|
90
|
+
}
|
|
91
|
+
/** `ttl` is in milliseconds (the throttler convention); `blockDuration` is unused. */
|
|
92
|
+
async increment(key, ttl, limit, _blockDuration, throttlerName) {
|
|
93
|
+
try {
|
|
94
|
+
const [hits, pttl, blocked] = (await this.redis.eval(INCREMENT_LUA, 1, throttlerRedisKey(key, throttlerName), ttl, limit));
|
|
95
|
+
// seconds, at least 1 and at most the window: the Retry-After value
|
|
96
|
+
const timeToExpire = Math.min(Math.max(Math.ceil(pttl / 1000), 1), Math.ceil(ttl / 1000));
|
|
97
|
+
return {
|
|
98
|
+
totalHits: hits,
|
|
99
|
+
timeToExpire,
|
|
100
|
+
isBlocked: blocked === 1,
|
|
101
|
+
timeToBlockExpire: blocked === 1 ? timeToExpire : 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
this.log.warn("throttler storage unavailable, request allowed (fail-open)", {
|
|
106
|
+
throttler: throttlerName,
|
|
107
|
+
err,
|
|
108
|
+
});
|
|
109
|
+
return NOT_BLOCKED;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reserves one attempt on every key (which must share one hash tag), all or nothing. Unlike
|
|
114
|
+
* `increment` it does NOT swallow Redis errors: the caller fails closed.
|
|
115
|
+
*/
|
|
116
|
+
async reserve(keys, limits, ttlMs) {
|
|
117
|
+
const out = (await this.redis.eval(RESERVE_LUA, keys.length, ...keys, ttlMs, ...limits));
|
|
118
|
+
if (out[0] === 1)
|
|
119
|
+
return { allowed: true };
|
|
120
|
+
const window = Math.ceil(ttlMs / 1000);
|
|
121
|
+
return {
|
|
122
|
+
allowed: false,
|
|
123
|
+
retryAfterSeconds: Math.min(Math.max(Math.ceil((out[1] ?? ttlMs) / 1000), 1), window),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** A successful attempt: deletes the `clear` counters and refunds one on the `refund` ones. Fails closed. */
|
|
127
|
+
async succeeded(clear, refund) {
|
|
128
|
+
await this.redis.eval(SUCCEEDED_LUA, clear.length + refund.length, ...clear, ...refund, clear.length);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
|
+
import { ThrottlerGuard, type ThrottlerModuleOptions, type ThrottlerRequest, type ThrottlerStorage } from "@nestjs/throttler";
|
|
4
|
+
/** The throttler names selected for the route `context` runs (empty: none, the guard passes). */
|
|
5
|
+
export declare function selectedThrottlers(context: ExecutionContext): readonly string[];
|
|
6
|
+
/**
|
|
7
|
+
* Opts a route (or controller) in to the named throttlers `names` (see `NAMED_THROTTLERS`).
|
|
8
|
+
* Throttling is per-route opt-in because the kernel limits only credential-checking routes today;
|
|
9
|
+
* health, SSE and extension routes stay unlimited.
|
|
10
|
+
*/
|
|
11
|
+
export declare const Throttled: (...names: string[]) => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
|
|
12
|
+
/**
|
|
13
|
+
* `@nestjs/throttler`'s guard with the kernel's error contract: the first named throttler of the
|
|
14
|
+
* route at its limit answers 429 `auth.rate_limited` with its own message and `retry-after`
|
|
15
|
+
* (seconds, 1..window), as the credential routes answer; no `x-ratelimit-*` headers (the
|
|
16
|
+
* kernel sends none today). Which throttlers apply to a route is chosen by `@Throttled`; they are
|
|
17
|
+
* counted one after another in `NAMED_THROTTLERS` order (table order is check order). Fail-open on
|
|
18
|
+
* Redis errors (see `RedisThrottlerStorage`). For credential checks use `CredentialThrottler`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class KernelThrottlerGuard extends ThrottlerGuard {
|
|
21
|
+
constructor(options: ThrottlerModuleOptions, storage: ThrottlerStorage, reflector: Reflector);
|
|
22
|
+
protected handleRequest(props: ThrottlerRequest): Promise<boolean>;
|
|
23
|
+
}
|