@plitzi/sdk-server 0.37.2 → 0.37.4
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 +1170 -0
- package/README.md +100 -7
- package/dist/actions.d.ts +6 -2
- package/dist/actions.js +5 -2
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/adapters/jsonAdapters.test.d.ts +1 -0
- 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 +10 -0
- package/dist/core/server/baseServer.js +42 -11
- package/dist/core/server/closeOnSignals.js +4 -3
- 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 +34 -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 +6 -1
- package/dist/index.js +5 -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 +29 -10
- package/dist/modules/actions/index.js +43 -6
- 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 +2 -1
- package/dist/modules/actions/jobs/index.js +5 -3
- package/dist/modules/actions/jobs/memoryQueue.d.ts +3 -0
- package/dist/modules/actions/jobs/memoryQueue.js +17 -1
- package/dist/modules/actions/jobs/scheduler.d.ts +2 -0
- package/dist/modules/actions/jobs/scheduler.js +12 -7
- package/dist/modules/actions/jobs/worker.d.ts +3 -1
- package/dist/modules/actions/jobs/worker.js +9 -4
- package/dist/modules/actions/moduleFor.js +5 -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 +4 -8
- 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/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 +5 -2
- 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 +4 -3
- package/dist/modules/actions/types.d.ts +86 -3
- 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/kv.js +31 -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 +1 -1
- package/dist/modules/mysql/jobs/kv.js +30 -0
- package/dist/modules/mysql/jobs/schema.d.ts +9 -0
- package/dist/modules/mysql/jobs/schema.js +21 -4
- 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 +3 -1
- package/dist/modules/ssr/Component.js +2 -1
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -10
- 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/mysql.d.ts +1 -1
- package/dist/mysql.js +2 -2
- 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 +39 -8
- package/skills/plitzi-authoring/SKILL.md +98 -14
- 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 +84 -18
- package/skills/plitzi-authoring/reference/elements-and-styles.md +61 -7
- package/skills/plitzi-authoring/reference/flows.md +108 -17
- 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 +19 -2
- package/skills/plitzi-authoring/reference/templates.md +67 -27
- package/skills/plitzi-authoring/reference/testing.md +72 -5
- package/skills/plitzi-authoring/reference/validation.md +73 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** One entry of a list: what it is called, where it sorts, and what it carries. */
|
|
2
|
+
export type KvListEntry = {
|
|
3
|
+
id: string;
|
|
4
|
+
score: number;
|
|
5
|
+
value: unknown;
|
|
6
|
+
};
|
|
7
|
+
export type KvListRange = {
|
|
8
|
+
/** How many entries to skip first. 0 when absent. */
|
|
9
|
+
offset?: number;
|
|
10
|
+
/** How many to answer. All of them when absent. */
|
|
11
|
+
limit?: number;
|
|
12
|
+
/** `desc` — the highest score first — when absent. */
|
|
13
|
+
order?: 'asc' | 'desc';
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The most a list holds. A list is one value of the store, read and written whole, so it is for what a page shows at
|
|
17
|
+
* once — the latest twenty, a leaderboard, a gallery — not for a table.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_LIST_ENTRIES = 500;
|
|
20
|
+
/**
|
|
21
|
+
* Its largest size, as JSON. Every store holds far more; this is what a list is read and written whole for — a page's
|
|
22
|
+
* worth of entries, each an id and what the page shows — and past it, a list is being used as a table.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_LIST_BYTES = 128000;
|
|
25
|
+
export declare const listIdProblem: (id: unknown) => string | undefined;
|
|
26
|
+
/** The entries a stored list holds — none for a key that holds nothing, or something that is not a list. */
|
|
27
|
+
export declare const parseList: (raw: string | undefined) => KvListEntry[];
|
|
28
|
+
export type KvListPutOptions = {
|
|
29
|
+
/** How many to keep, the highest scores; what falls past it is dropped. {@link MAX_LIST_ENTRIES} at most. */
|
|
30
|
+
keep?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Replace an entry already there only with a score at least as high — a player's best, or the latest of two writes
|
|
33
|
+
* that arrived out of order. Otherwise the one there stays and nothing is written.
|
|
34
|
+
*/
|
|
35
|
+
higherOnly?: boolean;
|
|
36
|
+
};
|
|
37
|
+
/** What a put did: whether the entry went in, and what `keep` dropped to make room. */
|
|
38
|
+
export type KvListPut = {
|
|
39
|
+
stored: boolean;
|
|
40
|
+
dropped: KvListEntry[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The list with `entry` in it — replacing the one with its id — sorted and cut to the highest `keep`, and what the cut
|
|
44
|
+
* dropped; `undefined` when `higherOnly` keeps the one there. Refused, with what to do instead, when the result would
|
|
45
|
+
* be larger than a list may be.
|
|
46
|
+
*/
|
|
47
|
+
export declare const withEntry: (entries: KvListEntry[], entry: KvListEntry, { keep, higherOnly }?: KvListPutOptions) => {
|
|
48
|
+
kept: KvListEntry[];
|
|
49
|
+
dropped: KvListEntry[];
|
|
50
|
+
} | undefined;
|
|
51
|
+
export declare const rangeOf: (entries: KvListEntry[], { offset, limit, order }?: KvListRange) => KvListEntry[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/modules/actions/runtime/kvList.ts
|
|
2
|
+
/**
|
|
3
|
+
* The most a list holds. A list is one value of the store, read and written whole, so it is for what a page shows at
|
|
4
|
+
* once — the latest twenty, a leaderboard, a gallery — not for a table.
|
|
5
|
+
*/
|
|
6
|
+
var MAX_LIST_ENTRIES = 500;
|
|
7
|
+
/**
|
|
8
|
+
* Its largest size, as JSON. Every store holds far more; this is what a list is read and written whole for — a page's
|
|
9
|
+
* worth of entries, each an id and what the page shows — and past it, a list is being used as a table.
|
|
10
|
+
*/
|
|
11
|
+
var MAX_LIST_BYTES = 128e3;
|
|
12
|
+
var ID = /^[A-Za-z0-9:_.@-]{1,128}$/;
|
|
13
|
+
var listIdProblem = (id) => typeof id === "string" && ID.test(id) ? void 0 : "A list entry’s id is 1-128 of A-Z a-z 0-9 : _ . @ -";
|
|
14
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
15
|
+
var isEntry = (value) => isRecord(value) && typeof value.id === "string" && typeof value.score === "number";
|
|
16
|
+
/** The entries a stored list holds — none for a key that holds nothing, or something that is not a list. */
|
|
17
|
+
var parseList = (raw) => {
|
|
18
|
+
if (raw === void 0) return [];
|
|
19
|
+
try {
|
|
20
|
+
const value = JSON.parse(raw);
|
|
21
|
+
return Array.isArray(value) ? value.filter(isEntry) : [];
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/** Highest score first; entries that tie keep a stable order, by id. */
|
|
27
|
+
var byScore = (a, b) => b.score - a.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
28
|
+
/**
|
|
29
|
+
* The list with `entry` in it — replacing the one with its id — sorted and cut to the highest `keep`, and what the cut
|
|
30
|
+
* dropped; `undefined` when `higherOnly` keeps the one there. Refused, with what to do instead, when the result would
|
|
31
|
+
* be larger than a list may be.
|
|
32
|
+
*/
|
|
33
|
+
var withEntry = (entries, entry, { keep = 500, higherOnly = false } = {}) => {
|
|
34
|
+
const there = entries.find((existing) => existing.id === entry.id);
|
|
35
|
+
if (higherOnly && there && there.score > entry.score) return;
|
|
36
|
+
const sorted = [...entries.filter((existing) => existing.id !== entry.id), entry].sort(byScore);
|
|
37
|
+
const cut = Math.min(Math.max(0, Math.floor(keep)), 500);
|
|
38
|
+
const kept = sorted.slice(0, cut);
|
|
39
|
+
const bytes = Buffer.byteLength(JSON.stringify(kept));
|
|
40
|
+
if (bytes > 128e3) throw new Error(`A list is at most ${MAX_LIST_BYTES} bytes and this one would be ${bytes}: keep fewer entries (\`keep\`), or store less in each one — an id and what a page shows, with the rest under its own key`);
|
|
41
|
+
return {
|
|
42
|
+
kept,
|
|
43
|
+
dropped: sorted.slice(cut)
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
var rangeOf = (entries, { offset = 0, limit, order = "desc" } = {}) => {
|
|
47
|
+
const ordered = order === "asc" ? entries.toReversed() : entries;
|
|
48
|
+
const start = Math.max(0, Math.floor(offset));
|
|
49
|
+
return ordered.slice(start, limit === void 0 ? void 0 : start + Math.max(0, Math.floor(limit)));
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { MAX_LIST_BYTES, MAX_LIST_ENTRIES, listIdProblem, parseList, rangeOf, withEntry };
|
|
@@ -23,5 +23,13 @@ export type KvStoreConfig = {
|
|
|
23
23
|
* table.
|
|
24
24
|
* 3. **Nothing is caught.** This is not a cache: a miss means the rate limit did not count and the idempotency key
|
|
25
25
|
* was not seen, so an adapter that cannot answer fails the run rather than being read as "no value".
|
|
26
|
+
*
|
|
27
|
+
* A list is one value, changed only by `swap`: read, change, write back if nobody wrote in between, and read again if
|
|
28
|
+
* somebody did. Every adapter can do that much, so lists work on all of them without a command of their own. They
|
|
29
|
+
* live under `list:` rather than beside the values, so no key a flow writes can be one.
|
|
26
30
|
*/
|
|
31
|
+
/** What every key is prefixed with unless the store was given another. */
|
|
32
|
+
export declare const DEFAULT_KV_PREFIX = "kv:";
|
|
33
|
+
/** Where a list lives, after the prefix: its own segment, so a list and a value of the same name never meet. */
|
|
34
|
+
export declare const LIST_SEGMENT = "list:";
|
|
27
35
|
export declare const createKvStore: (adapter: ActionKvAdapter, { prefix }?: KvStoreConfig) => ActionKvStore;
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* 1. **Values round-trip through JSON**, so a flow reads back what it wrote instead of its own `toString`. A value
|
|
9
|
-
* written by something else is answered as the string it is rather than failing.
|
|
10
|
-
* 2. **A counter's TTL is set once, by whoever created it, and never extended.** A window refreshed on every hit
|
|
11
|
-
* never closes while traffic keeps arriving — which is a rate limit that stops limiting exactly when it is
|
|
12
|
-
* being leant on. The counter is the one that created it precisely when the increment returns its own amount,
|
|
13
|
-
* so this needs no conditional-expire support from the adapter and works the same on Redis, Memcached or a
|
|
14
|
-
* table.
|
|
15
|
-
* 3. **Nothing is caught.** This is not a cache: a miss means the rate limit did not count and the idempotency key
|
|
16
|
-
* was not seen, so an adapter that cannot answer fails the run rather than being read as "no value".
|
|
17
|
-
*/
|
|
1
|
+
import { ActionRefusal } from "./errors.js";
|
|
2
|
+
import { changeKv } from "./kvChange.js";
|
|
3
|
+
import { listIdProblem, parseList, rangeOf, withEntry } from "./kvList.js";
|
|
4
|
+
/** Where a list lives, after the prefix: its own segment, so a list and a value of the same name never meet. */
|
|
5
|
+
var LIST_SEGMENT = "list:";
|
|
6
|
+
var refusal = (message) => new ActionRefusal(message);
|
|
18
7
|
var createKvStore = (adapter, { prefix = "kv:" } = {}) => {
|
|
19
8
|
const prefixed = (key) => `${prefix}${key}`;
|
|
20
|
-
|
|
9
|
+
const listKey = (list) => `${prefix}${LIST_SEGMENT}${list}`;
|
|
10
|
+
/** Applies `change` to the list and writes it back unless somebody wrote first — then reads again. */
|
|
11
|
+
const changeList = async (list, change) => {
|
|
12
|
+
return await changeKv(adapter, listKey(list), (raw) => {
|
|
13
|
+
const next = change(parseList(raw));
|
|
14
|
+
return next ? JSON.stringify(next) : void 0;
|
|
15
|
+
}, { refusal }) !== void 0;
|
|
16
|
+
};
|
|
17
|
+
const store = {
|
|
21
18
|
get: async (key) => {
|
|
22
19
|
const raw = await adapter.get(prefixed(key));
|
|
23
20
|
if (raw === void 0) return;
|
|
@@ -34,8 +31,37 @@ var createKvStore = (adapter, { prefix = "kv:" } = {}) => {
|
|
|
34
31
|
const value = await adapter.increment(full, amount);
|
|
35
32
|
if (ttlSeconds !== void 0 && value === amount) await adapter.expire(full, ttlSeconds);
|
|
36
33
|
return value;
|
|
37
|
-
}
|
|
34
|
+
},
|
|
35
|
+
swap: (key, expected, next, ttlSeconds) => adapter.swap(prefixed(key), expected === void 0 ? void 0 : JSON.stringify(expected), JSON.stringify(next), ttlSeconds),
|
|
36
|
+
listPut: async (list, entry, options = {}) => {
|
|
37
|
+
const problem = listIdProblem(entry.id);
|
|
38
|
+
if (problem) throw new Error(problem);
|
|
39
|
+
if (!Number.isFinite(entry.score)) throw new Error("A list entry’s score is a number");
|
|
40
|
+
let put = {
|
|
41
|
+
stored: false,
|
|
42
|
+
dropped: []
|
|
43
|
+
};
|
|
44
|
+
await changeList(list, (entries) => {
|
|
45
|
+
const next = withEntry(entries, entry, options);
|
|
46
|
+
put = next ? {
|
|
47
|
+
stored: true,
|
|
48
|
+
dropped: next.dropped
|
|
49
|
+
} : {
|
|
50
|
+
stored: false,
|
|
51
|
+
dropped: []
|
|
52
|
+
};
|
|
53
|
+
return next?.kept;
|
|
54
|
+
});
|
|
55
|
+
return put;
|
|
56
|
+
},
|
|
57
|
+
listRange: async (list, range) => rangeOf(parseList(await adapter.get(listKey(list))), range),
|
|
58
|
+
listRemove: (list, id) => changeList(list, (entries) => entries.some((entry) => entry.id === id) ? entries.filter((entry) => entry.id !== id) : void 0),
|
|
59
|
+
change: (key, change, lifetime) => changeKv(store, key, change, {
|
|
60
|
+
lifetime,
|
|
61
|
+
refusal
|
|
62
|
+
})
|
|
38
63
|
};
|
|
64
|
+
return store;
|
|
39
65
|
};
|
|
40
66
|
//#endregion
|
|
41
|
-
export { createKvStore };
|
|
67
|
+
export { LIST_SEGMENT, createKvStore };
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
+
import { StoreMethods } from '../../../core/server/fleet/channel';
|
|
1
2
|
import { ActionKvAdapter } from '../types';
|
|
3
|
+
/** Every method of the store, for the copy a server's workers share (see `fleet/stores.ts`). */
|
|
4
|
+
export declare const KV_METHODS: StoreMethods<ActionKvAdapter>;
|
|
2
5
|
export declare const createMemoryKv: () => ActionKvAdapter;
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** How many writes go by before the map is swept. Cheap enough to be unnoticeable, often enough to bound it. */
|
|
13
13
|
var SWEEP_EVERY = 256;
|
|
14
|
+
/** Every method of the store, for the copy a server's workers share (see `fleet/stores.ts`). */
|
|
15
|
+
var KV_METHODS = {
|
|
16
|
+
get: true,
|
|
17
|
+
set: true,
|
|
18
|
+
delete: true,
|
|
19
|
+
increment: true,
|
|
20
|
+
expire: true,
|
|
21
|
+
swap: true
|
|
22
|
+
};
|
|
14
23
|
var createMemoryKv = () => {
|
|
15
24
|
const entries = /* @__PURE__ */ new Map();
|
|
16
25
|
let writes = 0;
|
|
@@ -70,8 +79,17 @@ var createMemoryKv = () => {
|
|
|
70
79
|
expiresAt: expiry(ttlSeconds)
|
|
71
80
|
});
|
|
72
81
|
return Promise.resolve();
|
|
82
|
+
},
|
|
83
|
+
swap: (key, expected, next, ttlSeconds) => {
|
|
84
|
+
if (read(key)?.value !== expected) return Promise.resolve(false);
|
|
85
|
+
entries.set(key, {
|
|
86
|
+
value: next,
|
|
87
|
+
expiresAt: expiry(ttlSeconds)
|
|
88
|
+
});
|
|
89
|
+
sweep();
|
|
90
|
+
return Promise.resolve(true);
|
|
73
91
|
}
|
|
74
92
|
};
|
|
75
93
|
};
|
|
76
94
|
//#endregion
|
|
77
|
-
export { createMemoryKv };
|
|
95
|
+
export { KV_METHODS, createMemoryKv };
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ActionKvStore } from '../types';
|
|
2
|
+
export declare const namespaceKv: (store: ActionKvStore, spaceId: number) => ActionKvStore;
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* A shared store with unprefixed keys is one space reading — or overwriting — another's counters, which is the
|
|
6
|
-
* cross-tenant leak this whole design exists to avoid. Done at the runner rather than in each task so a
|
|
7
|
-
* deployment's own tasks inherit it too.
|
|
4
|
+
* Every key a space's store holds, as patterns over the adapter's keys (`createKvStore`'s `prefix`, `kv:` unless it was
|
|
5
|
+
* given another) — for whoever has to let all of them go at once: a space that is deleted, or reset to how it started.
|
|
8
6
|
*/
|
|
9
|
-
export declare const
|
|
7
|
+
export declare const spaceKvPatterns: (spaceId: number, prefix?: string) => string[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LIST_SEGMENT } from "./kvStore.js";
|
|
1
2
|
//#region src/modules/actions/runtime/namespaceKv.ts
|
|
2
3
|
/**
|
|
3
4
|
* Prefixes every key with the space that wrote it.
|
|
@@ -6,14 +7,25 @@
|
|
|
6
7
|
* cross-tenant leak this whole design exists to avoid. Done at the runner rather than in each task so a
|
|
7
8
|
* deployment's own tasks inherit it too.
|
|
8
9
|
*/
|
|
10
|
+
var spaceScope = (spaceId) => `action:${spaceId}:`;
|
|
9
11
|
var namespaceKv = (store, spaceId) => {
|
|
10
|
-
const scoped = (key) =>
|
|
12
|
+
const scoped = (key) => `${spaceScope(spaceId)}${key}`;
|
|
11
13
|
return {
|
|
12
14
|
get: (key) => store.get(scoped(key)),
|
|
13
15
|
set: (key, value, ttlSeconds) => store.set(scoped(key), value, ttlSeconds),
|
|
14
16
|
delete: (key) => store.delete(scoped(key)),
|
|
15
|
-
increment: (key, amount, ttlSeconds) => store.increment(scoped(key), amount, ttlSeconds)
|
|
17
|
+
increment: (key, amount, ttlSeconds) => store.increment(scoped(key), amount, ttlSeconds),
|
|
18
|
+
swap: (key, expected, next, ttlSeconds) => store.swap(scoped(key), expected, next, ttlSeconds),
|
|
19
|
+
listPut: (list, entry, options) => store.listPut(scoped(list), entry, options),
|
|
20
|
+
listRange: (list, range) => store.listRange(scoped(list), range),
|
|
21
|
+
listRemove: (list, id) => store.listRemove(scoped(list), id),
|
|
22
|
+
change: (key, change, lifetime) => store.change(scoped(key), change, lifetime)
|
|
16
23
|
};
|
|
17
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Every key a space's store holds, as patterns over the adapter's keys (`createKvStore`'s `prefix`, `kv:` unless it was
|
|
27
|
+
* given another) — for whoever has to let all of them go at once: a space that is deleted, or reset to how it started.
|
|
28
|
+
*/
|
|
29
|
+
var spaceKvPatterns = (spaceId, prefix = "kv:") => [`${prefix}${spaceScope(spaceId)}*`, `${prefix}${LIST_SEGMENT}${spaceScope(spaceId)}*`];
|
|
18
30
|
//#endregion
|
|
19
|
-
export { namespaceKv };
|
|
31
|
+
export { namespaceKv, spaceKvPatterns };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { findTriggerNode, triggerParams } from "./triggers.js";
|
|
2
2
|
import { ActionRunError } from "./errors.js";
|
|
3
3
|
import { applyFields } from "./scope.js";
|
|
4
|
-
import { triggerAccess, triggerInput } from "@plitzi/sdk-shared/actions";
|
|
4
|
+
import { accessRefusal, triggerAccess, triggerInput } from "@plitzi/sdk-shared/actions";
|
|
5
5
|
//#region src/modules/actions/runtime/precheck.ts
|
|
6
6
|
var authorize = (access, kind, user) => {
|
|
7
7
|
if (kind === "schedule") return;
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
if (
|
|
11
|
-
if (access.mode === "role") {
|
|
12
|
-
const held = new Set(user.permissions);
|
|
13
|
-
if (access.permissions.some((permission) => !held.has(permission))) throw new ActionRunError("forbidden", "This action requires permissions the caller does not hold");
|
|
14
|
-
}
|
|
8
|
+
const refusal = accessRefusal(access, user);
|
|
9
|
+
if (refusal === "unauthenticated") throw new ActionRunError("unauthenticated", "This action requires a signed-in visitor");
|
|
10
|
+
if (refusal) throw new ActionRunError("forbidden", access ? "This action requires permissions the caller does not hold" : "This trigger declares no access rule");
|
|
15
11
|
};
|
|
16
12
|
/**
|
|
17
13
|
* Everything that decides whether a run may happen at all, before it costs anything.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ActionKvStore } from '../types';
|
|
2
|
+
/** How much of something is allowed: `most` in every `perSeconds`, for each caller or for everyone together. */
|
|
3
|
+
export type RateLimit = {
|
|
4
|
+
most: number;
|
|
5
|
+
perSeconds: number;
|
|
6
|
+
per?: 'caller' | 'everyone';
|
|
7
|
+
};
|
|
8
|
+
/** Where a limit stands after counting one more: whether that one was within it, and how many are left. */
|
|
9
|
+
export type RateCount = {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
count: number;
|
|
12
|
+
remaining: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Counts one more of `bucket` in the current window and says whether it was within the limit — the one count behind
|
|
16
|
+
* the `flow.rateLimit` step and a function's `ctx.rateLimit`, so a flow and code limiting the same bucket count
|
|
17
|
+
* together. Refusing is the caller's: a step refuses the run, a function decides what to answer.
|
|
18
|
+
*/
|
|
19
|
+
export declare const countRate: (kv: ActionKvStore, callerId: string, bucket: string, { most, perSeconds, per }: RateLimit) => Promise<RateCount>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/modules/actions/runtime/rateLimit.ts
|
|
2
|
+
/**
|
|
3
|
+
* Counts one more of `bucket` in the current window and says whether it was within the limit — the one count behind
|
|
4
|
+
* the `flow.rateLimit` step and a function's `ctx.rateLimit`, so a flow and code limiting the same bucket count
|
|
5
|
+
* together. Refusing is the caller's: a step refuses the run, a function decides what to answer.
|
|
6
|
+
*/
|
|
7
|
+
var countRate = async (kv, callerId, bucket, { most, perSeconds, per = "caller" }) => {
|
|
8
|
+
if (!bucket || !Number.isInteger(most) || most < 1 || !Number.isInteger(perSeconds) || perSeconds < 1) throw new Error("A rate limit names what it counts, and allows a whole number of runs over whole seconds");
|
|
9
|
+
const window = Math.floor(Date.now() / (perSeconds * 1e3));
|
|
10
|
+
const who = per === "everyone" ? "everyone" : callerId;
|
|
11
|
+
const count = await kv.increment(`$rate:${bucket}:${who}:${String(window)}`, 1, perSeconds + 1);
|
|
12
|
+
return {
|
|
13
|
+
allowed: count <= most,
|
|
14
|
+
count,
|
|
15
|
+
remaining: Math.max(0, most - count)
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { countRate };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ActionKvAdapter } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The commands this adapter sends, typed by shape rather than by a client library: ioredis answers them as they are,
|
|
4
|
+
* and any client that speaks them does. `eval` is for `swap`, the one operation Redis has no single command for.
|
|
5
|
+
*/
|
|
6
|
+
export type RedisKvClient = {
|
|
7
|
+
get: (key: string) => Promise<string | null>;
|
|
8
|
+
set: {
|
|
9
|
+
(key: string, value: string): Promise<unknown>;
|
|
10
|
+
(key: string, value: string, mode: 'EX', seconds: number): Promise<unknown>;
|
|
11
|
+
};
|
|
12
|
+
del: (key: string) => Promise<number>;
|
|
13
|
+
incrby: (key: string, amount: number) => Promise<number>;
|
|
14
|
+
expire: (key: string, seconds: number) => Promise<number>;
|
|
15
|
+
eval: (script: string, keys: number, ...args: (string | number)[]) => Promise<unknown>;
|
|
16
|
+
};
|
|
17
|
+
export type RedisKvOptions = {
|
|
18
|
+
/** Put in front of every key, for a Redis that holds other things too. None when absent. */
|
|
19
|
+
prefix?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* An {@link ActionKvAdapter} over Redis: each operation one command, and `swap` one script.
|
|
23
|
+
*
|
|
24
|
+
* `client` may be a function, for a deployment whose connection comes and goes — it is asked on every call, and a
|
|
25
|
+
* client it cannot give is its to refuse (throwing), never this adapter's to read as "no value".
|
|
26
|
+
*/
|
|
27
|
+
export declare const createRedisKv: (client: RedisKvClient | (() => RedisKvClient), { prefix }?: RedisKvOptions) => ActionKvAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/modules/actions/runtime/redisKv.ts
|
|
2
|
+
/**
|
|
3
|
+
* Read and write as ONE step: Redis runs a script with nothing else in between, which is what makes it the
|
|
4
|
+
* compare-and-set it has no command for. `ARGV[1]` says whether a value is expected at all — an absent key and one
|
|
5
|
+
* holding the empty string are different things.
|
|
6
|
+
*/
|
|
7
|
+
var SWAP = `
|
|
8
|
+
local current = redis.call('GET', KEYS[1])
|
|
9
|
+
if ARGV[1] == 'absent' then
|
|
10
|
+
if current then return 0 end
|
|
11
|
+
elseif current ~= ARGV[2] then
|
|
12
|
+
return 0
|
|
13
|
+
end
|
|
14
|
+
if ARGV[4] == '' then
|
|
15
|
+
redis.call('SET', KEYS[1], ARGV[3])
|
|
16
|
+
else
|
|
17
|
+
redis.call('SET', KEYS[1], ARGV[3], 'EX', tonumber(ARGV[4]))
|
|
18
|
+
end
|
|
19
|
+
return 1
|
|
20
|
+
`;
|
|
21
|
+
/**
|
|
22
|
+
* An {@link ActionKvAdapter} over Redis: each operation one command, and `swap` one script.
|
|
23
|
+
*
|
|
24
|
+
* `client` may be a function, for a deployment whose connection comes and goes — it is asked on every call, and a
|
|
25
|
+
* client it cannot give is its to refuse (throwing), never this adapter's to read as "no value".
|
|
26
|
+
*/
|
|
27
|
+
var createRedisKv = (client, { prefix = "" } = {}) => {
|
|
28
|
+
const redis = typeof client === "function" ? client : () => client;
|
|
29
|
+
const key = (name) => `${prefix}${name}`;
|
|
30
|
+
return {
|
|
31
|
+
get: async (name) => await redis().get(key(name)) ?? void 0,
|
|
32
|
+
set: async (name, value, ttlSeconds) => {
|
|
33
|
+
await (ttlSeconds === void 0 ? redis().set(key(name), value) : redis().set(key(name), value, "EX", ttlSeconds));
|
|
34
|
+
},
|
|
35
|
+
delete: async (name) => {
|
|
36
|
+
await redis().del(key(name));
|
|
37
|
+
},
|
|
38
|
+
increment: (name, amount) => redis().incrby(key(name), amount),
|
|
39
|
+
expire: async (name, ttlSeconds) => {
|
|
40
|
+
await redis().expire(key(name), ttlSeconds);
|
|
41
|
+
},
|
|
42
|
+
swap: async (name, expected, next, ttlSeconds) => await redis().eval(SWAP, 1, key(name), expected === void 0 ? "absent" : "equals", expected ?? "", next, ttlSeconds === void 0 ? "" : ttlSeconds) === 1
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createRedisKv };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serverLog } from "../../../helpers/serverLog.js";
|
|
1
2
|
//#region src/modules/actions/runtime/report.ts
|
|
2
3
|
/**
|
|
3
4
|
* Reports a request that never became a run.
|
|
@@ -15,7 +16,7 @@ var reportReject = async (config, record) => {
|
|
|
15
16
|
try {
|
|
16
17
|
await config.action?.onReject?.(record);
|
|
17
18
|
} catch (error) {
|
|
18
|
-
|
|
19
|
+
serverLog.error("Actions", "reject record failed", error);
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
//#endregion
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TaskContextRequest } from './taskContext';
|
|
2
|
+
import { ActionRunRequest, ActionRunResult, ActionsConfig, ActionTaskContext, ActionTaskRegistry } from '../types';
|
|
2
3
|
export type ActionRunner = {
|
|
3
4
|
runAction: (request: ActionRunRequest) => Promise<ActionRunResult>;
|
|
4
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* What a function answering a route runs with: the context a run's step gets — the same stores, the same redaction, the
|
|
8
|
+
* same outbound budget under the deployment's ceilings — for work that is not a flow.
|
|
9
|
+
*/
|
|
10
|
+
export type TaskContextSource = (request: TaskContextRequest, signal: AbortSignal, lineage: string[]) => Omit<ActionTaskContext, 'log'>;
|
|
5
11
|
/**
|
|
6
12
|
* Builds the runner every trigger goes through.
|
|
7
13
|
*
|
|
@@ -9,4 +15,6 @@ export type ActionRunner = {
|
|
|
9
15
|
* input contract — lives HERE rather than in a transport, because a deployment mounting its own trigger calls this
|
|
10
16
|
* directly. A check in the endpoint would be a check a custom trigger silently skips.
|
|
11
17
|
*/
|
|
12
|
-
export declare const createActionRunner: (config: ActionsConfig,
|
|
18
|
+
export declare const createActionRunner: (config: ActionsConfig, registryFor: (request: ActionRunRequest) => ActionTaskRegistry | Promise<ActionTaskRegistry>, baseFetch?: typeof fetch) => ActionRunner & {
|
|
19
|
+
taskContext: TaskContextSource;
|
|
20
|
+
};
|