dotdo 0.0.1 → 0.1.0
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/LICENSE +1 -1
- package/README.md +446 -315
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/index.js +19 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/template-literals.js +852 -0
- package/dist/ai/template-literals.js.map +1 -0
- package/dist/api/middleware/auth-federation.js +573 -0
- package/dist/api/middleware/auth-federation.js.map +1 -0
- package/dist/api/middleware/auth.js +545 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/db/actions.js +212 -0
- package/dist/db/actions.js.map +1 -0
- package/dist/db/auth.js +506 -0
- package/dist/db/auth.js.map +1 -0
- package/dist/db/branches.js +65 -0
- package/dist/db/branches.js.map +1 -0
- package/dist/db/clickhouse.js +1074 -0
- package/dist/db/clickhouse.js.map +1 -0
- package/dist/db/dlq.js +39 -0
- package/dist/db/dlq.js.map +1 -0
- package/dist/db/events.js +28 -0
- package/dist/db/events.js.map +1 -0
- package/dist/db/exec.js +64 -0
- package/dist/db/exec.js.map +1 -0
- package/dist/db/files.js +85 -0
- package/dist/db/files.js.map +1 -0
- package/dist/db/flags.js +24 -0
- package/dist/db/flags.js.map +1 -0
- package/dist/db/git.js +116 -0
- package/dist/db/git.js.map +1 -0
- package/dist/db/iceberg/inverted-index.js +862 -0
- package/dist/db/iceberg/inverted-index.js.map +1 -0
- package/dist/db/iceberg/puffin.js +878 -0
- package/dist/db/iceberg/puffin.js.map +1 -0
- package/dist/db/iceberg/search-manifest.js +422 -0
- package/dist/db/iceberg/search-manifest.js.map +1 -0
- package/dist/db/iceberg/types.js +8 -0
- package/dist/db/iceberg/types.js.map +1 -0
- package/dist/db/index.js +121 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/integrations.js +368 -0
- package/dist/db/integrations.js.map +1 -0
- package/dist/db/json-indexes.js +332 -0
- package/dist/db/json-indexes.js.map +1 -0
- package/dist/db/linked-accounts.js +287 -0
- package/dist/db/linked-accounts.js.map +1 -0
- package/dist/db/nouns.js +183 -0
- package/dist/db/nouns.js.map +1 -0
- package/dist/db/objects.js +170 -0
- package/dist/db/objects.js.map +1 -0
- package/dist/db/primitives/dag-scheduler/index.js +869 -0
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
- package/dist/db/primitives/exactly-once-context.js +237 -0
- package/dist/db/primitives/exactly-once-context.js.map +1 -0
- package/dist/db/primitives/index.js +62 -0
- package/dist/db/primitives/index.js.map +1 -0
- package/dist/db/primitives/keyed-router.js +145 -0
- package/dist/db/primitives/keyed-router.js.map +1 -0
- package/dist/db/primitives/observability.js +162 -0
- package/dist/db/primitives/observability.js.map +1 -0
- package/dist/db/primitives/schema-evolution.js +643 -0
- package/dist/db/primitives/schema-evolution.js.map +1 -0
- package/dist/db/primitives/stateful-operator/index.js +770 -0
- package/dist/db/primitives/stateful-operator/index.js.map +1 -0
- package/dist/db/primitives/temporal-store.js +306 -0
- package/dist/db/primitives/temporal-store.js.map +1 -0
- package/dist/db/primitives/typed-column-store.js +1229 -0
- package/dist/db/primitives/typed-column-store.js.map +1 -0
- package/dist/db/primitives/utils/duration.js +162 -0
- package/dist/db/primitives/utils/duration.js.map +1 -0
- package/dist/db/primitives/utils/murmur3.js +116 -0
- package/dist/db/primitives/utils/murmur3.js.map +1 -0
- package/dist/db/primitives/watermark-service.js +136 -0
- package/dist/db/primitives/watermark-service.js.map +1 -0
- package/dist/db/primitives/window-manager.js +764 -0
- package/dist/db/primitives/window-manager.js.map +1 -0
- package/dist/db/relationships.js +66 -0
- package/dist/db/relationships.js.map +1 -0
- package/dist/db/schema-minimal.js +61 -0
- package/dist/db/schema-minimal.js.map +1 -0
- package/dist/db/search.js +28 -0
- package/dist/db/search.js.map +1 -0
- package/dist/db/stores.js +1665 -0
- package/dist/db/stores.js.map +1 -0
- package/dist/db/things.js +297 -0
- package/dist/db/things.js.map +1 -0
- package/dist/db/vault.js +171 -0
- package/dist/db/vault.js.map +1 -0
- package/dist/db/verbs.js +102 -0
- package/dist/db/verbs.js.map +1 -0
- package/dist/do/base.js +48 -0
- package/dist/do/base.js.map +1 -0
- package/dist/do/tiny.js +31 -0
- package/dist/do/tiny.js.map +1 -0
- package/dist/lib/DOAuth.js +261 -0
- package/dist/lib/DOAuth.js.map +1 -0
- package/dist/lib/DODispatcher.js +72 -0
- package/dist/lib/DODispatcher.js.map +1 -0
- package/dist/lib/Modifier.js +189 -0
- package/dist/lib/Modifier.js.map +1 -0
- package/dist/lib/StateStorage.js +403 -0
- package/dist/lib/StateStorage.js.map +1 -0
- package/dist/lib/TypeRegistry.js +122 -0
- package/dist/lib/TypeRegistry.js.map +1 -0
- package/dist/lib/ai/gateway.js +247 -0
- package/dist/lib/ai/gateway.js.map +1 -0
- package/dist/lib/ai/tool-loop-agent.js +591 -0
- package/dist/lib/ai/tool-loop-agent.js.map +1 -0
- package/dist/lib/auto-wiring.js +439 -0
- package/dist/lib/auto-wiring.js.map +1 -0
- package/dist/lib/browse/browserbase.js +163 -0
- package/dist/lib/browse/browserbase.js.map +1 -0
- package/dist/lib/browse/cloudflare.js +144 -0
- package/dist/lib/browse/cloudflare.js.map +1 -0
- package/dist/lib/browse/index.js +62 -0
- package/dist/lib/browse/index.js.map +1 -0
- package/dist/lib/browse/types.js +13 -0
- package/dist/lib/browse/types.js.map +1 -0
- package/dist/lib/cache/index.js +37 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/visibility.js +638 -0
- package/dist/lib/cache/visibility.js.map +1 -0
- package/dist/lib/capabilities.js +268 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/channels/base.js +106 -0
- package/dist/lib/channels/base.js.map +1 -0
- package/dist/lib/channels/discord.js +94 -0
- package/dist/lib/channels/discord.js.map +1 -0
- package/dist/lib/channels/email.js +204 -0
- package/dist/lib/channels/email.js.map +1 -0
- package/dist/lib/channels/index.js +90 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/mdxui-chat.js +95 -0
- package/dist/lib/channels/mdxui-chat.js.map +1 -0
- package/dist/lib/channels/slack-blockkit.js +121 -0
- package/dist/lib/channels/slack-blockkit.js.map +1 -0
- package/dist/lib/channels/types.js +7 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/cloudflare/ai.js +654 -0
- package/dist/lib/cloudflare/ai.js.map +1 -0
- package/dist/lib/cloudflare/index.js +88 -0
- package/dist/lib/cloudflare/index.js.map +1 -0
- package/dist/lib/cloudflare/kv.js +342 -0
- package/dist/lib/cloudflare/kv.js.map +1 -0
- package/dist/lib/cloudflare/queues.js +434 -0
- package/dist/lib/cloudflare/queues.js.map +1 -0
- package/dist/lib/cloudflare/r2.js +604 -0
- package/dist/lib/cloudflare/r2.js.map +1 -0
- package/dist/lib/cloudflare/vectorize.js +494 -0
- package/dist/lib/cloudflare/vectorize.js.map +1 -0
- package/dist/lib/cloudflare/workflows.js +569 -0
- package/dist/lib/cloudflare/workflows.js.map +1 -0
- package/dist/lib/colo/caching.js +196 -0
- package/dist/lib/colo/caching.js.map +1 -0
- package/dist/lib/colo/detection.js +194 -0
- package/dist/lib/colo/detection.js.map +1 -0
- package/dist/lib/colo/external-data.js +219 -0
- package/dist/lib/colo/external-data.js.map +1 -0
- package/dist/lib/colo/globe-data.js +179 -0
- package/dist/lib/colo/globe-data.js.map +1 -0
- package/dist/lib/colo/index.js +16 -0
- package/dist/lib/colo/index.js.map +1 -0
- package/dist/lib/decorators.js +37 -0
- package/dist/lib/decorators.js.map +1 -0
- package/dist/lib/discovery.js +81 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
- package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/CascadeExecutor.js +418 -0
- package/dist/lib/executors/CascadeExecutor.js.map +1 -0
- package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/ParallelStepExecutor.js +308 -0
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
- package/dist/lib/executors/types.js +12 -0
- package/dist/lib/executors/types.js.map +1 -0
- package/dist/lib/experiments.js +89 -0
- package/dist/lib/experiments.js.map +1 -0
- package/dist/lib/flags/store.js +262 -0
- package/dist/lib/flags/store.js.map +1 -0
- package/dist/lib/functions/FunctionComposition.js +467 -0
- package/dist/lib/functions/FunctionComposition.js.map +1 -0
- package/dist/lib/functions/FunctionMiddleware.js +457 -0
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
- package/dist/lib/functions/FunctionRegistry.js +426 -0
- package/dist/lib/functions/FunctionRegistry.js.map +1 -0
- package/dist/lib/functions/createFunction.js +1048 -0
- package/dist/lib/functions/createFunction.js.map +1 -0
- package/dist/lib/humans/index.js +68 -0
- package/dist/lib/humans/index.js.map +1 -0
- package/dist/lib/humans/templates.js +117 -0
- package/dist/lib/humans/templates.js.map +1 -0
- package/dist/lib/identity.js +98 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging/error-logger.js +163 -0
- package/dist/lib/logging/error-logger.js.map +1 -0
- package/dist/lib/logging/index.js +160 -0
- package/dist/lib/logging/index.js.map +1 -0
- package/dist/lib/mixins/bash.js +753 -0
- package/dist/lib/mixins/bash.js.map +1 -0
- package/dist/lib/mixins/fs.js +648 -0
- package/dist/lib/mixins/fs.js.map +1 -0
- package/dist/lib/mixins/git.js +1006 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/npm.js +662 -0
- package/dist/lib/mixins/npm.js.map +1 -0
- package/dist/lib/noun-id.js +278 -0
- package/dist/lib/noun-id.js.map +1 -0
- package/dist/lib/rate-limit/sliding-window.js +148 -0
- package/dist/lib/rate-limit/sliding-window.js.map +1 -0
- package/dist/lib/rate-limit.js +110 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/rpc/bindings.js +548 -0
- package/dist/lib/rpc/bindings.js.map +1 -0
- package/dist/lib/rpc/index.js +64 -0
- package/dist/lib/rpc/index.js.map +1 -0
- package/dist/lib/safe-stringify.js +223 -0
- package/dist/lib/safe-stringify.js.map +1 -0
- package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
- package/dist/lib/sqids.js +110 -0
- package/dist/lib/sqids.js.map +1 -0
- package/dist/lib/sql/adapters/index.js +10 -0
- package/dist/lib/sql/adapters/index.js.map +1 -0
- package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
- package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
- package/dist/lib/sql/index.js +277 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql/types.js +56 -0
- package/dist/lib/sql/types.js.map +1 -0
- package/dist/lib/type-classifier.js +126 -0
- package/dist/lib/type-classifier.js.map +1 -0
- package/dist/lib/utils/html.js +47 -0
- package/dist/lib/utils/html.js.map +1 -0
- package/dist/lib/validation.js +48 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/vault/store.js +411 -0
- package/dist/lib/vault/store.js.map +1 -0
- package/dist/metrics/hunch.js +739 -0
- package/dist/metrics/hunch.js.map +1 -0
- package/dist/objects/API.js +302 -0
- package/dist/objects/API.js.map +1 -0
- package/dist/objects/Agent.js +179 -0
- package/dist/objects/Agent.js.map +1 -0
- package/dist/objects/AgenticFunctionExecutor.js +8 -0
- package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
- package/dist/objects/App.js +83 -0
- package/dist/objects/App.js.map +1 -0
- package/dist/objects/Browser.js +884 -0
- package/dist/objects/Browser.js.map +1 -0
- package/dist/objects/Business.js +107 -0
- package/dist/objects/Business.js.map +1 -0
- package/dist/objects/CLI.js +221 -0
- package/dist/objects/CLI.js.map +1 -0
- package/dist/objects/CodeFunctionExecutor.js +8 -0
- package/dist/objects/CodeFunctionExecutor.js.map +1 -0
- package/dist/objects/Collection.js +161 -0
- package/dist/objects/Collection.js.map +1 -0
- package/dist/objects/DO.js +41 -0
- package/dist/objects/DO.js.map +1 -0
- package/dist/objects/DOBase.js +2309 -0
- package/dist/objects/DOBase.js.map +1 -0
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js +1676 -0
- package/dist/objects/DOFull.js.map +1 -0
- package/dist/objects/DOTiny.js +207 -0
- package/dist/objects/DOTiny.js.map +1 -0
- package/dist/objects/Directory.js +199 -0
- package/dist/objects/Directory.js.map +1 -0
- package/dist/objects/Entity.js +413 -0
- package/dist/objects/Entity.js.map +1 -0
- package/dist/objects/Function.js +116 -0
- package/dist/objects/Function.js.map +1 -0
- package/dist/objects/Human.js +231 -0
- package/dist/objects/Human.js.map +1 -0
- package/dist/objects/HumanFunctionExecutor.js +8 -0
- package/dist/objects/HumanFunctionExecutor.js.map +1 -0
- package/dist/objects/IcebergMetadataDO.js +938 -0
- package/dist/objects/IcebergMetadataDO.js.map +1 -0
- package/dist/objects/IntegrationsDO.js +1174 -0
- package/dist/objects/IntegrationsDO.js.map +1 -0
- package/dist/objects/ObservabilityBroadcaster.js +149 -0
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
- package/dist/objects/Package.js +154 -0
- package/dist/objects/Package.js.map +1 -0
- package/dist/objects/Product.js +193 -0
- package/dist/objects/Product.js.map +1 -0
- package/dist/objects/SDK.js +152 -0
- package/dist/objects/SDK.js.map +1 -0
- package/dist/objects/SaaS.js +235 -0
- package/dist/objects/SaaS.js.map +1 -0
- package/dist/objects/SandboxDO.js +759 -0
- package/dist/objects/SandboxDO.js.map +1 -0
- package/dist/objects/Service.js +337 -0
- package/dist/objects/Service.js.map +1 -0
- package/dist/objects/Site.js +80 -0
- package/dist/objects/Site.js.map +1 -0
- package/dist/objects/Startup.js +479 -0
- package/dist/objects/Startup.js.map +1 -0
- package/dist/objects/ThingsDO.js +170 -0
- package/dist/objects/ThingsDO.js.map +1 -0
- package/dist/objects/VectorShardDO.js +650 -0
- package/dist/objects/VectorShardDO.js.map +1 -0
- package/dist/objects/Worker.js +144 -0
- package/dist/objects/Worker.js.map +1 -0
- package/dist/objects/Workflow.js +196 -0
- package/dist/objects/Workflow.js.map +1 -0
- package/dist/objects/WorkflowFactory.js +313 -0
- package/dist/objects/WorkflowFactory.js.map +1 -0
- package/dist/objects/WorkflowRuntime.js +863 -0
- package/dist/objects/WorkflowRuntime.js.map +1 -0
- package/dist/objects/circuit-breaker-bulkhead.js +178 -0
- package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
- package/dist/objects/createFunction.js +934 -0
- package/dist/objects/createFunction.js.map +1 -0
- package/dist/objects/index.js +80 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/lifecycle/Branch.js +275 -0
- package/dist/objects/lifecycle/Branch.js.map +1 -0
- package/dist/objects/lifecycle/Clone.js +1499 -0
- package/dist/objects/lifecycle/Clone.js.map +1 -0
- package/dist/objects/lifecycle/Compact.js +237 -0
- package/dist/objects/lifecycle/Compact.js.map +1 -0
- package/dist/objects/lifecycle/Promote.js +476 -0
- package/dist/objects/lifecycle/Promote.js.map +1 -0
- package/dist/objects/lifecycle/Shard.js +560 -0
- package/dist/objects/lifecycle/Shard.js.map +1 -0
- package/dist/objects/lifecycle/index.js +15 -0
- package/dist/objects/lifecycle/index.js.map +1 -0
- package/dist/objects/lifecycle/types.js +33 -0
- package/dist/objects/lifecycle/types.js.map +1 -0
- package/dist/objects/mixins/infrastructure.js +171 -0
- package/dist/objects/mixins/infrastructure.js.map +1 -0
- package/dist/objects/modules/StoresModule.js +153 -0
- package/dist/objects/modules/StoresModule.js.map +1 -0
- package/dist/objects/persistence/checkpoint-manager.js +606 -0
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
- package/dist/objects/persistence/index.js +72 -0
- package/dist/objects/persistence/index.js.map +1 -0
- package/dist/objects/persistence/migration-runner.js +562 -0
- package/dist/objects/persistence/migration-runner.js.map +1 -0
- package/dist/objects/persistence/replication-manager.js +501 -0
- package/dist/objects/persistence/replication-manager.js.map +1 -0
- package/dist/objects/persistence/tiered-storage-manager.js +595 -0
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
- package/dist/objects/persistence/types.js +14 -0
- package/dist/objects/persistence/types.js.map +1 -0
- package/dist/objects/persistence/wal-manager.js +653 -0
- package/dist/objects/persistence/wal-manager.js.map +1 -0
- package/dist/objects/presets/index.js +20 -0
- package/dist/objects/presets/index.js.map +1 -0
- package/dist/objects/presets/primitives.js +188 -0
- package/dist/objects/presets/primitives.js.map +1 -0
- package/dist/objects/primitives/alarm-adapter.js +141 -0
- package/dist/objects/primitives/alarm-adapter.js.map +1 -0
- package/dist/objects/primitives/index.js +337 -0
- package/dist/objects/primitives/index.js.map +1 -0
- package/dist/objects/primitives/storage-adapter.js +182 -0
- package/dist/objects/primitives/storage-adapter.js.map +1 -0
- package/dist/objects/primitives/with-primitives.js +102 -0
- package/dist/objects/primitives/with-primitives.js.map +1 -0
- package/dist/objects/services/StoreManager.js +227 -0
- package/dist/objects/services/StoreManager.js.map +1 -0
- package/dist/objects/services/index.js +13 -0
- package/dist/objects/services/index.js.map +1 -0
- package/dist/objects/transport/auth-layer.js +1451 -0
- package/dist/objects/transport/auth-layer.js.map +1 -0
- package/dist/objects/transport/capnweb-target.js +355 -0
- package/dist/objects/transport/capnweb-target.js.map +1 -0
- package/dist/objects/transport/chain.js +441 -0
- package/dist/objects/transport/chain.js.map +1 -0
- package/dist/objects/transport/handler.js +58 -0
- package/dist/objects/transport/handler.js.map +1 -0
- package/dist/objects/transport/index.js +53 -0
- package/dist/objects/transport/index.js.map +1 -0
- package/dist/objects/transport/mcp-server.js +691 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1508 -0
- package/dist/objects/transport/rest-autowire.js.map +1 -0
- package/dist/objects/transport/rest-router.js +440 -0
- package/dist/objects/transport/rest-router.js.map +1 -0
- package/dist/objects/transport/rpc-server.js +1539 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +576 -0
- package/dist/objects/transport/shared.js.map +1 -0
- package/dist/objects/transport/sync-engine.js +291 -0
- package/dist/objects/transport/sync-engine.js.map +1 -0
- package/dist/objects/transport/types.js +8 -0
- package/dist/objects/transport/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/snippets/artifacts-config.js +241 -0
- package/dist/snippets/artifacts-config.js.map +1 -0
- package/dist/snippets/artifacts-ingest.js +832 -0
- package/dist/snippets/artifacts-ingest.js.map +1 -0
- package/dist/snippets/artifacts-serve.js +1035 -0
- package/dist/snippets/artifacts-serve.js.map +1 -0
- package/dist/snippets/artifacts-types.js +161 -0
- package/dist/snippets/artifacts-types.js.map +1 -0
- package/dist/snippets/cache-probe.js +376 -0
- package/dist/snippets/cache-probe.js.map +1 -0
- package/dist/snippets/cache.js +10 -0
- package/dist/snippets/cache.js.map +1 -0
- package/dist/snippets/events.js +469 -0
- package/dist/snippets/events.js.map +1 -0
- package/dist/snippets/index.js +7 -0
- package/dist/snippets/index.js.map +1 -0
- package/dist/snippets/proxy.js +495 -0
- package/dist/snippets/proxy.js.map +1 -0
- package/dist/snippets/search.js +1759 -0
- package/dist/snippets/search.js.map +1 -0
- package/dist/streams/index.js +30 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/observability.js +68 -0
- package/dist/streams/observability.js.map +1 -0
- package/dist/types/AI.js +92 -0
- package/dist/types/AI.js.map +1 -0
- package/dist/types/AIFunction.js +171 -0
- package/dist/types/AIFunction.js.map +1 -0
- package/dist/types/BrowseVerb.js +89 -0
- package/dist/types/BrowseVerb.js.map +1 -0
- package/dist/types/Browser.js +31 -0
- package/dist/types/Browser.js.map +1 -0
- package/dist/types/Chaos.js +15 -0
- package/dist/types/Chaos.js.map +1 -0
- package/dist/types/CloudflareBindings.js +109 -0
- package/dist/types/CloudflareBindings.js.map +1 -0
- package/dist/types/Collection.js +50 -0
- package/dist/types/Collection.js.map +1 -0
- package/dist/types/DO.js +2 -0
- package/dist/types/DO.js.map +1 -0
- package/dist/types/DOLocation.js +63 -0
- package/dist/types/DOLocation.js.map +1 -0
- package/dist/types/EventHandler.js +57 -0
- package/dist/types/EventHandler.js.map +1 -0
- package/dist/types/Experiment.js +33 -0
- package/dist/types/Experiment.js.map +1 -0
- package/dist/types/Flag.js +57 -0
- package/dist/types/Flag.js.map +1 -0
- package/dist/types/Lifecycle.js +13 -0
- package/dist/types/Lifecycle.js.map +1 -0
- package/dist/types/Location.js +169 -0
- package/dist/types/Location.js.map +1 -0
- package/dist/types/Noun.js +66 -0
- package/dist/types/Noun.js.map +1 -0
- package/dist/types/SessionEvent.js +194 -0
- package/dist/types/SessionEvent.js.map +1 -0
- package/dist/types/Thing.js +55 -0
- package/dist/types/Thing.js.map +1 -0
- package/dist/types/ThingDO.js +153 -0
- package/dist/types/ThingDO.js.map +1 -0
- package/dist/types/Things.js +2 -0
- package/dist/types/Things.js.map +1 -0
- package/dist/types/Verb.js +119 -0
- package/dist/types/Verb.js.map +1 -0
- package/dist/types/WorkflowContext.js +70 -0
- package/dist/types/WorkflowContext.js.map +1 -0
- package/dist/types/analytics-api.js +13 -0
- package/dist/types/analytics-api.js.map +1 -0
- package/dist/types/capabilities.js +135 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/drizzle.js +12 -0
- package/dist/types/drizzle.js.map +1 -0
- package/dist/types/event.js +201 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/fn.js +12 -0
- package/dist/types/fn.js.map +1 -0
- package/dist/types/iceberg.js +48 -0
- package/dist/types/iceberg.js.map +1 -0
- package/dist/types/ids.js +170 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/introspect.js +54 -0
- package/dist/types/introspect.js.map +1 -0
- package/dist/types/observability.js +124 -0
- package/dist/types/observability.js.map +1 -0
- package/dist/types/sync-protocol.js +175 -0
- package/dist/types/sync-protocol.js.map +1 -0
- package/dist/types/vector.js +13 -0
- package/dist/types/vector.js.map +1 -0
- package/dist/workflows/ScheduleManager.js +473 -0
- package/dist/workflows/ScheduleManager.js.map +1 -0
- package/dist/workflows/StepDOBridge.js +149 -0
- package/dist/workflows/StepDOBridge.js.map +1 -0
- package/dist/workflows/StepResultStorage.js +232 -0
- package/dist/workflows/StepResultStorage.js.map +1 -0
- package/dist/workflows/WaitForEventManager.js +461 -0
- package/dist/workflows/WaitForEventManager.js.map +1 -0
- package/dist/workflows/analyzer.js +332 -0
- package/dist/workflows/analyzer.js.map +1 -0
- package/dist/workflows/compat/activity-router.js +484 -0
- package/dist/workflows/compat/activity-router.js.map +1 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
- package/dist/workflows/compat/backends/index.js +14 -0
- package/dist/workflows/compat/backends/index.js.map +1 -0
- package/dist/workflows/compat/errors/index.js +375 -0
- package/dist/workflows/compat/errors/index.js.map +1 -0
- package/dist/workflows/compat/index.js +79 -0
- package/dist/workflows/compat/index.js.map +1 -0
- package/dist/workflows/compat/inngest/index.js +989 -0
- package/dist/workflows/compat/inngest/index.js.map +1 -0
- package/dist/workflows/compat/qstash/index.js +1263 -0
- package/dist/workflows/compat/qstash/index.js.map +1 -0
- package/dist/workflows/compat/temporal/activities.js +739 -0
- package/dist/workflows/compat/temporal/activities.js.map +1 -0
- package/dist/workflows/compat/temporal/child-workflows.js +154 -0
- package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
- package/dist/workflows/compat/temporal/client.js +381 -0
- package/dist/workflows/compat/temporal/client.js.map +1 -0
- package/dist/workflows/compat/temporal/context.js +309 -0
- package/dist/workflows/compat/temporal/context.js.map +1 -0
- package/dist/workflows/compat/temporal/determinism.js +216 -0
- package/dist/workflows/compat/temporal/determinism.js.map +1 -0
- package/dist/workflows/compat/temporal/errors.js +128 -0
- package/dist/workflows/compat/temporal/errors.js.map +1 -0
- package/dist/workflows/compat/temporal/index.js +2464 -0
- package/dist/workflows/compat/temporal/index.js.map +1 -0
- package/dist/workflows/compat/temporal/saga.js +504 -0
- package/dist/workflows/compat/temporal/saga.js.map +1 -0
- package/dist/workflows/compat/temporal/signals.js +364 -0
- package/dist/workflows/compat/temporal/signals.js.map +1 -0
- package/dist/workflows/compat/temporal/storage.js +271 -0
- package/dist/workflows/compat/temporal/storage.js.map +1 -0
- package/dist/workflows/compat/temporal/timers.js +347 -0
- package/dist/workflows/compat/temporal/timers.js.map +1 -0
- package/dist/workflows/compat/temporal/types.js +7 -0
- package/dist/workflows/compat/temporal/types.js.map +1 -0
- package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
- package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
- package/dist/workflows/compat/trigger/index.js +468 -0
- package/dist/workflows/compat/trigger/index.js.map +1 -0
- package/dist/workflows/compat/utils/index.js +69 -0
- package/dist/workflows/compat/utils/index.js.map +1 -0
- package/dist/workflows/context/correlation-capability.js +266 -0
- package/dist/workflows/context/correlation-capability.js.map +1 -0
- package/dist/workflows/context/correlation.js +484 -0
- package/dist/workflows/context/correlation.js.map +1 -0
- package/dist/workflows/context/experiment.js +289 -0
- package/dist/workflows/context/experiment.js.map +1 -0
- package/dist/workflows/context/flag.js +244 -0
- package/dist/workflows/context/flag.js.map +1 -0
- package/dist/workflows/context/foundation.js +648 -0
- package/dist/workflows/context/foundation.js.map +1 -0
- package/dist/workflows/context/human-base.js +106 -0
- package/dist/workflows/context/human-base.js.map +1 -0
- package/dist/workflows/context/human.js +368 -0
- package/dist/workflows/context/human.js.map +1 -0
- package/dist/workflows/context/measure.js +354 -0
- package/dist/workflows/context/measure.js.map +1 -0
- package/dist/workflows/context/rate-limit.js +358 -0
- package/dist/workflows/context/rate-limit.js.map +1 -0
- package/dist/workflows/context/user.js +117 -0
- package/dist/workflows/context/user.js.map +1 -0
- package/dist/workflows/context/vault.js +360 -0
- package/dist/workflows/context/vault.js.map +1 -0
- package/dist/workflows/data/entity-events/entity-events.js +489 -0
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
- package/dist/workflows/data/experiment/index.js +599 -0
- package/dist/workflows/data/experiment/index.js.map +1 -0
- package/dist/workflows/data/goal/context.js +558 -0
- package/dist/workflows/data/goal/context.js.map +1 -0
- package/dist/workflows/data/goal/index.js +32 -0
- package/dist/workflows/data/goal/index.js.map +1 -0
- package/dist/workflows/data/measure/index.js +840 -0
- package/dist/workflows/data/measure/index.js.map +1 -0
- package/dist/workflows/data/stream/index.js +1149 -0
- package/dist/workflows/data/stream/index.js.map +1 -0
- package/dist/workflows/data/track/context.js +883 -0
- package/dist/workflows/data/track/context.js.map +1 -0
- package/dist/workflows/data/track/index.js +15 -0
- package/dist/workflows/data/track/index.js.map +1 -0
- package/dist/workflows/data/view/context.js +864 -0
- package/dist/workflows/data/view/context.js.map +1 -0
- package/dist/workflows/domain.js +93 -0
- package/dist/workflows/domain.js.map +1 -0
- package/dist/workflows/flag.js +176 -0
- package/dist/workflows/flag.js.map +1 -0
- package/dist/workflows/flags.js +217 -0
- package/dist/workflows/flags.js.map +1 -0
- package/dist/workflows/hash.js +209 -0
- package/dist/workflows/hash.js.map +1 -0
- package/dist/workflows/index.js +50 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/on.js +378 -0
- package/dist/workflows/on.js.map +1 -0
- package/dist/workflows/pipeline-promise.js +481 -0
- package/dist/workflows/pipeline-promise.js.map +1 -0
- package/dist/workflows/pipeline-types.js +20 -0
- package/dist/workflows/pipeline-types.js.map +1 -0
- package/dist/workflows/proxy.js +76 -0
- package/dist/workflows/proxy.js.map +1 -0
- package/dist/workflows/runtime.js +310 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/schedule-builder.js +327 -0
- package/dist/workflows/schedule-builder.js.map +1 -0
- package/dist/workflows/visibility/index.js +146 -0
- package/dist/workflows/visibility/index.js.map +1 -0
- package/dist/workflows/visibility/query-parser.js +150 -0
- package/dist/workflows/visibility/query-parser.js.map +1 -0
- package/dist/workflows/visibility/store.js +223 -0
- package/dist/workflows/visibility/store.js.map +1 -0
- package/dist/workflows/visibility/types.js +30 -0
- package/dist/workflows/visibility/types.js.map +1 -0
- package/dist/workflows/workflow.js +53 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/package.json +294 -46
|
@@ -0,0 +1,1539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC Server for DO - Legacy RPC Protocol Implementation
|
|
3
|
+
*
|
|
4
|
+
* @deprecated This module is deprecated in favor of the official capnweb library.
|
|
5
|
+
* Use the root endpoint (/) for Cap'n Web RPC via `objects/transport/capnweb-target.ts`.
|
|
6
|
+
*
|
|
7
|
+
* This module is kept for backward compatibility at the /rpc endpoint and supports:
|
|
8
|
+
* - JSON-RPC 2.0: Standard JSON-RPC protocol (still useful for many clients)
|
|
9
|
+
* - Chain RPC: Legacy chain-based format (deprecated, use capnweb)
|
|
10
|
+
* - Custom Cap'n Web emulation: (deprecated, use real capnweb at /)
|
|
11
|
+
*
|
|
12
|
+
* Migration guide:
|
|
13
|
+
* - For new clients: Use capnweb client connecting to root endpoint (/)
|
|
14
|
+
* - For JSON-RPC 2.0: Continue using /rpc endpoint
|
|
15
|
+
* - For Chain RPC: Migrate to capnweb protocol
|
|
16
|
+
*
|
|
17
|
+
* @see {@link ./capnweb-target.ts} for the new capnweb integration
|
|
18
|
+
*/
|
|
19
|
+
// Standard JSON-RPC error codes
|
|
20
|
+
const JSON_RPC_ERRORS = {
|
|
21
|
+
PARSE_ERROR: { code: -32700, message: 'Parse error' },
|
|
22
|
+
INVALID_REQUEST: { code: -32600, message: 'Invalid Request' },
|
|
23
|
+
METHOD_NOT_FOUND: { code: -32601, message: 'Method not found' },
|
|
24
|
+
INVALID_PARAMS: { code: -32602, message: 'Invalid params' },
|
|
25
|
+
INTERNAL_ERROR: { code: -32603, message: 'Internal error' },
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Chain execution error codes
|
|
29
|
+
*/
|
|
30
|
+
const CHAIN_ERRORS = {
|
|
31
|
+
INVALID_CHAIN: { code: 'INVALID_CHAIN', message: 'Chain must be a non-empty array' },
|
|
32
|
+
INVALID_STEP: { code: 'INVALID_STEP', message: 'Invalid step type' },
|
|
33
|
+
NOT_FOUND: { code: 'NOT_FOUND', message: 'Property not found' },
|
|
34
|
+
NOT_CALLABLE: { code: 'NOT_CALLABLE', message: 'Value is not a function' },
|
|
35
|
+
NOT_INDEXABLE: { code: 'NOT_INDEXABLE', message: 'Value is not an array' },
|
|
36
|
+
INDEX_OUT_OF_BOUNDS: { code: 'INDEX_OUT_OF_BOUNDS', message: 'Array index out of bounds' },
|
|
37
|
+
EXECUTION_ERROR: { code: 'EXECUTION_ERROR', message: 'Chain execution failed' },
|
|
38
|
+
BLOCKED_ACCESS: { code: 'BLOCKED_ACCESS', message: 'Access to this property is blocked' },
|
|
39
|
+
};
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// PROMISE STORE - Manages stored promise results for pipelining
|
|
42
|
+
// ============================================================================
|
|
43
|
+
class PromiseStore {
|
|
44
|
+
promises = new Map();
|
|
45
|
+
disposed = new Set();
|
|
46
|
+
depths = new Map();
|
|
47
|
+
set(id, value, depth = 0) {
|
|
48
|
+
this.promises.set(id, value);
|
|
49
|
+
this.depths.set(id, depth);
|
|
50
|
+
}
|
|
51
|
+
get(id) {
|
|
52
|
+
if (this.disposed.has(id)) {
|
|
53
|
+
throw { code: 'DISPOSED_REFERENCE', message: `Promise ${id} has been disposed` };
|
|
54
|
+
}
|
|
55
|
+
return this.promises.get(id);
|
|
56
|
+
}
|
|
57
|
+
getDepth(id) {
|
|
58
|
+
return this.depths.get(id) ?? 0;
|
|
59
|
+
}
|
|
60
|
+
has(id) {
|
|
61
|
+
return this.promises.has(id) && !this.disposed.has(id);
|
|
62
|
+
}
|
|
63
|
+
dispose(id) {
|
|
64
|
+
if (this.promises.has(id)) {
|
|
65
|
+
this.disposed.add(id);
|
|
66
|
+
this.promises.delete(id);
|
|
67
|
+
this.depths.delete(id);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
isDisposed(id) {
|
|
73
|
+
return this.disposed.has(id);
|
|
74
|
+
}
|
|
75
|
+
clear() {
|
|
76
|
+
this.promises.clear();
|
|
77
|
+
this.disposed.clear();
|
|
78
|
+
this.depths.clear();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
class SubscriptionManager {
|
|
82
|
+
subscriptions = new Map();
|
|
83
|
+
eventSubscriptions = new Map();
|
|
84
|
+
subscribe(event, callback) {
|
|
85
|
+
const id = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
86
|
+
this.subscriptions.set(id, { id, event, callback });
|
|
87
|
+
if (!this.eventSubscriptions.has(event)) {
|
|
88
|
+
this.eventSubscriptions.set(event, new Set());
|
|
89
|
+
}
|
|
90
|
+
this.eventSubscriptions.get(event).add(id);
|
|
91
|
+
return id;
|
|
92
|
+
}
|
|
93
|
+
unsubscribe(subscriptionId) {
|
|
94
|
+
const sub = this.subscriptions.get(subscriptionId);
|
|
95
|
+
if (!sub)
|
|
96
|
+
return false;
|
|
97
|
+
this.subscriptions.delete(subscriptionId);
|
|
98
|
+
this.eventSubscriptions.get(sub.event)?.delete(subscriptionId);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
emit(event, data) {
|
|
102
|
+
const subIds = this.eventSubscriptions.get(event);
|
|
103
|
+
if (!subIds)
|
|
104
|
+
return;
|
|
105
|
+
for (const id of subIds) {
|
|
106
|
+
const sub = this.subscriptions.get(id);
|
|
107
|
+
if (sub) {
|
|
108
|
+
try {
|
|
109
|
+
sub.callback(data);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// Log callback errors so broken subscriptions can be detected
|
|
113
|
+
console.error('[rpc] Subscription callback error:', {
|
|
114
|
+
subscriptionId: id,
|
|
115
|
+
event,
|
|
116
|
+
error: error instanceof Error ? error.message : 'unknown',
|
|
117
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
clear() {
|
|
124
|
+
this.subscriptions.clear();
|
|
125
|
+
this.eventSubscriptions.clear();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Default blocked methods - internal DO methods that should never be exposed
|
|
129
|
+
const DEFAULT_BLOCKED_METHODS = new Set([
|
|
130
|
+
// Lifecycle methods
|
|
131
|
+
'initialize',
|
|
132
|
+
'fetch',
|
|
133
|
+
'handleFetch',
|
|
134
|
+
'alarm',
|
|
135
|
+
// Internal state
|
|
136
|
+
'db',
|
|
137
|
+
'ctx',
|
|
138
|
+
'storage',
|
|
139
|
+
'env',
|
|
140
|
+
// Private accessors
|
|
141
|
+
'_users',
|
|
142
|
+
'_posts',
|
|
143
|
+
'_things',
|
|
144
|
+
'_rels',
|
|
145
|
+
'_actions',
|
|
146
|
+
'_events',
|
|
147
|
+
'_search',
|
|
148
|
+
'_objects',
|
|
149
|
+
'_dlq',
|
|
150
|
+
'_typeCache',
|
|
151
|
+
'_eventHandlers',
|
|
152
|
+
'_scheduleHandlers',
|
|
153
|
+
'_scheduleManager',
|
|
154
|
+
'_currentActor',
|
|
155
|
+
'_stepCache',
|
|
156
|
+
// Constructor
|
|
157
|
+
'constructor',
|
|
158
|
+
]);
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// COLLECTION RPC PATTERN MATCHING
|
|
161
|
+
// ============================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Valid collection methods
|
|
164
|
+
*/
|
|
165
|
+
const COLLECTION_METHODS = new Set(['create', 'update', 'delete', 'get', 'list', 'find']);
|
|
166
|
+
/**
|
|
167
|
+
* Pattern for valid PascalCase noun names (no numbers, no special chars)
|
|
168
|
+
*/
|
|
169
|
+
const VALID_NOUN_PATTERN = /^[A-Z][a-zA-Z]*$/;
|
|
170
|
+
/**
|
|
171
|
+
* Pattern for {Noun}.{method} format (exactly one dot)
|
|
172
|
+
* Noun must be PascalCase, method can be any valid identifier starting with lowercase
|
|
173
|
+
*/
|
|
174
|
+
const COLLECTION_RPC_PATTERN = /^([A-Z][a-zA-Z]*)\.([a-z][a-zA-Z]*)$/;
|
|
175
|
+
/**
|
|
176
|
+
* Check if a method name looks like a collection RPC call
|
|
177
|
+
*/
|
|
178
|
+
function isCollectionRpcMethod(method) {
|
|
179
|
+
return method.includes('.') && !method.startsWith('_');
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parse a collection RPC method into noun and action
|
|
183
|
+
* Returns null if invalid format
|
|
184
|
+
*/
|
|
185
|
+
function parseCollectionRpcMethod(method) {
|
|
186
|
+
// Check for multiple dots (invalid)
|
|
187
|
+
if ((method.match(/\./g) || []).length !== 1) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const match = method.match(COLLECTION_RPC_PATTERN);
|
|
191
|
+
if (!match)
|
|
192
|
+
return null;
|
|
193
|
+
const [, noun, action] = match;
|
|
194
|
+
return { noun: noun, action: action };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Validate noun name
|
|
198
|
+
* @returns error message if invalid, null if valid
|
|
199
|
+
*/
|
|
200
|
+
function validateNounName(noun) {
|
|
201
|
+
if (!noun || noun.trim() === '') {
|
|
202
|
+
return 'Noun name cannot be empty';
|
|
203
|
+
}
|
|
204
|
+
if (!VALID_NOUN_PATTERN.test(noun)) {
|
|
205
|
+
return `Invalid noun '${noun}': must be PascalCase letters only`;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Validate collection method name
|
|
211
|
+
* @returns error message if invalid, null if valid
|
|
212
|
+
*/
|
|
213
|
+
function validateCollectionMethod(noun, method) {
|
|
214
|
+
if (!COLLECTION_METHODS.has(method)) {
|
|
215
|
+
return `Unknown method '${method}' on ${noun}. Valid methods: ${Array.from(COLLECTION_METHODS).join(', ')}`;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* RPC Server class that wraps a DO instance
|
|
221
|
+
*/
|
|
222
|
+
export class RPCServer {
|
|
223
|
+
doInstance;
|
|
224
|
+
config;
|
|
225
|
+
exposedMethods;
|
|
226
|
+
blockedMethods;
|
|
227
|
+
sessions = new Map();
|
|
228
|
+
defaultHttpSession;
|
|
229
|
+
constructor(doInstance, config = {}) {
|
|
230
|
+
this.doInstance = doInstance;
|
|
231
|
+
this.config = {
|
|
232
|
+
maxPipelineDepth: config.maxPipelineDepth ?? 20,
|
|
233
|
+
...config,
|
|
234
|
+
};
|
|
235
|
+
// Build blocked methods set
|
|
236
|
+
this.blockedMethods = new Set([
|
|
237
|
+
...DEFAULT_BLOCKED_METHODS,
|
|
238
|
+
...(config.blockedMethods ?? []),
|
|
239
|
+
]);
|
|
240
|
+
// Build exposed methods set
|
|
241
|
+
this.exposedMethods = new Set();
|
|
242
|
+
if (config.exposedMethods && config.exposedMethods.length > 0) {
|
|
243
|
+
for (const method of config.exposedMethods) {
|
|
244
|
+
if (!this.blockedMethods.has(method)) {
|
|
245
|
+
this.exposedMethods.add(method);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Auto-discover methods from the DO instance
|
|
251
|
+
this.discoverMethods();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Discover exposed methods from the DO instance
|
|
256
|
+
*/
|
|
257
|
+
discoverMethods() {
|
|
258
|
+
// Get own property names from instance and prototype chain
|
|
259
|
+
const visited = new Set();
|
|
260
|
+
let obj = this.doInstance;
|
|
261
|
+
while (obj && obj !== Object.prototype) {
|
|
262
|
+
for (const key of Object.getOwnPropertyNames(obj)) {
|
|
263
|
+
if (visited.has(key))
|
|
264
|
+
continue;
|
|
265
|
+
visited.add(key);
|
|
266
|
+
// Skip blocked methods
|
|
267
|
+
if (this.blockedMethods.has(key))
|
|
268
|
+
continue;
|
|
269
|
+
// Skip private methods (starting with _)
|
|
270
|
+
if (key.startsWith('_'))
|
|
271
|
+
continue;
|
|
272
|
+
// Skip getter/setter only properties
|
|
273
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
274
|
+
if (descriptor && (descriptor.get || descriptor.set) && !descriptor.value) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// Check if it's a function
|
|
278
|
+
const value = this.doInstance[key];
|
|
279
|
+
if (typeof value === 'function') {
|
|
280
|
+
this.exposedMethods.add(key);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
obj = Object.getPrototypeOf(obj);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get list of exposed methods
|
|
288
|
+
*/
|
|
289
|
+
get methods() {
|
|
290
|
+
return Array.from(this.exposedMethods);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if a method is exposed
|
|
294
|
+
*/
|
|
295
|
+
isRpcExposed(method) {
|
|
296
|
+
if (this.blockedMethods.has(method))
|
|
297
|
+
return false;
|
|
298
|
+
if (method.startsWith('_'))
|
|
299
|
+
return false;
|
|
300
|
+
return this.exposedMethods.has(method);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Check if a method requires authentication and return an error if auth is missing
|
|
304
|
+
*/
|
|
305
|
+
checkMethodAuth(method, request) {
|
|
306
|
+
// Get the $auth config from the DO instance's constructor
|
|
307
|
+
const DOClass = this.doInstance.constructor;
|
|
308
|
+
const authConfig = DOClass.$auth?.[method];
|
|
309
|
+
if (!authConfig)
|
|
310
|
+
return null;
|
|
311
|
+
// If method is public, no auth needed
|
|
312
|
+
if (authConfig.public)
|
|
313
|
+
return null;
|
|
314
|
+
// If method requires auth, check for Authorization header
|
|
315
|
+
if (authConfig.requireAuth || authConfig.roles) {
|
|
316
|
+
const authHeader = request.headers.get('Authorization');
|
|
317
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
318
|
+
return {
|
|
319
|
+
code: -32001, // Custom auth error code
|
|
320
|
+
message: 'Authentication required',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
// Basic token validation (could be extended to actually validate JWT)
|
|
324
|
+
const token = authHeader.slice(7);
|
|
325
|
+
if (!token || token.split('.').length !== 3) {
|
|
326
|
+
return {
|
|
327
|
+
code: -32001,
|
|
328
|
+
message: 'Invalid authentication token',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Handle collection RPC call pattern: {Noun}.{method}
|
|
336
|
+
* Routes to the DO's collection() method for typed data access.
|
|
337
|
+
*
|
|
338
|
+
* @param method - The method name (e.g., "Task.create")
|
|
339
|
+
* @param args - The resolved arguments
|
|
340
|
+
* @returns The result of the collection operation, or throws an error
|
|
341
|
+
*/
|
|
342
|
+
async handleCollectionRpc(method, args) {
|
|
343
|
+
// Check if this looks like a collection RPC call
|
|
344
|
+
if (!isCollectionRpcMethod(method)) {
|
|
345
|
+
return null; // Not a collection RPC, let normal handling proceed
|
|
346
|
+
}
|
|
347
|
+
// Handle multiple dots (invalid pattern)
|
|
348
|
+
if ((method.match(/\./g) || []).length !== 1) {
|
|
349
|
+
throw { code: 'INVALID_METHOD', message: `Invalid method pattern: ${method}` };
|
|
350
|
+
}
|
|
351
|
+
// Parse the method
|
|
352
|
+
const parsed = parseCollectionRpcMethod(method);
|
|
353
|
+
// Check for invalid noun pattern (lowercase, special chars, etc.)
|
|
354
|
+
if (!parsed) {
|
|
355
|
+
// Extract noun part for better error message
|
|
356
|
+
const [nounPart] = method.split('.');
|
|
357
|
+
// Check if it's a lowercase noun issue
|
|
358
|
+
if (nounPart && /^[a-z]/.test(nounPart)) {
|
|
359
|
+
throw { code: 'INVALID_NOUN', message: `Invalid noun '${nounPart}': must start with uppercase letter` };
|
|
360
|
+
}
|
|
361
|
+
// Check for numbers or special chars
|
|
362
|
+
if (nounPart && !/^[A-Za-z]+$/.test(nounPart)) {
|
|
363
|
+
throw { code: 'INVALID_NOUN', message: `Invalid noun '${nounPart}': must contain only letters` };
|
|
364
|
+
}
|
|
365
|
+
// Empty noun
|
|
366
|
+
if (!nounPart || nounPart === '') {
|
|
367
|
+
throw { code: 'INVALID_NOUN', message: 'Noun name cannot be empty' };
|
|
368
|
+
}
|
|
369
|
+
throw { code: 'INVALID_METHOD', message: `Invalid method pattern: ${method}` };
|
|
370
|
+
}
|
|
371
|
+
const { noun, action } = parsed;
|
|
372
|
+
// Validate noun name
|
|
373
|
+
const nounError = validateNounName(noun);
|
|
374
|
+
if (nounError) {
|
|
375
|
+
throw { code: 'INVALID_NOUN', message: nounError };
|
|
376
|
+
}
|
|
377
|
+
// Validate collection method
|
|
378
|
+
const methodError = validateCollectionMethod(noun, action);
|
|
379
|
+
if (methodError) {
|
|
380
|
+
throw { code: 'UNKNOWN_METHOD', message: methodError };
|
|
381
|
+
}
|
|
382
|
+
// Get the collection method from the DO instance
|
|
383
|
+
// The collection method is protected, but we can access it via the instance
|
|
384
|
+
const collectionFn = this.doInstance['collection'];
|
|
385
|
+
if (typeof collectionFn !== 'function') {
|
|
386
|
+
// Fallback: the DO doesn't have a collection method, so we can't handle this
|
|
387
|
+
throw { code: 'NOT_SUPPORTED', message: 'This DO does not support collection operations' };
|
|
388
|
+
}
|
|
389
|
+
// Get the collection for this noun
|
|
390
|
+
const collection = collectionFn.call(this.doInstance, noun);
|
|
391
|
+
if (!collection || typeof collection !== 'object') {
|
|
392
|
+
throw { code: 'COLLECTION_ERROR', message: `Failed to get collection for '${noun}'` };
|
|
393
|
+
}
|
|
394
|
+
const actionFn = collection[action];
|
|
395
|
+
if (typeof actionFn !== 'function') {
|
|
396
|
+
throw { code: 'UNKNOWN_METHOD', message: `Method '${action}' not found on ${noun} collection` };
|
|
397
|
+
}
|
|
398
|
+
// Execute the collection method with the provided arguments
|
|
399
|
+
let result = await actionFn.apply(collection, args);
|
|
400
|
+
// For mutations (create, update, delete), ensure $rowid is included
|
|
401
|
+
if (action === 'create' || action === 'update' || action === 'delete') {
|
|
402
|
+
result = this.ensureRowidInResult(result, action);
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Ensure $rowid is present in mutation results.
|
|
408
|
+
* If not present, generate a synthetic one for compatibility.
|
|
409
|
+
*/
|
|
410
|
+
ensureRowidInResult(result, action) {
|
|
411
|
+
if (result === null || result === undefined) {
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
if (typeof result === 'object' && !Array.isArray(result)) {
|
|
415
|
+
const obj = result;
|
|
416
|
+
// If $rowid is already present, return as-is
|
|
417
|
+
if ('$rowid' in obj && typeof obj.$rowid === 'number') {
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
// For delete, also add 'deleted' flag if not present
|
|
421
|
+
if (action === 'delete') {
|
|
422
|
+
return {
|
|
423
|
+
...obj,
|
|
424
|
+
deleted: obj.deleted ?? true,
|
|
425
|
+
$rowid: obj.$rowid ?? this.generateSyntheticRowid(),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// For create/update, add $rowid if missing
|
|
429
|
+
return {
|
|
430
|
+
...obj,
|
|
431
|
+
$rowid: this.generateSyntheticRowid(),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Generate a synthetic rowid for collection operations.
|
|
438
|
+
* This is used when the underlying collection doesn't return a rowid.
|
|
439
|
+
*/
|
|
440
|
+
syntheticRowidCounter = 1;
|
|
441
|
+
generateSyntheticRowid() {
|
|
442
|
+
return this.syntheticRowidCounter++;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Create a new RPC session
|
|
446
|
+
*/
|
|
447
|
+
createSession() {
|
|
448
|
+
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
449
|
+
const promiseStore = new PromiseStore();
|
|
450
|
+
const subscriptions = new SubscriptionManager();
|
|
451
|
+
const ctx = {
|
|
452
|
+
promiseStore,
|
|
453
|
+
subscriptions,
|
|
454
|
+
rootObject: this.doInstance,
|
|
455
|
+
sendNotification: () => { },
|
|
456
|
+
sessionId,
|
|
457
|
+
exposedMethods: this.exposedMethods,
|
|
458
|
+
blockedMethods: this.blockedMethods,
|
|
459
|
+
};
|
|
460
|
+
this.sessions.set(sessionId, ctx);
|
|
461
|
+
return ctx;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Get or create a session
|
|
465
|
+
*/
|
|
466
|
+
getSession(sessionId) {
|
|
467
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
468
|
+
return this.sessions.get(sessionId);
|
|
469
|
+
}
|
|
470
|
+
return this.createSession();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Get the default HTTP session (persists across HTTP requests to same DO)
|
|
474
|
+
*/
|
|
475
|
+
getDefaultHttpSession() {
|
|
476
|
+
if (!this.defaultHttpSession) {
|
|
477
|
+
this.defaultHttpSession = this.createSession();
|
|
478
|
+
}
|
|
479
|
+
return this.defaultHttpSession;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Clean up a session
|
|
483
|
+
*/
|
|
484
|
+
cleanupSession(sessionId) {
|
|
485
|
+
const ctx = this.sessions.get(sessionId);
|
|
486
|
+
if (ctx) {
|
|
487
|
+
ctx.promiseStore.clear();
|
|
488
|
+
ctx.subscriptions.clear();
|
|
489
|
+
this.sessions.delete(sessionId);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Handle an HTTP RPC request
|
|
494
|
+
*/
|
|
495
|
+
async handleRpcRequest(request) {
|
|
496
|
+
// Use persistent session for HTTP requests (state persists across requests to same DO)
|
|
497
|
+
const ctx = this.getDefaultHttpSession();
|
|
498
|
+
// Parse request body
|
|
499
|
+
let body;
|
|
500
|
+
try {
|
|
501
|
+
body = await request.json();
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return Response.json({
|
|
505
|
+
id: '',
|
|
506
|
+
type: 'error',
|
|
507
|
+
error: { code: 'PARSE_ERROR', message: 'Invalid JSON' },
|
|
508
|
+
}, { status: 400, headers: { 'Content-Type': 'application/json' } });
|
|
509
|
+
}
|
|
510
|
+
// Check if JSON-RPC 2.0
|
|
511
|
+
if (this.isJSONRPCRequest(body)) {
|
|
512
|
+
// Check if the method requires auth
|
|
513
|
+
const authError = this.checkMethodAuth(body.method, request);
|
|
514
|
+
if (authError) {
|
|
515
|
+
return Response.json({
|
|
516
|
+
jsonrpc: '2.0',
|
|
517
|
+
error: authError,
|
|
518
|
+
id: body.id ?? null,
|
|
519
|
+
}, { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
520
|
+
}
|
|
521
|
+
const response = await this.handleJSONRPCRequest(body, ctx);
|
|
522
|
+
if (!response) {
|
|
523
|
+
// Notification - no response
|
|
524
|
+
return new Response(null, { status: 204 });
|
|
525
|
+
}
|
|
526
|
+
return Response.json(response, { headers: { 'Content-Type': 'application/json' } });
|
|
527
|
+
}
|
|
528
|
+
// Check if JSON-RPC 2.0 batch
|
|
529
|
+
if (this.isJSONRPCBatch(body)) {
|
|
530
|
+
const responses = [];
|
|
531
|
+
for (const req of body) {
|
|
532
|
+
const response = await this.handleJSONRPCRequest(req, ctx);
|
|
533
|
+
if (response) {
|
|
534
|
+
responses.push(response);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (responses.length === 0) {
|
|
538
|
+
return new Response(null, { status: 204 });
|
|
539
|
+
}
|
|
540
|
+
return Response.json(responses, { headers: { 'Content-Type': 'application/json' } });
|
|
541
|
+
}
|
|
542
|
+
// Cap'n Web RPC
|
|
543
|
+
if (this.isCapnWebRequest(body)) {
|
|
544
|
+
const response = await this.executeCapnWebRequest(body, ctx);
|
|
545
|
+
return Response.json(response, { headers: { 'Content-Type': 'application/json' } });
|
|
546
|
+
}
|
|
547
|
+
// Chain RPC - Check for chain request format
|
|
548
|
+
if (this.isChainRequest(body)) {
|
|
549
|
+
// Get the WorkflowContext if available on the DO instance
|
|
550
|
+
const workflowContext = this.doInstance.$;
|
|
551
|
+
return this.executeChainRequest(body, workflowContext);
|
|
552
|
+
}
|
|
553
|
+
// Check for invalid chain format (has 'chain' property but it's not an array)
|
|
554
|
+
if (body !== null && typeof body === 'object' && 'chain' in body) {
|
|
555
|
+
return Response.json({
|
|
556
|
+
error: { message: 'Chain must be a non-empty array', code: 'INVALID_CHAIN' },
|
|
557
|
+
}, { status: 400, headers: { 'Content-Type': 'application/json' } });
|
|
558
|
+
}
|
|
559
|
+
return Response.json({
|
|
560
|
+
id: '',
|
|
561
|
+
type: 'error',
|
|
562
|
+
error: { code: 'INVALID_REQUEST', message: 'Unknown request format' },
|
|
563
|
+
}, { status: 400, headers: { 'Content-Type': 'application/json' } });
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Handle WebSocket RPC connection
|
|
567
|
+
*/
|
|
568
|
+
handleWebSocketRpc() {
|
|
569
|
+
const pair = new WebSocketPair();
|
|
570
|
+
const [client, server] = Object.values(pair);
|
|
571
|
+
const ctx = this.createSession();
|
|
572
|
+
// Set up notification sender
|
|
573
|
+
ctx.sendNotification = (method, params) => {
|
|
574
|
+
try {
|
|
575
|
+
server.send(JSON.stringify({
|
|
576
|
+
jsonrpc: '2.0',
|
|
577
|
+
method,
|
|
578
|
+
params,
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
// Log send errors for debugging WebSocket connection issues
|
|
583
|
+
console.debug('[rpc] WebSocket notification send failed:', {
|
|
584
|
+
method,
|
|
585
|
+
readyState: server.readyState,
|
|
586
|
+
error: error instanceof Error ? error.message : 'unknown',
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
server.accept();
|
|
591
|
+
// Send connection acknowledgment
|
|
592
|
+
server.send(JSON.stringify({
|
|
593
|
+
type: 'connected',
|
|
594
|
+
sessionId: ctx.sessionId,
|
|
595
|
+
}));
|
|
596
|
+
server.addEventListener('message', async (event) => {
|
|
597
|
+
const rawData = event.data;
|
|
598
|
+
// Handle binary data
|
|
599
|
+
if (rawData instanceof ArrayBuffer) {
|
|
600
|
+
server.send(JSON.stringify({
|
|
601
|
+
type: 'binary_received',
|
|
602
|
+
size: rawData.byteLength,
|
|
603
|
+
}));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
// Parse message
|
|
607
|
+
let data;
|
|
608
|
+
try {
|
|
609
|
+
data = JSON.parse(rawData);
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
server.send(JSON.stringify({
|
|
613
|
+
jsonrpc: '2.0',
|
|
614
|
+
error: JSON_RPC_ERRORS.PARSE_ERROR,
|
|
615
|
+
id: null,
|
|
616
|
+
}));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
// Handle JSON-RPC 2.0 batch
|
|
620
|
+
if (this.isJSONRPCBatch(data)) {
|
|
621
|
+
const responses = [];
|
|
622
|
+
for (const req of data) {
|
|
623
|
+
const response = await this.handleJSONRPCRequest(req, ctx);
|
|
624
|
+
if (response) {
|
|
625
|
+
responses.push(response);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (responses.length > 0) {
|
|
629
|
+
server.send(JSON.stringify(responses));
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
// Handle single JSON-RPC 2.0 request
|
|
634
|
+
if (this.isJSONRPCRequest(data)) {
|
|
635
|
+
const response = await this.handleJSONRPCRequest(data, ctx);
|
|
636
|
+
if (response) {
|
|
637
|
+
server.send(JSON.stringify(response));
|
|
638
|
+
}
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
// Handle Cap'n Web request
|
|
642
|
+
if (this.isCapnWebRequest(data)) {
|
|
643
|
+
const response = await this.executeCapnWebRequest(data, ctx);
|
|
644
|
+
server.send(JSON.stringify(response));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
// Invalid request format
|
|
648
|
+
server.send(JSON.stringify({
|
|
649
|
+
jsonrpc: '2.0',
|
|
650
|
+
error: JSON_RPC_ERRORS.INVALID_REQUEST,
|
|
651
|
+
id: null,
|
|
652
|
+
}));
|
|
653
|
+
});
|
|
654
|
+
server.addEventListener('close', () => {
|
|
655
|
+
this.cleanupSession(ctx.sessionId);
|
|
656
|
+
});
|
|
657
|
+
server.addEventListener('error', () => {
|
|
658
|
+
this.cleanupSession(ctx.sessionId);
|
|
659
|
+
server.close();
|
|
660
|
+
});
|
|
661
|
+
return new Response(null, {
|
|
662
|
+
status: 101,
|
|
663
|
+
webSocket: client,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
// ============================================================================
|
|
667
|
+
// PROTOCOL DETECTION
|
|
668
|
+
// ============================================================================
|
|
669
|
+
isJSONRPCRequest(data) {
|
|
670
|
+
return (data !== null &&
|
|
671
|
+
typeof data === 'object' &&
|
|
672
|
+
'jsonrpc' in data &&
|
|
673
|
+
data.jsonrpc === '2.0');
|
|
674
|
+
}
|
|
675
|
+
isJSONRPCBatch(data) {
|
|
676
|
+
return (Array.isArray(data) &&
|
|
677
|
+
data.length > 0 &&
|
|
678
|
+
data.every((item) => this.isJSONRPCRequest(item)));
|
|
679
|
+
}
|
|
680
|
+
isCapnWebRequest(data) {
|
|
681
|
+
return (data !== null &&
|
|
682
|
+
typeof data === 'object' &&
|
|
683
|
+
'type' in data &&
|
|
684
|
+
typeof data.type === 'string');
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Check if request is a Chain RPC request
|
|
688
|
+
*/
|
|
689
|
+
isChainRequest(data) {
|
|
690
|
+
return (data !== null &&
|
|
691
|
+
typeof data === 'object' &&
|
|
692
|
+
'chain' in data &&
|
|
693
|
+
Array.isArray(data.chain));
|
|
694
|
+
}
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// CHAIN RPC HANDLER
|
|
697
|
+
// ============================================================================
|
|
698
|
+
/**
|
|
699
|
+
* Execute a chain-based RPC request.
|
|
700
|
+
*
|
|
701
|
+
* The chain starts from the root object (the DO instance) and executes
|
|
702
|
+
* each step in sequence. If a WorkflowContext ($) is available, the chain
|
|
703
|
+
* can access it via the 'chain' starting from root.
|
|
704
|
+
*
|
|
705
|
+
* @param request - The chain RPC request
|
|
706
|
+
* @param workflowContext - Optional WorkflowContext to use as $ root
|
|
707
|
+
* @returns Response with data or error
|
|
708
|
+
*/
|
|
709
|
+
async executeChainRequest(request, workflowContext) {
|
|
710
|
+
const { chain } = request;
|
|
711
|
+
// Validate chain
|
|
712
|
+
if (!Array.isArray(chain) || chain.length === 0) {
|
|
713
|
+
return Response.json({
|
|
714
|
+
error: { message: 'Chain must be a non-empty array', code: 'INVALID_CHAIN' },
|
|
715
|
+
}, { status: 400, headers: { 'Content-Type': 'application/json' } });
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const result = await this.executeChain(chain, workflowContext);
|
|
719
|
+
return Response.json({
|
|
720
|
+
result,
|
|
721
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
const chainError = error;
|
|
725
|
+
const status = chainError.status ?? 400;
|
|
726
|
+
return Response.json({
|
|
727
|
+
error: {
|
|
728
|
+
message: chainError.message ?? 'Chain execution failed',
|
|
729
|
+
code: chainError.code ?? 'EXECUTION_ERROR',
|
|
730
|
+
},
|
|
731
|
+
}, { status, headers: { 'Content-Type': 'application/json' } });
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Execute a chain of steps starting from the DO instance.
|
|
736
|
+
*
|
|
737
|
+
* The chain can access:
|
|
738
|
+
* - DO instance properties and methods directly (e.g., config, getStatus)
|
|
739
|
+
* - WorkflowContext via the $ property (e.g., $.things, $.on)
|
|
740
|
+
*
|
|
741
|
+
* @param chain - Array of chain steps
|
|
742
|
+
* @param workflowContext - Optional WorkflowContext accessible via $ property
|
|
743
|
+
* @returns The final result of the chain
|
|
744
|
+
*/
|
|
745
|
+
async executeChain(chain, workflowContext) {
|
|
746
|
+
// Always start from the DO instance
|
|
747
|
+
// WorkflowContext ($) is accessible as a property of the instance
|
|
748
|
+
let current = this.doInstance;
|
|
749
|
+
// If the first step is accessing '$', use the WorkflowContext directly
|
|
750
|
+
if (chain.length > 0 && chain[0].type === 'property' && chain[0].key === '$' && workflowContext) {
|
|
751
|
+
current = workflowContext;
|
|
752
|
+
chain = chain.slice(1); // Skip the '$' step
|
|
753
|
+
}
|
|
754
|
+
for (let i = 0; i < chain.length; i++) {
|
|
755
|
+
const step = chain[i];
|
|
756
|
+
switch (step.type) {
|
|
757
|
+
case 'property': {
|
|
758
|
+
const propStep = step;
|
|
759
|
+
// Validate the step
|
|
760
|
+
if (typeof propStep.key !== 'string') {
|
|
761
|
+
throw { code: 'INVALID_STEP', message: 'Property step requires a string key', status: 400 };
|
|
762
|
+
}
|
|
763
|
+
// Block access to private/internal properties
|
|
764
|
+
if (propStep.key.startsWith('_')) {
|
|
765
|
+
throw { code: 'BLOCKED_ACCESS', message: `Access to private property '${propStep.key}' is blocked`, status: 404 };
|
|
766
|
+
}
|
|
767
|
+
// Block access to internal DO methods on first step (from root)
|
|
768
|
+
if (i === 0 && this.blockedMethods.has(propStep.key)) {
|
|
769
|
+
throw { code: 'BLOCKED_ACCESS', message: `Access to '${propStep.key}' is blocked`, status: 404 };
|
|
770
|
+
}
|
|
771
|
+
if (current === null || current === undefined) {
|
|
772
|
+
throw { code: 'NOT_FOUND', message: `Cannot access property '${propStep.key}' of ${current}`, status: 404 };
|
|
773
|
+
}
|
|
774
|
+
// For objects with a Proxy get trap (like WorkflowContext), accessing the property triggers the proxy
|
|
775
|
+
const value = current[propStep.key];
|
|
776
|
+
if (value === undefined && !(propStep.key in current)) {
|
|
777
|
+
throw { code: 'NOT_FOUND', message: `Property '${propStep.key}' not found`, status: 404 };
|
|
778
|
+
}
|
|
779
|
+
current = value;
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
case 'call': {
|
|
783
|
+
const callStep = step;
|
|
784
|
+
const args = callStep.args ?? [];
|
|
785
|
+
// SDK Combined Format: { type: 'call', key: 'methodName', args: [...] }
|
|
786
|
+
// This combines property access and method call in one step.
|
|
787
|
+
// When 'key' is present, first access the property, then call it.
|
|
788
|
+
const combinedKey = step.key;
|
|
789
|
+
if (combinedKey !== undefined) {
|
|
790
|
+
// Combined call step - first access the property, then call it
|
|
791
|
+
if (typeof combinedKey !== 'string') {
|
|
792
|
+
throw { code: 'INVALID_STEP', message: 'Combined call step requires a string key', status: 400 };
|
|
793
|
+
}
|
|
794
|
+
// Block access to private methods
|
|
795
|
+
if (combinedKey.startsWith('_')) {
|
|
796
|
+
throw { code: 'BLOCKED_ACCESS', message: `Access to private method '${combinedKey}' is blocked`, status: 404 };
|
|
797
|
+
}
|
|
798
|
+
// Block access to internal DO methods on first step (from root)
|
|
799
|
+
if (i === 0 && this.blockedMethods.has(combinedKey)) {
|
|
800
|
+
throw { code: 'BLOCKED_ACCESS', message: `Access to '${combinedKey}' is blocked`, status: 404 };
|
|
801
|
+
}
|
|
802
|
+
if (current === null || current === undefined) {
|
|
803
|
+
throw { code: 'NOT_FOUND', message: `Cannot access method '${combinedKey}' of ${current}`, status: 404 };
|
|
804
|
+
}
|
|
805
|
+
// Access the method
|
|
806
|
+
const method = current[combinedKey];
|
|
807
|
+
if (method === undefined && !(combinedKey in current)) {
|
|
808
|
+
throw { code: 'NOT_FOUND', message: `Method '${combinedKey}' not found`, status: 404 };
|
|
809
|
+
}
|
|
810
|
+
if (typeof method !== 'function') {
|
|
811
|
+
throw { code: 'NOT_CALLABLE', message: `'${combinedKey}' is not a function`, status: 400 };
|
|
812
|
+
}
|
|
813
|
+
// Execute the method with 'current' as 'this' context
|
|
814
|
+
const result = method.apply(current, args);
|
|
815
|
+
// Await if it's a promise
|
|
816
|
+
current = result instanceof Promise ? await result : result;
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
// Traditional call step - 'current' should already be a function
|
|
820
|
+
if (typeof current !== 'function') {
|
|
821
|
+
throw { code: 'NOT_CALLABLE', message: 'Value is not a function, cannot call', status: 400 };
|
|
822
|
+
}
|
|
823
|
+
// Find the 'this' context for the call
|
|
824
|
+
// Look back for the last property access to get the parent object
|
|
825
|
+
let thisContext = this.doInstance;
|
|
826
|
+
if (i > 0) {
|
|
827
|
+
// Re-execute chain up to the previous step to get the parent context
|
|
828
|
+
const prevChain = chain.slice(0, i - 1);
|
|
829
|
+
if (prevChain.length > 0) {
|
|
830
|
+
thisContext = await this.executeChain(prevChain, workflowContext);
|
|
831
|
+
}
|
|
832
|
+
else if (i === 1) {
|
|
833
|
+
// First call after property access - use root
|
|
834
|
+
thisContext = workflowContext ?? this.doInstance;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// Execute the function
|
|
838
|
+
const result = current.apply(thisContext, args);
|
|
839
|
+
// Await if it's a promise
|
|
840
|
+
current = result instanceof Promise ? await result : result;
|
|
841
|
+
}
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
case 'index': {
|
|
845
|
+
const indexStep = step;
|
|
846
|
+
if (typeof indexStep.index !== 'number') {
|
|
847
|
+
throw { code: 'INVALID_STEP', message: 'Index step requires a numeric index', status: 400 };
|
|
848
|
+
}
|
|
849
|
+
if (!Array.isArray(current)) {
|
|
850
|
+
throw { code: 'NOT_INDEXABLE', message: 'Value is not an array, cannot use index access', status: 400 };
|
|
851
|
+
}
|
|
852
|
+
if (indexStep.index < 0 || indexStep.index >= current.length) {
|
|
853
|
+
throw {
|
|
854
|
+
code: 'INDEX_OUT_OF_BOUNDS',
|
|
855
|
+
message: `Index ${indexStep.index} is out of bounds (array length: ${current.length})`,
|
|
856
|
+
status: 404
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
current = current[indexStep.index];
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
default: {
|
|
863
|
+
throw {
|
|
864
|
+
code: 'INVALID_STEP',
|
|
865
|
+
message: `Invalid step type: ${step.type}`,
|
|
866
|
+
status: 400
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return current;
|
|
872
|
+
}
|
|
873
|
+
// ============================================================================
|
|
874
|
+
// JSON-RPC 2.0 HANDLER
|
|
875
|
+
// ============================================================================
|
|
876
|
+
async handleJSONRPCRequest(request, ctx) {
|
|
877
|
+
const { method, params, id } = request;
|
|
878
|
+
const isNotification = id === undefined;
|
|
879
|
+
try {
|
|
880
|
+
// Handle subscription methods
|
|
881
|
+
if (method === 'subscribe') {
|
|
882
|
+
const p = params;
|
|
883
|
+
const subscriptionId = ctx.subscriptions.subscribe(p.event, (data) => {
|
|
884
|
+
ctx.sendNotification(p.event, data);
|
|
885
|
+
});
|
|
886
|
+
if (isNotification)
|
|
887
|
+
return null;
|
|
888
|
+
return {
|
|
889
|
+
jsonrpc: '2.0',
|
|
890
|
+
result: { subscriptionId },
|
|
891
|
+
id: id ?? null,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
if (method === 'unsubscribe') {
|
|
895
|
+
const p = params;
|
|
896
|
+
const success = ctx.subscriptions.unsubscribe(p.subscriptionId);
|
|
897
|
+
if (isNotification)
|
|
898
|
+
return null;
|
|
899
|
+
return {
|
|
900
|
+
jsonrpc: '2.0',
|
|
901
|
+
result: { success },
|
|
902
|
+
id: id ?? null,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
// Handle triggerEvent (for testing)
|
|
906
|
+
if (method === 'triggerEvent') {
|
|
907
|
+
const p = params;
|
|
908
|
+
ctx.subscriptions.emit(p.event, p.data);
|
|
909
|
+
if (isNotification)
|
|
910
|
+
return null;
|
|
911
|
+
return {
|
|
912
|
+
jsonrpc: '2.0',
|
|
913
|
+
result: { triggered: true },
|
|
914
|
+
id: id ?? null,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
// Check for collection RPC pattern: {Noun}.{method}
|
|
918
|
+
if (isCollectionRpcMethod(method)) {
|
|
919
|
+
// Convert params to args array
|
|
920
|
+
const args = params === undefined
|
|
921
|
+
? []
|
|
922
|
+
: Array.isArray(params)
|
|
923
|
+
? params
|
|
924
|
+
: [params];
|
|
925
|
+
const result = await this.handleCollectionRpc(method, args);
|
|
926
|
+
if (isNotification)
|
|
927
|
+
return null;
|
|
928
|
+
return {
|
|
929
|
+
jsonrpc: '2.0',
|
|
930
|
+
result,
|
|
931
|
+
id: id ?? null,
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
// Check if method exists and is exposed
|
|
935
|
+
if (!this.isRpcExposed(method)) {
|
|
936
|
+
if (isNotification)
|
|
937
|
+
return null;
|
|
938
|
+
return {
|
|
939
|
+
jsonrpc: '2.0',
|
|
940
|
+
error: { ...JSON_RPC_ERRORS.METHOD_NOT_FOUND, message: `Method '${method}' not found` },
|
|
941
|
+
id: id ?? null,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const methodFn = ctx.rootObject[method];
|
|
945
|
+
if (typeof methodFn !== 'function') {
|
|
946
|
+
if (isNotification)
|
|
947
|
+
return null;
|
|
948
|
+
return {
|
|
949
|
+
jsonrpc: '2.0',
|
|
950
|
+
error: { ...JSON_RPC_ERRORS.METHOD_NOT_FOUND, message: `Method '${method}' not found` },
|
|
951
|
+
id: id ?? null,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
// Execute method
|
|
955
|
+
let result;
|
|
956
|
+
if (params === undefined) {
|
|
957
|
+
result = await methodFn.call(ctx.rootObject);
|
|
958
|
+
}
|
|
959
|
+
else if (Array.isArray(params)) {
|
|
960
|
+
result = await methodFn.apply(ctx.rootObject, params);
|
|
961
|
+
}
|
|
962
|
+
else if (typeof params === 'object' && params !== null) {
|
|
963
|
+
// Named parameters - try to extract positional args from object
|
|
964
|
+
const namedParams = params;
|
|
965
|
+
const paramNames = this.extractParamNames(methodFn);
|
|
966
|
+
if (paramNames.length > 0 && paramNames.every((name) => name in namedParams)) {
|
|
967
|
+
// All parameter names found in the object - convert to positional args
|
|
968
|
+
const args = paramNames.map((name) => namedParams[name]);
|
|
969
|
+
result = await methodFn.apply(ctx.rootObject, args);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
// Fall back to passing the object as a single argument
|
|
973
|
+
result = await methodFn.call(ctx.rootObject, params);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
result = await methodFn.call(ctx.rootObject, params);
|
|
978
|
+
}
|
|
979
|
+
if (isNotification)
|
|
980
|
+
return null;
|
|
981
|
+
return {
|
|
982
|
+
jsonrpc: '2.0',
|
|
983
|
+
result,
|
|
984
|
+
id: id ?? null,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
if (isNotification)
|
|
989
|
+
return null;
|
|
990
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
991
|
+
return {
|
|
992
|
+
jsonrpc: '2.0',
|
|
993
|
+
error: {
|
|
994
|
+
code: JSON_RPC_ERRORS.INTERNAL_ERROR.code,
|
|
995
|
+
message: errorMessage,
|
|
996
|
+
},
|
|
997
|
+
id: id ?? null,
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
// ============================================================================
|
|
1002
|
+
// CAP'N WEB RPC HANDLER
|
|
1003
|
+
// ============================================================================
|
|
1004
|
+
async executeCapnWebRequest(request, ctx) {
|
|
1005
|
+
switch (request.type) {
|
|
1006
|
+
case 'call':
|
|
1007
|
+
case 'batch':
|
|
1008
|
+
return this.executeBatch(request, ctx);
|
|
1009
|
+
case 'resolve':
|
|
1010
|
+
return this.executeResolve(request, ctx);
|
|
1011
|
+
case 'dispose':
|
|
1012
|
+
return this.executeDispose(request, ctx);
|
|
1013
|
+
default:
|
|
1014
|
+
return {
|
|
1015
|
+
id: request.id,
|
|
1016
|
+
type: 'error',
|
|
1017
|
+
error: { code: 'INVALID_REQUEST', message: 'Unknown request type' },
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async executeBatch(request, ctx) {
|
|
1022
|
+
if (!request.calls || request.calls.length === 0) {
|
|
1023
|
+
return {
|
|
1024
|
+
id: request.id,
|
|
1025
|
+
type: 'error',
|
|
1026
|
+
error: { code: 'INVALID_REQUEST', message: 'No calls provided' },
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
const results = [];
|
|
1030
|
+
for (const call of request.calls) {
|
|
1031
|
+
const result = await this.executeCall(call, ctx);
|
|
1032
|
+
results.push(result);
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
id: request.id,
|
|
1036
|
+
type: 'batch',
|
|
1037
|
+
results,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
async executeResolve(request, ctx) {
|
|
1041
|
+
if (!request.resolve?.promiseId) {
|
|
1042
|
+
return {
|
|
1043
|
+
id: request.id,
|
|
1044
|
+
type: 'error',
|
|
1045
|
+
error: { code: 'INVALID_REQUEST', message: 'No promiseId provided' },
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
const promiseId = request.resolve.promiseId;
|
|
1049
|
+
if (ctx.promiseStore.isDisposed(promiseId)) {
|
|
1050
|
+
return {
|
|
1051
|
+
id: request.id,
|
|
1052
|
+
type: 'error',
|
|
1053
|
+
error: { code: 'DISPOSED_REFERENCE', message: `Promise ${promiseId} has been disposed` },
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
if (!ctx.promiseStore.has(promiseId)) {
|
|
1057
|
+
return {
|
|
1058
|
+
id: request.id,
|
|
1059
|
+
type: 'error',
|
|
1060
|
+
error: { code: 'INVALID_PROMISE', message: `Promise ${promiseId} not found` },
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
const value = ctx.promiseStore.get(promiseId);
|
|
1064
|
+
return {
|
|
1065
|
+
id: request.id,
|
|
1066
|
+
type: 'result',
|
|
1067
|
+
results: [
|
|
1068
|
+
{
|
|
1069
|
+
promiseId,
|
|
1070
|
+
type: 'value',
|
|
1071
|
+
value,
|
|
1072
|
+
},
|
|
1073
|
+
],
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
async executeDispose(request, ctx) {
|
|
1077
|
+
if (!request.dispose?.promiseIds) {
|
|
1078
|
+
return {
|
|
1079
|
+
id: request.id,
|
|
1080
|
+
type: 'error',
|
|
1081
|
+
error: { code: 'INVALID_REQUEST', message: 'No promiseIds provided' },
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
for (const promiseId of request.dispose.promiseIds) {
|
|
1085
|
+
ctx.promiseStore.dispose(promiseId);
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
id: request.id,
|
|
1089
|
+
type: 'result',
|
|
1090
|
+
results: [],
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
async executeCall(call, ctx) {
|
|
1094
|
+
// Calculate depth for this call (outside try for catch access)
|
|
1095
|
+
let callDepth = 0;
|
|
1096
|
+
try {
|
|
1097
|
+
if (call.target.type === 'promise') {
|
|
1098
|
+
const baseDepth = ctx.promiseStore.getDepth(call.target.promiseId);
|
|
1099
|
+
callDepth = baseDepth + 1;
|
|
1100
|
+
// Check max pipeline depth
|
|
1101
|
+
const maxDepth = this.config.maxPipelineDepth ?? 20;
|
|
1102
|
+
if (callDepth >= maxDepth) {
|
|
1103
|
+
throw { code: 'MAX_PIPELINE_DEPTH', message: `Pipeline depth ${callDepth} exceeds maximum ${maxDepth}` };
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
else if (call.target.type === 'property') {
|
|
1107
|
+
const base = call.target.base;
|
|
1108
|
+
if (base.type === 'promise') {
|
|
1109
|
+
const baseDepth = ctx.promiseStore.getDepth(base.promiseId);
|
|
1110
|
+
callDepth = baseDepth + 1;
|
|
1111
|
+
// Check max pipeline depth
|
|
1112
|
+
if (callDepth > (this.config.maxPipelineDepth ?? 20)) {
|
|
1113
|
+
throw { code: 'MAX_PIPELINE_DEPTH', message: `Pipeline depth ${callDepth} exceeds maximum ${this.config.maxPipelineDepth ?? 20}` };
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
const target = this.resolveTarget(call.target, ctx);
|
|
1118
|
+
const args = call.args.map((arg) => this.resolveArg(arg, ctx));
|
|
1119
|
+
let result;
|
|
1120
|
+
// Handle magic methods
|
|
1121
|
+
if (call.method === '__get__') {
|
|
1122
|
+
// Property getter - target is already the value from property access
|
|
1123
|
+
result = target;
|
|
1124
|
+
}
|
|
1125
|
+
else if (call.method === '__map__' && Array.isArray(target)) {
|
|
1126
|
+
// Magic map operation
|
|
1127
|
+
const mapSpec = args[0];
|
|
1128
|
+
if (mapSpec?.property) {
|
|
1129
|
+
result = target.map((item) => item[mapSpec.property]);
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
result = target;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
else if (call.method === '__filter__' && Array.isArray(target)) {
|
|
1136
|
+
// Magic filter operation
|
|
1137
|
+
const filterSpec = args[0];
|
|
1138
|
+
if (filterSpec?.property) {
|
|
1139
|
+
result = target.filter((item) => item[filterSpec.property] === filterSpec.equals);
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
result = target;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
else if (target === ctx.rootObject) {
|
|
1146
|
+
// Check for collection RPC pattern first: {Noun}.{method}
|
|
1147
|
+
if (isCollectionRpcMethod(call.method)) {
|
|
1148
|
+
result = await this.handleCollectionRpc(call.method, args);
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
// Regular root object method call - MUST check if exposed
|
|
1152
|
+
if (!this.isRpcExposed(call.method)) {
|
|
1153
|
+
throw { code: 'METHOD_NOT_FOUND', message: `Method ${call.method} not found` };
|
|
1154
|
+
}
|
|
1155
|
+
const method = target[call.method];
|
|
1156
|
+
if (typeof method !== 'function') {
|
|
1157
|
+
throw { code: 'METHOD_NOT_FOUND', message: `Method ${call.method} not found` };
|
|
1158
|
+
}
|
|
1159
|
+
result = await method.apply(target, args);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
else if (target && typeof target === 'object' && call.method in target) {
|
|
1163
|
+
// Method call on non-root object (from pipelining)
|
|
1164
|
+
const method = target[call.method];
|
|
1165
|
+
if (typeof method === 'function') {
|
|
1166
|
+
result = await method.apply(target, args);
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
result = method;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
else if (target && typeof target === 'object') {
|
|
1173
|
+
// Property access on object
|
|
1174
|
+
result = target[call.method];
|
|
1175
|
+
if (result === undefined && !(call.method in target)) {
|
|
1176
|
+
throw { code: 'METHOD_NOT_FOUND', message: `Method ${call.method} not found` };
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
throw { code: 'INVALID_TARGET', message: 'Cannot call method on null or primitive' };
|
|
1181
|
+
}
|
|
1182
|
+
// Store result for pipelining (with depth tracking)
|
|
1183
|
+
ctx.promiseStore.set(call.promiseId, result, callDepth);
|
|
1184
|
+
return {
|
|
1185
|
+
promiseId: call.promiseId,
|
|
1186
|
+
type: 'value',
|
|
1187
|
+
value: result,
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
const rpcError = error && typeof error === 'object' && 'code' in error
|
|
1192
|
+
? error
|
|
1193
|
+
: {
|
|
1194
|
+
code: 'EXECUTION_ERROR',
|
|
1195
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1196
|
+
};
|
|
1197
|
+
// Store error marker for pipeline propagation (with depth tracking)
|
|
1198
|
+
ctx.promiseStore.set(call.promiseId, { __error__: rpcError }, callDepth);
|
|
1199
|
+
return {
|
|
1200
|
+
promiseId: call.promiseId,
|
|
1201
|
+
type: 'error',
|
|
1202
|
+
error: rpcError,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
resolveTarget(target, ctx) {
|
|
1207
|
+
switch (target.type) {
|
|
1208
|
+
case 'root':
|
|
1209
|
+
return ctx.rootObject;
|
|
1210
|
+
case 'promise': {
|
|
1211
|
+
if (ctx.promiseStore.isDisposed(target.promiseId)) {
|
|
1212
|
+
throw { code: 'DISPOSED_REFERENCE', message: `Promise ${target.promiseId} has been disposed` };
|
|
1213
|
+
}
|
|
1214
|
+
const value = ctx.promiseStore.get(target.promiseId);
|
|
1215
|
+
if (value === undefined && !ctx.promiseStore.has(target.promiseId)) {
|
|
1216
|
+
throw { code: 'INVALID_PROMISE', message: `Promise ${target.promiseId} not found` };
|
|
1217
|
+
}
|
|
1218
|
+
// Check if the value is an error marker (propagate errors through pipeline)
|
|
1219
|
+
if (value && typeof value === 'object' && '__error__' in value) {
|
|
1220
|
+
throw value.__error__;
|
|
1221
|
+
}
|
|
1222
|
+
// Check for null/undefined target
|
|
1223
|
+
if (value === null || value === undefined) {
|
|
1224
|
+
throw { code: 'INVALID_TARGET', message: 'Cannot access property of null or undefined' };
|
|
1225
|
+
}
|
|
1226
|
+
return value;
|
|
1227
|
+
}
|
|
1228
|
+
case 'property': {
|
|
1229
|
+
const base = this.resolveNestedTarget(target.base, ctx);
|
|
1230
|
+
if (base === null || typeof base !== 'object') {
|
|
1231
|
+
throw { code: 'INVALID_TARGET', message: 'Cannot access property of non-object' };
|
|
1232
|
+
}
|
|
1233
|
+
// Handle array index access
|
|
1234
|
+
if (Array.isArray(base) && /^\d+$/.test(target.property)) {
|
|
1235
|
+
return base[parseInt(target.property, 10)];
|
|
1236
|
+
}
|
|
1237
|
+
return base[target.property];
|
|
1238
|
+
}
|
|
1239
|
+
default:
|
|
1240
|
+
throw { code: 'INVALID_TARGET', message: 'Unknown target type' };
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
resolveNestedTarget(target, ctx) {
|
|
1244
|
+
switch (target.type) {
|
|
1245
|
+
case 'root':
|
|
1246
|
+
return ctx.rootObject;
|
|
1247
|
+
case 'promise': {
|
|
1248
|
+
if (ctx.promiseStore.isDisposed(target.promiseId)) {
|
|
1249
|
+
throw { code: 'DISPOSED_REFERENCE', message: `Promise ${target.promiseId} has been disposed` };
|
|
1250
|
+
}
|
|
1251
|
+
const value = ctx.promiseStore.get(target.promiseId);
|
|
1252
|
+
if (value === undefined && !ctx.promiseStore.has(target.promiseId)) {
|
|
1253
|
+
throw { code: 'INVALID_PROMISE', message: `Promise ${target.promiseId} not found` };
|
|
1254
|
+
}
|
|
1255
|
+
// Check if the value is an error marker
|
|
1256
|
+
if (value && typeof value === 'object' && '__error__' in value) {
|
|
1257
|
+
throw value.__error__;
|
|
1258
|
+
}
|
|
1259
|
+
return value;
|
|
1260
|
+
}
|
|
1261
|
+
default:
|
|
1262
|
+
throw { code: 'INVALID_TARGET', message: 'Unknown nested target type' };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
resolveArg(arg, ctx) {
|
|
1266
|
+
switch (arg.type) {
|
|
1267
|
+
case 'value':
|
|
1268
|
+
return arg.value;
|
|
1269
|
+
case 'promise': {
|
|
1270
|
+
if (ctx.promiseStore.isDisposed(arg.promiseId)) {
|
|
1271
|
+
throw { code: 'DISPOSED_REFERENCE', message: `Promise ${arg.promiseId} has been disposed` };
|
|
1272
|
+
}
|
|
1273
|
+
const value = ctx.promiseStore.get(arg.promiseId);
|
|
1274
|
+
// If the value is an object with an id, extract it for method calls
|
|
1275
|
+
// This handles the case where we pass a user object to getUserPosts
|
|
1276
|
+
if (value && typeof value === 'object' && 'id' in value) {
|
|
1277
|
+
return value.id;
|
|
1278
|
+
}
|
|
1279
|
+
return value;
|
|
1280
|
+
}
|
|
1281
|
+
case 'callback':
|
|
1282
|
+
throw { code: 'NOT_IMPLEMENTED', message: 'Callbacks not yet supported' };
|
|
1283
|
+
default:
|
|
1284
|
+
throw { code: 'INVALID_ARG', message: 'Unknown argument type' };
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Extract parameter names from a function.
|
|
1289
|
+
* Uses function.toString() parsing to extract parameter names.
|
|
1290
|
+
*/
|
|
1291
|
+
extractParamNames(fn) {
|
|
1292
|
+
const fnStr = fn.toString();
|
|
1293
|
+
// Match function parameters - handles various function syntaxes
|
|
1294
|
+
const arrowMatch = fnStr.match(/^\s*\(?([^)=]*)\)?\s*=>/);
|
|
1295
|
+
const funcMatch = fnStr.match(/^(?:async\s+)?(?:function\s*)?\s*\w*\s*\(([^)]*)\)/);
|
|
1296
|
+
const paramsStr = arrowMatch?.[1] || funcMatch?.[1] || '';
|
|
1297
|
+
if (!paramsStr.trim()) {
|
|
1298
|
+
return [];
|
|
1299
|
+
}
|
|
1300
|
+
return paramsStr
|
|
1301
|
+
.split(',')
|
|
1302
|
+
.map((p) => {
|
|
1303
|
+
// Handle destructuring, default values, rest params
|
|
1304
|
+
const cleaned = p.trim()
|
|
1305
|
+
.replace(/\s*=\s*.*$/, '') // Remove default values
|
|
1306
|
+
.replace(/\.\.\.\s*/, '') // Remove rest operator
|
|
1307
|
+
.replace(/[{}[\]]/g, ''); // Remove destructuring braces
|
|
1308
|
+
// Extract the first identifier
|
|
1309
|
+
const match = cleaned.match(/^\s*(\w+)/);
|
|
1310
|
+
return match?.[1] || '';
|
|
1311
|
+
})
|
|
1312
|
+
.filter((p) => p.length > 0);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
// ============================================================================
|
|
1316
|
+
// FACTORY FUNCTIONS
|
|
1317
|
+
// ============================================================================
|
|
1318
|
+
/**
|
|
1319
|
+
* Create an RPC handler for a DO instance
|
|
1320
|
+
*/
|
|
1321
|
+
export function createRpcHandler(doInstance, config) {
|
|
1322
|
+
return new RPCServer(doInstance, config);
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Handle an RPC request (HTTP)
|
|
1326
|
+
*/
|
|
1327
|
+
export async function handleRpcRequest(request, doInstance, config) {
|
|
1328
|
+
const server = createRpcHandler(doInstance, config);
|
|
1329
|
+
return server.handleRpcRequest(request);
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Handle WebSocket RPC upgrade
|
|
1333
|
+
*/
|
|
1334
|
+
export function handleWebSocketRpc(doInstance, config) {
|
|
1335
|
+
const server = createRpcHandler(doInstance, config);
|
|
1336
|
+
return server.handleWebSocketRpc();
|
|
1337
|
+
}
|
|
1338
|
+
export function withRpcServer(Base, config) {
|
|
1339
|
+
return class extends Base {
|
|
1340
|
+
_rpcServer;
|
|
1341
|
+
get rpcServer() {
|
|
1342
|
+
if (!this._rpcServer) {
|
|
1343
|
+
this._rpcServer = new RPCServer(this, config);
|
|
1344
|
+
}
|
|
1345
|
+
return this._rpcServer;
|
|
1346
|
+
}
|
|
1347
|
+
isRpcExposed(method) {
|
|
1348
|
+
return this.rpcServer.isRpcExposed(method);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Override fetch to handle /rpc endpoint
|
|
1352
|
+
*/
|
|
1353
|
+
async fetch(request) {
|
|
1354
|
+
const url = new URL(request.url);
|
|
1355
|
+
// Handle /rpc endpoint
|
|
1356
|
+
if (url.pathname === '/rpc') {
|
|
1357
|
+
// Check for WebSocket upgrade
|
|
1358
|
+
const upgradeHeader = request.headers.get('upgrade');
|
|
1359
|
+
const connectionHeader = request.headers.get('connection')?.toLowerCase() || '';
|
|
1360
|
+
const hasConnectionUpgrade = connectionHeader.includes('upgrade');
|
|
1361
|
+
if (upgradeHeader?.toLowerCase() === 'websocket' && hasConnectionUpgrade) {
|
|
1362
|
+
return this.rpcServer.handleWebSocketRpc();
|
|
1363
|
+
}
|
|
1364
|
+
// HTTP RPC request
|
|
1365
|
+
if (request.method === 'POST') {
|
|
1366
|
+
return this.rpcServer.handleRpcRequest(request);
|
|
1367
|
+
}
|
|
1368
|
+
// GET request - return RPC info
|
|
1369
|
+
return Response.json({
|
|
1370
|
+
message: 'RPC endpoint - use POST for HTTP batch mode or WebSocket for streaming',
|
|
1371
|
+
methods: this.rpcServer.methods,
|
|
1372
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
1373
|
+
}
|
|
1374
|
+
// Delegate to parent fetch
|
|
1375
|
+
return super.fetch(request);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Apply RPC integration to an existing DO instance
|
|
1381
|
+
* This is useful when you can't use the mixin pattern
|
|
1382
|
+
*/
|
|
1383
|
+
export function applyRpcIntegration(doInstance, config) {
|
|
1384
|
+
const rpcServer = new RPCServer(doInstance, config);
|
|
1385
|
+
// Add rpcServer property
|
|
1386
|
+
Object.defineProperty(doInstance, 'rpcServer', {
|
|
1387
|
+
get() {
|
|
1388
|
+
return rpcServer;
|
|
1389
|
+
},
|
|
1390
|
+
enumerable: true,
|
|
1391
|
+
configurable: false,
|
|
1392
|
+
});
|
|
1393
|
+
// Add isRpcExposed method
|
|
1394
|
+
doInstance.isRpcExposed = (method) => rpcServer.isRpcExposed(method);
|
|
1395
|
+
// Wrap the existing fetch method
|
|
1396
|
+
const originalFetch = doInstance.fetch.bind(doInstance);
|
|
1397
|
+
doInstance.fetch = async (request) => {
|
|
1398
|
+
const url = new URL(request.url);
|
|
1399
|
+
// Handle /rpc endpoint
|
|
1400
|
+
if (url.pathname === '/rpc') {
|
|
1401
|
+
// Check for WebSocket upgrade
|
|
1402
|
+
const upgradeHeader = request.headers.get('upgrade');
|
|
1403
|
+
const connectionHeader = request.headers.get('connection')?.toLowerCase() || '';
|
|
1404
|
+
const hasConnectionUpgrade = connectionHeader.includes('upgrade');
|
|
1405
|
+
if (upgradeHeader?.toLowerCase() === 'websocket' && hasConnectionUpgrade) {
|
|
1406
|
+
return rpcServer.handleWebSocketRpc();
|
|
1407
|
+
}
|
|
1408
|
+
// HTTP RPC request
|
|
1409
|
+
if (request.method === 'POST') {
|
|
1410
|
+
return rpcServer.handleRpcRequest(request);
|
|
1411
|
+
}
|
|
1412
|
+
// GET request - return RPC info
|
|
1413
|
+
return Response.json({
|
|
1414
|
+
message: 'RPC endpoint - use POST for HTTP batch mode or WebSocket for streaming',
|
|
1415
|
+
methods: rpcServer.methods,
|
|
1416
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
1417
|
+
}
|
|
1418
|
+
// Delegate to original fetch
|
|
1419
|
+
return originalFetch(request);
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
export default RPCServer;
|
|
1423
|
+
import { buildJsonResponse, buildErrorResponse, } from './shared';
|
|
1424
|
+
/**
|
|
1425
|
+
* Cache for RPC servers per DO instance
|
|
1426
|
+
*/
|
|
1427
|
+
const rpcServerCache = new WeakMap();
|
|
1428
|
+
/**
|
|
1429
|
+
* RPC Handler implementing TransportHandler interface
|
|
1430
|
+
*
|
|
1431
|
+
* Provides Cap'n Web RPC functionality:
|
|
1432
|
+
* - Promise pipelining
|
|
1433
|
+
* - JSON-RPC 2.0 support
|
|
1434
|
+
* - WebSocket streaming RPC
|
|
1435
|
+
* - Method discovery and invocation
|
|
1436
|
+
*
|
|
1437
|
+
* @example
|
|
1438
|
+
* ```typescript
|
|
1439
|
+
* const rpcHandler = new RpcHandler({
|
|
1440
|
+
* path: '/rpc',
|
|
1441
|
+
* maxPipelineDepth: 20,
|
|
1442
|
+
* })
|
|
1443
|
+
*
|
|
1444
|
+
* // Use in handler chain
|
|
1445
|
+
* chain.use(rpcHandler, 40)
|
|
1446
|
+
* ```
|
|
1447
|
+
*/
|
|
1448
|
+
export class RpcHandler {
|
|
1449
|
+
name = 'rpc';
|
|
1450
|
+
options;
|
|
1451
|
+
server = null;
|
|
1452
|
+
constructor(options = {}) {
|
|
1453
|
+
this.options = {
|
|
1454
|
+
path: '/rpc',
|
|
1455
|
+
maxPipelineDepth: 20,
|
|
1456
|
+
...options,
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Check if this handler can process the request
|
|
1461
|
+
* RPC handler processes requests to the /rpc endpoint
|
|
1462
|
+
*/
|
|
1463
|
+
canHandle(request) {
|
|
1464
|
+
const url = new URL(request.url);
|
|
1465
|
+
const rpcPath = this.options.path || '/rpc';
|
|
1466
|
+
// Check if request is to RPC endpoint
|
|
1467
|
+
if (url.pathname === rpcPath) {
|
|
1468
|
+
// RPC handler has medium-high priority
|
|
1469
|
+
return {
|
|
1470
|
+
canHandle: true,
|
|
1471
|
+
priority: 40,
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
return { canHandle: false, reason: 'Path does not match RPC endpoint' };
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Handle the RPC request
|
|
1478
|
+
*/
|
|
1479
|
+
async handle(request, context) {
|
|
1480
|
+
// Get or create RPC server for this instance
|
|
1481
|
+
const server = this.getOrCreateServer(context.instance);
|
|
1482
|
+
// Check for WebSocket upgrade
|
|
1483
|
+
const upgradeHeader = request.headers.get('upgrade');
|
|
1484
|
+
const connectionHeader = request.headers.get('connection')?.toLowerCase() || '';
|
|
1485
|
+
const hasConnectionUpgrade = connectionHeader.includes('upgrade');
|
|
1486
|
+
if (upgradeHeader?.toLowerCase() === 'websocket' && hasConnectionUpgrade) {
|
|
1487
|
+
return server.handleWebSocketRpc();
|
|
1488
|
+
}
|
|
1489
|
+
// Handle HTTP methods
|
|
1490
|
+
if (request.method === 'POST') {
|
|
1491
|
+
return server.handleRpcRequest(request);
|
|
1492
|
+
}
|
|
1493
|
+
if (request.method === 'GET') {
|
|
1494
|
+
// Return RPC info
|
|
1495
|
+
return buildJsonResponse({
|
|
1496
|
+
message: 'RPC endpoint - use POST for HTTP batch mode or WebSocket for streaming',
|
|
1497
|
+
methods: server.methods,
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
return buildErrorResponse({ message: 'Method not allowed', code: 'METHOD_NOT_ALLOWED' }, 405, { headers: { 'Allow': 'GET, POST' } });
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Get or create RPC server for an instance
|
|
1504
|
+
*/
|
|
1505
|
+
getOrCreateServer(instance) {
|
|
1506
|
+
// Check cache
|
|
1507
|
+
if (rpcServerCache.has(instance)) {
|
|
1508
|
+
return rpcServerCache.get(instance);
|
|
1509
|
+
}
|
|
1510
|
+
// Create new server
|
|
1511
|
+
const server = new RPCServer(instance, {
|
|
1512
|
+
maxPipelineDepth: this.options.maxPipelineDepth,
|
|
1513
|
+
exposedMethods: this.options.exposedMethods,
|
|
1514
|
+
blockedMethods: this.options.blockedMethods,
|
|
1515
|
+
});
|
|
1516
|
+
rpcServerCache.set(instance, server);
|
|
1517
|
+
this.server = server;
|
|
1518
|
+
return server;
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Get current RPC server
|
|
1522
|
+
*/
|
|
1523
|
+
getServer() {
|
|
1524
|
+
return this.server;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Clear server cache for an instance
|
|
1528
|
+
*/
|
|
1529
|
+
static clearCache(instance) {
|
|
1530
|
+
rpcServerCache.delete(instance);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Dispose handler resources
|
|
1534
|
+
*/
|
|
1535
|
+
dispose() {
|
|
1536
|
+
this.server = null;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
//# sourceMappingURL=rpc-server.js.map
|