@capybara-hq/kernel 0.1.0-rc.1 → 0.1.0-rc.3
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/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 +187 -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 +280 -335
- package/dist/agents/definition.d.ts +39 -8
- package/dist/agents/definition.js +201 -17
- 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 +39 -8
- 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 +326 -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/claude/agents.js +13 -10
- 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 +31 -14
- 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 +10 -4
- package/dist/channels/cards.js +16 -8
- 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 -194
- 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 +1 -0
- package/dist/config/schema.js +12 -6
- package/dist/conversations/access.d.ts +29 -0
- package/dist/conversations/access.js +86 -0
- package/dist/conversations/artifacts.d.ts +11 -11
- package/dist/conversations/artifacts.js +36 -10
- 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 +39 -18
- package/dist/conversations/event-log.js +93 -27
- package/dist/conversations/events.d.ts +23 -3
- package/dist/conversations/events.js +26 -14
- 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 -63
- 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/secrets.d.ts +10 -2
- package/dist/ctx/secrets.js +7 -3
- package/dist/db/kernel-schema.d.ts +4 -4
- package/dist/db/pool.d.ts +5 -0
- package/dist/db/pool.js +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +1 -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/recorded-tools.d.ts +27 -1
- package/dist/evals/recorded-tools.js +85 -10
- package/dist/evals/run-replay.d.ts +61 -8
- package/dist/evals/run-replay.js +159 -85
- 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.d.ts +2 -2
- 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 -121
- 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 +58 -31
- package/dist/extensions/bootstrap.js +175 -121
- 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 +17 -62
- 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 +86 -58
- 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 +16 -11
- package/dist/index.js +9 -6
- 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 -244
- 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 +456 -0
- package/dist/lifecycle/tokens.d.ts +3 -0
- package/dist/lifecycle/tokens.js +3 -0
- package/dist/log-context.d.ts +2 -0
- package/dist/log-context.js +4 -0
- 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/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 +16 -10
- package/dist/orgs/settings.service.js +77 -48
- 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/context/build.d.ts +6 -6
- package/dist/runtime/context/build.js +18 -8
- package/dist/runtime/context/claude-tools.d.ts +2 -1
- package/dist/runtime/context/claude-tools.js +9 -10
- package/dist/runtime/context/legacy-text.d.ts +22 -0
- package/dist/runtime/context/legacy-text.js +37 -0
- package/dist/runtime/context/skills.d.ts +29 -11
- package/dist/runtime/context/skills.js +37 -30
- 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 +8 -1
- 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 -137
- package/dist/runtime/model-call.d.ts +7 -4
- package/dist/runtime/model-call.js +25 -58
- 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 +27 -18
- 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 +122 -76
- package/dist/runtime/run-worker.js +343 -229
- 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 +5 -10
- package/dist/runtime/subagents/definition.js +13 -14
- package/dist/runtime/subagents/dispatch.d.ts +89 -8
- package/dist/runtime/subagents/dispatch.js +137 -142
- 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 +3 -2
- package/dist/runtime/subagents/progress.d.ts +2 -2
- package/dist/runtime/subagents/roster.d.ts +4 -4
- package/dist/runtime/subagents/roster.js +2 -2
- package/dist/runtime/subagents/skill-fork.d.ts +8 -9
- package/dist/runtime/subagents/skill-fork.js +17 -6
- 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 -100
- package/dist/runtime/waits.d.ts +127 -59
- package/dist/runtime/waits.js +433 -210
- 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 +37 -24
- package/dist/skills/catalog.js +100 -57
- package/dist/skills/changes.controller.d.ts +4 -4
- package/dist/skills/changes.controller.js +15 -9
- package/dist/skills/changes.service.d.ts +30 -20
- package/dist/skills/changes.service.js +425 -367
- package/dist/skills/invocable.controller.d.ts +1 -1
- package/dist/skills/invocable.controller.js +8 -5
- package/dist/skills/invocation-keys.d.ts +1 -1
- package/dist/skills/invocation-keys.js +1 -1
- package/dist/skills/invoke.d.ts +17 -16
- package/dist/skills/invoke.js +53 -21
- 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 +33 -25
- package/dist/tools/approvals.service.js +343 -259
- package/dist/tools/breaker.d.ts +2 -2
- package/dist/tools/breaker.js +21 -3
- package/dist/tools/builtin-names.d.ts +73 -0
- package/dist/tools/builtin-names.js +116 -0
- package/dist/tools/builtins/agent.d.ts +7 -7
- package/dist/tools/builtins/agent.js +7 -11
- package/dist/tools/builtins/ask-user.d.ts +3 -3
- package/dist/tools/builtins/ask-user.js +3 -3
- 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 +3 -3
- package/dist/tools/builtins/read-tool-output.js +3 -3
- package/dist/tools/builtins/sandbox/bash.d.ts +5 -5
- package/dist/tools/builtins/sandbox/bash.js +33 -30
- package/dist/tools/builtins/sandbox/common.d.ts +5 -4
- package/dist/tools/builtins/sandbox/common.js +2 -2
- package/dist/tools/builtins/sandbox/deliver.d.ts +1 -1
- package/dist/tools/builtins/sandbox/deliver.js +13 -13
- package/dist/tools/builtins/sandbox/edit.d.ts +1 -1
- package/dist/tools/builtins/sandbox/edit.js +42 -31
- package/dist/tools/builtins/sandbox/file-state.d.ts +34 -0
- package/dist/tools/builtins/sandbox/file-state.js +34 -0
- package/dist/tools/builtins/sandbox/glob-match.d.ts +1 -1
- package/dist/tools/builtins/sandbox/glob-match.js +1 -1
- package/dist/tools/builtins/sandbox/glob.d.ts +1 -1
- package/dist/tools/builtins/sandbox/glob.js +6 -6
- package/dist/tools/builtins/sandbox/grep.d.ts +7 -4
- package/dist/tools/builtins/sandbox/grep.js +149 -32
- package/dist/tools/builtins/sandbox/index.d.ts +1 -1
- package/dist/tools/builtins/sandbox/index.js +1 -1
- package/dist/tools/builtins/sandbox/read.d.ts +2 -2
- package/dist/tools/builtins/sandbox/read.js +22 -15
- package/dist/tools/builtins/sandbox/write.d.ts +1 -1
- package/dist/tools/builtins/sandbox/write.js +38 -34
- package/dist/tools/builtins/skill-load.d.ts +6 -6
- package/dist/tools/builtins/skill-load.js +33 -17
- package/dist/tools/builtins/skill-write.d.ts +4 -4
- package/dist/tools/builtins/skill-write.js +10 -10
- package/dist/tools/builtins/tool-search.d.ts +13 -9
- package/dist/tools/builtins/tool-search.js +25 -14
- 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 +438 -348
- package/dist/tools/credentials.d.ts +17 -20
- package/dist/tools/credentials.js +55 -34
- package/dist/tools/display-names.d.ts +9 -0
- package/dist/tools/display-names.js +24 -0
- package/dist/tools/drift.d.ts +29 -41
- package/dist/tools/drift.js +116 -84
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/info.d.ts +32 -13
- package/dist/tools/info.js +88 -39
- 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/legacy-data.d.ts +34 -0
- package/dist/tools/legacy-data.js +76 -0
- package/dist/tools/legacy-keys.d.ts +51 -0
- package/dist/tools/legacy-keys.js +115 -0
- package/dist/tools/naming.d.ts +1 -5
- package/dist/tools/naming.js +1 -7
- 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 +48 -4
- package/dist/tools/pipeline.d.ts +72 -50
- package/dist/tools/pipeline.js +239 -100
- package/dist/tools/policy.d.ts +3 -16
- package/dist/tools/policy.js +13 -19
- package/dist/tools/questions.controller.d.ts +2 -2
- package/dist/tools/questions.controller.js +9 -6
- package/dist/tools/questions.service.d.ts +26 -25
- package/dist/tools/questions.service.js +201 -142
- package/dist/tools/registry.d.ts +43 -38
- package/dist/tools/registry.js +100 -72
- package/dist/tools/resources.d.ts +1 -1
- package/dist/tools/resources.js +1 -1
- 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 +10 -3
- package/src/migrations/0063_tool_keys_claude_code.sql +220 -0
- 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/tools/builtins/sandbox/names.d.ts +0 -7
- package/dist/tools/builtins/sandbox/names.js +0 -20
- /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,343 @@
|
|
|
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
|
+
// Ported from octok-platform libs/common/src/decorators/with-lock.decorator.ts and
|
|
11
|
+
// libs/flow/src/runtime/redis-token-lock.ts (owner-approved, 2026-10-28).
|
|
12
|
+
// Adapted to the kernel: pino logger, ApiError, and a `lost` signal when renewal fails.
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
15
|
+
import { ApiError } from "../../errors.js";
|
|
16
|
+
import { currentStore } from "../database/transaction-host.js";
|
|
17
|
+
import { KERNEL_LOGGER, REDIS } from "../tokens.js";
|
|
18
|
+
import { ACQUIRE_EXCLUSIVE, ACQUIRE_SHARED, COUNT_SHARED_LEASES, DOWNGRADE_SHARED_LEASE, RELEASE_SHARED_LEASE, RENEW_SHARED_LEASE, } from "./shared-lease.js";
|
|
19
|
+
/** Deletes the key only while it holds our token. */
|
|
20
|
+
const RELEASE_IF = `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end`;
|
|
21
|
+
/** Extends the key only while it holds our token. */
|
|
22
|
+
const RENEW_IF = `if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], tonumber(ARGV[2])) else return 0 end`;
|
|
23
|
+
/** Resolves after `ms`; rejects with the abort reason when `signal` aborts first. */
|
|
24
|
+
function sleep(ms, signal) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
if (signal?.aborted) {
|
|
27
|
+
reject(signal.reason);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const onAbort = () => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
reject(signal?.reason);
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
signal?.removeEventListener("abort", onAbort);
|
|
36
|
+
resolve();
|
|
37
|
+
}, ms);
|
|
38
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Locks and leases are never taken inside a transaction: a lock released before the COMMIT lets
|
|
43
|
+
* the next holder read rows not yet committed, and one held across it holds a connection while
|
|
44
|
+
* waiting. Take the lock first, then open the transaction.
|
|
45
|
+
*/
|
|
46
|
+
function assertNoTransaction(op) {
|
|
47
|
+
if (currentStore()) {
|
|
48
|
+
throw new Error(`LockService.${op}() must not run inside a transaction: take the lock first, then open it.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const prefixed = (key) => (key.startsWith("lock:") ? key : `lock:${key}`);
|
|
52
|
+
/**
|
|
53
|
+
* Redis distributed locks: `SET NX PX` with a Lua compare-and-delete release and a watchdog that
|
|
54
|
+
* renews every TTL/3 (short lock plus renewal: a long job keeps it, a crashed holder's lock
|
|
55
|
+
* expires within the TTL). Also the primitives for leases whose holder is a caller-chosen string
|
|
56
|
+
* (`lease` / `renew` / `releaseIf`, used by the sandbox leases).
|
|
57
|
+
*/
|
|
58
|
+
let LockService = class LockService {
|
|
59
|
+
redis;
|
|
60
|
+
log;
|
|
61
|
+
constructor(redis, log) {
|
|
62
|
+
this.redis = redis;
|
|
63
|
+
this.log = log;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Tries to take `lock:<key>`; resolves `undefined` when it stays held by someone else until
|
|
67
|
+
* `waitMs` ran out. An aborted `signal` throws its reason instead.
|
|
68
|
+
*/
|
|
69
|
+
async acquire(key, opts) {
|
|
70
|
+
assertNoTransaction("acquire");
|
|
71
|
+
const lockKey = prefixed(key);
|
|
72
|
+
const token = randomUUID();
|
|
73
|
+
const deadline = Date.now() + (opts.waitMs ?? 0);
|
|
74
|
+
const retryMs = opts.retryMs ?? 100;
|
|
75
|
+
for (;;) {
|
|
76
|
+
opts.signal?.throwIfAborted();
|
|
77
|
+
const sentAt = Date.now();
|
|
78
|
+
if ((await this.redis.set(lockKey, token, "PX", Math.ceil(opts.ttlMs), "NX")) === "OK") {
|
|
79
|
+
this.log.debug("lock acquired", { lockKey });
|
|
80
|
+
return this.watch(lockKey, token, opts.ttlMs, sentAt);
|
|
81
|
+
}
|
|
82
|
+
const remaining = deadline - Date.now();
|
|
83
|
+
if (remaining <= 0) {
|
|
84
|
+
// Contention is normal in a multi-replica deployment, not an error.
|
|
85
|
+
this.log.info("lock held elsewhere", { lockKey });
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
await sleep(Math.min(retryMs, remaining), opts.signal);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Runs `fn` under the lock; throws `lock.acquire_failed` when it cannot be taken. */
|
|
92
|
+
async run(key, opts, fn) {
|
|
93
|
+
const handle = await this.acquire(key, opts);
|
|
94
|
+
if (!handle)
|
|
95
|
+
throw lockAcquireFailed(prefixed(key));
|
|
96
|
+
try {
|
|
97
|
+
return await fn(handle);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await handle.release();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Sets `key` to `holder` for `ttlMs` unless it exists (no `lock:` prefix, no renewal timer). */
|
|
104
|
+
async lease(key, holder, ttlMs) {
|
|
105
|
+
assertNoTransaction("lease");
|
|
106
|
+
return (await this.redis.set(key, holder, "PX", Math.ceil(ttlMs), "NX")) === "OK";
|
|
107
|
+
}
|
|
108
|
+
/** Extends `key` to `ttlMs` when `holder` still owns it. */
|
|
109
|
+
async renew(key, holder, ttlMs) {
|
|
110
|
+
return (await this.redis.eval(RENEW_IF, 1, key, holder, String(Math.ceil(ttlMs)))) === 1;
|
|
111
|
+
}
|
|
112
|
+
/** The holder value of `key` (a lease's or a lock's token); null when nobody holds it. */
|
|
113
|
+
holderOf(key) {
|
|
114
|
+
return this.redis.get(key);
|
|
115
|
+
}
|
|
116
|
+
/** Deletes `key` when `holder` still owns it. */
|
|
117
|
+
async releaseIf(key, holder) {
|
|
118
|
+
return (await this.redis.eval(RELEASE_IF, 1, key, holder)) === 1;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Takes `key` (no `lock:` prefix) with the caller's `holder` value for `ttlMs`, unless it is
|
|
122
|
+
* held, and renews it every TTL/3 like {@link acquire} (a run longer than the TTL keeps it, a
|
|
123
|
+
* crashed holder's key expires within the TTL). Resolves `undefined` when the key is held; tries
|
|
124
|
+
* once. For leases whose holder other replicas read (the conversation run lock: the run id).
|
|
125
|
+
*/
|
|
126
|
+
async hold(key, holder, ttlMs) {
|
|
127
|
+
assertNoTransaction("hold");
|
|
128
|
+
const sentAt = Date.now();
|
|
129
|
+
if (!(await this.lease(key, holder, ttlMs)))
|
|
130
|
+
return undefined;
|
|
131
|
+
return this.watch(key, holder, ttlMs, sentAt);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* One try at a shared lease of `keys` for `token` (at most `cap` shared holders; not granted
|
|
135
|
+
* while an exclusive holder, another `SET NX` value of the lock key or another token's intent
|
|
136
|
+
* exists). Resolves `undefined` when not granted; a granted lease is renewed every TTL/3.
|
|
137
|
+
*/
|
|
138
|
+
async tryShared(keys, token, cap, ttlMs) {
|
|
139
|
+
assertNoTransaction("tryShared");
|
|
140
|
+
const sentAt = Date.now();
|
|
141
|
+
const ok = await this.redis.eval(ACQUIRE_SHARED, 3, keys.lock, keys.users, keys.intent, token, cap, Math.ceil(ttlMs));
|
|
142
|
+
return Number(ok) === 1 ? this.watchShared(keys, token, "shared", ttlMs, sentAt) : undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* One try at the exclusive lease of `keys` for `token` (granted only when no lease of either
|
|
146
|
+
* kind exists). With `intentTtlMs` a failed try sets (or refreshes) the intent to `token` unless
|
|
147
|
+
* another token's intent exists, so new shared leases wait; a successful try clears our intent.
|
|
148
|
+
*/
|
|
149
|
+
async tryExclusive(keys, token, ttlMs, intentTtlMs) {
|
|
150
|
+
assertNoTransaction("tryExclusive");
|
|
151
|
+
const sentAt = Date.now();
|
|
152
|
+
const ok = await this.redis.eval(ACQUIRE_EXCLUSIVE, 3, keys.lock, keys.users, keys.intent, token, Math.ceil(ttlMs), intentTtlMs === undefined ? 0 : 1, Math.ceil(intentTtlMs ?? 0));
|
|
153
|
+
return Number(ok) === 1 ? this.watchShared(keys, token, "exclusive", ttlMs, sentAt) : undefined;
|
|
154
|
+
}
|
|
155
|
+
/** Live shared leases of `keys` (expired ones are purged first). */
|
|
156
|
+
async sharedCount(keys) {
|
|
157
|
+
return Number(await this.redis.eval(COUNT_SHARED_LEASES, 2, keys.lock, keys.users));
|
|
158
|
+
}
|
|
159
|
+
/** The watchdog of a shared lease we just took; its kind changes once, on a downgrade. */
|
|
160
|
+
watchShared(keys, token, initial, ttlMs, sentAt) {
|
|
161
|
+
let kind = initial;
|
|
162
|
+
let released = false;
|
|
163
|
+
/** The DOWNGRADE eval in flight, if any. */
|
|
164
|
+
let downgrading;
|
|
165
|
+
const ttl = String(Math.ceil(ttlMs));
|
|
166
|
+
const w = this.watchdog(keys.lock, ttlMs, {
|
|
167
|
+
renew: async () => {
|
|
168
|
+
// a downgrade still in flight decides the kind to renew (Redis may have made it shared)
|
|
169
|
+
await downgrading?.catch(() => { });
|
|
170
|
+
return (Number(await this.redis.eval(RENEW_SHARED_LEASE, 2, keys.lock, keys.users, token, ttl, kind)) === 1);
|
|
171
|
+
},
|
|
172
|
+
release: async () => {
|
|
173
|
+
// a downgrade still in flight decides the kind the lease has in Redis
|
|
174
|
+
await downgrading?.catch(() => { });
|
|
175
|
+
await this.redis.eval(RELEASE_SHARED_LEASE, 2, keys.lock, keys.users, token, kind);
|
|
176
|
+
},
|
|
177
|
+
}, sentAt);
|
|
178
|
+
return {
|
|
179
|
+
token,
|
|
180
|
+
get kind() {
|
|
181
|
+
return kind;
|
|
182
|
+
},
|
|
183
|
+
lost: w.lost,
|
|
184
|
+
renew: () => w.renew(),
|
|
185
|
+
abandon: () => w.abandon(),
|
|
186
|
+
release: async () => {
|
|
187
|
+
released = true;
|
|
188
|
+
await w.release();
|
|
189
|
+
},
|
|
190
|
+
downgrade: async () => {
|
|
191
|
+
if (kind !== "exclusive" || released)
|
|
192
|
+
throw new Error("lease is not exclusive");
|
|
193
|
+
w.lost.throwIfAborted();
|
|
194
|
+
return w.renewWith(async () => {
|
|
195
|
+
downgrading = this.redis
|
|
196
|
+
.eval(DOWNGRADE_SHARED_LEASE, 2, keys.lock, keys.users, token, ttl)
|
|
197
|
+
.then((r) => Number(r) === 1);
|
|
198
|
+
try {
|
|
199
|
+
const done = await downgrading;
|
|
200
|
+
// Redis made it shared: record that even when the lease was lost or released while the
|
|
201
|
+
// call was in flight, so the release removes the shared member (no leak)
|
|
202
|
+
if (done)
|
|
203
|
+
kind = "shared";
|
|
204
|
+
return done;
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
downgrading = undefined;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** The renewal watchdog of a key we just took (the `SET NX` was sent at `sentAt`). */
|
|
214
|
+
watch(lockKey, token, ttlMs, sentAt) {
|
|
215
|
+
return this.keepAlive(lockKey, ttlMs, {
|
|
216
|
+
renew: () => this.renew(lockKey, token, ttlMs),
|
|
217
|
+
release: async () => {
|
|
218
|
+
if (!(await this.releaseIf(lockKey, token))) {
|
|
219
|
+
this.log.warn("lock was not ours at release (expired or taken)", { lockKey });
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
}, sentAt);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The renewal watchdog for a lease kept by other means than a `SET NX` key (e.g. an org quota
|
|
226
|
+
* slot, a member of a Redis sorted set): calls `ops.renew` every TTL/3 while held. A renewal
|
|
227
|
+
* answered `false` marks it lost at once (`lost` aborts, one WARN `lock lost` with `label`).
|
|
228
|
+
* Loss is a deadline (decision R19 of the Nest kernel spec), not a counter. The deadline is the
|
|
229
|
+
* send time of the last successful renewal (or of the acquire, `acquiredAt`, default now) + TTL −
|
|
230
|
+
* TTL/3; any successful renewal through the handle (the watchdog's or an explicit `renew()`)
|
|
231
|
+
* moves it; when it passes without one, `lost` aborts, even with a renewal still in flight. So a
|
|
232
|
+
* holder gives up one renewal period before the key can expire elsewhere. `release` stops
|
|
233
|
+
* renewing and calls `ops.release` once.
|
|
234
|
+
*/
|
|
235
|
+
keepAlive(label, ttlMs, ops, acquiredAt = Date.now()) {
|
|
236
|
+
return this.watchdog(label, ttlMs, ops, acquiredAt);
|
|
237
|
+
}
|
|
238
|
+
/** {@link keepAlive}'s implementation, with {@link Watched.renewWith}. */
|
|
239
|
+
watchdog(label, ttlMs, ops, acquiredAt) {
|
|
240
|
+
const lockKey = label;
|
|
241
|
+
const lost = new AbortController();
|
|
242
|
+
let released = false;
|
|
243
|
+
let stopped = false;
|
|
244
|
+
const intervalMs = Math.max(1, Math.floor(ttlMs / 3));
|
|
245
|
+
/** Send time of the last successful renewal (or of the acquire). */
|
|
246
|
+
let lastOkSentAt = acquiredAt;
|
|
247
|
+
let deadline;
|
|
248
|
+
const stop = () => {
|
|
249
|
+
stopped = true;
|
|
250
|
+
clearInterval(timer);
|
|
251
|
+
clearTimeout(deadline);
|
|
252
|
+
};
|
|
253
|
+
const lose = (reason) => {
|
|
254
|
+
// released or abandoned: the holder gave the lease up, so a late answer loses nothing
|
|
255
|
+
if (released || stopped || lost.signal.aborted)
|
|
256
|
+
return;
|
|
257
|
+
this.log.warn(reason, { lockKey });
|
|
258
|
+
lost.abort(new Error(`lock lost: ${lockKey}`));
|
|
259
|
+
stop();
|
|
260
|
+
};
|
|
261
|
+
const deadlineAt = () => lastOkSentAt + ttlMs - intervalMs;
|
|
262
|
+
const arm = () => {
|
|
263
|
+
clearTimeout(deadline);
|
|
264
|
+
if (stopped)
|
|
265
|
+
return;
|
|
266
|
+
const ms = Math.max(0, deadlineAt() - Date.now());
|
|
267
|
+
deadline = setTimeout(() => lose("lock lost: renewal kept failing"), ms);
|
|
268
|
+
deadline.unref();
|
|
269
|
+
};
|
|
270
|
+
/** One renewal: a success moves the deadline (by its send time), `false` loses the lease. */
|
|
271
|
+
const renewOnce = async (op = () => ops.renew()) => {
|
|
272
|
+
const sentAt = Date.now();
|
|
273
|
+
const ok = await op();
|
|
274
|
+
if (released || lost.signal.aborted)
|
|
275
|
+
return false;
|
|
276
|
+
if (!ok) {
|
|
277
|
+
lose("lock lost: the key is gone or taken");
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (sentAt > lastOkSentAt) {
|
|
281
|
+
lastOkSentAt = sentAt;
|
|
282
|
+
arm();
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
};
|
|
286
|
+
const timer = setInterval(() => {
|
|
287
|
+
// the deadline timer decides the loss; this check only backs it up on the watchdog's own
|
|
288
|
+
// clock (both fire at the deadline: never earlier)
|
|
289
|
+
if (Date.now() >= deadlineAt()) {
|
|
290
|
+
lose("lock lost: renewal kept failing");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
renewOnce().catch((err) => {
|
|
294
|
+
if (released || stopped)
|
|
295
|
+
return;
|
|
296
|
+
// a transient Redis error: the next tick retries; the deadline decides the loss
|
|
297
|
+
this.log.warn("lock renewal failed", { lockKey, err });
|
|
298
|
+
});
|
|
299
|
+
}, intervalMs);
|
|
300
|
+
timer.unref();
|
|
301
|
+
arm();
|
|
302
|
+
return {
|
|
303
|
+
lost: lost.signal,
|
|
304
|
+
// an explicit renewal: a Redis error is thrown to the caller, not logged (the watchdog's
|
|
305
|
+
// own failures are, once each)
|
|
306
|
+
renew: async () => {
|
|
307
|
+
if (released || lost.signal.aborted)
|
|
308
|
+
return false;
|
|
309
|
+
return renewOnce();
|
|
310
|
+
},
|
|
311
|
+
renewWith: async (op) => {
|
|
312
|
+
if (released || lost.signal.aborted)
|
|
313
|
+
return false;
|
|
314
|
+
return renewOnce(op);
|
|
315
|
+
},
|
|
316
|
+
abandon: stop,
|
|
317
|
+
release: async () => {
|
|
318
|
+
if (released)
|
|
319
|
+
return;
|
|
320
|
+
released = true;
|
|
321
|
+
stop();
|
|
322
|
+
try {
|
|
323
|
+
await ops.release();
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
// warn: the key expires within its TTL anyway
|
|
327
|
+
this.log.warn("lock release failed", { lockKey, err });
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
LockService = __decorate([
|
|
334
|
+
Injectable(),
|
|
335
|
+
__param(0, Inject(REDIS)),
|
|
336
|
+
__param(1, Inject(KERNEL_LOGGER))
|
|
337
|
+
], LockService);
|
|
338
|
+
export { LockService };
|
|
339
|
+
export function lockAcquireFailed(lockKey) {
|
|
340
|
+
return new ApiError("lock.acquire_failed", "The operation is already in progress; retry later.", [
|
|
341
|
+
{ lockKey },
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared and exclusive leases on one resource (a reader-writer lease across replicas): up to `cap`
|
|
3
|
+
* shared holders at once, or one exclusive holder alone, with writer preference through an intent
|
|
4
|
+
* key. The Redis layout (the caller names the three keys; no `lock:` prefix is added):
|
|
5
|
+
*
|
|
6
|
+
* - `lock`: the exclusive holder's token, or `shared` while shared leases exist (so a plain
|
|
7
|
+
* `SET NX` holder of the same key and the shared holders exclude each other);
|
|
8
|
+
* - `users`: sorted set of shared holders' tokens scored by their expiry (ms, Redis server clock);
|
|
9
|
+
* - `intent`: an exclusive waiter's token; while it exists no new shared lease is granted except
|
|
10
|
+
* to the waiter's own token.
|
|
11
|
+
*
|
|
12
|
+
* Every script purges expired shared leases first, and every key carries a TTL. The scripts are
|
|
13
|
+
* the protocol of {@link LockService.tryShared} / {@link LockService.tryExclusive} and the handle
|
|
14
|
+
* they return (renewal, release, downgrade).
|
|
15
|
+
*/
|
|
16
|
+
/** The three keys of one shared lease (see the module comment). */
|
|
17
|
+
export interface SharedLeaseKeys {
|
|
18
|
+
lock: string;
|
|
19
|
+
users: string;
|
|
20
|
+
intent: string;
|
|
21
|
+
}
|
|
22
|
+
export type SharedLeaseKind = "shared" | "exclusive";
|
|
23
|
+
/** A Lua script whose `body` sees `now` (ms, the Redis server clock). */
|
|
24
|
+
export declare function redisClockScript(body: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* A Lua script that reads a shared lease atomically with other keys (a domain's own records next
|
|
27
|
+
* to the lease, e.g. the sandbox holder records): KEYS[1] is the lease's lock key and KEYS[2] its
|
|
28
|
+
* users set (as {@link SharedLeaseKeys}); `body` sees `now` (ms, the Redis server clock) and `cur`,
|
|
29
|
+
* the lock key's value once expired shared leases are dropped (`'shared'`, an exclusive holder's
|
|
30
|
+
* token, or false). The lease layout stays in this module.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sharedLeaseReadScript(body: string): string;
|
|
33
|
+
/** KEYS lock, users, intent; ARGV token, cap, ttl. */
|
|
34
|
+
export declare const ACQUIRE_SHARED = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\n\nredis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)\nlocal cur = redis.call('GET', KEYS[1])\nif cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then\n redis.call('DEL', KEYS[1])\n cur = false\nend\nif cur and cur ~= 'shared' then return 0 end\nlocal it = redis.call('GET', KEYS[3])\nif it and it ~= ARGV[1] then return 0 end\nif redis.call('ZCARD', KEYS[2]) >= tonumber(ARGV[2]) then return 0 end\nlocal ttl = tonumber(ARGV[3])\nredis.call('ZADD', KEYS[2], now + ttl, ARGV[1])\n\nif redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end\nlocal lt = redis.call('PTTL', KEYS[1])\nif redis.call('GET', KEYS[1]) ~= 'shared' or lt < ttl then\n redis.call('SET', KEYS[1], 'shared', 'PX', math.max(ttl, lt))\nend\nreturn 1";
|
|
35
|
+
/** KEYS lock, users, intent; ARGV token, ttl, setIntent (0/1), intentTtl. */
|
|
36
|
+
export declare const ACQUIRE_EXCLUSIVE = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\n\nredis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)\nlocal cur = redis.call('GET', KEYS[1])\nif cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then\n redis.call('DEL', KEYS[1])\n cur = false\nend\nif cur or redis.call('ZCARD', KEYS[2]) > 0 then\n if ARGV[3] == '1' then\n local it = redis.call('GET', KEYS[3])\n if (not it) or it == ARGV[1] then\n redis.call('SET', KEYS[3], ARGV[1], 'PX', tonumber(ARGV[4]))\n end\n end\n return 0\nend\nredis.call('SET', KEYS[1], ARGV[1], 'PX', tonumber(ARGV[2]))\nif redis.call('GET', KEYS[3]) == ARGV[1] then redis.call('DEL', KEYS[3]) end\nreturn 1";
|
|
37
|
+
/** KEYS lock, users; ARGV token, ttl, kind. Shared: lost when the member is gone or the lock holds a token. */
|
|
38
|
+
export declare const RENEW_SHARED_LEASE = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nlocal ttl = tonumber(ARGV[2])\nif ARGV[3] == 'exclusive' then\n if redis.call('GET', KEYS[1]) == ARGV[1] then\n redis.call('PEXPIRE', KEYS[1], ttl)\n return 1\n end\n return 0\nend\nredis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)\nif not redis.call('ZSCORE', KEYS[2], ARGV[1]) then return 0 end\nlocal cur = redis.call('GET', KEYS[1])\nif cur and cur ~= 'shared' then\n redis.call('ZREM', KEYS[2], ARGV[1])\n return 0\nend\nredis.call('ZADD', KEYS[2], 'XX', now + ttl, ARGV[1])\n\nif redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end\nlocal lt = redis.call('PTTL', KEYS[1])\nif redis.call('GET', KEYS[1]) ~= 'shared' or lt < ttl then\n redis.call('SET', KEYS[1], 'shared', 'PX', math.max(ttl, lt))\nend\nreturn 1";
|
|
39
|
+
/** KEYS lock, users; ARGV token, kind. */
|
|
40
|
+
export declare const RELEASE_SHARED_LEASE = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nif ARGV[2] == 'exclusive' then\n if redis.call('GET', KEYS[1]) == ARGV[1] then redis.call('DEL', KEYS[1]) end\n return 1\nend\nredis.call('ZREM', KEYS[2], ARGV[1])\n\nredis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)\nlocal cur = redis.call('GET', KEYS[1])\nif cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then\n redis.call('DEL', KEYS[1])\n cur = false\nend\nreturn 1";
|
|
41
|
+
/** KEYS lock, users; ARGV token, ttl. */
|
|
42
|
+
export declare const DOWNGRADE_SHARED_LEASE = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nif redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end\nlocal ttl = tonumber(ARGV[2])\nredis.call('ZADD', KEYS[2], now + ttl, ARGV[1])\n\nif redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end\nlocal lt = redis.call('PTTL', KEYS[1])\nif redis.call('GET', KEYS[1]) ~= 'shared' or lt < ttl then\n redis.call('SET', KEYS[1], 'shared', 'PX', math.max(ttl, lt))\nend\nreturn 1";
|
|
43
|
+
/** KEYS lock, users; returns the live shared count. */
|
|
44
|
+
export declare const COUNT_SHARED_LEASES = "local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\n\nredis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)\nlocal cur = redis.call('GET', KEYS[1])\nif cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then\n redis.call('DEL', KEYS[1])\n cur = false\nend\nreturn redis.call('ZCARD', KEYS[2])";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared and exclusive leases on one resource (a reader-writer lease across replicas): up to `cap`
|
|
3
|
+
* shared holders at once, or one exclusive holder alone, with writer preference through an intent
|
|
4
|
+
* key. The Redis layout (the caller names the three keys; no `lock:` prefix is added):
|
|
5
|
+
*
|
|
6
|
+
* - `lock`: the exclusive holder's token, or `shared` while shared leases exist (so a plain
|
|
7
|
+
* `SET NX` holder of the same key and the shared holders exclude each other);
|
|
8
|
+
* - `users`: sorted set of shared holders' tokens scored by their expiry (ms, Redis server clock);
|
|
9
|
+
* - `intent`: an exclusive waiter's token; while it exists no new shared lease is granted except
|
|
10
|
+
* to the waiter's own token.
|
|
11
|
+
*
|
|
12
|
+
* Every script purges expired shared leases first, and every key carries a TTL. The scripts are
|
|
13
|
+
* the protocol of {@link LockService.tryShared} / {@link LockService.tryExclusive} and the handle
|
|
14
|
+
* they return (renewal, release, downgrade).
|
|
15
|
+
*/
|
|
16
|
+
/** Sets `now` (ms, the Redis server clock, the same on every replica). */
|
|
17
|
+
const NOW = `local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)`;
|
|
18
|
+
/**
|
|
19
|
+
* After {@link NOW} (KEYS[1] the lock key, KEYS[2] the users set): drops expired shared leases and
|
|
20
|
+
* a `shared` value no live lease backs, and sets `cur` to the lock key's value (false when absent).
|
|
21
|
+
*/
|
|
22
|
+
const PURGE = `
|
|
23
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)
|
|
24
|
+
local cur = redis.call('GET', KEYS[1])
|
|
25
|
+
if cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then
|
|
26
|
+
redis.call('DEL', KEYS[1])
|
|
27
|
+
cur = false
|
|
28
|
+
end`;
|
|
29
|
+
/** A Lua script whose `body` sees `now` (ms, the Redis server clock). */
|
|
30
|
+
export function redisClockScript(body) {
|
|
31
|
+
return `${NOW}
|
|
32
|
+
${body}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A Lua script that reads a shared lease atomically with other keys (a domain's own records next
|
|
36
|
+
* to the lease, e.g. the sandbox holder records): KEYS[1] is the lease's lock key and KEYS[2] its
|
|
37
|
+
* users set (as {@link SharedLeaseKeys}); `body` sees `now` (ms, the Redis server clock) and `cur`,
|
|
38
|
+
* the lock key's value once expired shared leases are dropped (`'shared'`, an exclusive holder's
|
|
39
|
+
* token, or false). The lease layout stays in this module.
|
|
40
|
+
*/
|
|
41
|
+
export function sharedLeaseReadScript(body) {
|
|
42
|
+
return `${NOW}
|
|
43
|
+
${PURGE}
|
|
44
|
+
${body}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Keeps the users set and the `shared` lock value alive for at least `ttl` without shortening a
|
|
48
|
+
* longer TTL another holder set (holders may use different TTLs).
|
|
49
|
+
*/
|
|
50
|
+
const KEEP_SHARED = `
|
|
51
|
+
if redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end
|
|
52
|
+
local lt = redis.call('PTTL', KEYS[1])
|
|
53
|
+
if redis.call('GET', KEYS[1]) ~= 'shared' or lt < ttl then
|
|
54
|
+
redis.call('SET', KEYS[1], 'shared', 'PX', math.max(ttl, lt))
|
|
55
|
+
end`;
|
|
56
|
+
/** KEYS lock, users, intent; ARGV token, cap, ttl. */
|
|
57
|
+
export const ACQUIRE_SHARED = `${NOW}
|
|
58
|
+
${PURGE}
|
|
59
|
+
if cur and cur ~= 'shared' then return 0 end
|
|
60
|
+
local it = redis.call('GET', KEYS[3])
|
|
61
|
+
if it and it ~= ARGV[1] then return 0 end
|
|
62
|
+
if redis.call('ZCARD', KEYS[2]) >= tonumber(ARGV[2]) then return 0 end
|
|
63
|
+
local ttl = tonumber(ARGV[3])
|
|
64
|
+
redis.call('ZADD', KEYS[2], now + ttl, ARGV[1])
|
|
65
|
+
${KEEP_SHARED}
|
|
66
|
+
return 1`;
|
|
67
|
+
/** KEYS lock, users, intent; ARGV token, ttl, setIntent (0/1), intentTtl. */
|
|
68
|
+
export const ACQUIRE_EXCLUSIVE = `${NOW}
|
|
69
|
+
${PURGE}
|
|
70
|
+
if cur or redis.call('ZCARD', KEYS[2]) > 0 then
|
|
71
|
+
if ARGV[3] == '1' then
|
|
72
|
+
local it = redis.call('GET', KEYS[3])
|
|
73
|
+
if (not it) or it == ARGV[1] then
|
|
74
|
+
redis.call('SET', KEYS[3], ARGV[1], 'PX', tonumber(ARGV[4]))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
return 0
|
|
78
|
+
end
|
|
79
|
+
redis.call('SET', KEYS[1], ARGV[1], 'PX', tonumber(ARGV[2]))
|
|
80
|
+
if redis.call('GET', KEYS[3]) == ARGV[1] then redis.call('DEL', KEYS[3]) end
|
|
81
|
+
return 1`;
|
|
82
|
+
/** KEYS lock, users; ARGV token, ttl, kind. Shared: lost when the member is gone or the lock holds a token. */
|
|
83
|
+
export const RENEW_SHARED_LEASE = `${NOW}
|
|
84
|
+
local ttl = tonumber(ARGV[2])
|
|
85
|
+
if ARGV[3] == 'exclusive' then
|
|
86
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
87
|
+
redis.call('PEXPIRE', KEYS[1], ttl)
|
|
88
|
+
return 1
|
|
89
|
+
end
|
|
90
|
+
return 0
|
|
91
|
+
end
|
|
92
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)
|
|
93
|
+
if not redis.call('ZSCORE', KEYS[2], ARGV[1]) then return 0 end
|
|
94
|
+
local cur = redis.call('GET', KEYS[1])
|
|
95
|
+
if cur and cur ~= 'shared' then
|
|
96
|
+
redis.call('ZREM', KEYS[2], ARGV[1])
|
|
97
|
+
return 0
|
|
98
|
+
end
|
|
99
|
+
redis.call('ZADD', KEYS[2], 'XX', now + ttl, ARGV[1])
|
|
100
|
+
${KEEP_SHARED}
|
|
101
|
+
return 1`;
|
|
102
|
+
/** KEYS lock, users; ARGV token, kind. */
|
|
103
|
+
export const RELEASE_SHARED_LEASE = `${NOW}
|
|
104
|
+
if ARGV[2] == 'exclusive' then
|
|
105
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then redis.call('DEL', KEYS[1]) end
|
|
106
|
+
return 1
|
|
107
|
+
end
|
|
108
|
+
redis.call('ZREM', KEYS[2], ARGV[1])
|
|
109
|
+
${PURGE}
|
|
110
|
+
return 1`;
|
|
111
|
+
/** KEYS lock, users; ARGV token, ttl. */
|
|
112
|
+
export const DOWNGRADE_SHARED_LEASE = `${NOW}
|
|
113
|
+
if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
114
|
+
local ttl = tonumber(ARGV[2])
|
|
115
|
+
redis.call('ZADD', KEYS[2], now + ttl, ARGV[1])
|
|
116
|
+
${KEEP_SHARED}
|
|
117
|
+
return 1`;
|
|
118
|
+
/** KEYS lock, users; returns the live shared count. */
|
|
119
|
+
export const COUNT_SHARED_LEASES = `${NOW}
|
|
120
|
+
${PURGE}
|
|
121
|
+
return redis.call('ZCARD', KEYS[2])`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface WithLockOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Lock key; `lock:` is prepended unless present. Placeholders expand from the method
|
|
4
|
+
* arguments: `#{0}`, `#{1.book.title}`, `#{user.id}` (= `#{0.user.id}`).
|
|
5
|
+
*/
|
|
6
|
+
key: string;
|
|
7
|
+
/** Lock lifetime without renewal, ms (default 30000); the lock is renewed every TTL/3. */
|
|
8
|
+
ttl?: number;
|
|
9
|
+
/** How long to wait for the lock, ms (default 5000); 0 tries once. */
|
|
10
|
+
waitTimeout?: number;
|
|
11
|
+
/** Pause between attempts, ms (default 100). */
|
|
12
|
+
retryInterval?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Method decorator: only one caller at a time, across replicas, runs the method for a given key.
|
|
16
|
+
* Throws `ApiError("lock.acquire_failed")` (409, `details[0].lockKey`) when the lock is not
|
|
17
|
+
* obtained in `waitTimeout`. The class must inject the service as a property named `lockService`:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* constructor(@Inject(LockService) readonly lockService: LockService) {}
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Never inside a transaction: the lock would be released before COMMIT.
|
|
24
|
+
*
|
|
25
|
+
* The method's arguments are the caller's, so it gets no signal of the lock's loss: a body that
|
|
26
|
+
* must stop when the lock is lost uses `LockService.run`, whose callback gets the handle (`lost`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function WithLock(options: WithLockOptions): (_target: object, _key: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Ported from octok-platform libs/common/src/decorators/with-lock.decorator.ts (owner-approved, 2026-10-28).
|
|
2
|
+
// Adapted: the LockService comes from the `lockService` property, as `@Transactional` reads `txHost`.
|
|
3
|
+
import { currentStore } from "../database/transaction-host.js";
|
|
4
|
+
import { generateKey } from "./key-template.js";
|
|
5
|
+
import { lockAcquireFailed, prefixed } from "./lock.service.js";
|
|
6
|
+
/**
|
|
7
|
+
* Method decorator: only one caller at a time, across replicas, runs the method for a given key.
|
|
8
|
+
* Throws `ApiError("lock.acquire_failed")` (409, `details[0].lockKey`) when the lock is not
|
|
9
|
+
* obtained in `waitTimeout`. The class must inject the service as a property named `lockService`:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* constructor(@Inject(LockService) readonly lockService: LockService) {}
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Never inside a transaction: the lock would be released before COMMIT.
|
|
16
|
+
*
|
|
17
|
+
* The method's arguments are the caller's, so it gets no signal of the lock's loss: a body that
|
|
18
|
+
* must stop when the lock is lost uses `LockService.run`, whose callback gets the handle (`lost`).
|
|
19
|
+
*/
|
|
20
|
+
export function WithLock(options) {
|
|
21
|
+
return (_target, _key, descriptor) => {
|
|
22
|
+
const original = descriptor.value;
|
|
23
|
+
descriptor.value = async function (...args) {
|
|
24
|
+
const service = this.lockService;
|
|
25
|
+
if (!service) {
|
|
26
|
+
throw new Error("@WithLock() needs the class to inject `LockService` as `lockService` " +
|
|
27
|
+
"(`@Inject(LockService) readonly lockService: LockService`).");
|
|
28
|
+
}
|
|
29
|
+
if (currentStore()) {
|
|
30
|
+
throw new Error("@WithLock() must not run inside a transaction: lock first, then open it.");
|
|
31
|
+
}
|
|
32
|
+
const key = generateKey(options.key, args);
|
|
33
|
+
const handle = await service.acquire(key, {
|
|
34
|
+
ttlMs: options.ttl ?? 30_000,
|
|
35
|
+
waitMs: options.waitTimeout ?? 5_000,
|
|
36
|
+
retryMs: options.retryInterval ?? 100,
|
|
37
|
+
});
|
|
38
|
+
if (!handle)
|
|
39
|
+
throw lockAcquireFailed(prefixed(key));
|
|
40
|
+
try {
|
|
41
|
+
return await original.apply(this, args);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await handle.release();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return descriptor;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type DynamicModule } from "@nestjs/common";
|
|
2
|
+
import type { KernelConfig } from "../../config/schema.js";
|
|
3
|
+
import type { KernelLogger } from "../../log.js";
|
|
4
|
+
export interface LoggingModuleOptions {
|
|
5
|
+
/** An existing logger (tests, embedders); default: the one the config describes. */
|
|
6
|
+
logger?: KernelLogger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Provides the process logger as `KERNEL_LOGGER`. The logger is created before the Nest context
|
|
10
|
+
* (it must exist for startup errors), so it is passed in or derived from the startup config here.
|
|
11
|
+
*/
|
|
12
|
+
export declare class LoggingModule {
|
|
13
|
+
static forRoot(config: KernelConfig, opts?: LoggingModuleOptions): DynamicModule;
|
|
14
|
+
/** Provides `logger` (a context without a config: `KernelCliModule.overPool`). */
|
|
15
|
+
static forLogger(logger: KernelLogger): DynamicModule;
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 LoggingModule_1;
|
|
8
|
+
import { Global, Module } from "@nestjs/common";
|
|
9
|
+
import { createConfiguredLogger } from "../../log-config.js";
|
|
10
|
+
import { KERNEL_LOGGER } from "../tokens.js";
|
|
11
|
+
/**
|
|
12
|
+
* Provides the process logger as `KERNEL_LOGGER`. The logger is created before the Nest context
|
|
13
|
+
* (it must exist for startup errors), so it is passed in or derived from the startup config here.
|
|
14
|
+
*/
|
|
15
|
+
let LoggingModule = LoggingModule_1 = class LoggingModule {
|
|
16
|
+
static forRoot(config, opts = {}) {
|
|
17
|
+
return LoggingModule_1.forLogger(opts.logger ?? createConfiguredLogger(config));
|
|
18
|
+
}
|
|
19
|
+
/** Provides `logger` (a context without a config: `KernelCliModule.overPool`). */
|
|
20
|
+
static forLogger(logger) {
|
|
21
|
+
return {
|
|
22
|
+
module: LoggingModule_1,
|
|
23
|
+
global: true,
|
|
24
|
+
providers: [{ provide: KERNEL_LOGGER, useValue: logger }],
|
|
25
|
+
exports: [KERNEL_LOGGER],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
LoggingModule = LoggingModule_1 = __decorate([
|
|
30
|
+
Global(),
|
|
31
|
+
Module({})
|
|
32
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: a Nest dynamic module
|
|
33
|
+
], LoggingModule);
|
|
34
|
+
export { LoggingModule };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type WorkerHost } from "@nestjs/bullmq";
|
|
2
|
+
import type { ClassProvider, Type } from "@nestjs/common";
|
|
3
|
+
import type { WorkerOptions } from "bullmq";
|
|
4
|
+
/** The BullMQ worker options a configured processor takes (connection and prefix come from the queue). */
|
|
5
|
+
export type ProcessorWorkerOptions = Omit<WorkerOptions, "connection" | "autorun" | "prefix" | "sharedConnection">;
|
|
6
|
+
/**
|
|
7
|
+
* A `@Processor(queue)` provider whose worker options come from the application's startup config
|
|
8
|
+
* (`@Processor` reads them from class metadata when the worker is built, at module init, so they
|
|
9
|
+
* cannot be read from an injected service). Returns `{ provide: base, useClass }` with a subclass
|
|
10
|
+
* of `base` decorated for this application only, so two applications in one process (tests, the
|
|
11
|
+
* dual-replica harness) each get their own options. `base` is an `@Injectable` `WorkerHost` without
|
|
12
|
+
* `@Processor` of its own; inject it by `base`.
|
|
13
|
+
*
|
|
14
|
+
* The worker never starts by itself (`autorun: false`): the role that consumes calls
|
|
15
|
+
* `worker.run()` once the start sequence reached it (the kernel's worker role, after the extension
|
|
16
|
+
* load), and pauses it on stop.
|
|
17
|
+
*/
|
|
18
|
+
export declare function configuredProcessor<T extends WorkerHost>(base: Type<T>, queue: string, options: ProcessorWorkerOptions): ClassProvider<T>;
|