@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,347 @@
|
|
|
1
|
+
import { changeKv } from "../../actions/runtime/kvChange.js";
|
|
2
|
+
import { FunctionFailure } from "../protocol.js";
|
|
3
|
+
import { installGuest } from "./guest.js";
|
|
4
|
+
import { guestPrelude } from "./prelude.js";
|
|
5
|
+
import { createFunctionsDriver, describeFunctions } from "../driver.js";
|
|
6
|
+
import { createHash, createHmac, pbkdf2, randomBytes } from "node:crypto";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
//#region src/modules/functions/sandbox/isolate.ts
|
|
9
|
+
/** What reading a bundle's declaration may spend: its top level runs, and nothing else. */
|
|
10
|
+
var DESCRIBE_LIMITS = {
|
|
11
|
+
cpuMs: 1e3,
|
|
12
|
+
wallMs: 5e3,
|
|
13
|
+
memoryMb: 64,
|
|
14
|
+
outputBytes: 1e6,
|
|
15
|
+
calls: 100
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* What a bundle's top level can call while it is read: only the console, since `ctx` exists only in an invocation — and
|
|
19
|
+
* a line logged while loading is nobody's to read.
|
|
20
|
+
*/
|
|
21
|
+
var describeAnswer = (call) => typeof call === "object" && call !== null && "op" in call && call.op === "log" ? Promise.resolve(void 0) : Promise.reject(/* @__PURE__ */ new Error("Nothing is reachable while a bundle is read"));
|
|
22
|
+
var WATCHDOG_MS = 5;
|
|
23
|
+
/** What isolated-vm throws when a `timeout` it was given runs out. */
|
|
24
|
+
var ISOLATE_TIMEOUT = "Script execution timed out.";
|
|
25
|
+
/** How long code that listens to `ctx.signal` gets to stop by itself before the isolate is disposed under it. */
|
|
26
|
+
var ABORT_GRACE_MS = 50;
|
|
27
|
+
var NO_NODE_SNAPSHOT = "--no-node-snapshot";
|
|
28
|
+
/**
|
|
29
|
+
* Since Node 20 the process starts from Node's own V8 snapshot, and isolated-vm's isolates crash beside it — not always,
|
|
30
|
+
* which is why this refuses up front rather than letting a process run until the one invocation that brings it down.
|
|
31
|
+
*/
|
|
32
|
+
var nodeSnapshotOff = () => Number(process.versions.node.split(".")[0]) < 20 || process.execArgv.includes(NO_NODE_SNAPSHOT) || (process.env.NODE_OPTIONS ?? "").split(/\s+/).includes(NO_NODE_SNAPSHOT);
|
|
33
|
+
var loadIvm = async () => {
|
|
34
|
+
if (!nodeSnapshotOff()) throw new Error(`Running space functions in isolates needs Node started with ${NO_NODE_SNAPSHOT} (e.g. NODE_OPTIONS=${NO_NODE_SNAPSHOT}): isolated-vm crashes beside Node's startup snapshot`);
|
|
35
|
+
try {
|
|
36
|
+
return (await import("isolated-vm")).default;
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error("Running space functions in isolates needs isolated-vm installed beside @plitzi/sdk-server");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The code cache a compile produced. isolated-vm sets it on the compiled script or module, and its declarations leave
|
|
43
|
+
* it out — so it is looked for rather than assumed.
|
|
44
|
+
*/
|
|
45
|
+
var cachedDataOf = (ivm, compiled) => "cachedData" in compiled && compiled.cachedData instanceof ivm.ExternalCopy ? compiled.cachedData : void 0;
|
|
46
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
47
|
+
var stopMessage = (reason, limits) => {
|
|
48
|
+
switch (reason) {
|
|
49
|
+
case "cpu": return `Stopped after ${String(limits.cpuMs)} ms of CPU`;
|
|
50
|
+
case "wall": return `Did not finish within ${String(limits.wallMs)} ms`;
|
|
51
|
+
case "memory": return `Went over its ${String(limits.memoryMb)} MB of memory`;
|
|
52
|
+
case "output": return `Answered more than the ${String(limits.outputBytes)} bytes it may`;
|
|
53
|
+
case "calls": return `Made more than the ${String(limits.calls)} calls to the platform it may`;
|
|
54
|
+
case "aborted": return "The run was aborted";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The most a PBKDF2 derivation may ask for. The runner does the work, outside the isolate's clock, so each is bounded
|
|
59
|
+
* here and its time is charged to the run's CPU: a million rounds is past any password guidance, and 1024 bits past
|
|
60
|
+
* any key.
|
|
61
|
+
*/
|
|
62
|
+
var PBKDF2_MAX_ITERATIONS = 1e6;
|
|
63
|
+
var PBKDF2_MAX_BITS = 1024;
|
|
64
|
+
var derive = promisify(pbkdf2);
|
|
65
|
+
var cryptoField = (request, name) => {
|
|
66
|
+
const value = request[name];
|
|
67
|
+
if (typeof value !== "string") throw new Error(`The operation has no ${name}`);
|
|
68
|
+
return value;
|
|
69
|
+
};
|
|
70
|
+
var boundedInteger = (text, what, max) => {
|
|
71
|
+
const value = Number(text);
|
|
72
|
+
if (!Number.isInteger(value) || value < 1 || value > max) throw new Error(`${what} is a whole number from 1 to ${String(max)}`);
|
|
73
|
+
return value;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The `crypto.subtle` a guest cannot do by itself, done with the runner's own: digests, HMAC and PBKDF2. A derivation
|
|
77
|
+
* is the one that costs, and `charge` is handed what it took.
|
|
78
|
+
*/
|
|
79
|
+
var cryptoAnswer = async (message, charge) => {
|
|
80
|
+
try {
|
|
81
|
+
const request = JSON.parse(message);
|
|
82
|
+
if (typeof request !== "object" || request === null || Array.isArray(request)) throw new Error("Not a crypto operation");
|
|
83
|
+
const fields = { ...request };
|
|
84
|
+
const hash = cryptoField(fields, "hash").replace("-", "").toLowerCase();
|
|
85
|
+
const bytes = (name) => Buffer.from(cryptoField(fields, name), "base64");
|
|
86
|
+
const op = cryptoField(fields, "op");
|
|
87
|
+
if (op === "pbkdf2") {
|
|
88
|
+
const iterations = boundedInteger(cryptoField(fields, "iterations"), "PBKDF2 iterations", PBKDF2_MAX_ITERATIONS);
|
|
89
|
+
const bits = boundedInteger(cryptoField(fields, "bits"), "A PBKDF2 length in bits", PBKDF2_MAX_BITS);
|
|
90
|
+
if (bits % 8 !== 0) throw new Error("A PBKDF2 length is a multiple of 8 bits");
|
|
91
|
+
const started = performance.now();
|
|
92
|
+
const derived = await derive(bytes("key"), bytes("salt"), iterations, bits / 8, hash);
|
|
93
|
+
charge(performance.now() - started);
|
|
94
|
+
return JSON.stringify({
|
|
95
|
+
ok: true,
|
|
96
|
+
value: derived.toString("base64")
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (op !== "hmac" && op !== "digest") throw new Error(`${op} is not a crypto operation`);
|
|
100
|
+
const digest = op === "hmac" ? createHmac(hash, bytes("key")).update(bytes("data")).digest() : createHash(hash).update(bytes("data")).digest();
|
|
101
|
+
return JSON.stringify({
|
|
102
|
+
ok: true,
|
|
103
|
+
value: digest.toString("base64")
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return JSON.stringify({
|
|
107
|
+
ok: false,
|
|
108
|
+
error: messageOf(error)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Sets the guest up in a fresh context: the runner's four calls, taken off the global the moment they are captured,
|
|
114
|
+
* and the guest printed from its own source — with the platform's functions it runs as they are (`GuestShared`),
|
|
115
|
+
* printed from theirs. Answers the driver.
|
|
116
|
+
*/
|
|
117
|
+
var BOOTSTRAP = `(() => {
|
|
118
|
+
const refs = { call: __plitziCall, sleep: __plitziSleep, random: __plitziRandom, crypto: __plitziCrypto };
|
|
119
|
+
delete globalThis.__plitziCall;
|
|
120
|
+
delete globalThis.__plitziSleep;
|
|
121
|
+
delete globalThis.__plitziRandom;
|
|
122
|
+
delete globalThis.__plitziCrypto;
|
|
123
|
+
const transfer = { arguments: { copy: true }, result: { promise: true, copy: true } };
|
|
124
|
+
return (${installGuest.toString()})(globalThis, {
|
|
125
|
+
call: message => refs.call.apply(undefined, [message], transfer),
|
|
126
|
+
sleep: ms => refs.sleep.apply(undefined, [ms], transfer),
|
|
127
|
+
random: length => refs.random(length),
|
|
128
|
+
crypto: message => refs.crypto.apply(undefined, [message], transfer)
|
|
129
|
+
}, { changeKv: ${changeKv.toString()}, describeFunctions: ${describeFunctions.toString()}, createFunctionsDriver: ${createFunctionsDriver.toString()} });
|
|
130
|
+
})()`;
|
|
131
|
+
/**
|
|
132
|
+
* The runner as V8 isolates in this process — what the runner service serves, and what a deployment may use directly
|
|
133
|
+
* when the process holds nothing a space's code must not reach (tests, a sandbox of its own).
|
|
134
|
+
*
|
|
135
|
+
* ONE ISOLATE PER INVOCATION: each limit is then exactly that invocation's — its CPU, its memory — and stopping it
|
|
136
|
+
* disposes nothing anybody else was using. No state survives an invocation, by construction. The cost is ~2 ms: the
|
|
137
|
+
* prelude comes from a V8 snapshot, the guest and the bundle from V8's code cache after their first compile.
|
|
138
|
+
*/
|
|
139
|
+
var createIsolateRunner = ({ concurrency = 8, cacheBytes = 67108864 } = {}) => {
|
|
140
|
+
const cache = /* @__PURE__ */ new Map();
|
|
141
|
+
let cachedBytes = 0;
|
|
142
|
+
/** Every isolate starts from a heap with the prelude already run in it: running it each time was most of the cost. */
|
|
143
|
+
let snapshot;
|
|
144
|
+
let guestCache;
|
|
145
|
+
let running = 0;
|
|
146
|
+
const waiting = [];
|
|
147
|
+
const acquire = async () => {
|
|
148
|
+
if (running >= concurrency) await new Promise((resolve) => waiting.push(resolve));
|
|
149
|
+
running++;
|
|
150
|
+
};
|
|
151
|
+
const release = () => {
|
|
152
|
+
running--;
|
|
153
|
+
waiting.shift()?.();
|
|
154
|
+
};
|
|
155
|
+
/** The bundle's entry, most recently used now: its code fetched only when this runner has never kept it. */
|
|
156
|
+
const cached = async (bundle) => {
|
|
157
|
+
const kept = cache.get(bundle.id);
|
|
158
|
+
if (kept) {
|
|
159
|
+
cache.delete(bundle.id);
|
|
160
|
+
cache.set(bundle.id, kept);
|
|
161
|
+
return kept;
|
|
162
|
+
}
|
|
163
|
+
const code = await bundle.load();
|
|
164
|
+
const entry = {
|
|
165
|
+
code,
|
|
166
|
+
bytes: Buffer.byteLength(code)
|
|
167
|
+
};
|
|
168
|
+
cache.set(bundle.id, entry);
|
|
169
|
+
cachedBytes += entry.bytes;
|
|
170
|
+
for (const [id, oldest] of cache) {
|
|
171
|
+
if (cachedBytes <= cacheBytes || id === bundle.id) break;
|
|
172
|
+
cache.delete(id);
|
|
173
|
+
cachedBytes -= oldest.bytes;
|
|
174
|
+
}
|
|
175
|
+
return entry;
|
|
176
|
+
};
|
|
177
|
+
/** What every isolate is made from, once per process: the module, the prelude, the heap it starts as. */
|
|
178
|
+
const ready = async () => {
|
|
179
|
+
const ivm = await loadIvm();
|
|
180
|
+
snapshot ??= ivm.Isolate.createSnapshot([{
|
|
181
|
+
code: await guestPrelude(),
|
|
182
|
+
filename: "plitzi:prelude"
|
|
183
|
+
}]);
|
|
184
|
+
return ivm;
|
|
185
|
+
};
|
|
186
|
+
const session = async ({ bundle, limits, answer, signal, onUsage, drive }) => {
|
|
187
|
+
const ivm = await ready();
|
|
188
|
+
const entry = await cached(bundle);
|
|
189
|
+
await acquire();
|
|
190
|
+
const isolate = new ivm.Isolate({
|
|
191
|
+
memoryLimit: limits.memoryMb,
|
|
192
|
+
snapshot
|
|
193
|
+
});
|
|
194
|
+
const timers = /* @__PURE__ */ new Set();
|
|
195
|
+
let stopped;
|
|
196
|
+
let calls = 0;
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
/** The CPU the isolate used, kept as it is read: a disposed isolate has none left to ask. */
|
|
199
|
+
let isolateCpuMs = 0;
|
|
200
|
+
/** What the runner spent on the run's behalf — a PBKDF2 derivation — which the isolate's clock never sees. */
|
|
201
|
+
let hostCpuMs = 0;
|
|
202
|
+
const readCpu = () => {
|
|
203
|
+
if (!isolate.isDisposed) isolateCpuMs = Number(isolate.cpuTime) / 1e6;
|
|
204
|
+
return isolateCpuMs + hostCpuMs;
|
|
205
|
+
};
|
|
206
|
+
const stop = (reason) => {
|
|
207
|
+
stopped ??= reason;
|
|
208
|
+
readCpu();
|
|
209
|
+
if (!isolate.isDisposed) isolate.dispose();
|
|
210
|
+
};
|
|
211
|
+
const later = (ms, run) => {
|
|
212
|
+
const timer = setTimeout(() => {
|
|
213
|
+
timers.delete(timer);
|
|
214
|
+
run();
|
|
215
|
+
}, ms);
|
|
216
|
+
timers.add(timer);
|
|
217
|
+
};
|
|
218
|
+
const watchdog = setInterval(() => {
|
|
219
|
+
if (!isolate.isDisposed && readCpu() > limits.cpuMs) stop("cpu");
|
|
220
|
+
}, WATCHDOG_MS);
|
|
221
|
+
later(limits.wallMs, () => stop("wall"));
|
|
222
|
+
let driver;
|
|
223
|
+
const onAbort = () => {
|
|
224
|
+
driver?.get("abort", { reference: true }).then((abort) => abort.apply(void 0, ["The run was aborted"])).catch(() => void 0);
|
|
225
|
+
later(ABORT_GRACE_MS, () => stop("aborted"));
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
const context = isolate.createContextSync();
|
|
229
|
+
const global = context.global;
|
|
230
|
+
global.setSync("globalThis", global.derefInto());
|
|
231
|
+
global.setSync("__plitziCall", new ivm.Reference(async (message) => {
|
|
232
|
+
calls++;
|
|
233
|
+
if (calls > limits.calls) {
|
|
234
|
+
stop("calls");
|
|
235
|
+
return JSON.stringify({
|
|
236
|
+
ok: false,
|
|
237
|
+
error: stopMessage("calls", limits)
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const value = await answer(JSON.parse(message));
|
|
242
|
+
return JSON.stringify({
|
|
243
|
+
ok: true,
|
|
244
|
+
value: value ?? null
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return JSON.stringify({
|
|
248
|
+
ok: false,
|
|
249
|
+
error: messageOf(error)
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
global.setSync("__plitziSleep", new ivm.Reference((ms) => new Promise((resolve) => later(Math.min(Math.max(0, ms), limits.wallMs), resolve))));
|
|
254
|
+
global.setSync("__plitziRandom", new ivm.Callback((length) => [...randomBytes(Math.min(Math.max(0, length), 65536))]));
|
|
255
|
+
global.setSync("__plitziCrypto", new ivm.Reference((message) => cryptoAnswer(message, (ms) => {
|
|
256
|
+
hostCpuMs += ms;
|
|
257
|
+
if (readCpu() > limits.cpuMs) stop("cpu");
|
|
258
|
+
})));
|
|
259
|
+
const guestScript = isolate.compileScriptSync(BOOTSTRAP, {
|
|
260
|
+
filename: "plitzi:guest",
|
|
261
|
+
...guestCache ? { cachedData: guestCache } : { produceCachedData: true }
|
|
262
|
+
});
|
|
263
|
+
guestCache ??= cachedDataOf(ivm, guestScript);
|
|
264
|
+
driver = guestScript.runSync(context, { reference: true });
|
|
265
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
266
|
+
if (signal?.aborted) onAbort();
|
|
267
|
+
const module = isolate.compileModuleSync(entry.code, {
|
|
268
|
+
filename: "functions.js",
|
|
269
|
+
...entry.cachedData ? { cachedData: entry.cachedData } : { produceCachedData: true }
|
|
270
|
+
});
|
|
271
|
+
entry.cachedData ??= cachedDataOf(ivm, module);
|
|
272
|
+
module.instantiateSync(context, (specifier) => {
|
|
273
|
+
throw new Error(`"${specifier}" cannot be imported: a bundle is one module`);
|
|
274
|
+
});
|
|
275
|
+
await module.evaluate({ timeout: limits.cpuMs });
|
|
276
|
+
const definition = module.namespace.getSync("default", { reference: true });
|
|
277
|
+
if (definition.typeof !== "object") throw new FunctionFailure("error", "functions/index.ts exports its definition by default: export default defineFunctions({ … })");
|
|
278
|
+
const text = await drive(driver, definition);
|
|
279
|
+
if (Buffer.byteLength(text) > limits.outputBytes) stop("output");
|
|
280
|
+
if (stopped) throw new FunctionFailure(stopped, stopMessage(stopped, limits));
|
|
281
|
+
const outcome = JSON.parse(text);
|
|
282
|
+
if (typeof outcome !== "object" || outcome === null || !("ok" in outcome)) throw new FunctionFailure("error", "The runtime answered something that is not an outcome");
|
|
283
|
+
if (outcome.ok !== true) throw new FunctionFailure("refused" in outcome && outcome.refused === true ? "refused" : "error", "error" in outcome ? String(outcome.error) : "The function failed");
|
|
284
|
+
return "value" in outcome ? outcome.value : null;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error instanceof FunctionFailure && !stopped) throw error;
|
|
287
|
+
const timedOut = error instanceof Error && error.message === ISOLATE_TIMEOUT;
|
|
288
|
+
const reason = stopped ?? (timedOut ? "cpu" : isolate.isDisposed ? "memory" : void 0);
|
|
289
|
+
throw reason ? new FunctionFailure(reason, stopMessage(reason, limits)) : new FunctionFailure("error", messageOf(error));
|
|
290
|
+
} finally {
|
|
291
|
+
onUsage?.({
|
|
292
|
+
cpuMs: Math.round(readCpu()),
|
|
293
|
+
wallMs: Date.now() - startedAt,
|
|
294
|
+
calls
|
|
295
|
+
});
|
|
296
|
+
clearInterval(watchdog);
|
|
297
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
298
|
+
signal?.removeEventListener("abort", onAbort);
|
|
299
|
+
if (!isolate.isDisposed) isolate.dispose();
|
|
300
|
+
release();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const describe = (bundle) => session({
|
|
304
|
+
bundle: {
|
|
305
|
+
id: bundle.id,
|
|
306
|
+
load: () => Promise.resolve(bundle.code)
|
|
307
|
+
},
|
|
308
|
+
limits: DESCRIBE_LIMITS,
|
|
309
|
+
answer: describeAnswer,
|
|
310
|
+
drive: async (driver, definition) => {
|
|
311
|
+
return driver.getSync("describe", { reference: true }).apply(driver.derefInto(), [definition.derefInto()], {
|
|
312
|
+
result: { copy: true },
|
|
313
|
+
timeout: DESCRIBE_LIMITS.cpuMs
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
describe,
|
|
319
|
+
invoke: ({ bundle, invocation, limits, answer, signal, onUsage }) => session({
|
|
320
|
+
bundle,
|
|
321
|
+
limits,
|
|
322
|
+
answer,
|
|
323
|
+
signal,
|
|
324
|
+
onUsage,
|
|
325
|
+
drive: async (driver, definition) => {
|
|
326
|
+
const invoke = driver.getSync("invoke", { reference: true });
|
|
327
|
+
const message = JSON.stringify(invocation);
|
|
328
|
+
return invoke.apply(driver.derefInto(), [definition.derefInto(), message], {
|
|
329
|
+
result: {
|
|
330
|
+
promise: true,
|
|
331
|
+
copy: true
|
|
332
|
+
},
|
|
333
|
+
timeout: limits.cpuMs
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}),
|
|
337
|
+
warm: async () => {
|
|
338
|
+
await ready();
|
|
339
|
+
await describe({
|
|
340
|
+
id: "plitzi:warm-up",
|
|
341
|
+
code: "export default {};"
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
//#endregion
|
|
347
|
+
export { PBKDF2_MAX_BITS, PBKDF2_MAX_ITERATIONS, createIsolateRunner };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import esbuild from "esbuild";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/modules/functions/sandbox/prelude.ts
|
|
6
|
+
/**
|
|
7
|
+
* The web APIs a bare V8 lacks that are the spec's to get right rather than ours: `URL`, `URLSearchParams`, `atob`,
|
|
8
|
+
* `btoa`, `structuredClone`, `DOMException` — core-js's, bundled once per runner process. What is left (`Headers`,
|
|
9
|
+
* `Request`, `Response`, timers, `crypto`, `ctx`) is the guest's, because each of those is a call to the runner.
|
|
10
|
+
*/
|
|
11
|
+
var POLYFILLS = [
|
|
12
|
+
"core-js/actual/dom-exception",
|
|
13
|
+
"core-js/actual/url",
|
|
14
|
+
"core-js/actual/url-search-params",
|
|
15
|
+
"core-js/actual/structured-clone",
|
|
16
|
+
"core-js/actual/atob",
|
|
17
|
+
"core-js/actual/btoa"
|
|
18
|
+
];
|
|
19
|
+
var build = async () => {
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = POLYFILLS.map((entry) => require.resolve(entry));
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error("Running space functions in isolates needs core-js@3 installed beside @plitzi/sdk-server");
|
|
26
|
+
}
|
|
27
|
+
return (await esbuild.build({
|
|
28
|
+
stdin: {
|
|
29
|
+
contents: entries.map((entry) => `import ${JSON.stringify(entry)};`).join("\n"),
|
|
30
|
+
loader: "js",
|
|
31
|
+
resolveDir: path.dirname(fileURLToPath(import.meta.url))
|
|
32
|
+
},
|
|
33
|
+
bundle: true,
|
|
34
|
+
write: false,
|
|
35
|
+
format: "iife",
|
|
36
|
+
platform: "browser",
|
|
37
|
+
target: "es2022",
|
|
38
|
+
minify: true,
|
|
39
|
+
logLevel: "silent"
|
|
40
|
+
})).outputFiles[0]?.text ?? "";
|
|
41
|
+
};
|
|
42
|
+
var prelude;
|
|
43
|
+
/** The prelude's source, built on first use and the same for every isolate this process makes. */
|
|
44
|
+
var guestPrelude = () => {
|
|
45
|
+
prelude ??= build();
|
|
46
|
+
return prelude;
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { guestPrelude };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FunctionsSource } from './build';
|
|
2
|
+
import { FunctionsConfig } from './config';
|
|
3
|
+
import { FunctionsBundle, FunctionLimits, FunctionRunner, SpaceFunctions } from './protocol';
|
|
4
|
+
import { ActionTask, ActionTaskRegistry } from '../actions/types';
|
|
5
|
+
import { FunctionsManifest, FunctionsProblem } from '@plitzi/sdk-shared';
|
|
6
|
+
/**
|
|
7
|
+
* A space's tasks as the registry holds them — each run by the runner, answering the code's calls with the run's own
|
|
8
|
+
* {@link FunctionContext}: the same one native functions get, built by the same function.
|
|
9
|
+
*/
|
|
10
|
+
export declare const spaceTasks: (functions: SpaceFunctions, runner: FunctionRunner, limits: FunctionLimits, { admit, onUsage }?: Pick<FunctionsConfig, "admit" | "onUsage">) => ActionTask<Record<string, unknown>>[];
|
|
11
|
+
export type SpaceRegistries = {
|
|
12
|
+
/** The tasks one run of that space can use: the deployment's, and its own functions' when it has any. */
|
|
13
|
+
registryFor: (functions: SpaceFunctions | undefined) => ActionTaskRegistry;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Registries per space bundle, built once per bundle and limits — a space's functions change when they are saved, and
|
|
17
|
+
* a saved bundle is a new id.
|
|
18
|
+
*/
|
|
19
|
+
export declare const createSpaceRegistries: (base: ActionTaskRegistry, config?: FunctionsConfig) => SpaceRegistries;
|
|
20
|
+
/** Functions whose code is already in hand — prepared a moment ago, or a deployment's own — as a run reaches them. */
|
|
21
|
+
export declare const functionsInHand: ({ bundle, manifest }: {
|
|
22
|
+
bundle: FunctionsBundle;
|
|
23
|
+
manifest: FunctionsManifest;
|
|
24
|
+
}) => SpaceFunctions;
|
|
25
|
+
/** A source made ready to store: the bundle whole — storing is the one place its code travels — and what it declared. */
|
|
26
|
+
export type PreparedFunctions = {
|
|
27
|
+
ok: true;
|
|
28
|
+
functions: {
|
|
29
|
+
bundle: FunctionsBundle;
|
|
30
|
+
manifest: FunctionsManifest;
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
problems: FunctionsProblem[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A space's source made ready to save: built, read by the runner, and checked — the only way a space's functions are
|
|
38
|
+
* stored, so what is stored always passed every rule. `reserved` is every namespace the deployment's own tasks use.
|
|
39
|
+
*/
|
|
40
|
+
export declare const prepareFunctions: (source: FunctionsSource, runner: FunctionRunner, reserved: ReadonlySet<string>) => Promise<PreparedFunctions>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ActionRefusal } from "../actions/runtime/errors.js";
|
|
2
|
+
import { functionContextFor } from "./context.js";
|
|
3
|
+
import { taskName } from "../actions/tasks/registry.js";
|
|
4
|
+
import { readManifest } from "./manifest.js";
|
|
5
|
+
import { answerCall, readCall } from "./capabilities.js";
|
|
6
|
+
import { functionLimitsFor } from "./config.js";
|
|
7
|
+
import { FunctionFailure } from "./protocol.js";
|
|
8
|
+
import { FunctionsBuildError, buildFunctions } from "./build.js";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
//#region src/modules/functions/space.ts
|
|
11
|
+
var invocationContextOf = ({ spaceId, environment, runId, trigger, callerId, user }) => ({
|
|
12
|
+
spaceId,
|
|
13
|
+
environment,
|
|
14
|
+
runId,
|
|
15
|
+
trigger,
|
|
16
|
+
callerId,
|
|
17
|
+
...user ? { user } : {}
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* A space's tasks as the registry holds them — each run by the runner, answering the code's calls with the run's own
|
|
21
|
+
* {@link FunctionContext}: the same one native functions get, built by the same function.
|
|
22
|
+
*/
|
|
23
|
+
var spaceTasks = (functions, runner, limits, { admit, onUsage } = {}) => functions.manifest.tasks.map((task) => {
|
|
24
|
+
const name = taskName(task);
|
|
25
|
+
return {
|
|
26
|
+
namespace: task.namespace,
|
|
27
|
+
action: task.action,
|
|
28
|
+
title: task.title,
|
|
29
|
+
...task.description ? { description: task.description } : {},
|
|
30
|
+
params: task.params,
|
|
31
|
+
run: async (params, ctx) => {
|
|
32
|
+
const refusal = await admit?.(ctx.spaceId);
|
|
33
|
+
if (refusal) throw new Error(refusal);
|
|
34
|
+
const fnCtx = functionContextFor(ctx, functions.manifest.hosts);
|
|
35
|
+
let ok = false;
|
|
36
|
+
let spent;
|
|
37
|
+
try {
|
|
38
|
+
const value = await runner.invoke({
|
|
39
|
+
bundle: functions.bundle,
|
|
40
|
+
invocation: {
|
|
41
|
+
kind: "task",
|
|
42
|
+
name,
|
|
43
|
+
params,
|
|
44
|
+
context: invocationContextOf(fnCtx)
|
|
45
|
+
},
|
|
46
|
+
limits,
|
|
47
|
+
answer: (call) => answerCall(fnCtx, readCall(call)),
|
|
48
|
+
signal: ctx.signal,
|
|
49
|
+
onUsage: (usage) => {
|
|
50
|
+
spent = usage;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
ok = true;
|
|
54
|
+
return value;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof FunctionFailure && error.reason === "refused") throw new ActionRefusal(error.message);
|
|
57
|
+
throw error;
|
|
58
|
+
} finally {
|
|
59
|
+
if (spent) onUsage?.({
|
|
60
|
+
spaceId: ctx.spaceId,
|
|
61
|
+
task: name,
|
|
62
|
+
ok,
|
|
63
|
+
usage: spent
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
/** The deployment's tasks and a space's, one registry: a space's never answers a name the deployment's already does. */
|
|
70
|
+
var composeRegistry = (base, tasks) => {
|
|
71
|
+
const own = /* @__PURE__ */ new Map();
|
|
72
|
+
tasks.forEach((task) => {
|
|
73
|
+
const name = taskName(task);
|
|
74
|
+
if (!base.get(name)) own.set(name, {
|
|
75
|
+
...task,
|
|
76
|
+
name
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
get: (name) => base.get(name) ?? own.get(name),
|
|
81
|
+
list: () => [...base.list(), ...own.values()]
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
var REGISTRY_CACHE = 500;
|
|
85
|
+
/**
|
|
86
|
+
* Registries per space bundle, built once per bundle and limits — a space's functions change when they are saved, and
|
|
87
|
+
* a saved bundle is a new id.
|
|
88
|
+
*/
|
|
89
|
+
var createSpaceRegistries = (base, config = {}) => {
|
|
90
|
+
const cache = /* @__PURE__ */ new Map();
|
|
91
|
+
return { registryFor: (functions) => {
|
|
92
|
+
const runner = functions?.runner ?? config.runner;
|
|
93
|
+
if (!functions || !runner) return base;
|
|
94
|
+
const limits = functionLimitsFor(config.limits, functions.limits);
|
|
95
|
+
const key = `${functions.bundle.id}:${JSON.stringify(limits)}`;
|
|
96
|
+
const found = cache.get(key);
|
|
97
|
+
if (found) return found;
|
|
98
|
+
const registry = composeRegistry(base, spaceTasks(functions, runner, limits, config));
|
|
99
|
+
cache.set(key, registry);
|
|
100
|
+
if (cache.size > REGISTRY_CACHE) {
|
|
101
|
+
const oldest = cache.keys().next().value;
|
|
102
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
103
|
+
}
|
|
104
|
+
return registry;
|
|
105
|
+
} };
|
|
106
|
+
};
|
|
107
|
+
/** Functions whose code is already in hand — prepared a moment ago, or a deployment's own — as a run reaches them. */
|
|
108
|
+
var functionsInHand = ({ bundle, manifest }) => ({
|
|
109
|
+
bundle: {
|
|
110
|
+
id: bundle.id,
|
|
111
|
+
load: () => Promise.resolve(bundle.code)
|
|
112
|
+
},
|
|
113
|
+
manifest
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* A space's source made ready to save: built, read by the runner, and checked — the only way a space's functions are
|
|
117
|
+
* stored, so what is stored always passed every rule. `reserved` is every namespace the deployment's own tasks use.
|
|
118
|
+
*/
|
|
119
|
+
var prepareFunctions = async (source, runner, reserved) => {
|
|
120
|
+
let code;
|
|
121
|
+
try {
|
|
122
|
+
({code} = await buildFunctions(source));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof FunctionsBuildError) return {
|
|
125
|
+
ok: false,
|
|
126
|
+
problems: error.problems
|
|
127
|
+
};
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
const bundle = {
|
|
131
|
+
id: createHash("sha256").update(code).digest("hex"),
|
|
132
|
+
code
|
|
133
|
+
};
|
|
134
|
+
let declared;
|
|
135
|
+
try {
|
|
136
|
+
declared = await runner.describe(bundle);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
problems: [{
|
|
141
|
+
file: "index.ts",
|
|
142
|
+
message: error instanceof Error ? error.message : String(error)
|
|
143
|
+
}]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const { manifest, problems } = readManifest(declared, reserved);
|
|
147
|
+
return problems.length ? {
|
|
148
|
+
ok: false,
|
|
149
|
+
problems: problems.map((message) => ({
|
|
150
|
+
file: "index.ts",
|
|
151
|
+
message
|
|
152
|
+
}))
|
|
153
|
+
} : {
|
|
154
|
+
ok: true,
|
|
155
|
+
functions: {
|
|
156
|
+
bundle,
|
|
157
|
+
manifest
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { createSpaceRegistries, functionsInHand, prepareFunctions, spaceTasks };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { FunctionsConfig } from '../config';
|
|
2
|
+
import { FunctionTask } from '../contract';
|
|
3
|
+
/** A server's native functions made of just these tasks — what a test registers the tasks it runs with. */
|
|
4
|
+
export declare const tasksOf: (...tasks: FunctionTask<never>[]) => FunctionsConfig;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ActionEntry } from '@plitzi/sdk-shared';
|
|
2
|
+
/** The id of the step that runs the task in a {@link functionTryEntry}: where its value, logs and error are found. */
|
|
3
|
+
export declare const TRY_STEP = "task";
|
|
4
|
+
/**
|
|
5
|
+
* One task as the smallest flow that runs it: a session call, the task, and its answer — so trying a function is an
|
|
6
|
+
* ordinary run, with every check, limit and record a run has, and nothing of its own to keep in step. The builder's
|
|
7
|
+
* Try, an agent's and `plitzi functions dev` are all this.
|
|
8
|
+
*/
|
|
9
|
+
export declare const functionTryEntry: (task: string, params: Record<string, unknown>) => ActionEntry;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/modules/functions/tryEntry.ts
|
|
2
|
+
var step = (id, fields) => ({
|
|
3
|
+
id,
|
|
4
|
+
title: id,
|
|
5
|
+
type: "task",
|
|
6
|
+
action: "",
|
|
7
|
+
params: {},
|
|
8
|
+
preview: {},
|
|
9
|
+
elementId: null,
|
|
10
|
+
beforeNode: "",
|
|
11
|
+
afterNode: "",
|
|
12
|
+
flowId: "try",
|
|
13
|
+
enabled: true,
|
|
14
|
+
...fields
|
|
15
|
+
});
|
|
16
|
+
/** The id of the step that runs the task in a {@link functionTryEntry}: where its value, logs and error are found. */
|
|
17
|
+
var TRY_STEP = "task";
|
|
18
|
+
/**
|
|
19
|
+
* One task as the smallest flow that runs it: a session call, the task, and its answer — so trying a function is an
|
|
20
|
+
* ordinary run, with every check, limit and record a run has, and nothing of its own to keep in step. The builder's
|
|
21
|
+
* Try, an agent's and `plitzi functions dev` are all this.
|
|
22
|
+
*/
|
|
23
|
+
var functionTryEntry = (task, params) => ({
|
|
24
|
+
id: `try:${task}`,
|
|
25
|
+
document: {
|
|
26
|
+
name: `Try ${task}`,
|
|
27
|
+
output: { value: { type: "json" } },
|
|
28
|
+
nodes: {
|
|
29
|
+
start: step("start", {
|
|
30
|
+
type: "trigger",
|
|
31
|
+
action: "call",
|
|
32
|
+
params: { access: "session" },
|
|
33
|
+
afterNode: TRY_STEP
|
|
34
|
+
}),
|
|
35
|
+
[TRY_STEP]: step(TRY_STEP, {
|
|
36
|
+
title: task,
|
|
37
|
+
action: task,
|
|
38
|
+
params,
|
|
39
|
+
afterNode: "answer"
|
|
40
|
+
}),
|
|
41
|
+
answer: step("answer", {
|
|
42
|
+
action: "flow.output",
|
|
43
|
+
params: { values: `{"value": {{ ${TRY_STEP}|json_encode }}}` }
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export { TRY_STEP, functionTryEntry };
|