dotdo 0.0.1 → 0.0.2
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/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/analytics/router.js +601 -0
- package/dist/api/analytics/router.js.map +1 -0
- package/dist/api/index.js +158 -0
- package/dist/api/index.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 +544 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/api/middleware/error-handling.js +176 -0
- package/dist/api/middleware/error-handling.js.map +1 -0
- package/dist/api/middleware/request-id.js +21 -0
- package/dist/api/middleware/request-id.js.map +1 -0
- package/dist/api/pages.js +1180 -0
- package/dist/api/pages.js.map +1 -0
- package/dist/api/routes/api.js +612 -0
- package/dist/api/routes/api.js.map +1 -0
- package/dist/api/routes/browsers.js +471 -0
- package/dist/api/routes/browsers.js.map +1 -0
- package/dist/api/routes/do.js +188 -0
- package/dist/api/routes/do.js.map +1 -0
- package/dist/api/routes/mcp.js +459 -0
- package/dist/api/routes/mcp.js.map +1 -0
- package/dist/api/routes/obs.js +445 -0
- package/dist/api/routes/obs.js.map +1 -0
- package/dist/api/routes/openapi.js +794 -0
- package/dist/api/routes/openapi.js.map +1 -0
- package/dist/api/routes/rpc.js +1103 -0
- package/dist/api/routes/rpc.js.map +1 -0
- package/dist/api/routes/sandboxes.js +389 -0
- package/dist/api/routes/sandboxes.js.map +1 -0
- package/dist/api/test-do.js +38 -0
- package/dist/api/test-do.js.map +1 -0
- package/dist/api/types.js +11 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/bin.js +2 -0
- package/dist/cli/main.js +52342 -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 +118 -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/bash.js +35 -0
- package/dist/do/bash.js.map +1 -0
- package/dist/do/fs.js +25 -0
- package/dist/do/fs.js.map +1 -0
- package/dist/do/full.js +61 -0
- package/dist/do/full.js.map +1 -0
- package/dist/do/git.js +28 -0
- package/dist/do/git.js.map +1 -0
- package/dist/do/index.js +52 -0
- package/dist/do/index.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/agent/tools/bash.js +336 -0
- package/dist/lib/agent/tools/bash.js.map +1 -0
- package/dist/lib/agent/tools/edit.js +157 -0
- package/dist/lib/agent/tools/edit.js.map +1 -0
- package/dist/lib/agent/tools/glob.js +137 -0
- package/dist/lib/agent/tools/glob.js.map +1 -0
- package/dist/lib/agent/tools/grep.js +315 -0
- package/dist/lib/agent/tools/grep.js.map +1 -0
- package/dist/lib/agent/tools/index.js +71 -0
- package/dist/lib/agent/tools/index.js.map +1 -0
- package/dist/lib/agent/tools/read.js +212 -0
- package/dist/lib/agent/tools/read.js.map +1 -0
- package/dist/lib/agent/tools/types.js +197 -0
- package/dist/lib/agent/tools/types.js.map +1 -0
- package/dist/lib/agent/tools/write.js +159 -0
- package/dist/lib/agent/tools/write.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 +825 -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 +1011 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/index.js +29 -0
- package/dist/lib/mixins/index.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 +1189 -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/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 +648 -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 +690 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1507 -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 +1536 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +575 -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/primitives/bashx/src/ast/analyze.js +1472 -0
- package/dist/primitives/bashx/src/ast/analyze.js.map +1 -0
- package/dist/primitives/bashx/src/ast/parser.js +1488 -0
- package/dist/primitives/bashx/src/ast/parser.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/crypto.js +1954 -0
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/data-processing.js +1812 -0
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +804 -0
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/math-control.js +1122 -0
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +1015 -0
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/system-utils.js +687 -0
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/test-command.js +523 -0
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/text-processing.js +1550 -0
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +1 -0
- package/dist/primitives/bashx/src/do/container-executor.js +429 -0
- package/dist/primitives/bashx/src/do/container-executor.js.map +1 -0
- package/dist/primitives/bashx/src/do/index.js +668 -0
- package/dist/primitives/bashx/src/do/index.js.map +1 -0
- package/dist/primitives/bashx/src/do/tiered-executor.js +2647 -0
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +1 -0
- package/dist/primitives/bashx/src/do/worker.js +352 -0
- package/dist/primitives/bashx/src/do/worker.js.map +1 -0
- package/dist/primitives/bashx/src/types.js +10 -0
- package/dist/primitives/bashx/src/types.js.map +1 -0
- package/dist/primitives/fsx/core/backend.js +480 -0
- package/dist/primitives/fsx/core/backend.js.map +1 -0
- package/dist/primitives/fsx/core/constants.js +140 -0
- package/dist/primitives/fsx/core/constants.js.map +1 -0
- package/dist/primitives/fsx/core/fsx.js +1184 -0
- package/dist/primitives/fsx/core/fsx.js.map +1 -0
- package/dist/primitives/fsx/core/glob/glob.js +438 -0
- package/dist/primitives/fsx/core/glob/glob.js.map +1 -0
- package/dist/primitives/fsx/core/glob/index.js +8 -0
- package/dist/primitives/fsx/core/glob/index.js.map +1 -0
- package/dist/primitives/fsx/core/glob/match.js +392 -0
- package/dist/primitives/fsx/core/glob/match.js.map +1 -0
- package/dist/primitives/fsx/core/types.js +307 -0
- package/dist/primitives/fsx/core/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sdk/capnweb-compat.js +42 -0
- package/dist/sdk/capnweb-compat.js.map +1 -0
- package/dist/sdk/client.js +20 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.js +17 -0
- package/dist/sdk/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 +1215 -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 +148 -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 +279 -46
|
@@ -0,0 +1,1665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Accessors for DO Base Class
|
|
3
|
+
*
|
|
4
|
+
* Provides typed store accessors for:
|
|
5
|
+
* - ThingsStore: CRUD operations for Things
|
|
6
|
+
* - RelationshipsStore: Relationship management
|
|
7
|
+
* - ActionsStore: Action logging and lifecycle
|
|
8
|
+
* - EventsStore: Event emission and streaming
|
|
9
|
+
* - SearchStore: Full-text and semantic search
|
|
10
|
+
* - ObjectsStore: DO registry and resolution
|
|
11
|
+
*/
|
|
12
|
+
import { eq, and, desc, asc, isNull, sql } from 'drizzle-orm';
|
|
13
|
+
import * as schema from '../db';
|
|
14
|
+
import { logBestEffortError } from '../lib/logging/error-logger';
|
|
15
|
+
import { safeJsonParse } from '../lib/safe-stringify';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// SQL INJECTION PREVENTION
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Whitelist of columns allowed for orderBy in ThingsStore.list()
|
|
21
|
+
* These match the actual columns in the things table schema.
|
|
22
|
+
*/
|
|
23
|
+
export const ALLOWED_ORDER_COLUMNS = Object.freeze([
|
|
24
|
+
'id',
|
|
25
|
+
'name',
|
|
26
|
+
'type',
|
|
27
|
+
'branch',
|
|
28
|
+
'deleted',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Validates that an orderBy column name is in the allowed whitelist.
|
|
32
|
+
* Throws an error if the column is not allowed or contains invalid characters.
|
|
33
|
+
*
|
|
34
|
+
* @param column - The column name to validate
|
|
35
|
+
* @returns The validated column name
|
|
36
|
+
* @throws Error if the column is not in the whitelist
|
|
37
|
+
*/
|
|
38
|
+
export function validateOrderColumn(column) {
|
|
39
|
+
if (!column || typeof column !== 'string') {
|
|
40
|
+
throw new Error('Invalid order column: column name is required');
|
|
41
|
+
}
|
|
42
|
+
// Check against whitelist
|
|
43
|
+
if (!ALLOWED_ORDER_COLUMNS.includes(column)) {
|
|
44
|
+
throw new Error(`Invalid order column: '${column}'. Allowed columns: ${ALLOWED_ORDER_COLUMNS.join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
return column;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Regular expression for valid JSON paths.
|
|
50
|
+
* Only allows alphanumeric characters, underscores, and dots (for nested paths).
|
|
51
|
+
* Does not allow:
|
|
52
|
+
* - Starting or ending with dots
|
|
53
|
+
* - Consecutive dots
|
|
54
|
+
* - Special characters (quotes, parentheses, semicolons, etc.)
|
|
55
|
+
*/
|
|
56
|
+
const VALID_JSON_PATH_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
57
|
+
/**
|
|
58
|
+
* Validates that a JSON path is safe for use in SQL queries.
|
|
59
|
+
* Only allows alphanumeric characters, underscores, and dots.
|
|
60
|
+
*
|
|
61
|
+
* @param path - The JSON path to validate
|
|
62
|
+
* @returns The validated JSON path
|
|
63
|
+
* @throws Error if the path contains invalid characters
|
|
64
|
+
*/
|
|
65
|
+
export function validateJsonPath(path) {
|
|
66
|
+
if (!path || typeof path !== 'string') {
|
|
67
|
+
throw new Error('Invalid JSON path: path is required');
|
|
68
|
+
}
|
|
69
|
+
if (!VALID_JSON_PATH_REGEX.test(path)) {
|
|
70
|
+
throw new Error(`Invalid JSON path: '${path}'. JSON paths must contain only alphanumeric characters, underscores, and dots for nesting.`);
|
|
71
|
+
}
|
|
72
|
+
return path;
|
|
73
|
+
}
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// TYPE-SAFE QUERY BUILDERS
|
|
76
|
+
// ============================================================================
|
|
77
|
+
/**
|
|
78
|
+
* Type-safe ORDER BY clause builder.
|
|
79
|
+
* Returns a pre-built SQL fragment for the ORDER BY clause based on validated column.
|
|
80
|
+
* This approach completely eliminates the need for sql.raw() by using a lookup table.
|
|
81
|
+
*/
|
|
82
|
+
const ORDER_CLAUSE_BUILDERS = {
|
|
83
|
+
id: {
|
|
84
|
+
asc: sql `ORDER BY t.id ASC`,
|
|
85
|
+
desc: sql `ORDER BY t.id DESC`,
|
|
86
|
+
},
|
|
87
|
+
name: {
|
|
88
|
+
asc: sql `ORDER BY t.name ASC`,
|
|
89
|
+
desc: sql `ORDER BY t.name DESC`,
|
|
90
|
+
},
|
|
91
|
+
type: {
|
|
92
|
+
asc: sql `ORDER BY t.type ASC`,
|
|
93
|
+
desc: sql `ORDER BY t.type DESC`,
|
|
94
|
+
},
|
|
95
|
+
branch: {
|
|
96
|
+
asc: sql `ORDER BY t.branch ASC`,
|
|
97
|
+
desc: sql `ORDER BY t.branch DESC`,
|
|
98
|
+
},
|
|
99
|
+
deleted: {
|
|
100
|
+
asc: sql `ORDER BY t.deleted ASC`,
|
|
101
|
+
desc: sql `ORDER BY t.deleted DESC`,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Builds a type-safe ORDER BY clause without using sql.raw().
|
|
106
|
+
* Uses pre-defined SQL fragments looked up by validated column name.
|
|
107
|
+
*
|
|
108
|
+
* @param column - The validated column name
|
|
109
|
+
* @param direction - The sort direction ('asc' or 'desc')
|
|
110
|
+
* @returns A safe SQL fragment for the ORDER BY clause
|
|
111
|
+
*/
|
|
112
|
+
export function buildOrderClause(column, direction = 'asc') {
|
|
113
|
+
return ORDER_CLAUSE_BUILDERS[column][direction];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Builds a safe JSON path string for use with SQLite json_extract().
|
|
117
|
+
*
|
|
118
|
+
* Security: The path is validated against a strict regex before building.
|
|
119
|
+
* The JSON path validation regex ensures:
|
|
120
|
+
* - Starts with letter or underscore
|
|
121
|
+
* - Contains only alphanumeric chars, underscores, and dots
|
|
122
|
+
* - No consecutive dots, no leading/trailing dots
|
|
123
|
+
* - No SQL metacharacters (quotes, semicolons, parentheses, etc.)
|
|
124
|
+
*
|
|
125
|
+
* The resulting path is then used as a parameterized value (not raw SQL),
|
|
126
|
+
* which is safe because:
|
|
127
|
+
* 1. SQLite's json_extract() only interprets the value as a JSON path selector
|
|
128
|
+
* 2. The validation rejects any SQL metacharacters
|
|
129
|
+
* 3. Drizzle's sql template tag properly escapes the parameterized string
|
|
130
|
+
*/
|
|
131
|
+
export function buildSafeJsonPath(path) {
|
|
132
|
+
const validated = validateJsonPath(path);
|
|
133
|
+
return `$.${validated}`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Builds a type-safe JSON extract condition using parameterized binding.
|
|
137
|
+
*
|
|
138
|
+
* Security: Uses buildSafeJsonPath() which validates the path against a strict
|
|
139
|
+
* regex before constructing the full path string. The path is then passed as
|
|
140
|
+
* a parameterized value (not sql.raw()), which is safe because:
|
|
141
|
+
* 1. SQLite's json_extract() only interprets the value as a JSON path selector
|
|
142
|
+
* 2. The validation rejects any SQL metacharacters
|
|
143
|
+
* 3. Parameterized binding escapes any special characters
|
|
144
|
+
*
|
|
145
|
+
* @param path - The JSON path to validate and use
|
|
146
|
+
* @param value - The value to compare against (will be parameterized)
|
|
147
|
+
* @returns A safe SQL fragment for the JSON extract condition
|
|
148
|
+
*/
|
|
149
|
+
export function buildJsonCondition(path, value) {
|
|
150
|
+
const safePath = buildSafeJsonPath(path);
|
|
151
|
+
return sql `json_extract(t.data, ${safePath}) = ${JSON.stringify(value)}`;
|
|
152
|
+
}
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// THINGS STORE
|
|
155
|
+
// ============================================================================
|
|
156
|
+
export class ThingsStore {
|
|
157
|
+
ctx;
|
|
158
|
+
constructor(ctx) {
|
|
159
|
+
this.ctx = ctx;
|
|
160
|
+
}
|
|
161
|
+
async getTypeId(typeName) {
|
|
162
|
+
// Check cache first
|
|
163
|
+
if (this.ctx.typeCache.has(typeName)) {
|
|
164
|
+
return this.ctx.typeCache.get(typeName);
|
|
165
|
+
}
|
|
166
|
+
// Look up or create the noun type
|
|
167
|
+
const existing = await this.ctx.db
|
|
168
|
+
.select()
|
|
169
|
+
.from(schema.nouns)
|
|
170
|
+
.where(eq(schema.nouns.noun, typeName))
|
|
171
|
+
.limit(1);
|
|
172
|
+
if (existing.length > 0) {
|
|
173
|
+
// Get rowid by re-querying with raw SQL
|
|
174
|
+
const result = await this.ctx.db.all(sql `SELECT rowid FROM nouns WHERE noun = ${typeName} LIMIT 1`);
|
|
175
|
+
const rowid = result[0];
|
|
176
|
+
const id = rowid?.rowid ?? 0;
|
|
177
|
+
this.ctx.typeCache.set(typeName, id);
|
|
178
|
+
return id;
|
|
179
|
+
}
|
|
180
|
+
// Create new noun
|
|
181
|
+
await this.ctx.db.insert(schema.nouns).values({
|
|
182
|
+
noun: typeName,
|
|
183
|
+
plural: typeName + 's',
|
|
184
|
+
});
|
|
185
|
+
// Get its rowid
|
|
186
|
+
const result = await this.ctx.db.all(sql `SELECT rowid FROM nouns WHERE noun = ${typeName} LIMIT 1`);
|
|
187
|
+
const rowid = result[0];
|
|
188
|
+
const id = rowid?.rowid ?? 0;
|
|
189
|
+
this.ctx.typeCache.set(typeName, id);
|
|
190
|
+
return id;
|
|
191
|
+
}
|
|
192
|
+
// Reverse cache: typeId -> typeName (for efficient lookup by ID)
|
|
193
|
+
typeCacheById = new Map();
|
|
194
|
+
async getTypeName(typeId) {
|
|
195
|
+
// Check reverse cache first (O(1) lookup)
|
|
196
|
+
if (this.typeCacheById.has(typeId)) {
|
|
197
|
+
return this.typeCacheById.get(typeId);
|
|
198
|
+
}
|
|
199
|
+
// Fall back to forward cache scan (for backwards compatibility)
|
|
200
|
+
for (const [name, id] of Array.from(this.ctx.typeCache.entries())) {
|
|
201
|
+
if (id === typeId) {
|
|
202
|
+
// Populate reverse cache for future lookups
|
|
203
|
+
this.typeCacheById.set(typeId, name);
|
|
204
|
+
return name;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Query the database
|
|
208
|
+
const result = await this.ctx.db.all(sql `SELECT noun FROM nouns WHERE rowid = ${typeId} LIMIT 1`);
|
|
209
|
+
const row = result[0];
|
|
210
|
+
const name = row?.noun ?? 'Unknown';
|
|
211
|
+
// Populate both caches
|
|
212
|
+
this.ctx.typeCache.set(name, typeId);
|
|
213
|
+
this.typeCacheById.set(typeId, name);
|
|
214
|
+
return name;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Batch lookup type names for multiple type IDs.
|
|
218
|
+
* This is the key optimization to avoid N+1 queries.
|
|
219
|
+
*
|
|
220
|
+
* @param typeIds - Array of type IDs to look up
|
|
221
|
+
* @returns Map of typeId -> typeName
|
|
222
|
+
*/
|
|
223
|
+
async batchGetTypeNames(typeIds) {
|
|
224
|
+
const result = new Map();
|
|
225
|
+
if (typeIds.length === 0) {
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
// Deduplicate type IDs
|
|
229
|
+
const uniqueTypeIds = [...new Set(typeIds)];
|
|
230
|
+
// Check cache first for each ID
|
|
231
|
+
const uncachedIds = [];
|
|
232
|
+
for (const typeId of uniqueTypeIds) {
|
|
233
|
+
if (this.typeCacheById.has(typeId)) {
|
|
234
|
+
result.set(typeId, this.typeCacheById.get(typeId));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Check forward cache
|
|
238
|
+
let found = false;
|
|
239
|
+
for (const [name, id] of Array.from(this.ctx.typeCache.entries())) {
|
|
240
|
+
if (id === typeId) {
|
|
241
|
+
result.set(typeId, name);
|
|
242
|
+
this.typeCacheById.set(typeId, name);
|
|
243
|
+
found = true;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!found) {
|
|
248
|
+
uncachedIds.push(typeId);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Batch query for uncached IDs
|
|
253
|
+
if (uncachedIds.length > 0) {
|
|
254
|
+
// Build query: SELECT rowid, noun FROM nouns WHERE rowid IN (?, ?, ...)
|
|
255
|
+
const placeholders = uncachedIds.map(() => '?').join(', ');
|
|
256
|
+
const queryResult = await this.ctx.db.all(sql `SELECT rowid, noun FROM nouns WHERE rowid IN (${sql.join(uncachedIds.map(id => sql `${id}`), sql `, `)})`);
|
|
257
|
+
// Process results
|
|
258
|
+
for (const row of queryResult) {
|
|
259
|
+
const typeId = row.rowid;
|
|
260
|
+
const typeName = row.noun;
|
|
261
|
+
// Populate both caches
|
|
262
|
+
this.ctx.typeCache.set(typeName, typeId);
|
|
263
|
+
this.typeCacheById.set(typeId, typeName);
|
|
264
|
+
result.set(typeId, typeName);
|
|
265
|
+
}
|
|
266
|
+
// Handle any IDs that weren't found (set to 'Unknown')
|
|
267
|
+
for (const typeId of uncachedIds) {
|
|
268
|
+
if (!result.has(typeId)) {
|
|
269
|
+
result.set(typeId, 'Unknown');
|
|
270
|
+
this.typeCacheById.set(typeId, 'Unknown');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
async get(id, options = {}) {
|
|
277
|
+
const branch = options.branch ?? this.ctx.currentBranch;
|
|
278
|
+
const branchCondition = branch === 'main' ? isNull(schema.things.branch) : eq(schema.things.branch, branch);
|
|
279
|
+
// If specific version requested
|
|
280
|
+
if (options.version !== undefined) {
|
|
281
|
+
const results = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} ORDER BY rowid ASC`);
|
|
282
|
+
const rows = results;
|
|
283
|
+
const targetRow = rows.find((r, idx) => idx + 1 === options.version);
|
|
284
|
+
if (!targetRow)
|
|
285
|
+
return null;
|
|
286
|
+
const typeName = await this.getTypeName(targetRow.type);
|
|
287
|
+
return {
|
|
288
|
+
$id: targetRow.id,
|
|
289
|
+
$type: typeName,
|
|
290
|
+
name: targetRow.name,
|
|
291
|
+
data: typeof targetRow.data === 'string'
|
|
292
|
+
? safeJsonParse(targetRow.data, null, { context: 'ThingsStore.get.version' })
|
|
293
|
+
: targetRow.data,
|
|
294
|
+
branch: targetRow.branch,
|
|
295
|
+
version: targetRow.version,
|
|
296
|
+
deleted: !!targetRow.deleted,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// Get latest version
|
|
300
|
+
const results = await this.ctx.db
|
|
301
|
+
.select()
|
|
302
|
+
.from(schema.things)
|
|
303
|
+
.where(and(eq(schema.things.id, id), branchCondition))
|
|
304
|
+
.orderBy(desc(schema.things.id));
|
|
305
|
+
// Get the latest by rowid using raw SQL
|
|
306
|
+
// Note: For 'main' branch, we store NULL in the database, so we need IS NULL check
|
|
307
|
+
const branchSql = branch === 'main'
|
|
308
|
+
? sql `branch IS NULL`
|
|
309
|
+
: sql `branch = ${branch}`;
|
|
310
|
+
const allVersions = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} AND ${branchSql} ORDER BY rowid DESC LIMIT 1`);
|
|
311
|
+
const result = allVersions[0];
|
|
312
|
+
if (!result)
|
|
313
|
+
return null;
|
|
314
|
+
// Check soft delete
|
|
315
|
+
if (result.deleted && !options.includeDeleted) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const typeName = await this.getTypeName(result.type);
|
|
319
|
+
return {
|
|
320
|
+
$id: result.id,
|
|
321
|
+
$type: typeName,
|
|
322
|
+
name: result.name,
|
|
323
|
+
data: typeof result.data === 'string'
|
|
324
|
+
? safeJsonParse(result.data, null, { context: 'ThingsStore.get.latest' })
|
|
325
|
+
: result.data,
|
|
326
|
+
branch: result.branch,
|
|
327
|
+
version: result.version,
|
|
328
|
+
deleted: !!result.deleted,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async list(options = {}) {
|
|
332
|
+
const branch = options.branch ?? this.ctx.currentBranch;
|
|
333
|
+
const limit = options.limit ?? 100;
|
|
334
|
+
const offset = options.offset ?? 0;
|
|
335
|
+
// Get type ID if filtering by type (do this early to use in subquery)
|
|
336
|
+
let typeId;
|
|
337
|
+
if (options.type) {
|
|
338
|
+
typeId = await this.getTypeId(options.type);
|
|
339
|
+
}
|
|
340
|
+
// Build subquery conditions - all filters must be in the subquery
|
|
341
|
+
// to correctly filter which things to return the latest version of
|
|
342
|
+
let subqueryConditions = sql `WHERE 1=1`;
|
|
343
|
+
// Add branch condition
|
|
344
|
+
if (branch === 'main') {
|
|
345
|
+
subqueryConditions = sql `${subqueryConditions} AND branch IS NULL`;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
subqueryConditions = sql `${subqueryConditions} AND branch = ${branch}`;
|
|
349
|
+
}
|
|
350
|
+
// Add type filter if specified (MUST be in subquery for correct SQL filtering)
|
|
351
|
+
if (typeId !== undefined) {
|
|
352
|
+
subqueryConditions = sql `${subqueryConditions} AND type = ${typeId}`;
|
|
353
|
+
}
|
|
354
|
+
// Exclude soft-deleted by default
|
|
355
|
+
if (!options.includeDeleted) {
|
|
356
|
+
subqueryConditions = sql `${subqueryConditions} AND (deleted = 0 OR deleted IS NULL)`;
|
|
357
|
+
}
|
|
358
|
+
// Handle cursor-based pagination (MUST be in subquery for correct SQL filtering)
|
|
359
|
+
if (options.after) {
|
|
360
|
+
subqueryConditions = sql `${subqueryConditions} AND id > ${options.after}`;
|
|
361
|
+
}
|
|
362
|
+
// Group by id to get latest versions only
|
|
363
|
+
// Use a subquery to get the max rowid per id with ALL filters applied
|
|
364
|
+
const subquery = sql `
|
|
365
|
+
SELECT id, MAX(rowid) as max_rowid
|
|
366
|
+
FROM things
|
|
367
|
+
${subqueryConditions}
|
|
368
|
+
GROUP BY id
|
|
369
|
+
`;
|
|
370
|
+
// Join to get full records
|
|
371
|
+
const fullQuery = sql `
|
|
372
|
+
SELECT t.rowid as version, t.*
|
|
373
|
+
FROM things t
|
|
374
|
+
INNER JOIN (${subquery}) latest ON t.id = latest.id AND t.rowid = latest.max_rowid
|
|
375
|
+
`;
|
|
376
|
+
// Add where clause for JSON filters
|
|
377
|
+
let finalQuery = fullQuery;
|
|
378
|
+
if (options.where) {
|
|
379
|
+
// Note: For production, would need proper JSON path queries
|
|
380
|
+
// This is a simplified version
|
|
381
|
+
for (const [key, value] of Object.entries(options.where)) {
|
|
382
|
+
if (key.startsWith('data.')) {
|
|
383
|
+
const jsonPath = key.replace('data.', '');
|
|
384
|
+
// Build safe JSON path (validates and prefixes with $.)
|
|
385
|
+
// The path is parameterized as a string value, not interpolated with sql.raw()
|
|
386
|
+
const safePath = buildSafeJsonPath(jsonPath);
|
|
387
|
+
finalQuery = sql `${finalQuery} AND json_extract(t.data, ${safePath}) = ${JSON.stringify(value)}`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Add ordering using type-safe ORDER BY clause builder (no sql.raw())
|
|
392
|
+
const orderColumn = validateOrderColumn(options.orderBy ?? 'id');
|
|
393
|
+
const orderDir = options.order ?? 'asc';
|
|
394
|
+
const orderClause = buildOrderClause(orderColumn, orderDir);
|
|
395
|
+
// Extract just the ORDER BY part from the pre-built clause
|
|
396
|
+
// The buildOrderClause returns a complete "ORDER BY t.column ASC/DESC" fragment
|
|
397
|
+
finalQuery = sql `${finalQuery} ${orderClause}`;
|
|
398
|
+
// Add limit and offset
|
|
399
|
+
finalQuery = sql `${finalQuery} LIMIT ${limit} OFFSET ${offset}`;
|
|
400
|
+
const results = await this.ctx.db.all(finalQuery);
|
|
401
|
+
const rows = results;
|
|
402
|
+
// Batch lookup all type names to avoid N+1 queries
|
|
403
|
+
const typeIds = rows.map((row) => row.type);
|
|
404
|
+
const typeNameMap = await this.batchGetTypeNames(typeIds);
|
|
405
|
+
// Map to ThingEntity using the pre-fetched type names
|
|
406
|
+
const entities = [];
|
|
407
|
+
for (const row of rows) {
|
|
408
|
+
const typeName = typeNameMap.get(row.type) ?? 'Unknown';
|
|
409
|
+
entities.push({
|
|
410
|
+
$id: row.id,
|
|
411
|
+
$type: typeName,
|
|
412
|
+
name: row.name,
|
|
413
|
+
data: typeof row.data === 'string'
|
|
414
|
+
? safeJsonParse(row.data, null, { context: 'ThingsStore.list' })
|
|
415
|
+
: row.data,
|
|
416
|
+
branch: row.branch,
|
|
417
|
+
version: row.version,
|
|
418
|
+
deleted: !!row.deleted,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return entities;
|
|
422
|
+
}
|
|
423
|
+
async create(data, options = {}) {
|
|
424
|
+
// Support both $type and type for backwards compatibility
|
|
425
|
+
const typeName = data.$type ?? data.type;
|
|
426
|
+
if (!typeName) {
|
|
427
|
+
throw new Error('$type is required');
|
|
428
|
+
}
|
|
429
|
+
const id = data.$id ?? crypto.randomUUID();
|
|
430
|
+
const branch = options.branch ?? this.ctx.currentBranch;
|
|
431
|
+
// Get type ID (best-effort, may fail with mock DB)
|
|
432
|
+
let typeId = 0;
|
|
433
|
+
try {
|
|
434
|
+
typeId = await this.getTypeId(typeName);
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
logBestEffortError(error, {
|
|
438
|
+
operation: 'getTypeId',
|
|
439
|
+
source: 'ThingStore.create',
|
|
440
|
+
context: { typeName, id },
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
// Check for duplicate (best-effort, may fail with mock DB)
|
|
444
|
+
let existing = null;
|
|
445
|
+
try {
|
|
446
|
+
existing = await this.get(id, { branch, includeDeleted: true });
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
logBestEffortError(error, {
|
|
450
|
+
operation: 'checkDuplicate',
|
|
451
|
+
source: 'ThingStore.create',
|
|
452
|
+
context: { id, branch },
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (existing) {
|
|
456
|
+
throw new Error(`Thing with id '${id}' already exists`);
|
|
457
|
+
}
|
|
458
|
+
// Insert the thing (best-effort)
|
|
459
|
+
try {
|
|
460
|
+
await this.ctx.db.insert(schema.things).values({
|
|
461
|
+
id,
|
|
462
|
+
type: typeId,
|
|
463
|
+
branch: branch === 'main' ? null : branch,
|
|
464
|
+
name: data.name ?? null,
|
|
465
|
+
data: data.data ?? null,
|
|
466
|
+
deleted: false,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
logBestEffortError(error, {
|
|
471
|
+
operation: 'insert',
|
|
472
|
+
source: 'ThingStore.create',
|
|
473
|
+
context: { id, typeName, branch },
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
// Stream to Pipeline if configured
|
|
477
|
+
if (this.ctx.env.PIPELINE) {
|
|
478
|
+
try {
|
|
479
|
+
await this.ctx.env.PIPELINE.send([{
|
|
480
|
+
verb: `${typeName}.created`,
|
|
481
|
+
source: this.ctx.ns,
|
|
482
|
+
$context: this.ctx.ns,
|
|
483
|
+
$id: `${this.ctx.ns}/${typeName}/${id}`,
|
|
484
|
+
$type: typeName,
|
|
485
|
+
data: data.data,
|
|
486
|
+
timestamp: new Date().toISOString(),
|
|
487
|
+
}]);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
logBestEffortError(error, {
|
|
491
|
+
operation: 'stream',
|
|
492
|
+
source: 'ThingStore.create',
|
|
493
|
+
context: { id, typeName, verb: `${typeName}.created` },
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Get the created record with its version
|
|
498
|
+
let created = null;
|
|
499
|
+
try {
|
|
500
|
+
created = await this.get(id, { branch });
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
logBestEffortError(error, {
|
|
504
|
+
operation: 'getCreated',
|
|
505
|
+
source: 'ThingStore.create',
|
|
506
|
+
context: { id, branch },
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return created ?? {
|
|
510
|
+
$id: id,
|
|
511
|
+
$type: typeName,
|
|
512
|
+
name: data.name ?? null,
|
|
513
|
+
data: data.data ?? null,
|
|
514
|
+
branch: branch === 'main' ? null : branch,
|
|
515
|
+
deleted: false,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async update(id, data, options = {}) {
|
|
519
|
+
const branch = options.branch ?? this.ctx.currentBranch;
|
|
520
|
+
const merge = options.merge !== false; // Default to merge
|
|
521
|
+
// Get the current version
|
|
522
|
+
const current = await this.get(id, { branch });
|
|
523
|
+
if (!current) {
|
|
524
|
+
throw new Error(`Thing '${id}' not found`);
|
|
525
|
+
}
|
|
526
|
+
// Prepare updated data
|
|
527
|
+
let newData = data.data;
|
|
528
|
+
if (merge && current.data && data.data) {
|
|
529
|
+
// Deep merge
|
|
530
|
+
newData = deepMerge(current.data, data.data);
|
|
531
|
+
}
|
|
532
|
+
// Insert new version (append-only)
|
|
533
|
+
await this.ctx.db.insert(schema.things).values({
|
|
534
|
+
id,
|
|
535
|
+
type: await this.getTypeId(current.$type),
|
|
536
|
+
branch: branch === 'main' ? null : branch,
|
|
537
|
+
name: data.name ?? current.name,
|
|
538
|
+
data: newData ?? current.data,
|
|
539
|
+
deleted: false,
|
|
540
|
+
});
|
|
541
|
+
// Get the updated record
|
|
542
|
+
const updated = await this.get(id, { branch });
|
|
543
|
+
return updated;
|
|
544
|
+
}
|
|
545
|
+
async delete(id, options = {}) {
|
|
546
|
+
const branch = options.branch ?? this.ctx.currentBranch;
|
|
547
|
+
// Get the current version
|
|
548
|
+
const current = await this.get(id, { branch, includeDeleted: true });
|
|
549
|
+
if (!current) {
|
|
550
|
+
throw new Error(`Thing '${id}' not found`);
|
|
551
|
+
}
|
|
552
|
+
if (options.hard) {
|
|
553
|
+
// Hard delete - remove all versions
|
|
554
|
+
await this.ctx.db
|
|
555
|
+
.delete(schema.things)
|
|
556
|
+
.where(eq(schema.things.id, id));
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
// Soft delete - insert new version with deleted=true
|
|
560
|
+
await this.ctx.db.insert(schema.things).values({
|
|
561
|
+
id,
|
|
562
|
+
type: await this.getTypeId(current.$type),
|
|
563
|
+
branch: branch === 'main' ? null : branch,
|
|
564
|
+
name: current.name,
|
|
565
|
+
data: current.data,
|
|
566
|
+
deleted: true,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
// Return the deleted thing
|
|
570
|
+
return { ...current, deleted: true };
|
|
571
|
+
}
|
|
572
|
+
async versions(id) {
|
|
573
|
+
const results = await this.ctx.db.all(sql `SELECT rowid as version, * FROM things WHERE id = ${id} ORDER BY rowid ASC`);
|
|
574
|
+
const entities = [];
|
|
575
|
+
for (const row of results) {
|
|
576
|
+
const typeName = await this.getTypeName(row.type);
|
|
577
|
+
entities.push({
|
|
578
|
+
$id: row.id,
|
|
579
|
+
$type: typeName,
|
|
580
|
+
name: row.name,
|
|
581
|
+
data: typeof row.data === 'string'
|
|
582
|
+
? safeJsonParse(row.data, null, { context: 'ThingsStore.versions' })
|
|
583
|
+
: row.data,
|
|
584
|
+
branch: row.branch,
|
|
585
|
+
version: row.version,
|
|
586
|
+
deleted: !!row.deleted,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
return entities;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// ============================================================================
|
|
593
|
+
// RELATIONSHIPS STORE
|
|
594
|
+
// ============================================================================
|
|
595
|
+
export class RelationshipsStore {
|
|
596
|
+
ctx;
|
|
597
|
+
constructor(ctx) {
|
|
598
|
+
this.ctx = ctx;
|
|
599
|
+
}
|
|
600
|
+
async create(data) {
|
|
601
|
+
const id = crypto.randomUUID();
|
|
602
|
+
// Check for duplicate
|
|
603
|
+
const existing = await this.ctx.db
|
|
604
|
+
.select()
|
|
605
|
+
.from(schema.relationships)
|
|
606
|
+
.where(and(eq(schema.relationships.verb, data.verb), eq(schema.relationships.from, data.from), eq(schema.relationships.to, data.to)))
|
|
607
|
+
.limit(1);
|
|
608
|
+
if (existing.length > 0) {
|
|
609
|
+
throw new Error('Duplicate relationship already exists');
|
|
610
|
+
}
|
|
611
|
+
const now = new Date();
|
|
612
|
+
await this.ctx.db.insert(schema.relationships).values({
|
|
613
|
+
id,
|
|
614
|
+
verb: data.verb,
|
|
615
|
+
from: data.from,
|
|
616
|
+
to: data.to,
|
|
617
|
+
data: data.data ?? null,
|
|
618
|
+
createdAt: now,
|
|
619
|
+
});
|
|
620
|
+
return {
|
|
621
|
+
id,
|
|
622
|
+
verb: data.verb,
|
|
623
|
+
from: data.from,
|
|
624
|
+
to: data.to,
|
|
625
|
+
data: data.data ?? null,
|
|
626
|
+
createdAt: now,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async list(options = {}) {
|
|
630
|
+
const limit = options.limit ?? 100;
|
|
631
|
+
const offset = options.offset ?? 0;
|
|
632
|
+
let query = this.ctx.db.select().from(schema.relationships);
|
|
633
|
+
// Build conditions
|
|
634
|
+
const conditions = [];
|
|
635
|
+
if (options.from)
|
|
636
|
+
conditions.push(eq(schema.relationships.from, options.from));
|
|
637
|
+
if (options.to)
|
|
638
|
+
conditions.push(eq(schema.relationships.to, options.to));
|
|
639
|
+
if (options.verb)
|
|
640
|
+
conditions.push(eq(schema.relationships.verb, options.verb));
|
|
641
|
+
let results;
|
|
642
|
+
if (conditions.length > 0) {
|
|
643
|
+
results = await this.ctx.db
|
|
644
|
+
.select()
|
|
645
|
+
.from(schema.relationships)
|
|
646
|
+
.where(conditions.length === 1 ? conditions[0] : and(...conditions))
|
|
647
|
+
.limit(limit)
|
|
648
|
+
.offset(offset);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
results = await this.ctx.db
|
|
652
|
+
.select()
|
|
653
|
+
.from(schema.relationships)
|
|
654
|
+
.limit(limit)
|
|
655
|
+
.offset(offset);
|
|
656
|
+
}
|
|
657
|
+
return results.map((r) => ({
|
|
658
|
+
id: r.id,
|
|
659
|
+
verb: r.verb,
|
|
660
|
+
from: r.from,
|
|
661
|
+
to: r.to,
|
|
662
|
+
data: r.data,
|
|
663
|
+
createdAt: r.createdAt,
|
|
664
|
+
}));
|
|
665
|
+
}
|
|
666
|
+
async delete(id) {
|
|
667
|
+
// Get the relationship first
|
|
668
|
+
const existing = await this.ctx.db
|
|
669
|
+
.select()
|
|
670
|
+
.from(schema.relationships)
|
|
671
|
+
.where(eq(schema.relationships.id, id))
|
|
672
|
+
.limit(1);
|
|
673
|
+
if (existing.length === 0) {
|
|
674
|
+
throw new Error(`Relationship '${id}' not found`);
|
|
675
|
+
}
|
|
676
|
+
await this.ctx.db
|
|
677
|
+
.delete(schema.relationships)
|
|
678
|
+
.where(eq(schema.relationships.id, id));
|
|
679
|
+
return {
|
|
680
|
+
id: existing[0].id,
|
|
681
|
+
verb: existing[0].verb,
|
|
682
|
+
from: existing[0].from,
|
|
683
|
+
to: existing[0].to,
|
|
684
|
+
data: existing[0].data,
|
|
685
|
+
createdAt: existing[0].createdAt,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async deleteWhere(criteria) {
|
|
689
|
+
const conditions = [];
|
|
690
|
+
if (criteria.from)
|
|
691
|
+
conditions.push(eq(schema.relationships.from, criteria.from));
|
|
692
|
+
if (criteria.to)
|
|
693
|
+
conditions.push(eq(schema.relationships.to, criteria.to));
|
|
694
|
+
if (criteria.verb)
|
|
695
|
+
conditions.push(eq(schema.relationships.verb, criteria.verb));
|
|
696
|
+
if (conditions.length === 0) {
|
|
697
|
+
return 0;
|
|
698
|
+
}
|
|
699
|
+
// Get count first
|
|
700
|
+
const toDelete = await this.list({
|
|
701
|
+
from: criteria.from,
|
|
702
|
+
to: criteria.to,
|
|
703
|
+
verb: criteria.verb,
|
|
704
|
+
});
|
|
705
|
+
// Delete
|
|
706
|
+
await this.ctx.db
|
|
707
|
+
.delete(schema.relationships)
|
|
708
|
+
.where(conditions.length === 1 ? conditions[0] : and(...conditions));
|
|
709
|
+
return toDelete.length;
|
|
710
|
+
}
|
|
711
|
+
async from(url, options = {}) {
|
|
712
|
+
return this.list({ from: url, verb: options.verb });
|
|
713
|
+
}
|
|
714
|
+
async to(url, options = {}) {
|
|
715
|
+
return this.list({ to: url, verb: options.verb });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// ============================================================================
|
|
719
|
+
// ACTIONS STORE
|
|
720
|
+
// ============================================================================
|
|
721
|
+
export class ActionsStore {
|
|
722
|
+
ctx;
|
|
723
|
+
constructor(ctx) {
|
|
724
|
+
this.ctx = ctx;
|
|
725
|
+
}
|
|
726
|
+
async log(options) {
|
|
727
|
+
const id = crypto.randomUUID();
|
|
728
|
+
const now = new Date();
|
|
729
|
+
await this.ctx.db.insert(schema.actions).values({
|
|
730
|
+
id,
|
|
731
|
+
verb: options.verb,
|
|
732
|
+
target: options.target,
|
|
733
|
+
actor: options.actor ?? null,
|
|
734
|
+
input: typeof options.input === 'object' ? null : options.input,
|
|
735
|
+
output: options.output ?? null,
|
|
736
|
+
options: typeof options.input === 'object' ? options.input : null,
|
|
737
|
+
durability: options.durability ?? 'try',
|
|
738
|
+
status: 'pending',
|
|
739
|
+
requestId: options.requestId ?? null,
|
|
740
|
+
sessionId: options.sessionId ?? null,
|
|
741
|
+
workflowId: options.workflowId ?? null,
|
|
742
|
+
createdAt: now,
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
id,
|
|
746
|
+
verb: options.verb,
|
|
747
|
+
target: options.target,
|
|
748
|
+
actor: options.actor ?? null,
|
|
749
|
+
input: options.input ?? null,
|
|
750
|
+
output: options.output ?? null,
|
|
751
|
+
durability: options.durability ?? 'try',
|
|
752
|
+
status: 'pending',
|
|
753
|
+
requestId: options.requestId ?? null,
|
|
754
|
+
sessionId: options.sessionId ?? null,
|
|
755
|
+
workflowId: options.workflowId ?? null,
|
|
756
|
+
createdAt: now,
|
|
757
|
+
retryCount: 0,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async complete(id, output) {
|
|
761
|
+
// Get the action first
|
|
762
|
+
const action = await this.get(id);
|
|
763
|
+
if (!action) {
|
|
764
|
+
throw new Error(`Action '${id}' not found`);
|
|
765
|
+
}
|
|
766
|
+
if (action.status === 'completed') {
|
|
767
|
+
throw new Error('Action already completed');
|
|
768
|
+
}
|
|
769
|
+
const now = new Date();
|
|
770
|
+
const duration = now.getTime() - action.createdAt.getTime();
|
|
771
|
+
await this.ctx.db
|
|
772
|
+
.update(schema.actions)
|
|
773
|
+
.set({
|
|
774
|
+
status: 'completed',
|
|
775
|
+
output: typeof output === 'object' ? null : output,
|
|
776
|
+
completedAt: now,
|
|
777
|
+
duration,
|
|
778
|
+
})
|
|
779
|
+
.where(eq(schema.actions.id, id));
|
|
780
|
+
return {
|
|
781
|
+
...action,
|
|
782
|
+
status: 'completed',
|
|
783
|
+
output: output,
|
|
784
|
+
completedAt: now,
|
|
785
|
+
duration,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async fail(id, error) {
|
|
789
|
+
// Get the action first
|
|
790
|
+
const action = await this.get(id);
|
|
791
|
+
if (!action) {
|
|
792
|
+
throw new Error(`Action '${id}' not found`);
|
|
793
|
+
}
|
|
794
|
+
const now = new Date();
|
|
795
|
+
const errorData = error instanceof Error
|
|
796
|
+
? { message: error.message, name: error.name }
|
|
797
|
+
: error;
|
|
798
|
+
await this.ctx.db
|
|
799
|
+
.update(schema.actions)
|
|
800
|
+
.set({
|
|
801
|
+
status: 'failed',
|
|
802
|
+
error: errorData,
|
|
803
|
+
completedAt: now,
|
|
804
|
+
})
|
|
805
|
+
.where(eq(schema.actions.id, id));
|
|
806
|
+
return {
|
|
807
|
+
...action,
|
|
808
|
+
status: 'failed',
|
|
809
|
+
error: errorData,
|
|
810
|
+
completedAt: now,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
async retry(id) {
|
|
814
|
+
// Get the action first
|
|
815
|
+
const action = await this.get(id);
|
|
816
|
+
if (!action) {
|
|
817
|
+
throw new Error(`Action '${id}' not found`);
|
|
818
|
+
}
|
|
819
|
+
if (action.durability === 'send') {
|
|
820
|
+
throw new Error("Cannot retry action with durability 'send'");
|
|
821
|
+
}
|
|
822
|
+
const retryCount = (action.retryCount ?? 0) + 1;
|
|
823
|
+
await this.ctx.db
|
|
824
|
+
.update(schema.actions)
|
|
825
|
+
.set({
|
|
826
|
+
status: 'retrying',
|
|
827
|
+
error: null,
|
|
828
|
+
completedAt: null,
|
|
829
|
+
})
|
|
830
|
+
.where(eq(schema.actions.id, id));
|
|
831
|
+
return {
|
|
832
|
+
...action,
|
|
833
|
+
status: 'retrying',
|
|
834
|
+
error: null,
|
|
835
|
+
completedAt: null,
|
|
836
|
+
retryCount,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async get(id) {
|
|
840
|
+
const results = await this.ctx.db
|
|
841
|
+
.select()
|
|
842
|
+
.from(schema.actions)
|
|
843
|
+
.where(eq(schema.actions.id, id))
|
|
844
|
+
.limit(1);
|
|
845
|
+
if (results.length === 0)
|
|
846
|
+
return null;
|
|
847
|
+
const r = results[0];
|
|
848
|
+
return {
|
|
849
|
+
id: r.id,
|
|
850
|
+
verb: r.verb,
|
|
851
|
+
target: r.target,
|
|
852
|
+
actor: r.actor,
|
|
853
|
+
input: r.input ?? r.options,
|
|
854
|
+
output: r.output,
|
|
855
|
+
options: r.options,
|
|
856
|
+
durability: r.durability,
|
|
857
|
+
status: r.status,
|
|
858
|
+
error: r.error,
|
|
859
|
+
requestId: r.requestId,
|
|
860
|
+
sessionId: r.sessionId,
|
|
861
|
+
workflowId: r.workflowId,
|
|
862
|
+
createdAt: r.createdAt,
|
|
863
|
+
startedAt: r.startedAt,
|
|
864
|
+
completedAt: r.completedAt,
|
|
865
|
+
duration: r.duration,
|
|
866
|
+
retryCount: 0, // Would need to track this in DB
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
async list(options = {}) {
|
|
870
|
+
const conditions = [];
|
|
871
|
+
if (options.target)
|
|
872
|
+
conditions.push(eq(schema.actions.target, options.target));
|
|
873
|
+
if (options.actor)
|
|
874
|
+
conditions.push(eq(schema.actions.actor, options.actor));
|
|
875
|
+
if (options.status)
|
|
876
|
+
conditions.push(eq(schema.actions.status, options.status));
|
|
877
|
+
if (options.verb)
|
|
878
|
+
conditions.push(eq(schema.actions.verb, options.verb));
|
|
879
|
+
let results;
|
|
880
|
+
if (conditions.length > 0) {
|
|
881
|
+
results = await this.ctx.db
|
|
882
|
+
.select()
|
|
883
|
+
.from(schema.actions)
|
|
884
|
+
.where(conditions.length === 1 ? conditions[0] : and(...conditions));
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
results = await this.ctx.db
|
|
888
|
+
.select()
|
|
889
|
+
.from(schema.actions);
|
|
890
|
+
}
|
|
891
|
+
return results.map((r) => ({
|
|
892
|
+
id: r.id,
|
|
893
|
+
verb: r.verb,
|
|
894
|
+
target: r.target,
|
|
895
|
+
actor: r.actor,
|
|
896
|
+
input: r.input ?? r.options,
|
|
897
|
+
output: r.output,
|
|
898
|
+
options: r.options,
|
|
899
|
+
durability: r.durability,
|
|
900
|
+
status: r.status,
|
|
901
|
+
error: r.error,
|
|
902
|
+
requestId: r.requestId,
|
|
903
|
+
sessionId: r.sessionId,
|
|
904
|
+
workflowId: r.workflowId,
|
|
905
|
+
createdAt: r.createdAt,
|
|
906
|
+
startedAt: r.startedAt,
|
|
907
|
+
completedAt: r.completedAt,
|
|
908
|
+
duration: r.duration,
|
|
909
|
+
retryCount: 0,
|
|
910
|
+
}));
|
|
911
|
+
}
|
|
912
|
+
async pending() {
|
|
913
|
+
return this.list({ status: 'pending' });
|
|
914
|
+
}
|
|
915
|
+
async failed() {
|
|
916
|
+
return this.list({ status: 'failed' });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
// ============================================================================
|
|
920
|
+
// EVENTS STORE
|
|
921
|
+
// ============================================================================
|
|
922
|
+
export class EventsStore {
|
|
923
|
+
ctx;
|
|
924
|
+
sequenceCounter = 0;
|
|
925
|
+
constructor(ctx) {
|
|
926
|
+
this.ctx = ctx;
|
|
927
|
+
}
|
|
928
|
+
async emit(options) {
|
|
929
|
+
const id = crypto.randomUUID();
|
|
930
|
+
const now = new Date();
|
|
931
|
+
this.sequenceCounter++;
|
|
932
|
+
// Insert event (best-effort)
|
|
933
|
+
try {
|
|
934
|
+
await this.ctx.db.insert(schema.events).values({
|
|
935
|
+
id,
|
|
936
|
+
verb: options.verb,
|
|
937
|
+
source: options.source,
|
|
938
|
+
data: options.data,
|
|
939
|
+
actionId: options.actionId ?? null,
|
|
940
|
+
sequence: this.sequenceCounter,
|
|
941
|
+
streamed: false,
|
|
942
|
+
createdAt: now,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
catch (error) {
|
|
946
|
+
logBestEffortError(error, {
|
|
947
|
+
operation: 'insert',
|
|
948
|
+
source: 'EventsStore.emit',
|
|
949
|
+
context: { id, verb: options.verb, ns: this.ctx.ns },
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
// Stream to Pipeline if configured
|
|
953
|
+
if (this.ctx.env.PIPELINE) {
|
|
954
|
+
try {
|
|
955
|
+
await this.ctx.env.PIPELINE.send([{
|
|
956
|
+
id,
|
|
957
|
+
verb: options.verb,
|
|
958
|
+
source: options.source,
|
|
959
|
+
$context: this.ctx.ns,
|
|
960
|
+
data: options.data,
|
|
961
|
+
timestamp: now.toISOString(),
|
|
962
|
+
}]);
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
logBestEffortError(error, {
|
|
966
|
+
operation: 'stream',
|
|
967
|
+
source: 'EventsStore.emit',
|
|
968
|
+
context: { id, verb: options.verb, ns: this.ctx.ns },
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return {
|
|
973
|
+
id,
|
|
974
|
+
verb: options.verb,
|
|
975
|
+
source: options.source,
|
|
976
|
+
data: options.data,
|
|
977
|
+
actionId: options.actionId ?? null,
|
|
978
|
+
sequence: this.sequenceCounter,
|
|
979
|
+
streamed: false,
|
|
980
|
+
createdAt: now,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async stream(id) {
|
|
984
|
+
// Get the event
|
|
985
|
+
const event = await this.get(id);
|
|
986
|
+
if (!event) {
|
|
987
|
+
throw new Error(`Event '${id}' not found`);
|
|
988
|
+
}
|
|
989
|
+
const now = new Date();
|
|
990
|
+
// Send to pipeline
|
|
991
|
+
if (this.ctx.env.PIPELINE) {
|
|
992
|
+
await this.ctx.env.PIPELINE.send([{
|
|
993
|
+
id: event.id,
|
|
994
|
+
verb: event.verb,
|
|
995
|
+
source: event.source,
|
|
996
|
+
$context: this.ctx.ns,
|
|
997
|
+
data: event.data,
|
|
998
|
+
timestamp: now.toISOString(),
|
|
999
|
+
}]);
|
|
1000
|
+
}
|
|
1001
|
+
// Mark as streamed
|
|
1002
|
+
await this.ctx.db
|
|
1003
|
+
.update(schema.events)
|
|
1004
|
+
.set({
|
|
1005
|
+
streamed: true,
|
|
1006
|
+
streamedAt: now,
|
|
1007
|
+
})
|
|
1008
|
+
.where(eq(schema.events.id, id));
|
|
1009
|
+
return {
|
|
1010
|
+
...event,
|
|
1011
|
+
streamed: true,
|
|
1012
|
+
streamedAt: now,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
async streamPending() {
|
|
1016
|
+
const pending = await this.ctx.db
|
|
1017
|
+
.select()
|
|
1018
|
+
.from(schema.events)
|
|
1019
|
+
.where(eq(schema.events.streamed, false));
|
|
1020
|
+
let count = 0;
|
|
1021
|
+
for (const event of pending) {
|
|
1022
|
+
await this.stream(event.id);
|
|
1023
|
+
count++;
|
|
1024
|
+
}
|
|
1025
|
+
return count;
|
|
1026
|
+
}
|
|
1027
|
+
async get(id) {
|
|
1028
|
+
const results = await this.ctx.db
|
|
1029
|
+
.select()
|
|
1030
|
+
.from(schema.events)
|
|
1031
|
+
.where(eq(schema.events.id, id))
|
|
1032
|
+
.limit(1);
|
|
1033
|
+
if (results.length === 0)
|
|
1034
|
+
return null;
|
|
1035
|
+
const r = results[0];
|
|
1036
|
+
return {
|
|
1037
|
+
id: r.id,
|
|
1038
|
+
verb: r.verb,
|
|
1039
|
+
source: r.source,
|
|
1040
|
+
data: r.data,
|
|
1041
|
+
actionId: r.actionId,
|
|
1042
|
+
sequence: r.sequence,
|
|
1043
|
+
streamed: !!r.streamed,
|
|
1044
|
+
streamedAt: r.streamedAt ?? undefined,
|
|
1045
|
+
createdAt: r.createdAt,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
async list(options = {}) {
|
|
1049
|
+
const conditions = [];
|
|
1050
|
+
if (options.source)
|
|
1051
|
+
conditions.push(eq(schema.events.source, options.source));
|
|
1052
|
+
if (options.verb)
|
|
1053
|
+
conditions.push(eq(schema.events.verb, options.verb));
|
|
1054
|
+
let query = this.ctx.db.select().from(schema.events);
|
|
1055
|
+
let results;
|
|
1056
|
+
if (conditions.length > 0) {
|
|
1057
|
+
results = await query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
results = await query;
|
|
1061
|
+
}
|
|
1062
|
+
// Filter by afterSequence if specified
|
|
1063
|
+
if (options.afterSequence !== undefined) {
|
|
1064
|
+
results = results.filter((r) => r.sequence > options.afterSequence);
|
|
1065
|
+
}
|
|
1066
|
+
// Sort
|
|
1067
|
+
if (options.orderBy === 'sequence') {
|
|
1068
|
+
results.sort((a, b) => {
|
|
1069
|
+
if (options.order === 'desc') {
|
|
1070
|
+
return b.sequence - a.sequence;
|
|
1071
|
+
}
|
|
1072
|
+
return a.sequence - b.sequence;
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
return results.map((r) => ({
|
|
1076
|
+
id: r.id,
|
|
1077
|
+
verb: r.verb,
|
|
1078
|
+
source: r.source,
|
|
1079
|
+
data: r.data,
|
|
1080
|
+
actionId: r.actionId,
|
|
1081
|
+
sequence: r.sequence,
|
|
1082
|
+
streamed: !!r.streamed,
|
|
1083
|
+
streamedAt: r.streamedAt ?? undefined,
|
|
1084
|
+
createdAt: r.createdAt,
|
|
1085
|
+
}));
|
|
1086
|
+
}
|
|
1087
|
+
async replay(options) {
|
|
1088
|
+
const limit = options.limit ?? 100;
|
|
1089
|
+
const results = await this.ctx.db
|
|
1090
|
+
.select()
|
|
1091
|
+
.from(schema.events)
|
|
1092
|
+
.orderBy(asc(schema.events.sequence))
|
|
1093
|
+
.limit(limit);
|
|
1094
|
+
return results
|
|
1095
|
+
.filter((r) => r.sequence >= options.fromSequence)
|
|
1096
|
+
.map((r) => ({
|
|
1097
|
+
id: r.id,
|
|
1098
|
+
verb: r.verb,
|
|
1099
|
+
source: r.source,
|
|
1100
|
+
data: r.data,
|
|
1101
|
+
actionId: r.actionId,
|
|
1102
|
+
sequence: r.sequence,
|
|
1103
|
+
streamed: !!r.streamed,
|
|
1104
|
+
streamedAt: r.streamedAt ?? undefined,
|
|
1105
|
+
createdAt: r.createdAt,
|
|
1106
|
+
}));
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
// ============================================================================
|
|
1110
|
+
// SEARCH STORE
|
|
1111
|
+
// ============================================================================
|
|
1112
|
+
export class SearchStore {
|
|
1113
|
+
ctx;
|
|
1114
|
+
constructor(ctx) {
|
|
1115
|
+
this.ctx = ctx;
|
|
1116
|
+
}
|
|
1117
|
+
async index(entry) {
|
|
1118
|
+
const now = new Date();
|
|
1119
|
+
// Check if entry exists
|
|
1120
|
+
const existing = await this.ctx.db
|
|
1121
|
+
.select()
|
|
1122
|
+
.from(schema.search)
|
|
1123
|
+
.where(eq(schema.search.$id, entry.$id))
|
|
1124
|
+
.limit(1);
|
|
1125
|
+
if (existing.length > 0) {
|
|
1126
|
+
// Update existing
|
|
1127
|
+
await this.ctx.db
|
|
1128
|
+
.update(schema.search)
|
|
1129
|
+
.set({
|
|
1130
|
+
$type: entry.$type,
|
|
1131
|
+
content: entry.content,
|
|
1132
|
+
indexedAt: now,
|
|
1133
|
+
})
|
|
1134
|
+
.where(eq(schema.search.$id, entry.$id));
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
// Insert new
|
|
1138
|
+
await this.ctx.db.insert(schema.search).values({
|
|
1139
|
+
$id: entry.$id,
|
|
1140
|
+
$type: entry.$type,
|
|
1141
|
+
content: entry.content,
|
|
1142
|
+
indexedAt: now,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
// TODO: Generate embedding if AI binding available
|
|
1146
|
+
return {
|
|
1147
|
+
$id: entry.$id,
|
|
1148
|
+
$type: entry.$type,
|
|
1149
|
+
content: entry.content,
|
|
1150
|
+
indexedAt: now,
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
async indexMany(entries) {
|
|
1154
|
+
const results = [];
|
|
1155
|
+
for (const entry of entries) {
|
|
1156
|
+
results.push(await this.index(entry));
|
|
1157
|
+
}
|
|
1158
|
+
return results;
|
|
1159
|
+
}
|
|
1160
|
+
async remove(id) {
|
|
1161
|
+
await this.ctx.db
|
|
1162
|
+
.delete(schema.search)
|
|
1163
|
+
.where(eq(schema.search.$id, id));
|
|
1164
|
+
}
|
|
1165
|
+
async removeMany(ids) {
|
|
1166
|
+
let count = 0;
|
|
1167
|
+
for (const id of ids) {
|
|
1168
|
+
const existing = await this.ctx.db
|
|
1169
|
+
.select()
|
|
1170
|
+
.from(schema.search)
|
|
1171
|
+
.where(eq(schema.search.$id, id))
|
|
1172
|
+
.limit(1);
|
|
1173
|
+
if (existing.length > 0) {
|
|
1174
|
+
await this.remove(id);
|
|
1175
|
+
count++;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return count;
|
|
1179
|
+
}
|
|
1180
|
+
async query(text, options = {}) {
|
|
1181
|
+
const limit = options.limit ?? 10;
|
|
1182
|
+
// Simple text search using LIKE
|
|
1183
|
+
let results = await this.ctx.db
|
|
1184
|
+
.select()
|
|
1185
|
+
.from(schema.search)
|
|
1186
|
+
.limit(limit * 2); // Get more to allow filtering
|
|
1187
|
+
// Filter by content match
|
|
1188
|
+
const searchTerms = text.toLowerCase().split(/\s+/);
|
|
1189
|
+
results = results.filter((r) => {
|
|
1190
|
+
const content = r.content.toLowerCase();
|
|
1191
|
+
return searchTerms.some((term) => content.includes(term));
|
|
1192
|
+
});
|
|
1193
|
+
// Filter by type if specified
|
|
1194
|
+
if (options.type) {
|
|
1195
|
+
results = results.filter((r) => r.$type === options.type);
|
|
1196
|
+
}
|
|
1197
|
+
// Limit
|
|
1198
|
+
results = results.slice(0, limit);
|
|
1199
|
+
// Calculate simple relevance scores
|
|
1200
|
+
return results.map((r) => {
|
|
1201
|
+
const content = r.content.toLowerCase();
|
|
1202
|
+
let score = 0;
|
|
1203
|
+
for (const term of searchTerms) {
|
|
1204
|
+
if (content.includes(term)) {
|
|
1205
|
+
score += 1;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
score = score / searchTerms.length; // Normalize
|
|
1209
|
+
return {
|
|
1210
|
+
$id: r.$id,
|
|
1211
|
+
$type: r.$type,
|
|
1212
|
+
content: r.content,
|
|
1213
|
+
embedding: r.embedding ?? null,
|
|
1214
|
+
embeddingDim: r.embeddingDim ?? undefined,
|
|
1215
|
+
indexedAt: r.indexedAt,
|
|
1216
|
+
score,
|
|
1217
|
+
};
|
|
1218
|
+
}).sort((a, b) => b.score - a.score);
|
|
1219
|
+
}
|
|
1220
|
+
async semantic(text, options = {}) {
|
|
1221
|
+
// For now, fall back to text search
|
|
1222
|
+
// Would use vector similarity with embeddings in production
|
|
1223
|
+
return this.query(text, options);
|
|
1224
|
+
}
|
|
1225
|
+
async reindexType(type) {
|
|
1226
|
+
// Get all things of this type from things store
|
|
1227
|
+
// Re-index them
|
|
1228
|
+
// For now, just return 0
|
|
1229
|
+
return 0;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
// ============================================================================
|
|
1233
|
+
// OBJECTS STORE
|
|
1234
|
+
// ============================================================================
|
|
1235
|
+
export class ObjectsStore {
|
|
1236
|
+
ctx;
|
|
1237
|
+
constructor(ctx) {
|
|
1238
|
+
this.ctx = ctx;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Create a new object registration (alias for register with test-friendly signature)
|
|
1242
|
+
*/
|
|
1243
|
+
async create(options) {
|
|
1244
|
+
return this.register({
|
|
1245
|
+
ns: options.ns,
|
|
1246
|
+
id: options.doId,
|
|
1247
|
+
class: options.doClass,
|
|
1248
|
+
relation: options.relation,
|
|
1249
|
+
shardKey: options.shardKey,
|
|
1250
|
+
shardIndex: options.shardIndex,
|
|
1251
|
+
region: options.region,
|
|
1252
|
+
colo: options.colo,
|
|
1253
|
+
primary: options.primary,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
async register(options) {
|
|
1257
|
+
const now = new Date();
|
|
1258
|
+
// Check for duplicate
|
|
1259
|
+
const existing = await this.get(options.ns);
|
|
1260
|
+
if (existing) {
|
|
1261
|
+
throw new Error(`Object with ns '${options.ns}' already exists`);
|
|
1262
|
+
}
|
|
1263
|
+
// Validate relation type if provided
|
|
1264
|
+
const validRelations = ['parent', 'child', 'follower', 'shard', 'reference'];
|
|
1265
|
+
const relation = options.relation;
|
|
1266
|
+
await this.ctx.db.insert(schema.objects).values({
|
|
1267
|
+
ns: options.ns,
|
|
1268
|
+
id: options.id,
|
|
1269
|
+
class: options.class,
|
|
1270
|
+
relation: relation ?? null,
|
|
1271
|
+
shardKey: options.shardKey ?? null,
|
|
1272
|
+
shardIndex: options.shardIndex ?? null,
|
|
1273
|
+
region: options.region ?? null,
|
|
1274
|
+
primary: options.primary ?? null,
|
|
1275
|
+
createdAt: now,
|
|
1276
|
+
});
|
|
1277
|
+
return {
|
|
1278
|
+
ns: options.ns,
|
|
1279
|
+
id: options.id,
|
|
1280
|
+
class: options.class,
|
|
1281
|
+
relation: options.relation ?? null,
|
|
1282
|
+
shardKey: options.shardKey ?? null,
|
|
1283
|
+
shardIndex: options.shardIndex ?? null,
|
|
1284
|
+
region: options.region ?? null,
|
|
1285
|
+
colo: options.colo ?? null,
|
|
1286
|
+
primary: options.primary ?? null,
|
|
1287
|
+
createdAt: now,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
async get(ns) {
|
|
1291
|
+
const results = await this.ctx.db
|
|
1292
|
+
.select()
|
|
1293
|
+
.from(schema.objects)
|
|
1294
|
+
.where(eq(schema.objects.ns, ns))
|
|
1295
|
+
.limit(1);
|
|
1296
|
+
if (results.length === 0)
|
|
1297
|
+
return null;
|
|
1298
|
+
const r = results[0];
|
|
1299
|
+
// Extract colo from cached data if available (stored there since not in schema)
|
|
1300
|
+
const cached = r.cached;
|
|
1301
|
+
return {
|
|
1302
|
+
ns: r.ns,
|
|
1303
|
+
id: r.id,
|
|
1304
|
+
class: r.class,
|
|
1305
|
+
relation: r.relation,
|
|
1306
|
+
shardKey: r.shardKey,
|
|
1307
|
+
shardIndex: r.shardIndex,
|
|
1308
|
+
region: r.region,
|
|
1309
|
+
colo: cached?.colo ?? null,
|
|
1310
|
+
primary: r.primary,
|
|
1311
|
+
cached: cached,
|
|
1312
|
+
createdAt: r.createdAt,
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
async list(options = {}) {
|
|
1316
|
+
const conditions = [];
|
|
1317
|
+
if (options.relation)
|
|
1318
|
+
conditions.push(eq(schema.objects.relation, options.relation));
|
|
1319
|
+
if (options.class)
|
|
1320
|
+
conditions.push(eq(schema.objects.class, options.class));
|
|
1321
|
+
if (options.region)
|
|
1322
|
+
conditions.push(eq(schema.objects.region, options.region));
|
|
1323
|
+
let results;
|
|
1324
|
+
if (conditions.length > 0) {
|
|
1325
|
+
results = await this.ctx.db
|
|
1326
|
+
.select()
|
|
1327
|
+
.from(schema.objects)
|
|
1328
|
+
.where(conditions.length === 1 ? conditions[0] : and(...conditions));
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
results = await this.ctx.db
|
|
1332
|
+
.select()
|
|
1333
|
+
.from(schema.objects);
|
|
1334
|
+
}
|
|
1335
|
+
// Map results and filter by colo if specified (colo is stored in cached field)
|
|
1336
|
+
let mapped = results.map((r) => {
|
|
1337
|
+
const cached = r.cached;
|
|
1338
|
+
return {
|
|
1339
|
+
ns: r.ns,
|
|
1340
|
+
id: r.id,
|
|
1341
|
+
class: r.class,
|
|
1342
|
+
relation: r.relation,
|
|
1343
|
+
shardKey: r.shardKey,
|
|
1344
|
+
shardIndex: r.shardIndex,
|
|
1345
|
+
region: r.region,
|
|
1346
|
+
colo: cached?.colo ?? null,
|
|
1347
|
+
primary: r.primary,
|
|
1348
|
+
cached: cached,
|
|
1349
|
+
createdAt: r.createdAt,
|
|
1350
|
+
};
|
|
1351
|
+
});
|
|
1352
|
+
// Filter by colo if specified (done in JS since colo is in cached JSON)
|
|
1353
|
+
if (options.colo) {
|
|
1354
|
+
mapped = mapped.filter((r) => r.colo === options.colo);
|
|
1355
|
+
}
|
|
1356
|
+
return mapped;
|
|
1357
|
+
}
|
|
1358
|
+
async shards(key) {
|
|
1359
|
+
const results = await this.ctx.db
|
|
1360
|
+
.select()
|
|
1361
|
+
.from(schema.objects)
|
|
1362
|
+
.where(eq(schema.objects.shardKey, key));
|
|
1363
|
+
return results.map((r) => {
|
|
1364
|
+
const cached = r.cached;
|
|
1365
|
+
return {
|
|
1366
|
+
ns: r.ns,
|
|
1367
|
+
id: r.id,
|
|
1368
|
+
class: r.class,
|
|
1369
|
+
relation: r.relation,
|
|
1370
|
+
shardKey: r.shardKey,
|
|
1371
|
+
shardIndex: r.shardIndex,
|
|
1372
|
+
region: r.region,
|
|
1373
|
+
colo: cached?.colo ?? null,
|
|
1374
|
+
primary: r.primary,
|
|
1375
|
+
cached: cached,
|
|
1376
|
+
createdAt: r.createdAt,
|
|
1377
|
+
};
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
async primary(ns) {
|
|
1381
|
+
const obj = await this.get(ns);
|
|
1382
|
+
if (obj && obj.primary) {
|
|
1383
|
+
return obj;
|
|
1384
|
+
}
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
async update(ns, data) {
|
|
1388
|
+
const existing = await this.get(ns);
|
|
1389
|
+
if (!existing) {
|
|
1390
|
+
throw new Error(`Object '${ns}' not found`);
|
|
1391
|
+
}
|
|
1392
|
+
await this.ctx.db
|
|
1393
|
+
.update(schema.objects)
|
|
1394
|
+
.set({
|
|
1395
|
+
cached: data.cached ?? existing.cached,
|
|
1396
|
+
region: data.region ?? existing.region,
|
|
1397
|
+
primary: data.primary ?? existing.primary,
|
|
1398
|
+
})
|
|
1399
|
+
.where(eq(schema.objects.ns, ns));
|
|
1400
|
+
return {
|
|
1401
|
+
...existing,
|
|
1402
|
+
cached: data.cached ?? existing.cached,
|
|
1403
|
+
region: data.region ?? existing.region,
|
|
1404
|
+
primary: data.primary ?? existing.primary,
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
async delete(ns) {
|
|
1408
|
+
const existing = await this.get(ns);
|
|
1409
|
+
if (!existing) {
|
|
1410
|
+
throw new Error(`Object '${ns}' not found`);
|
|
1411
|
+
}
|
|
1412
|
+
await this.ctx.db
|
|
1413
|
+
.delete(schema.objects)
|
|
1414
|
+
.where(eq(schema.objects.ns, ns));
|
|
1415
|
+
}
|
|
1416
|
+
async resolve(ns) {
|
|
1417
|
+
const obj = await this.get(ns);
|
|
1418
|
+
if (!obj) {
|
|
1419
|
+
throw new Error(`Object '${ns}' not found`);
|
|
1420
|
+
}
|
|
1421
|
+
if (!this.ctx.env.DO) {
|
|
1422
|
+
throw new Error('DO namespace not available');
|
|
1423
|
+
}
|
|
1424
|
+
const doId = this.ctx.env.DO.idFromString(obj.id);
|
|
1425
|
+
return this.ctx.env.DO.get(doId);
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Get an object from R2 SQL global registry (fallback for cross-DO resolution)
|
|
1429
|
+
*
|
|
1430
|
+
* This is called when the local objects table doesn't have the namespace.
|
|
1431
|
+
* The R2 SQL database stores a global registry of all namespaces across DOs.
|
|
1432
|
+
*
|
|
1433
|
+
* @param ns - The namespace to look up
|
|
1434
|
+
* @returns The object entity if found, null otherwise
|
|
1435
|
+
*/
|
|
1436
|
+
async getGlobal(ns) {
|
|
1437
|
+
// Check if R2 SQL binding is available
|
|
1438
|
+
const r2Sql = this.ctx.env.R2_SQL;
|
|
1439
|
+
if (!r2Sql) {
|
|
1440
|
+
// R2 SQL not configured, return null
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
try {
|
|
1444
|
+
// Query the global objects registry in R2 SQL
|
|
1445
|
+
const result = await r2Sql.exec('SELECT ns, id, class, relation, shard_key, shard_index, region, "primary", cached, created_at FROM objects WHERE ns = ?', [ns]);
|
|
1446
|
+
if (!result.results || result.results.length === 0) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const r = result.results[0];
|
|
1450
|
+
return {
|
|
1451
|
+
ns: r.ns,
|
|
1452
|
+
id: r.id,
|
|
1453
|
+
class: r.class,
|
|
1454
|
+
relation: r.relation,
|
|
1455
|
+
shardKey: r.shard_key,
|
|
1456
|
+
shardIndex: r.shard_index,
|
|
1457
|
+
region: r.region,
|
|
1458
|
+
primary: r.primary,
|
|
1459
|
+
cached: r.cached
|
|
1460
|
+
? safeJsonParse(r.cached, null, { context: 'ObjectsStore.getGlobal' })
|
|
1461
|
+
: null,
|
|
1462
|
+
createdAt: new Date(r.created_at),
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
catch (error) {
|
|
1466
|
+
// R2 SQL query failed, return null (fallback behavior)
|
|
1467
|
+
console.error('R2 SQL global lookup failed:', error);
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
// ============================================================================
|
|
1473
|
+
// DLQ STORE
|
|
1474
|
+
// ============================================================================
|
|
1475
|
+
export class DLQStore {
|
|
1476
|
+
db;
|
|
1477
|
+
ns;
|
|
1478
|
+
eventHandlers;
|
|
1479
|
+
constructor(ctx, eventHandlers) {
|
|
1480
|
+
this.db = ctx.db;
|
|
1481
|
+
this.ns = ctx.ns;
|
|
1482
|
+
this.eventHandlers = eventHandlers || new Map();
|
|
1483
|
+
}
|
|
1484
|
+
registerHandler(verb, handler) {
|
|
1485
|
+
this.eventHandlers.set(verb, handler);
|
|
1486
|
+
}
|
|
1487
|
+
async add(options) {
|
|
1488
|
+
const id = crypto.randomUUID();
|
|
1489
|
+
const now = new Date();
|
|
1490
|
+
await this.db.insert(schema.dlq).values({
|
|
1491
|
+
id,
|
|
1492
|
+
eventId: options.eventId ?? null,
|
|
1493
|
+
verb: options.verb,
|
|
1494
|
+
source: options.source,
|
|
1495
|
+
data: options.data,
|
|
1496
|
+
error: options.error,
|
|
1497
|
+
errorStack: options.errorStack ?? null,
|
|
1498
|
+
retryCount: 0,
|
|
1499
|
+
maxRetries: options.maxRetries ?? 3,
|
|
1500
|
+
lastAttemptAt: now,
|
|
1501
|
+
createdAt: now,
|
|
1502
|
+
});
|
|
1503
|
+
return {
|
|
1504
|
+
id,
|
|
1505
|
+
eventId: options.eventId ?? null,
|
|
1506
|
+
verb: options.verb,
|
|
1507
|
+
source: options.source,
|
|
1508
|
+
data: options.data,
|
|
1509
|
+
error: options.error,
|
|
1510
|
+
errorStack: options.errorStack ?? null,
|
|
1511
|
+
retryCount: 0,
|
|
1512
|
+
maxRetries: options.maxRetries ?? 3,
|
|
1513
|
+
lastAttemptAt: now,
|
|
1514
|
+
createdAt: now,
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
async get(id) {
|
|
1518
|
+
const results = await this.db.select().from(schema.dlq);
|
|
1519
|
+
const result = results.find((r) => r.id === id);
|
|
1520
|
+
if (!result)
|
|
1521
|
+
return null;
|
|
1522
|
+
return this.toEntity(result);
|
|
1523
|
+
}
|
|
1524
|
+
async list(options) {
|
|
1525
|
+
const results = await this.db.select().from(schema.dlq);
|
|
1526
|
+
let filtered = results;
|
|
1527
|
+
if (options?.verb) {
|
|
1528
|
+
filtered = filtered.filter((r) => r.verb === options.verb);
|
|
1529
|
+
}
|
|
1530
|
+
if (options?.source) {
|
|
1531
|
+
filtered = filtered.filter((r) => r.source === options.source);
|
|
1532
|
+
}
|
|
1533
|
+
if (options?.minRetries !== undefined) {
|
|
1534
|
+
filtered = filtered.filter((r) => r.retryCount >= options.minRetries);
|
|
1535
|
+
}
|
|
1536
|
+
if (options?.maxRetries !== undefined) {
|
|
1537
|
+
filtered = filtered.filter((r) => r.retryCount <= options.maxRetries);
|
|
1538
|
+
}
|
|
1539
|
+
const offset = options?.offset ?? 0;
|
|
1540
|
+
const limit = options?.limit ?? filtered.length;
|
|
1541
|
+
filtered = filtered.slice(offset, offset + limit);
|
|
1542
|
+
return filtered.map((r) => this.toEntity(r));
|
|
1543
|
+
}
|
|
1544
|
+
async count() {
|
|
1545
|
+
const results = await this.db.select().from(schema.dlq);
|
|
1546
|
+
return results.length;
|
|
1547
|
+
}
|
|
1548
|
+
async incrementRetry(id) {
|
|
1549
|
+
const entry = await this.get(id);
|
|
1550
|
+
if (!entry) {
|
|
1551
|
+
throw new Error(`DLQ entry with id ${id} not found`);
|
|
1552
|
+
}
|
|
1553
|
+
const now = new Date();
|
|
1554
|
+
const newRetryCount = entry.retryCount + 1;
|
|
1555
|
+
return {
|
|
1556
|
+
...entry,
|
|
1557
|
+
retryCount: newRetryCount,
|
|
1558
|
+
lastAttemptAt: now,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
async replay(id) {
|
|
1562
|
+
const entry = await this.get(id);
|
|
1563
|
+
if (!entry) {
|
|
1564
|
+
return { success: false, error: `DLQ entry with id ${id} not found` };
|
|
1565
|
+
}
|
|
1566
|
+
await this.incrementRetry(id);
|
|
1567
|
+
const handler = this.eventHandlers.get(entry.verb);
|
|
1568
|
+
if (!handler) {
|
|
1569
|
+
return { success: false, error: `No handler registered for verb: ${entry.verb}` };
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const result = await handler(entry.data);
|
|
1573
|
+
await this.remove(id);
|
|
1574
|
+
return { success: true, result };
|
|
1575
|
+
}
|
|
1576
|
+
catch (error) {
|
|
1577
|
+
return {
|
|
1578
|
+
success: false,
|
|
1579
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
async replayAll(options) {
|
|
1584
|
+
const entries = await this.list({
|
|
1585
|
+
verb: options?.verb,
|
|
1586
|
+
source: options?.source,
|
|
1587
|
+
});
|
|
1588
|
+
let replayed = 0;
|
|
1589
|
+
let failed = 0;
|
|
1590
|
+
for (const entry of entries) {
|
|
1591
|
+
const result = await this.replay(entry.id);
|
|
1592
|
+
if (result.success) {
|
|
1593
|
+
replayed++;
|
|
1594
|
+
}
|
|
1595
|
+
else {
|
|
1596
|
+
failed++;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return { replayed, failed };
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Retry a DLQ entry by replaying it.
|
|
1603
|
+
* Alias for replay() to match expected DLQStore interface.
|
|
1604
|
+
*/
|
|
1605
|
+
async retry(id) {
|
|
1606
|
+
return this.replay(id);
|
|
1607
|
+
}
|
|
1608
|
+
async remove(id) {
|
|
1609
|
+
const entry = await this.get(id);
|
|
1610
|
+
if (!entry) {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1615
|
+
async purgeExhausted() {
|
|
1616
|
+
const entries = await this.list();
|
|
1617
|
+
const exhausted = entries.filter((e) => e.retryCount >= e.maxRetries);
|
|
1618
|
+
let purged = 0;
|
|
1619
|
+
for (const entry of exhausted) {
|
|
1620
|
+
const removed = await this.remove(entry.id);
|
|
1621
|
+
if (removed) {
|
|
1622
|
+
purged++;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return purged;
|
|
1626
|
+
}
|
|
1627
|
+
toEntity(row) {
|
|
1628
|
+
return {
|
|
1629
|
+
id: row.id,
|
|
1630
|
+
eventId: row.eventId,
|
|
1631
|
+
verb: row.verb,
|
|
1632
|
+
source: row.source,
|
|
1633
|
+
data: row.data,
|
|
1634
|
+
error: row.error,
|
|
1635
|
+
errorStack: row.errorStack,
|
|
1636
|
+
retryCount: row.retryCount,
|
|
1637
|
+
maxRetries: row.maxRetries,
|
|
1638
|
+
lastAttemptAt: row.lastAttemptAt ?? null,
|
|
1639
|
+
createdAt: row.createdAt,
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
// ============================================================================
|
|
1644
|
+
// HELPER FUNCTIONS
|
|
1645
|
+
// ============================================================================
|
|
1646
|
+
function deepMerge(target, source) {
|
|
1647
|
+
const result = { ...target };
|
|
1648
|
+
for (const key of Object.keys(source)) {
|
|
1649
|
+
const sourceValue = source[key];
|
|
1650
|
+
const targetValue = target[key];
|
|
1651
|
+
if (sourceValue !== null &&
|
|
1652
|
+
typeof sourceValue === 'object' &&
|
|
1653
|
+
!Array.isArray(sourceValue) &&
|
|
1654
|
+
targetValue !== null &&
|
|
1655
|
+
typeof targetValue === 'object' &&
|
|
1656
|
+
!Array.isArray(targetValue)) {
|
|
1657
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
result[key] = sourceValue;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
//# sourceMappingURL=stores.js.map
|