@plitzi/sdk-server 0.37.1 → 0.37.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/CHANGELOG.md +1296 -0
- package/README.md +126 -7
- package/dist/actions.d.ts +20 -8
- package/dist/actions.js +11 -4
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/auth.d.ts +7 -4
- package/dist/auth.js +8 -6
- package/dist/core/auth/api.d.ts +29 -1
- package/dist/core/auth/api.js +47 -13
- package/dist/core/auth/createAuth.d.ts +4 -0
- package/dist/core/auth/createAuth.js +3 -3
- package/dist/core/auth/credentials.d.ts +3 -0
- package/dist/core/auth/credentials.js +1 -1
- package/dist/core/auth/csrf.d.ts +25 -1
- package/dist/core/auth/csrf.js +29 -4
- package/dist/core/auth/renewal.d.ts +26 -0
- package/dist/core/auth/renewal.js +24 -0
- package/dist/core/auth/routes.js +8 -6
- package/dist/core/auth/session.d.ts +45 -0
- package/dist/core/auth/session.js +47 -1
- package/dist/core/auth/sessionActivity.d.ts +11 -0
- package/dist/core/auth/sessionActivity.js +14 -0
- package/dist/core/auth/throttle.d.ts +15 -0
- package/dist/core/auth/throttle.js +29 -1
- package/dist/core/auth/totp.d.ts +9 -1
- package/dist/core/auth/totp.js +11 -6
- package/dist/core/createServer.actions.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +14 -1
- package/dist/core/createServer.functions.test.d.ts +1 -0
- package/dist/core/createServer.js +22 -12
- package/dist/core/fontStore.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/authRouteHandlers.js +57 -21
- package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
- package/dist/core/handlers/csrfMiddleware.js +31 -7
- package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
- package/dist/core/handlers/oauthRouteHandlers.js +5 -3
- package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/socialRouteHandlers.js +2 -1
- package/dist/core/http/dispatcher.d.ts +9 -1
- package/dist/core/http/dispatcher.js +38 -11
- package/dist/core/http/navigation.d.ts +5 -2
- package/dist/core/http/navigation.js +7 -4
- package/dist/core/http/socketResponse.d.ts +18 -0
- package/dist/core/http/socketResponse.js +59 -0
- package/dist/core/http/sse.d.ts +26 -0
- package/dist/core/http/sse.js +78 -0
- package/dist/core/http/stages/authRoutes.js +3 -1
- package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
- package/dist/core/http/stages/middlewares.d.ts +1 -1
- package/dist/core/http/stages/middlewares.js +16 -10
- package/dist/core/http/stages/middlewares.test.d.ts +1 -0
- package/dist/core/http/stages/oauth.js +2 -2
- package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
- package/dist/core/http/stages/sessionRenewal.js +81 -0
- package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
- package/dist/core/http/stages/signInRoute.d.ts +2 -0
- package/dist/core/http/stages/signInRoute.js +139 -0
- package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
- package/dist/core/http/types.d.ts +9 -0
- package/dist/core/http/webExchange.d.ts +18 -0
- package/dist/core/http/webExchange.js +53 -0
- package/dist/core/oauth/authorize.js +37 -13
- package/dist/core/oauth/consentPage.js +11 -0
- package/dist/core/oauth/issueContext.d.ts +14 -0
- package/dist/core/oauth/issueContext.js +32 -0
- package/dist/core/oauth/records.d.ts +7 -0
- package/dist/core/oauth/register.d.ts +2 -0
- package/dist/core/oauth/register.js +32 -11
- package/dist/core/oauth/register.test.d.ts +1 -0
- package/dist/core/oauth/token.js +21 -6
- package/dist/core/oauth/token.test.d.ts +1 -0
- package/dist/core/requestParser.d.ts +4 -0
- package/dist/core/requestParser.js +14 -7
- package/dist/core/sdkAssets.js +1 -1
- package/dist/core/server/baseServer.d.ts +14 -1
- package/dist/core/server/baseServer.js +43 -11
- package/dist/core/server/closeOnSignals.d.ts +28 -0
- package/dist/core/server/closeOnSignals.js +47 -0
- package/dist/core/server/closeOnSignals.test.d.ts +1 -0
- package/dist/core/server/drain.d.ts +18 -0
- package/dist/core/server/drain.js +62 -0
- package/dist/core/server/drain.test.d.ts +1 -0
- package/dist/core/server/fleet/channel.d.ts +103 -0
- package/dist/core/server/fleet/channel.js +191 -0
- package/dist/core/server/fleet/channel.test.d.ts +1 -0
- package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
- package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
- package/dist/core/server/fleet/link.d.ts +23 -0
- package/dist/core/server/fleet/link.js +70 -0
- package/dist/core/server/fleet/relay.d.ts +11 -0
- package/dist/core/server/fleet/relay.js +104 -0
- package/dist/core/server/fleet/relay.test.d.ts +1 -0
- package/dist/core/server/fleet/role.d.ts +46 -0
- package/dist/core/server/fleet/role.js +59 -0
- package/dist/core/server/fleet/role.test.d.ts +1 -0
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +19 -0
- package/dist/core/server/fleet/supervisor.d.ts +26 -0
- package/dist/core/server/fleet/supervisor.js +108 -0
- package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
- package/dist/core/server/pageServer.js +36 -5
- package/dist/core/services/action.js +10 -2
- package/dist/core/services/functionRoutes.d.ts +9 -0
- package/dist/core/services/functionRoutes.js +47 -0
- package/dist/core/services/realtime.d.ts +8 -0
- package/dist/core/services/realtime.js +57 -0
- package/dist/core/services/registry.js +6 -2
- package/dist/core/staticFiles.js +12 -4
- package/dist/core/staticFiles.test.d.ts +1 -0
- package/dist/core/transports.d.ts +13 -3
- package/dist/core/transports.js +14 -5
- package/dist/functions-api.d.ts +281 -0
- package/dist/functions.d.ts +19 -0
- package/dist/functions.js +5 -0
- package/dist/functionsRunner.d.ts +15 -0
- package/dist/functionsRunner.js +6 -0
- package/dist/handlers.d.ts +2 -2
- package/dist/handlers.js +2 -2
- package/dist/helpers/atomicFile.d.ts +10 -0
- package/dist/helpers/atomicFile.js +33 -0
- package/dist/helpers/atomicFile.test.d.ts +1 -0
- package/dist/helpers/buildResponseHelpers.js +35 -11
- package/dist/helpers/buildServerInfo.js +4 -2
- package/dist/helpers/buildServerInfo.test.d.ts +1 -0
- package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
- package/dist/helpers/cache/CompressedFileCache.js +44 -0
- package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
- package/dist/helpers/cache/cacheManager.d.ts +1 -1
- package/dist/helpers/cache/cacheManager.js +2 -1
- package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
- package/dist/helpers/cache/index.d.ts +1 -1
- package/dist/helpers/cache/keys.d.ts +15 -1
- package/dist/helpers/cache/keys.js +47 -5
- package/dist/helpers/cache/serverCaches.d.ts +7 -1
- package/dist/helpers/compress.d.ts +3 -1
- package/dist/helpers/compress.js +11 -3
- package/dist/helpers/hydrationPayload.d.ts +11 -0
- package/dist/helpers/hydrationPayload.js +51 -0
- package/dist/helpers/hydrationPayload.test.d.ts +1 -0
- package/dist/helpers/metrics.d.ts +1 -1
- package/dist/helpers/metrics.js +3 -2
- package/dist/helpers/outboundGuard.d.ts +14 -6
- package/dist/helpers/outboundGuard.js +132 -8
- package/dist/helpers/paintedCookie.d.ts +8 -0
- package/dist/helpers/paintedCookie.js +13 -0
- package/dist/helpers/reportReactBuild.d.ts +9 -0
- package/dist/helpers/reportReactBuild.js +18 -0
- package/dist/helpers/reportReactBuild.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +25 -1
- package/dist/helpers/serverLog.js +68 -3
- package/dist/helpers/serverLog.test.d.ts +1 -0
- package/dist/helpers/wsFrame.d.ts +3 -0
- package/dist/helpers/wsFrame.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -2
- package/dist/kernel.d.ts +2 -1
- package/dist/kernel.js +3 -2
- package/dist/modules/actions/connectorWrite.js +2 -1
- package/dist/modules/actions/index.d.ts +41 -13
- package/dist/modules/actions/index.js +52 -9
- package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
- package/dist/modules/actions/jobs/failureStreak.js +47 -0
- package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
- package/dist/modules/actions/jobs/index.d.ts +33 -0
- package/dist/modules/actions/jobs/index.js +59 -0
- package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
- package/dist/modules/actions/jobs/memoryQueue.js +194 -0
- package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
- package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
- package/dist/modules/actions/jobs/scheduler.js +130 -0
- package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
- package/dist/modules/actions/jobs/schedules.d.ts +23 -0
- package/dist/modules/actions/jobs/schedules.js +47 -0
- package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
- package/dist/modules/actions/jobs/storeClock.js +32 -0
- package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
- package/dist/modules/actions/jobs/worker.d.ts +64 -0
- package/dist/modules/actions/jobs/worker.js +267 -0
- package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
- package/dist/modules/actions/moduleFor.js +6 -2
- package/dist/modules/actions/runtime/errors.d.ts +12 -0
- package/dist/modules/actions/runtime/errors.js +16 -1
- package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
- package/dist/modules/actions/runtime/kvChange.js +23 -0
- package/dist/modules/actions/runtime/kvList.d.ts +51 -0
- package/dist/modules/actions/runtime/kvList.js +52 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
- package/dist/modules/actions/runtime/kvStore.js +46 -20
- package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
- package/dist/modules/actions/runtime/memoryKv.js +19 -1
- package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
- package/dist/modules/actions/runtime/namespaceKv.js +15 -3
- package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/precheck.js +5 -9
- package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
- package/dist/modules/actions/runtime/rateLimit.js +19 -0
- package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
- package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
- package/dist/modules/actions/runtime/redisKv.js +46 -0
- package/dist/modules/actions/runtime/renderResolver.js +2 -2
- package/dist/modules/actions/runtime/report.js +2 -1
- package/dist/modules/actions/runtime/runAction.d.ts +10 -2
- package/dist/modules/actions/runtime/runAction.js +52 -116
- package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
- package/dist/modules/actions/runtime/runLogger.js +8 -2
- package/dist/modules/actions/runtime/signing.d.ts +20 -0
- package/dist/modules/actions/runtime/signing.js +25 -0
- package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
- package/dist/modules/actions/runtime/taskContext.js +124 -0
- package/dist/modules/actions/tasks/builtins.js +5 -1
- package/dist/modules/actions/tasks/flow.js +81 -10
- package/dist/modules/actions/tasks/helpers.js +2 -2
- package/dist/modules/actions/tasks/http.js +2 -3
- package/dist/modules/actions/tasks/kv.js +39 -0
- package/dist/modules/actions/tasks/list.d.ts +2 -0
- package/dist/modules/actions/tasks/list.js +145 -0
- package/dist/modules/actions/tasks/realtime.d.ts +2 -0
- package/dist/modules/actions/tasks/realtime.js +104 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -1
- package/dist/modules/actions/tasks/registry.js +17 -6
- package/dist/modules/actions/transport/callHandler.js +6 -3
- package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
- package/dist/modules/actions/transport/catalogHandler.js +3 -2
- package/dist/modules/actions/transport/stream.d.ts +4 -5
- package/dist/modules/actions/transport/stream.js +12 -76
- package/dist/modules/actions/transport/webhookHandler.js +5 -4
- package/dist/modules/actions/types.d.ts +162 -4
- package/dist/modules/connectors/engine.js +3 -5
- package/dist/modules/functions/build.d.ts +22 -0
- package/dist/modules/functions/build.js +122 -0
- package/dist/modules/functions/build.test.d.ts +1 -0
- package/dist/modules/functions/capabilities.d.ts +22 -0
- package/dist/modules/functions/capabilities.js +228 -0
- package/dist/modules/functions/config.d.ts +34 -0
- package/dist/modules/functions/config.js +24 -0
- package/dist/modules/functions/context.d.ts +17 -0
- package/dist/modules/functions/context.js +88 -0
- package/dist/modules/functions/contract.d.ts +121 -0
- package/dist/modules/functions/contract.js +9 -0
- package/dist/modules/functions/driver.d.ts +36 -0
- package/dist/modules/functions/driver.js +150 -0
- package/dist/modules/functions/load.d.ts +15 -0
- package/dist/modules/functions/load.js +45 -0
- package/dist/modules/functions/load.test.d.ts +1 -0
- package/dist/modules/functions/local.d.ts +29 -0
- package/dist/modules/functions/local.js +61 -0
- package/dist/modules/functions/local.test.d.ts +1 -0
- package/dist/modules/functions/manifest.d.ts +24 -0
- package/dist/modules/functions/manifest.js +163 -0
- package/dist/modules/functions/manifest.test.d.ts +1 -0
- package/dist/modules/functions/native.d.ts +8 -0
- package/dist/modules/functions/native.js +21 -0
- package/dist/modules/functions/native.test.d.ts +1 -0
- package/dist/modules/functions/protocol.d.ts +216 -0
- package/dist/modules/functions/protocol.js +18 -0
- package/dist/modules/functions/routes.d.ts +36 -0
- package/dist/modules/functions/routes.js +181 -0
- package/dist/modules/functions/routes.test.d.ts +1 -0
- package/dist/modules/functions/runner/messages.d.ts +5 -0
- package/dist/modules/functions/runner/messages.js +188 -0
- package/dist/modules/functions/runner/remote.d.ts +19 -0
- package/dist/modules/functions/runner/remote.js +116 -0
- package/dist/modules/functions/runner/remote.test.d.ts +1 -0
- package/dist/modules/functions/runner/service.d.ts +32 -0
- package/dist/modules/functions/runner/service.js +209 -0
- package/dist/modules/functions/sandbox/guest.d.ts +45 -0
- package/dist/modules/functions/sandbox/guest.js +660 -0
- package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
- package/dist/modules/functions/sandbox/isolate.js +347 -0
- package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
- package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
- package/dist/modules/functions/sandbox/prelude.js +49 -0
- package/dist/modules/functions/space.d.ts +40 -0
- package/dist/modules/functions/space.js +162 -0
- package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
- package/dist/modules/functions/tryEntry.d.ts +9 -0
- package/dist/modules/functions/tryEntry.js +49 -0
- package/dist/modules/mongo/index.d.ts +4 -0
- package/dist/modules/mongo/jobQueue.d.ts +38 -0
- package/dist/modules/mongo/jobQueue.js +335 -0
- package/dist/modules/mongo/kv.d.ts +15 -0
- package/dist/modules/mongo/kv.js +129 -0
- package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/accounts.js +19 -9
- package/dist/modules/mysql/index.d.ts +2 -1
- package/dist/modules/mysql/jobs/index.d.ts +5 -0
- package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
- package/dist/modules/mysql/jobs/jobQueue.js +375 -0
- package/dist/modules/mysql/jobs/kv.d.ts +18 -0
- package/dist/modules/mysql/jobs/kv.js +108 -0
- package/dist/modules/mysql/jobs/schema.d.ts +33 -0
- package/dist/modules/mysql/jobs/schema.js +113 -0
- package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/schema.js +25 -0
- package/dist/modules/mysql/sessions.d.ts +1 -11
- package/dist/modules/mysql/sessions.js +6 -1
- package/dist/modules/realtime/declarations.d.ts +11 -0
- package/dist/modules/realtime/declarations.js +26 -0
- package/dist/modules/realtime/grants.d.ts +23 -0
- package/dist/modules/realtime/grants.js +61 -0
- package/dist/modules/realtime/handlers.d.ts +74 -0
- package/dist/modules/realtime/handlers.js +224 -0
- package/dist/modules/realtime/hub.d.ts +62 -0
- package/dist/modules/realtime/hub.js +141 -0
- package/dist/modules/realtime/index.d.ts +51 -0
- package/dist/modules/realtime/index.js +62 -0
- package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
- package/dist/modules/realtime/memoryPubSub.js +43 -0
- package/dist/modules/realtime/realtime.test.d.ts +1 -0
- package/dist/modules/realtime/redisPubSub.d.ts +24 -0
- package/dist/modules/realtime/redisPubSub.js +36 -0
- package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
- package/dist/modules/realtime/socket.d.ts +25 -0
- package/dist/modules/realtime/socket.js +122 -0
- package/dist/modules/realtime/socket.test.d.ts +1 -0
- package/dist/modules/rsc/handler.d.ts +16 -0
- package/dist/modules/rsc/handler.js +17 -10
- package/dist/modules/rsc/handler.test.d.ts +1 -0
- package/dist/modules/rsc/resolveRscData.js +2 -1
- package/dist/modules/runtime/bundle.d.ts +24 -0
- package/dist/modules/runtime/bundle.js +102 -0
- package/dist/modules/runtime/contract.d.ts +37 -0
- package/dist/modules/runtime/contract.js +21 -0
- package/dist/modules/runtime/entry.test.d.ts +1 -0
- package/dist/modules/runtime/host.d.ts +38 -0
- package/dist/modules/runtime/host.js +68 -0
- package/dist/modules/runtime/inside.d.ts +15 -0
- package/dist/modules/runtime/inside.js +47 -0
- package/dist/modules/runtime/inside.test.d.ts +1 -0
- package/dist/modules/runtime/runner.d.ts +11 -0
- package/dist/modules/runtime/runner.js +38 -0
- package/dist/modules/runtime/runtime.test.d.ts +1 -0
- package/dist/modules/runtime/stages.d.ts +50 -0
- package/dist/modules/runtime/stages.js +117 -0
- package/dist/modules/ssr/Component.d.ts +4 -5
- package/dist/modules/ssr/Component.js +2 -2
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -13
- package/dist/modules/ssr/preview.d.ts +3 -0
- package/dist/modules/ssr/preview.js +17 -7
- package/dist/modules/ssr/redirect.test.d.ts +1 -0
- package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
- package/dist/modules/ssr/registerExternalPlugins.js +19 -16
- package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
- package/dist/modules/ssr/reportMissingPlugins.js +24 -0
- package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/streamBody.d.ts +2 -2
- package/dist/modules/ssr/streamBody.js +4 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/modules/ssr/views/template.ejs +8 -5
- package/dist/mongo.d.ts +25 -0
- package/dist/mongo.js +3 -0
- package/dist/mysql.d.ts +11 -0
- package/dist/mysql.js +4 -1
- package/dist/oauth.js +2 -2
- package/dist/plugins/compile.js +9 -4
- package/dist/plugins/copy.js +3 -3
- package/dist/plugins/manager.d.ts +35 -3
- package/dist/plugins/manager.js +109 -38
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +7 -0
- package/package.json +52 -11
- package/skills/plitzi-authoring/SKILL.md +153 -263
- package/skills/plitzi-authoring/reference/accessibility.md +111 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
- package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
- package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
- package/skills/plitzi-authoring/reference/flows.md +171 -0
- package/skills/plitzi-authoring/reference/layouts.md +140 -0
- package/skills/plitzi-authoring/reference/lists.md +74 -0
- package/skills/plitzi-authoring/reference/performance.md +67 -0
- package/skills/plitzi-authoring/reference/plugins.md +167 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
- package/skills/plitzi-authoring/reference/structure.md +83 -0
- package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
- package/skills/plitzi-authoring/reference/templates.md +109 -0
- package/skills/plitzi-authoring/reference/testing.md +119 -0
- package/skills/plitzi-authoring/reference/validation.md +73 -0
- package/dist/modules/actions/runtime/schedule.d.ts +0 -35
- package/dist/modules/actions/runtime/schedule.js +0 -76
- /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ActionJobQueue } from '@plitzi/sdk-shared';
|
|
2
|
+
import { Db } from 'mongodb';
|
|
3
|
+
/** The database, or how to reach it — a getter for a deployment whose client is replaced when it reconnects. */
|
|
4
|
+
export type MongoDbSource = Db | (() => Db);
|
|
5
|
+
export declare const resolveDb: (source: MongoDbSource) => Db;
|
|
6
|
+
export type MongoJobQueueOptions = {
|
|
7
|
+
db: MongoDbSource;
|
|
8
|
+
/** Collection names, for a deployment with a naming scheme of its own. */
|
|
9
|
+
collections?: {
|
|
10
|
+
jobs?: string;
|
|
11
|
+
schedules?: string;
|
|
12
|
+
};
|
|
13
|
+
/** How long a finished job stays readable. A waiting or running one never expires. Default 90 days. */
|
|
14
|
+
retentionDays?: number;
|
|
15
|
+
};
|
|
16
|
+
export type MongoJobQueue = ActionJobQueue & {
|
|
17
|
+
/** Takes the store's clock again on the next call — after a failover to another primary. */
|
|
18
|
+
resyncClock: () => void;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* An {@link ActionJobQueue} over two collections of a Mongo database the deployment owns.
|
|
22
|
+
*
|
|
23
|
+
* The helper for a deployment that already runs Mongo: it holds no connection of its own and creates nothing but the
|
|
24
|
+
* indexes its queries need, on first use. Every replica pointed at the same database shares the jobs and the
|
|
25
|
+
* schedules, which is what running more than one needs.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { createMongoJobQueue } from '@plitzi/sdk-server/mongo';
|
|
29
|
+
*
|
|
30
|
+
* const queue = createMongoJobQueue({ db: client.db('app') });
|
|
31
|
+
* createServer({ action: { lookups, kv, jobs: { queue } } });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* The contract's rules, as Mongo keeps them: every instant is the SERVER's (`hello.localTime`), `enqueue` is idempotent
|
|
35
|
+
* by the job's `_id`, `claim` takes one document per `findOneAndUpdate` — the atomic unit — and reaps a lapsed lease in
|
|
36
|
+
* the same operation, and a schedule advances by compare-and-set on the instant that was read.
|
|
37
|
+
*/
|
|
38
|
+
export declare const createMongoJobQueue: ({ db, collections: names, retentionDays }: MongoJobQueueOptions) => MongoJobQueue;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { createStoreClock } from "../actions/jobs/storeClock.js";
|
|
2
|
+
//#region src/modules/mongo/jobQueue.ts
|
|
3
|
+
/** Mongo's code for a unique index that refused a second document with the same key. */
|
|
4
|
+
var DUPLICATE_KEY = 11e3;
|
|
5
|
+
var TERMINAL = [
|
|
6
|
+
"succeeded",
|
|
7
|
+
"failed",
|
|
8
|
+
"dead",
|
|
9
|
+
"cancelled"
|
|
10
|
+
];
|
|
11
|
+
var DAY_MS = 864e5;
|
|
12
|
+
var resolveDb = (source) => typeof source === "function" ? source() : source;
|
|
13
|
+
var scheduleId = (spaceId, actionId) => `${spaceId}:${actionId}`;
|
|
14
|
+
/**
|
|
15
|
+
* A stored document as the contract shapes it: an optional field is absent, never `null`.
|
|
16
|
+
*
|
|
17
|
+
* Mongo keeps what a `$set` wrote, and clearing a field in an update pipeline writes `null` — so a released job would
|
|
18
|
+
* otherwise come back with `workerId: null`, which every reader typed as `string | undefined` gets wrong.
|
|
19
|
+
*/
|
|
20
|
+
var withoutNulls = (document) => Object.fromEntries(Object.entries(document).filter(([, value]) => value !== null));
|
|
21
|
+
var toJob = ({ _id, expiresAt: _expiresAt, ...rest }) => withoutNulls({
|
|
22
|
+
...rest,
|
|
23
|
+
id: _id
|
|
24
|
+
});
|
|
25
|
+
var toSchedule = ({ _id: _unused, ...rest }) => withoutNulls(rest);
|
|
26
|
+
/** The history entry a reclaim writes on behalf of a worker that never came back. */
|
|
27
|
+
var lostAttempt = (at) => ({ $concatArrays: ["$history", [{
|
|
28
|
+
attempt: { $add: [{ $size: "$history" }, 1] },
|
|
29
|
+
status: "lost",
|
|
30
|
+
startedAt: "$updatedAt",
|
|
31
|
+
endedAt: at,
|
|
32
|
+
workerId: { $ifNull: ["$workerId", "unknown"] },
|
|
33
|
+
error: "the worker holding this job stopped reporting"
|
|
34
|
+
}]] });
|
|
35
|
+
/**
|
|
36
|
+
* An {@link ActionJobQueue} over two collections of a Mongo database the deployment owns.
|
|
37
|
+
*
|
|
38
|
+
* The helper for a deployment that already runs Mongo: it holds no connection of its own and creates nothing but the
|
|
39
|
+
* indexes its queries need, on first use. Every replica pointed at the same database shares the jobs and the
|
|
40
|
+
* schedules, which is what running more than one needs.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { createMongoJobQueue } from '@plitzi/sdk-server/mongo';
|
|
44
|
+
*
|
|
45
|
+
* const queue = createMongoJobQueue({ db: client.db('app') });
|
|
46
|
+
* createServer({ action: { lookups, kv, jobs: { queue } } });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* The contract's rules, as Mongo keeps them: every instant is the SERVER's (`hello.localTime`), `enqueue` is idempotent
|
|
50
|
+
* by the job's `_id`, `claim` takes one document per `findOneAndUpdate` — the atomic unit — and reaps a lapsed lease in
|
|
51
|
+
* the same operation, and a schedule advances by compare-and-set on the instant that was read.
|
|
52
|
+
*/
|
|
53
|
+
var createMongoJobQueue = ({ db, collections: names = {}, retentionDays = 90 }) => {
|
|
54
|
+
const jobsName = names.jobs ?? "action_jobs";
|
|
55
|
+
const schedulesName = names.schedules ?? "action_schedules";
|
|
56
|
+
const jobsOf = () => resolveDb(db).collection(jobsName);
|
|
57
|
+
const schedulesOf = () => resolveDb(db).collection(schedulesName);
|
|
58
|
+
const clock = createStoreClock(async () => {
|
|
59
|
+
const { localTime } = await resolveDb(db).command({ hello: 1 });
|
|
60
|
+
return localTime ? localTime.getTime() : Date.now();
|
|
61
|
+
});
|
|
62
|
+
let ready;
|
|
63
|
+
/**
|
|
64
|
+
* The indexes the queue cannot work without, built once on first use.
|
|
65
|
+
*
|
|
66
|
+
* `claim` is the one that matters: it runs on every poll of every replica, and without an index over status and the
|
|
67
|
+
* two time fields it is a collection scan several times a second. The two branches of its `$or` are answered by one
|
|
68
|
+
* index each and merged already sorted.
|
|
69
|
+
*/
|
|
70
|
+
const build = async () => {
|
|
71
|
+
await Promise.all([
|
|
72
|
+
jobsOf().createIndex({
|
|
73
|
+
status: 1,
|
|
74
|
+
runAt: 1
|
|
75
|
+
}),
|
|
76
|
+
jobsOf().createIndex({
|
|
77
|
+
status: 1,
|
|
78
|
+
leaseUntil: 1
|
|
79
|
+
}),
|
|
80
|
+
jobsOf().createIndex({
|
|
81
|
+
spaceId: 1,
|
|
82
|
+
updatedAt: -1
|
|
83
|
+
}),
|
|
84
|
+
jobsOf().createIndex({
|
|
85
|
+
spaceId: 1,
|
|
86
|
+
actionId: 1,
|
|
87
|
+
updatedAt: -1
|
|
88
|
+
}),
|
|
89
|
+
jobsOf().createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }),
|
|
90
|
+
schedulesOf().createIndex({
|
|
91
|
+
enabled: 1,
|
|
92
|
+
nextRunAt: 1
|
|
93
|
+
}),
|
|
94
|
+
schedulesOf().createIndex({ spaceId: 1 })
|
|
95
|
+
]);
|
|
96
|
+
};
|
|
97
|
+
const collections = async () => {
|
|
98
|
+
ready ??= build().catch((error) => {
|
|
99
|
+
ready = void 0;
|
|
100
|
+
throw error;
|
|
101
|
+
});
|
|
102
|
+
await ready;
|
|
103
|
+
return {
|
|
104
|
+
jobs: jobsOf(),
|
|
105
|
+
schedules: schedulesOf()
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const expiryOf = (at) => new Date(at + retentionDays * DAY_MS);
|
|
109
|
+
return {
|
|
110
|
+
resyncClock: clock.resync,
|
|
111
|
+
now: async () => new Date(await clock.now()),
|
|
112
|
+
enqueue: async (job) => {
|
|
113
|
+
const { jobs } = await collections();
|
|
114
|
+
const at = await clock.now();
|
|
115
|
+
const { id, ...fields } = job;
|
|
116
|
+
const document = {
|
|
117
|
+
...fields,
|
|
118
|
+
_id: id,
|
|
119
|
+
status: "pending",
|
|
120
|
+
runAt: job.dueAt,
|
|
121
|
+
attempts: 0,
|
|
122
|
+
createdAt: at,
|
|
123
|
+
updatedAt: at,
|
|
124
|
+
history: []
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
await jobs.insertOne(document);
|
|
128
|
+
return true;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error.code === DUPLICATE_KEY) return false;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
claim: async ({ workerId, leaseMs, limit }) => {
|
|
135
|
+
const { jobs } = await collections();
|
|
136
|
+
const at = await clock.now();
|
|
137
|
+
const taken = [];
|
|
138
|
+
for (let slot = 0; slot < limit; slot += 1) {
|
|
139
|
+
const claimed = await jobs.findOneAndUpdate({ $or: [{
|
|
140
|
+
status: "pending",
|
|
141
|
+
runAt: { $lte: at }
|
|
142
|
+
}, {
|
|
143
|
+
status: "running",
|
|
144
|
+
leaseUntil: { $lt: at }
|
|
145
|
+
}] }, [{ $set: { history: { $cond: [
|
|
146
|
+
{ $eq: ["$status", "running"] },
|
|
147
|
+
lostAttempt(at),
|
|
148
|
+
"$history"
|
|
149
|
+
] } } }, { $set: {
|
|
150
|
+
status: "running",
|
|
151
|
+
workerId,
|
|
152
|
+
leaseUntil: at + leaseMs,
|
|
153
|
+
updatedAt: at,
|
|
154
|
+
attempts: { $add: ["$attempts", 1] }
|
|
155
|
+
} }], {
|
|
156
|
+
sort: { runAt: 1 },
|
|
157
|
+
returnDocument: "after"
|
|
158
|
+
});
|
|
159
|
+
if (!claimed) break;
|
|
160
|
+
taken.push(toJob(claimed));
|
|
161
|
+
}
|
|
162
|
+
return taken;
|
|
163
|
+
},
|
|
164
|
+
heartbeat: async ({ jobIds, workerId, leaseMs }) => {
|
|
165
|
+
const { jobs } = await collections();
|
|
166
|
+
const at = await clock.now();
|
|
167
|
+
await jobs.updateMany({
|
|
168
|
+
_id: { $in: jobIds },
|
|
169
|
+
workerId,
|
|
170
|
+
status: "running"
|
|
171
|
+
}, { $set: { leaseUntil: at + leaseMs } });
|
|
172
|
+
return (await jobs.find({
|
|
173
|
+
_id: { $in: jobIds },
|
|
174
|
+
workerId,
|
|
175
|
+
status: "running",
|
|
176
|
+
cancelRequested: true
|
|
177
|
+
}).project({ _id: 1 }).toArray()).map((job) => job._id);
|
|
178
|
+
},
|
|
179
|
+
settle: async ({ jobId, workerId, status, delayMs, runId, error, attempt }) => {
|
|
180
|
+
const { jobs } = await collections();
|
|
181
|
+
const at = await clock.now();
|
|
182
|
+
const entry = {
|
|
183
|
+
attempt: { $add: [{ $size: "$history" }, 1] },
|
|
184
|
+
startedAt: "$updatedAt",
|
|
185
|
+
endedAt: at,
|
|
186
|
+
...attempt
|
|
187
|
+
};
|
|
188
|
+
await jobs.updateOne({
|
|
189
|
+
_id: jobId,
|
|
190
|
+
workerId
|
|
191
|
+
}, [{ $set: {
|
|
192
|
+
...attempt ? { history: { $concatArrays: ["$history", [entry]] } } : {},
|
|
193
|
+
status,
|
|
194
|
+
updatedAt: at,
|
|
195
|
+
leaseUntil: null,
|
|
196
|
+
error: error ?? null,
|
|
197
|
+
...runId ? { runId } : {},
|
|
198
|
+
...status === "pending" ? {
|
|
199
|
+
runAt: at + (delayMs ?? 0),
|
|
200
|
+
workerId: null,
|
|
201
|
+
...attempt ? {} : { attempts: { $max: [0, { $subtract: ["$attempts", 1] }] } }
|
|
202
|
+
} : {},
|
|
203
|
+
expiresAt: TERMINAL.includes(status) ? expiryOf(at) : null
|
|
204
|
+
} }]);
|
|
205
|
+
},
|
|
206
|
+
dueSchedules: async (limit) => {
|
|
207
|
+
const { schedules } = await collections();
|
|
208
|
+
const at = await clock.now();
|
|
209
|
+
return (await schedules.find({
|
|
210
|
+
enabled: true,
|
|
211
|
+
nextRunAt: { $lte: at }
|
|
212
|
+
}).sort({ nextRunAt: 1 }).limit(limit).toArray()).map(toSchedule);
|
|
213
|
+
},
|
|
214
|
+
advanceSchedule: async ({ spaceId, actionId, from, to, missed }) => {
|
|
215
|
+
const { schedules } = await collections();
|
|
216
|
+
const at = await clock.now();
|
|
217
|
+
return (await schedules.updateOne({
|
|
218
|
+
_id: scheduleId(spaceId, actionId),
|
|
219
|
+
nextRunAt: from
|
|
220
|
+
}, {
|
|
221
|
+
$set: {
|
|
222
|
+
nextRunAt: to,
|
|
223
|
+
lastFireAt: from,
|
|
224
|
+
updatedAt: at
|
|
225
|
+
},
|
|
226
|
+
$inc: { missed }
|
|
227
|
+
})).matchedCount === 1;
|
|
228
|
+
},
|
|
229
|
+
putSchedules: async ({ spaceId, schedules: incoming }) => {
|
|
230
|
+
const { schedules } = await collections();
|
|
231
|
+
const at = await clock.now();
|
|
232
|
+
if (incoming.length > 0) await schedules.bulkWrite(incoming.map((schedule) => ({ updateOne: {
|
|
233
|
+
filter: { _id: scheduleId(spaceId, schedule.actionId) },
|
|
234
|
+
update: [{ $set: {
|
|
235
|
+
...schedule,
|
|
236
|
+
timezone: schedule.timezone ?? null,
|
|
237
|
+
updatedAt: at,
|
|
238
|
+
missed: { $ifNull: ["$missed", 0] },
|
|
239
|
+
nextRunAt: { $cond: [
|
|
240
|
+
{ $and: [
|
|
241
|
+
{ $eq: ["$cron", schedule.cron] },
|
|
242
|
+
{ $eq: [{ $ifNull: ["$timezone", null] }, schedule.timezone ?? null] },
|
|
243
|
+
{ $eq: ["$environment", schedule.environment] },
|
|
244
|
+
{ $ne: [{ $ifNull: ["$nextRunAt", null] }, null] }
|
|
245
|
+
] },
|
|
246
|
+
"$nextRunAt",
|
|
247
|
+
schedule.nextRunAt
|
|
248
|
+
] }
|
|
249
|
+
} }],
|
|
250
|
+
upsert: true
|
|
251
|
+
} })));
|
|
252
|
+
await schedules.deleteMany({
|
|
253
|
+
spaceId,
|
|
254
|
+
actionId: { $nin: incoming.map((schedule) => schedule.actionId) }
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
listJobs: async ({ spaceIds, actionId, status, limit, offset }) => {
|
|
258
|
+
const { jobs } = await collections();
|
|
259
|
+
const filter = {
|
|
260
|
+
spaceId: { $in: spaceIds },
|
|
261
|
+
...actionId ? { actionId } : {},
|
|
262
|
+
...status ? { status } : {}
|
|
263
|
+
};
|
|
264
|
+
const [found, total] = await Promise.all([jobs.find(filter).sort({
|
|
265
|
+
updatedAt: -1,
|
|
266
|
+
_id: -1
|
|
267
|
+
}).skip(offset).limit(limit).toArray(), jobs.countDocuments(filter)]);
|
|
268
|
+
return {
|
|
269
|
+
jobs: found.map(toJob),
|
|
270
|
+
total
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
getJob: async (spaceIds, jobId) => {
|
|
274
|
+
const { jobs } = await collections();
|
|
275
|
+
const job = await jobs.findOne({
|
|
276
|
+
_id: jobId,
|
|
277
|
+
spaceId: { $in: spaceIds }
|
|
278
|
+
});
|
|
279
|
+
return job ? toJob(job) : void 0;
|
|
280
|
+
},
|
|
281
|
+
listSchedules: async (spaceIds) => {
|
|
282
|
+
const { schedules } = await collections();
|
|
283
|
+
return (await schedules.find({ spaceId: { $in: spaceIds } }).sort({ nextRunAt: 1 }).toArray()).map(toSchedule);
|
|
284
|
+
},
|
|
285
|
+
requeue: async (spaceIds, jobId) => {
|
|
286
|
+
const { jobs } = await collections();
|
|
287
|
+
const at = await clock.now();
|
|
288
|
+
return (await jobs.updateOne({
|
|
289
|
+
_id: jobId,
|
|
290
|
+
spaceId: { $in: spaceIds },
|
|
291
|
+
status: { $in: TERMINAL }
|
|
292
|
+
}, {
|
|
293
|
+
$set: {
|
|
294
|
+
status: "pending",
|
|
295
|
+
runAt: at,
|
|
296
|
+
attempts: 0,
|
|
297
|
+
updatedAt: at,
|
|
298
|
+
cancelRequested: false
|
|
299
|
+
},
|
|
300
|
+
$unset: {
|
|
301
|
+
workerId: "",
|
|
302
|
+
leaseUntil: "",
|
|
303
|
+
error: "",
|
|
304
|
+
expiresAt: ""
|
|
305
|
+
}
|
|
306
|
+
})).matchedCount === 1;
|
|
307
|
+
},
|
|
308
|
+
cancel: async (spaceIds, jobId) => {
|
|
309
|
+
const { jobs } = await collections();
|
|
310
|
+
const at = await clock.now();
|
|
311
|
+
if ((await jobs.updateOne({
|
|
312
|
+
_id: jobId,
|
|
313
|
+
spaceId: { $in: spaceIds },
|
|
314
|
+
status: "pending"
|
|
315
|
+
}, {
|
|
316
|
+
$set: {
|
|
317
|
+
status: "cancelled",
|
|
318
|
+
updatedAt: at,
|
|
319
|
+
expiresAt: expiryOf(at)
|
|
320
|
+
},
|
|
321
|
+
$unset: { workerId: "" }
|
|
322
|
+
})).matchedCount === 1) return true;
|
|
323
|
+
return (await jobs.updateOne({
|
|
324
|
+
_id: jobId,
|
|
325
|
+
spaceId: { $in: spaceIds },
|
|
326
|
+
status: "running"
|
|
327
|
+
}, { $set: {
|
|
328
|
+
cancelRequested: true,
|
|
329
|
+
updatedAt: at
|
|
330
|
+
} })).matchedCount === 1;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
//#endregion
|
|
335
|
+
export { createMongoJobQueue, resolveDb };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MongoDbSource } from './jobQueue';
|
|
2
|
+
import { ActionKvAdapter } from '../actions/types';
|
|
3
|
+
export type MongoKvOptions = {
|
|
4
|
+
db: MongoDbSource;
|
|
5
|
+
/** Default `action_kv`. */
|
|
6
|
+
collection?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* An {@link ActionKvAdapter} over a collection of a Mongo database the deployment owns.
|
|
10
|
+
*
|
|
11
|
+
* What the run guards' single-flight keys and the `kv` tasks' counters need from a store the replicas share: a TTL the
|
|
12
|
+
* store enforces and an increment nobody can race. Each operation is ONE document update, which Mongo applies
|
|
13
|
+
* atomically — so two replicas incrementing the same counter at once both count.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createMongoKv: ({ db, collection }: MongoKvOptions) => ActionKvAdapter;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { resolveDb } from "./jobQueue.js";
|
|
2
|
+
//#region src/modules/mongo/kv.ts
|
|
3
|
+
/** Mongo's code for a unique index that refused a second document with the same key. */
|
|
4
|
+
var DUPLICATE_KEY = 11e3;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a key is still alive, by the SERVER's clock.
|
|
7
|
+
*
|
|
8
|
+
* Mongo's TTL monitor sweeps once a minute, so a document past its lifetime can still be there to read. Expiry is
|
|
9
|
+
* decided here, against `$$NOW`, rather than by whether the sweeper has been round yet — the same answer Redis gives
|
|
10
|
+
* the instant a key lapses.
|
|
11
|
+
*/
|
|
12
|
+
var LIVE = { $or: [{ $eq: [{ $ifNull: ["$expiresAt", null] }, null] }, { $gt: ["$expiresAt", "$$NOW"] }] };
|
|
13
|
+
var expiryIn = (ttlSeconds) => ({ $add: ["$$NOW", ttlSeconds * 1e3] });
|
|
14
|
+
/**
|
|
15
|
+
* An {@link ActionKvAdapter} over a collection of a Mongo database the deployment owns.
|
|
16
|
+
*
|
|
17
|
+
* What the run guards' single-flight keys and the `kv` tasks' counters need from a store the replicas share: a TTL the
|
|
18
|
+
* store enforces and an increment nobody can race. Each operation is ONE document update, which Mongo applies
|
|
19
|
+
* atomically — so two replicas incrementing the same counter at once both count.
|
|
20
|
+
*/
|
|
21
|
+
var createMongoKv = ({ db, collection = "action_kv" }) => {
|
|
22
|
+
const keys = () => resolveDb(db).collection(collection);
|
|
23
|
+
let ready;
|
|
24
|
+
const store = async () => {
|
|
25
|
+
ready ??= keys().createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).then(() => void 0).catch((error) => {
|
|
26
|
+
ready = void 0;
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
await ready;
|
|
30
|
+
return keys();
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* An upsert two replicas race for can collide on `_id` before either has written; the server retries that itself
|
|
34
|
+
* from 4.2 on, and this covers the rest. Once is enough: the second attempt finds the document the first one made.
|
|
35
|
+
*/
|
|
36
|
+
const upserting = async (write) => {
|
|
37
|
+
try {
|
|
38
|
+
return await write();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.code !== DUPLICATE_KEY) throw error;
|
|
41
|
+
return write();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
get: async (key) => {
|
|
46
|
+
return (await (await store()).findOne({
|
|
47
|
+
_id: key,
|
|
48
|
+
$expr: LIVE
|
|
49
|
+
}))?.value;
|
|
50
|
+
},
|
|
51
|
+
set: async (key, value, ttlSeconds) => {
|
|
52
|
+
const keysNow = await store();
|
|
53
|
+
await upserting(() => keysNow.updateOne({ _id: key }, [{ $set: {
|
|
54
|
+
value,
|
|
55
|
+
expiresAt: ttlSeconds === void 0 ? null : expiryIn(ttlSeconds)
|
|
56
|
+
} }], { upsert: true }));
|
|
57
|
+
},
|
|
58
|
+
delete: async (key) => {
|
|
59
|
+
await (await store()).deleteOne({ _id: key });
|
|
60
|
+
},
|
|
61
|
+
increment: async (key, amount) => {
|
|
62
|
+
const keysNow = await store();
|
|
63
|
+
const updated = await upserting(() => keysNow.findOneAndUpdate({ _id: key }, [
|
|
64
|
+
{ $set: { live: LIVE } },
|
|
65
|
+
{ $set: {
|
|
66
|
+
value: { $toString: { $add: [{ $cond: [
|
|
67
|
+
"$live",
|
|
68
|
+
{ $convert: {
|
|
69
|
+
input: "$value",
|
|
70
|
+
to: "double",
|
|
71
|
+
onError: 0,
|
|
72
|
+
onNull: 0
|
|
73
|
+
} },
|
|
74
|
+
0
|
|
75
|
+
] }, amount] } },
|
|
76
|
+
expiresAt: { $cond: [
|
|
77
|
+
"$live",
|
|
78
|
+
{ $ifNull: ["$expiresAt", null] },
|
|
79
|
+
null
|
|
80
|
+
] }
|
|
81
|
+
} },
|
|
82
|
+
{ $unset: "live" }
|
|
83
|
+
], {
|
|
84
|
+
upsert: true,
|
|
85
|
+
returnDocument: "after"
|
|
86
|
+
}));
|
|
87
|
+
return Number(updated?.value ?? amount);
|
|
88
|
+
},
|
|
89
|
+
expire: async (key, ttlSeconds) => {
|
|
90
|
+
await (await store()).updateOne({
|
|
91
|
+
_id: key,
|
|
92
|
+
$expr: LIVE
|
|
93
|
+
}, [{ $set: { expiresAt: expiryIn(ttlSeconds) } }]);
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Each branch is one update Mongo applies to the document atomically. Expecting a value, the filter names it.
|
|
97
|
+
* Expecting nothing is two cases: a lapsed document, taken over by a filter that matches only a lapsed one; and no
|
|
98
|
+
* document, inserted by an upsert whose filter no stored document satisfies — so a live one makes it collide on
|
|
99
|
+
* `_id`, which is the refusal, not an error. (An upsert may not filter with `$expr`, or this would be one step.)
|
|
100
|
+
*/
|
|
101
|
+
swap: async (key, expected, next, ttlSeconds) => {
|
|
102
|
+
const keysNow = await store();
|
|
103
|
+
const write = [{ $set: {
|
|
104
|
+
value: next,
|
|
105
|
+
expiresAt: ttlSeconds === void 0 ? null : expiryIn(ttlSeconds)
|
|
106
|
+
} }];
|
|
107
|
+
if (expected !== void 0) return (await keysNow.updateOne({
|
|
108
|
+
_id: key,
|
|
109
|
+
value: expected,
|
|
110
|
+
$expr: LIVE
|
|
111
|
+
}, write)).matchedCount === 1;
|
|
112
|
+
if ((await keysNow.updateOne({
|
|
113
|
+
_id: key,
|
|
114
|
+
$expr: { $not: [LIVE] }
|
|
115
|
+
}, write)).matchedCount === 1) return true;
|
|
116
|
+
try {
|
|
117
|
+
return (await keysNow.updateOne({
|
|
118
|
+
_id: key,
|
|
119
|
+
value: { $exists: false }
|
|
120
|
+
}, write, { upsert: true })).upsertedCount === 1;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.code === DUPLICATE_KEY) return false;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { createMongoKv };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activityDue } from "../../core/auth/sessionActivity.js";
|
|
1
2
|
import { execute, selectOne, selectRows } from "./query.js";
|
|
2
3
|
import { createSessionStore } from "./sessions.js";
|
|
3
4
|
//#region src/modules/mysql/accounts.ts
|
|
@@ -50,12 +51,15 @@ var createAccountStore = (db, t) => {
|
|
|
50
51
|
* check — deactivating somebody has to end the session they are already holding, not just the next login.
|
|
51
52
|
*/
|
|
52
53
|
findAccountByToken: async (token) => {
|
|
53
|
-
const row = await selectOne(db, `SELECT ${ACCOUNT_COLUMNS}, s.expires_at AS session_expires_at
|
|
54
|
+
const row = await selectOne(db, `SELECT ${ACCOUNT_COLUMNS}, s.id AS session_id, s.expires_at AS session_expires_at,
|
|
55
|
+
s.last_active_at AS session_last_active_at
|
|
54
56
|
FROM ${t.session} s
|
|
55
57
|
JOIN ${t.account} a ON a.id = s.account_id
|
|
56
58
|
WHERE s.token = ? AND a.status = 'active'
|
|
57
59
|
LIMIT 1`, [token]);
|
|
58
60
|
if (!row) return;
|
|
61
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
62
|
+
if (row.session_id !== void 0 && activityDue(row.session_last_active_at, now)) execute(db, `UPDATE ${t.session} SET last_active_at = ? WHERE id = ?`, [now, row.session_id]).catch(() => void 0);
|
|
59
63
|
const { roles, permissions } = await loadAccess(row.id);
|
|
60
64
|
return {
|
|
61
65
|
id: row.id,
|
|
@@ -120,15 +124,18 @@ var createAccountStore = (db, t) => {
|
|
|
120
124
|
]);
|
|
121
125
|
return;
|
|
122
126
|
}
|
|
123
|
-
await execute(db, `INSERT INTO ${t.session}
|
|
124
|
-
|
|
127
|
+
await execute(db, `INSERT INTO ${t.session}
|
|
128
|
+
(account_id, token, expires_at, refresh_token, refresh_expires_at, user_agent, ip, app_name, app_id)
|
|
129
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
125
130
|
userId,
|
|
126
131
|
session.token,
|
|
127
132
|
session.expiresAt,
|
|
128
133
|
session.refreshToken ?? null,
|
|
129
134
|
session.refreshExpiresAt ?? null,
|
|
130
135
|
context?.client?.userAgent?.slice(0, 255) ?? null,
|
|
131
|
-
context?.client?.ip ?? null
|
|
136
|
+
context?.client?.ip ?? null,
|
|
137
|
+
context?.client?.app?.name.slice(0, 120) ?? null,
|
|
138
|
+
context?.client?.app?.softwareId?.slice(0, 64) ?? null
|
|
132
139
|
]);
|
|
133
140
|
},
|
|
134
141
|
/**
|
|
@@ -335,22 +342,25 @@ var createAccountStore = (db, t) => {
|
|
|
335
342
|
* read whole and written whole, and a text column that a human can read in a crisis beats one they cannot.
|
|
336
343
|
*/
|
|
337
344
|
loadMfa: async (userId) => {
|
|
338
|
-
const row = await selectOne(db, `SELECT secret, confirmed_at, recovery_codes FROM ${t.mfa} WHERE account_id = ? LIMIT 1`, [userId]);
|
|
345
|
+
const row = await selectOne(db, `SELECT secret, confirmed_at, recovery_codes, last_used_step FROM ${t.mfa} WHERE account_id = ? LIMIT 1`, [userId]);
|
|
339
346
|
if (!row) return;
|
|
340
347
|
return {
|
|
341
348
|
secret: row.secret,
|
|
342
349
|
...row.confirmed_at !== null ? { confirmedAt: row.confirmed_at } : {},
|
|
343
|
-
recoveryCodes: (row.recovery_codes ?? "").split("\n").filter(Boolean)
|
|
350
|
+
recoveryCodes: (row.recovery_codes ?? "").split("\n").filter(Boolean),
|
|
351
|
+
...row.last_used_step !== null ? { lastUsedStep: row.last_used_step } : {}
|
|
344
352
|
};
|
|
345
353
|
},
|
|
346
354
|
saveMfa: async (userId, mfaRecord) => {
|
|
347
|
-
await execute(db, `INSERT INTO ${t.mfa} (account_id, secret, confirmed_at, recovery_codes) VALUES (?, ?, ?, ?)
|
|
355
|
+
await execute(db, `INSERT INTO ${t.mfa} (account_id, secret, confirmed_at, recovery_codes, last_used_step) VALUES (?, ?, ?, ?, ?)
|
|
348
356
|
ON DUPLICATE KEY UPDATE
|
|
349
|
-
secret = VALUES(secret), confirmed_at = VALUES(confirmed_at), recovery_codes = VALUES(recovery_codes)
|
|
357
|
+
secret = VALUES(secret), confirmed_at = VALUES(confirmed_at), recovery_codes = VALUES(recovery_codes),
|
|
358
|
+
last_used_step = VALUES(last_used_step)`, [
|
|
350
359
|
userId,
|
|
351
360
|
mfaRecord.secret,
|
|
352
361
|
mfaRecord.confirmedAt ?? null,
|
|
353
|
-
(mfaRecord.recoveryCodes ?? []).join("\n")
|
|
362
|
+
(mfaRecord.recoveryCodes ?? []).join("\n"),
|
|
363
|
+
mfaRecord.lastUsedStep ?? null
|
|
354
364
|
]);
|
|
355
365
|
},
|
|
356
366
|
deleteMfa: async (userId) => {
|
|
@@ -63,5 +63,6 @@ export { TABLE_NAMES, tableNames } from './config';
|
|
|
63
63
|
export { SCHEMA_VERSION } from './schema';
|
|
64
64
|
export type { MysqlConfig, TableKey, Tables } from './config';
|
|
65
65
|
export type { AccountSeed, MysqlAdmin, SpaceTokenSeed } from './admin';
|
|
66
|
-
export type { SessionStore
|
|
66
|
+
export type { SessionStore } from './sessions';
|
|
67
|
+
export type { SessionSummary } from '../../core/auth/api';
|
|
67
68
|
export type { IdentityStore, LinkedIdentity } from './identities';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createMysqlJobQueue } from './jobQueue';
|
|
2
|
+
export { createMysqlKv } from './kv';
|
|
3
|
+
export { mysqlJobSchemaStatements, mysqlJobSchemaUpgrades } from './schema';
|
|
4
|
+
export type { MysqlJobQueue, MysqlJobQueueOptions } from './jobQueue';
|
|
5
|
+
export type { MysqlKvOptions } from './kv';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ActionJobQueue } from '@plitzi/sdk-shared';
|
|
2
|
+
import { Pool } from 'mysql2/promise';
|
|
3
|
+
export type MysqlJobQueueOptions = {
|
|
4
|
+
/** A pool the deployment opened. The helper borrows connections from it and never ends it. */
|
|
5
|
+
pool: Pool;
|
|
6
|
+
/** Prefix for the three tables — `action_jobs`, `action_schedules`, `action_kv`. Default none. */
|
|
7
|
+
tablePrefix?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Create the tables on first use (`CREATE TABLE IF NOT EXISTS`). Default true; false for a deployment that runs
|
|
10
|
+
* {@link mysqlJobSchemaStatements} through its own migrations.
|
|
11
|
+
*/
|
|
12
|
+
createTables?: boolean;
|
|
13
|
+
/** How long a finished job stays readable. A waiting or running one never expires. Default 90 days. */
|
|
14
|
+
retentionDays?: number;
|
|
15
|
+
};
|
|
16
|
+
export type MysqlJobQueue = ActionJobQueue & {
|
|
17
|
+
/** Takes the store's clock again on the next call — after a failover to another primary. */
|
|
18
|
+
resyncClock: () => void;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* An {@link ActionJobQueue} over tables in a MySQL database the deployment owns. Tested on MySQL 8; nothing in it
|
|
22
|
+
* needs more than 5.7.
|
|
23
|
+
*
|
|
24
|
+
* The helper for a deployment that already runs MySQL: it borrows connections from the pool it is handed, and creates
|
|
25
|
+
* its three tables on first use unless told not to. Every replica pointed at the same database shares the jobs and the
|
|
26
|
+
* schedules.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import mysql from 'mysql2/promise';
|
|
30
|
+
* import { createMysqlJobQueue, createMysqlKv } from '@plitzi/sdk-server/mysql';
|
|
31
|
+
*
|
|
32
|
+
* const pool = mysql.createPool(process.env.DATABASE_URL);
|
|
33
|
+
* createServer({ action: { lookups, kv: createMysqlKv({ pool }), jobs: { queue: createMysqlJobQueue({ pool }) } } });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* The contract's rules, as MySQL keeps them: every instant is the SERVER's (`NOW(3)`), `enqueue` is idempotent by the
|
|
37
|
+
* primary key, `claim` takes each job with a single-row compare-and-set — so two workers never take the same one —
|
|
38
|
+
* and reaps a lapsed lease in the same write, and a schedule advances by compare-and-set too.
|
|
39
|
+
*/
|
|
40
|
+
export declare const createMysqlJobQueue: ({ pool, tablePrefix, createTables, retentionDays }: MysqlJobQueueOptions) => MysqlJobQueue;
|