dotdo 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +446 -315
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/index.js +19 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/template-literals.js +852 -0
- package/dist/ai/template-literals.js.map +1 -0
- package/dist/api/middleware/auth-federation.js +573 -0
- package/dist/api/middleware/auth-federation.js.map +1 -0
- package/dist/api/middleware/auth.js +545 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/db/actions.js +212 -0
- package/dist/db/actions.js.map +1 -0
- package/dist/db/auth.js +506 -0
- package/dist/db/auth.js.map +1 -0
- package/dist/db/branches.js +65 -0
- package/dist/db/branches.js.map +1 -0
- package/dist/db/clickhouse.js +1074 -0
- package/dist/db/clickhouse.js.map +1 -0
- package/dist/db/dlq.js +39 -0
- package/dist/db/dlq.js.map +1 -0
- package/dist/db/events.js +28 -0
- package/dist/db/events.js.map +1 -0
- package/dist/db/exec.js +64 -0
- package/dist/db/exec.js.map +1 -0
- package/dist/db/files.js +85 -0
- package/dist/db/files.js.map +1 -0
- package/dist/db/flags.js +24 -0
- package/dist/db/flags.js.map +1 -0
- package/dist/db/git.js +116 -0
- package/dist/db/git.js.map +1 -0
- package/dist/db/iceberg/inverted-index.js +862 -0
- package/dist/db/iceberg/inverted-index.js.map +1 -0
- package/dist/db/iceberg/puffin.js +878 -0
- package/dist/db/iceberg/puffin.js.map +1 -0
- package/dist/db/iceberg/search-manifest.js +422 -0
- package/dist/db/iceberg/search-manifest.js.map +1 -0
- package/dist/db/iceberg/types.js +8 -0
- package/dist/db/iceberg/types.js.map +1 -0
- package/dist/db/index.js +121 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/integrations.js +368 -0
- package/dist/db/integrations.js.map +1 -0
- package/dist/db/json-indexes.js +332 -0
- package/dist/db/json-indexes.js.map +1 -0
- package/dist/db/linked-accounts.js +287 -0
- package/dist/db/linked-accounts.js.map +1 -0
- package/dist/db/nouns.js +183 -0
- package/dist/db/nouns.js.map +1 -0
- package/dist/db/objects.js +170 -0
- package/dist/db/objects.js.map +1 -0
- package/dist/db/primitives/dag-scheduler/index.js +869 -0
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
- package/dist/db/primitives/exactly-once-context.js +237 -0
- package/dist/db/primitives/exactly-once-context.js.map +1 -0
- package/dist/db/primitives/index.js +62 -0
- package/dist/db/primitives/index.js.map +1 -0
- package/dist/db/primitives/keyed-router.js +145 -0
- package/dist/db/primitives/keyed-router.js.map +1 -0
- package/dist/db/primitives/observability.js +162 -0
- package/dist/db/primitives/observability.js.map +1 -0
- package/dist/db/primitives/schema-evolution.js +643 -0
- package/dist/db/primitives/schema-evolution.js.map +1 -0
- package/dist/db/primitives/stateful-operator/index.js +770 -0
- package/dist/db/primitives/stateful-operator/index.js.map +1 -0
- package/dist/db/primitives/temporal-store.js +306 -0
- package/dist/db/primitives/temporal-store.js.map +1 -0
- package/dist/db/primitives/typed-column-store.js +1229 -0
- package/dist/db/primitives/typed-column-store.js.map +1 -0
- package/dist/db/primitives/utils/duration.js +162 -0
- package/dist/db/primitives/utils/duration.js.map +1 -0
- package/dist/db/primitives/utils/murmur3.js +116 -0
- package/dist/db/primitives/utils/murmur3.js.map +1 -0
- package/dist/db/primitives/watermark-service.js +136 -0
- package/dist/db/primitives/watermark-service.js.map +1 -0
- package/dist/db/primitives/window-manager.js +764 -0
- package/dist/db/primitives/window-manager.js.map +1 -0
- package/dist/db/relationships.js +66 -0
- package/dist/db/relationships.js.map +1 -0
- package/dist/db/schema-minimal.js +61 -0
- package/dist/db/schema-minimal.js.map +1 -0
- package/dist/db/search.js +28 -0
- package/dist/db/search.js.map +1 -0
- package/dist/db/stores.js +1665 -0
- package/dist/db/stores.js.map +1 -0
- package/dist/db/things.js +297 -0
- package/dist/db/things.js.map +1 -0
- package/dist/db/vault.js +171 -0
- package/dist/db/vault.js.map +1 -0
- package/dist/db/verbs.js +102 -0
- package/dist/db/verbs.js.map +1 -0
- package/dist/do/base.js +48 -0
- package/dist/do/base.js.map +1 -0
- package/dist/do/tiny.js +31 -0
- package/dist/do/tiny.js.map +1 -0
- package/dist/lib/DOAuth.js +261 -0
- package/dist/lib/DOAuth.js.map +1 -0
- package/dist/lib/DODispatcher.js +72 -0
- package/dist/lib/DODispatcher.js.map +1 -0
- package/dist/lib/Modifier.js +189 -0
- package/dist/lib/Modifier.js.map +1 -0
- package/dist/lib/StateStorage.js +403 -0
- package/dist/lib/StateStorage.js.map +1 -0
- package/dist/lib/TypeRegistry.js +122 -0
- package/dist/lib/TypeRegistry.js.map +1 -0
- package/dist/lib/ai/gateway.js +247 -0
- package/dist/lib/ai/gateway.js.map +1 -0
- package/dist/lib/ai/tool-loop-agent.js +591 -0
- package/dist/lib/ai/tool-loop-agent.js.map +1 -0
- package/dist/lib/auto-wiring.js +439 -0
- package/dist/lib/auto-wiring.js.map +1 -0
- package/dist/lib/browse/browserbase.js +163 -0
- package/dist/lib/browse/browserbase.js.map +1 -0
- package/dist/lib/browse/cloudflare.js +144 -0
- package/dist/lib/browse/cloudflare.js.map +1 -0
- package/dist/lib/browse/index.js +62 -0
- package/dist/lib/browse/index.js.map +1 -0
- package/dist/lib/browse/types.js +13 -0
- package/dist/lib/browse/types.js.map +1 -0
- package/dist/lib/cache/index.js +37 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/visibility.js +638 -0
- package/dist/lib/cache/visibility.js.map +1 -0
- package/dist/lib/capabilities.js +268 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/channels/base.js +106 -0
- package/dist/lib/channels/base.js.map +1 -0
- package/dist/lib/channels/discord.js +94 -0
- package/dist/lib/channels/discord.js.map +1 -0
- package/dist/lib/channels/email.js +204 -0
- package/dist/lib/channels/email.js.map +1 -0
- package/dist/lib/channels/index.js +90 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/mdxui-chat.js +95 -0
- package/dist/lib/channels/mdxui-chat.js.map +1 -0
- package/dist/lib/channels/slack-blockkit.js +121 -0
- package/dist/lib/channels/slack-blockkit.js.map +1 -0
- package/dist/lib/channels/types.js +7 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/cloudflare/ai.js +654 -0
- package/dist/lib/cloudflare/ai.js.map +1 -0
- package/dist/lib/cloudflare/index.js +88 -0
- package/dist/lib/cloudflare/index.js.map +1 -0
- package/dist/lib/cloudflare/kv.js +342 -0
- package/dist/lib/cloudflare/kv.js.map +1 -0
- package/dist/lib/cloudflare/queues.js +434 -0
- package/dist/lib/cloudflare/queues.js.map +1 -0
- package/dist/lib/cloudflare/r2.js +604 -0
- package/dist/lib/cloudflare/r2.js.map +1 -0
- package/dist/lib/cloudflare/vectorize.js +494 -0
- package/dist/lib/cloudflare/vectorize.js.map +1 -0
- package/dist/lib/cloudflare/workflows.js +569 -0
- package/dist/lib/cloudflare/workflows.js.map +1 -0
- package/dist/lib/colo/caching.js +196 -0
- package/dist/lib/colo/caching.js.map +1 -0
- package/dist/lib/colo/detection.js +194 -0
- package/dist/lib/colo/detection.js.map +1 -0
- package/dist/lib/colo/external-data.js +219 -0
- package/dist/lib/colo/external-data.js.map +1 -0
- package/dist/lib/colo/globe-data.js +179 -0
- package/dist/lib/colo/globe-data.js.map +1 -0
- package/dist/lib/colo/index.js +16 -0
- package/dist/lib/colo/index.js.map +1 -0
- package/dist/lib/decorators.js +37 -0
- package/dist/lib/decorators.js.map +1 -0
- package/dist/lib/discovery.js +81 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
- package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/CascadeExecutor.js +418 -0
- package/dist/lib/executors/CascadeExecutor.js.map +1 -0
- package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/ParallelStepExecutor.js +308 -0
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
- package/dist/lib/executors/types.js +12 -0
- package/dist/lib/executors/types.js.map +1 -0
- package/dist/lib/experiments.js +89 -0
- package/dist/lib/experiments.js.map +1 -0
- package/dist/lib/flags/store.js +262 -0
- package/dist/lib/flags/store.js.map +1 -0
- package/dist/lib/functions/FunctionComposition.js +467 -0
- package/dist/lib/functions/FunctionComposition.js.map +1 -0
- package/dist/lib/functions/FunctionMiddleware.js +457 -0
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
- package/dist/lib/functions/FunctionRegistry.js +426 -0
- package/dist/lib/functions/FunctionRegistry.js.map +1 -0
- package/dist/lib/functions/createFunction.js +1048 -0
- package/dist/lib/functions/createFunction.js.map +1 -0
- package/dist/lib/humans/index.js +68 -0
- package/dist/lib/humans/index.js.map +1 -0
- package/dist/lib/humans/templates.js +117 -0
- package/dist/lib/humans/templates.js.map +1 -0
- package/dist/lib/identity.js +98 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging/error-logger.js +163 -0
- package/dist/lib/logging/error-logger.js.map +1 -0
- package/dist/lib/logging/index.js +160 -0
- package/dist/lib/logging/index.js.map +1 -0
- package/dist/lib/mixins/bash.js +753 -0
- package/dist/lib/mixins/bash.js.map +1 -0
- package/dist/lib/mixins/fs.js +648 -0
- package/dist/lib/mixins/fs.js.map +1 -0
- package/dist/lib/mixins/git.js +1006 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/npm.js +662 -0
- package/dist/lib/mixins/npm.js.map +1 -0
- package/dist/lib/noun-id.js +278 -0
- package/dist/lib/noun-id.js.map +1 -0
- package/dist/lib/rate-limit/sliding-window.js +148 -0
- package/dist/lib/rate-limit/sliding-window.js.map +1 -0
- package/dist/lib/rate-limit.js +110 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/rpc/bindings.js +548 -0
- package/dist/lib/rpc/bindings.js.map +1 -0
- package/dist/lib/rpc/index.js +64 -0
- package/dist/lib/rpc/index.js.map +1 -0
- package/dist/lib/safe-stringify.js +223 -0
- package/dist/lib/safe-stringify.js.map +1 -0
- package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
- package/dist/lib/sqids.js +110 -0
- package/dist/lib/sqids.js.map +1 -0
- package/dist/lib/sql/adapters/index.js +10 -0
- package/dist/lib/sql/adapters/index.js.map +1 -0
- package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
- package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
- package/dist/lib/sql/index.js +277 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql/types.js +56 -0
- package/dist/lib/sql/types.js.map +1 -0
- package/dist/lib/type-classifier.js +126 -0
- package/dist/lib/type-classifier.js.map +1 -0
- package/dist/lib/utils/html.js +47 -0
- package/dist/lib/utils/html.js.map +1 -0
- package/dist/lib/validation.js +48 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/vault/store.js +411 -0
- package/dist/lib/vault/store.js.map +1 -0
- package/dist/metrics/hunch.js +739 -0
- package/dist/metrics/hunch.js.map +1 -0
- package/dist/objects/API.js +302 -0
- package/dist/objects/API.js.map +1 -0
- package/dist/objects/Agent.js +179 -0
- package/dist/objects/Agent.js.map +1 -0
- package/dist/objects/AgenticFunctionExecutor.js +8 -0
- package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
- package/dist/objects/App.js +83 -0
- package/dist/objects/App.js.map +1 -0
- package/dist/objects/Browser.js +884 -0
- package/dist/objects/Browser.js.map +1 -0
- package/dist/objects/Business.js +107 -0
- package/dist/objects/Business.js.map +1 -0
- package/dist/objects/CLI.js +221 -0
- package/dist/objects/CLI.js.map +1 -0
- package/dist/objects/CodeFunctionExecutor.js +8 -0
- package/dist/objects/CodeFunctionExecutor.js.map +1 -0
- package/dist/objects/Collection.js +161 -0
- package/dist/objects/Collection.js.map +1 -0
- package/dist/objects/DO.js +41 -0
- package/dist/objects/DO.js.map +1 -0
- package/dist/objects/DOBase.js +2309 -0
- package/dist/objects/DOBase.js.map +1 -0
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js +1676 -0
- package/dist/objects/DOFull.js.map +1 -0
- package/dist/objects/DOTiny.js +207 -0
- package/dist/objects/DOTiny.js.map +1 -0
- package/dist/objects/Directory.js +199 -0
- package/dist/objects/Directory.js.map +1 -0
- package/dist/objects/Entity.js +413 -0
- package/dist/objects/Entity.js.map +1 -0
- package/dist/objects/Function.js +116 -0
- package/dist/objects/Function.js.map +1 -0
- package/dist/objects/Human.js +231 -0
- package/dist/objects/Human.js.map +1 -0
- package/dist/objects/HumanFunctionExecutor.js +8 -0
- package/dist/objects/HumanFunctionExecutor.js.map +1 -0
- package/dist/objects/IcebergMetadataDO.js +938 -0
- package/dist/objects/IcebergMetadataDO.js.map +1 -0
- package/dist/objects/IntegrationsDO.js +1174 -0
- package/dist/objects/IntegrationsDO.js.map +1 -0
- package/dist/objects/ObservabilityBroadcaster.js +149 -0
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
- package/dist/objects/Package.js +154 -0
- package/dist/objects/Package.js.map +1 -0
- package/dist/objects/Product.js +193 -0
- package/dist/objects/Product.js.map +1 -0
- package/dist/objects/SDK.js +152 -0
- package/dist/objects/SDK.js.map +1 -0
- package/dist/objects/SaaS.js +235 -0
- package/dist/objects/SaaS.js.map +1 -0
- package/dist/objects/SandboxDO.js +759 -0
- package/dist/objects/SandboxDO.js.map +1 -0
- package/dist/objects/Service.js +337 -0
- package/dist/objects/Service.js.map +1 -0
- package/dist/objects/Site.js +80 -0
- package/dist/objects/Site.js.map +1 -0
- package/dist/objects/Startup.js +479 -0
- package/dist/objects/Startup.js.map +1 -0
- package/dist/objects/ThingsDO.js +170 -0
- package/dist/objects/ThingsDO.js.map +1 -0
- package/dist/objects/VectorShardDO.js +650 -0
- package/dist/objects/VectorShardDO.js.map +1 -0
- package/dist/objects/Worker.js +144 -0
- package/dist/objects/Worker.js.map +1 -0
- package/dist/objects/Workflow.js +196 -0
- package/dist/objects/Workflow.js.map +1 -0
- package/dist/objects/WorkflowFactory.js +313 -0
- package/dist/objects/WorkflowFactory.js.map +1 -0
- package/dist/objects/WorkflowRuntime.js +863 -0
- package/dist/objects/WorkflowRuntime.js.map +1 -0
- package/dist/objects/circuit-breaker-bulkhead.js +178 -0
- package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
- package/dist/objects/createFunction.js +934 -0
- package/dist/objects/createFunction.js.map +1 -0
- package/dist/objects/index.js +80 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/lifecycle/Branch.js +275 -0
- package/dist/objects/lifecycle/Branch.js.map +1 -0
- package/dist/objects/lifecycle/Clone.js +1499 -0
- package/dist/objects/lifecycle/Clone.js.map +1 -0
- package/dist/objects/lifecycle/Compact.js +237 -0
- package/dist/objects/lifecycle/Compact.js.map +1 -0
- package/dist/objects/lifecycle/Promote.js +476 -0
- package/dist/objects/lifecycle/Promote.js.map +1 -0
- package/dist/objects/lifecycle/Shard.js +560 -0
- package/dist/objects/lifecycle/Shard.js.map +1 -0
- package/dist/objects/lifecycle/index.js +15 -0
- package/dist/objects/lifecycle/index.js.map +1 -0
- package/dist/objects/lifecycle/types.js +33 -0
- package/dist/objects/lifecycle/types.js.map +1 -0
- package/dist/objects/mixins/infrastructure.js +171 -0
- package/dist/objects/mixins/infrastructure.js.map +1 -0
- package/dist/objects/modules/StoresModule.js +153 -0
- package/dist/objects/modules/StoresModule.js.map +1 -0
- package/dist/objects/persistence/checkpoint-manager.js +606 -0
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
- package/dist/objects/persistence/index.js +72 -0
- package/dist/objects/persistence/index.js.map +1 -0
- package/dist/objects/persistence/migration-runner.js +562 -0
- package/dist/objects/persistence/migration-runner.js.map +1 -0
- package/dist/objects/persistence/replication-manager.js +501 -0
- package/dist/objects/persistence/replication-manager.js.map +1 -0
- package/dist/objects/persistence/tiered-storage-manager.js +595 -0
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
- package/dist/objects/persistence/types.js +14 -0
- package/dist/objects/persistence/types.js.map +1 -0
- package/dist/objects/persistence/wal-manager.js +653 -0
- package/dist/objects/persistence/wal-manager.js.map +1 -0
- package/dist/objects/presets/index.js +20 -0
- package/dist/objects/presets/index.js.map +1 -0
- package/dist/objects/presets/primitives.js +188 -0
- package/dist/objects/presets/primitives.js.map +1 -0
- package/dist/objects/primitives/alarm-adapter.js +141 -0
- package/dist/objects/primitives/alarm-adapter.js.map +1 -0
- package/dist/objects/primitives/index.js +337 -0
- package/dist/objects/primitives/index.js.map +1 -0
- package/dist/objects/primitives/storage-adapter.js +182 -0
- package/dist/objects/primitives/storage-adapter.js.map +1 -0
- package/dist/objects/primitives/with-primitives.js +102 -0
- package/dist/objects/primitives/with-primitives.js.map +1 -0
- package/dist/objects/services/StoreManager.js +227 -0
- package/dist/objects/services/StoreManager.js.map +1 -0
- package/dist/objects/services/index.js +13 -0
- package/dist/objects/services/index.js.map +1 -0
- package/dist/objects/transport/auth-layer.js +1451 -0
- package/dist/objects/transport/auth-layer.js.map +1 -0
- package/dist/objects/transport/capnweb-target.js +355 -0
- package/dist/objects/transport/capnweb-target.js.map +1 -0
- package/dist/objects/transport/chain.js +441 -0
- package/dist/objects/transport/chain.js.map +1 -0
- package/dist/objects/transport/handler.js +58 -0
- package/dist/objects/transport/handler.js.map +1 -0
- package/dist/objects/transport/index.js +53 -0
- package/dist/objects/transport/index.js.map +1 -0
- package/dist/objects/transport/mcp-server.js +691 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1508 -0
- package/dist/objects/transport/rest-autowire.js.map +1 -0
- package/dist/objects/transport/rest-router.js +440 -0
- package/dist/objects/transport/rest-router.js.map +1 -0
- package/dist/objects/transport/rpc-server.js +1539 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +576 -0
- package/dist/objects/transport/shared.js.map +1 -0
- package/dist/objects/transport/sync-engine.js +291 -0
- package/dist/objects/transport/sync-engine.js.map +1 -0
- package/dist/objects/transport/types.js +8 -0
- package/dist/objects/transport/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/snippets/artifacts-config.js +241 -0
- package/dist/snippets/artifacts-config.js.map +1 -0
- package/dist/snippets/artifacts-ingest.js +832 -0
- package/dist/snippets/artifacts-ingest.js.map +1 -0
- package/dist/snippets/artifacts-serve.js +1035 -0
- package/dist/snippets/artifacts-serve.js.map +1 -0
- package/dist/snippets/artifacts-types.js +161 -0
- package/dist/snippets/artifacts-types.js.map +1 -0
- package/dist/snippets/cache-probe.js +376 -0
- package/dist/snippets/cache-probe.js.map +1 -0
- package/dist/snippets/cache.js +10 -0
- package/dist/snippets/cache.js.map +1 -0
- package/dist/snippets/events.js +469 -0
- package/dist/snippets/events.js.map +1 -0
- package/dist/snippets/index.js +7 -0
- package/dist/snippets/index.js.map +1 -0
- package/dist/snippets/proxy.js +495 -0
- package/dist/snippets/proxy.js.map +1 -0
- package/dist/snippets/search.js +1759 -0
- package/dist/snippets/search.js.map +1 -0
- package/dist/streams/index.js +30 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/observability.js +68 -0
- package/dist/streams/observability.js.map +1 -0
- package/dist/types/AI.js +92 -0
- package/dist/types/AI.js.map +1 -0
- package/dist/types/AIFunction.js +171 -0
- package/dist/types/AIFunction.js.map +1 -0
- package/dist/types/BrowseVerb.js +89 -0
- package/dist/types/BrowseVerb.js.map +1 -0
- package/dist/types/Browser.js +31 -0
- package/dist/types/Browser.js.map +1 -0
- package/dist/types/Chaos.js +15 -0
- package/dist/types/Chaos.js.map +1 -0
- package/dist/types/CloudflareBindings.js +109 -0
- package/dist/types/CloudflareBindings.js.map +1 -0
- package/dist/types/Collection.js +50 -0
- package/dist/types/Collection.js.map +1 -0
- package/dist/types/DO.js +2 -0
- package/dist/types/DO.js.map +1 -0
- package/dist/types/DOLocation.js +63 -0
- package/dist/types/DOLocation.js.map +1 -0
- package/dist/types/EventHandler.js +57 -0
- package/dist/types/EventHandler.js.map +1 -0
- package/dist/types/Experiment.js +33 -0
- package/dist/types/Experiment.js.map +1 -0
- package/dist/types/Flag.js +57 -0
- package/dist/types/Flag.js.map +1 -0
- package/dist/types/Lifecycle.js +13 -0
- package/dist/types/Lifecycle.js.map +1 -0
- package/dist/types/Location.js +169 -0
- package/dist/types/Location.js.map +1 -0
- package/dist/types/Noun.js +66 -0
- package/dist/types/Noun.js.map +1 -0
- package/dist/types/SessionEvent.js +194 -0
- package/dist/types/SessionEvent.js.map +1 -0
- package/dist/types/Thing.js +55 -0
- package/dist/types/Thing.js.map +1 -0
- package/dist/types/ThingDO.js +153 -0
- package/dist/types/ThingDO.js.map +1 -0
- package/dist/types/Things.js +2 -0
- package/dist/types/Things.js.map +1 -0
- package/dist/types/Verb.js +119 -0
- package/dist/types/Verb.js.map +1 -0
- package/dist/types/WorkflowContext.js +70 -0
- package/dist/types/WorkflowContext.js.map +1 -0
- package/dist/types/analytics-api.js +13 -0
- package/dist/types/analytics-api.js.map +1 -0
- package/dist/types/capabilities.js +135 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/drizzle.js +12 -0
- package/dist/types/drizzle.js.map +1 -0
- package/dist/types/event.js +201 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/fn.js +12 -0
- package/dist/types/fn.js.map +1 -0
- package/dist/types/iceberg.js +48 -0
- package/dist/types/iceberg.js.map +1 -0
- package/dist/types/ids.js +170 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/introspect.js +54 -0
- package/dist/types/introspect.js.map +1 -0
- package/dist/types/observability.js +124 -0
- package/dist/types/observability.js.map +1 -0
- package/dist/types/sync-protocol.js +175 -0
- package/dist/types/sync-protocol.js.map +1 -0
- package/dist/types/vector.js +13 -0
- package/dist/types/vector.js.map +1 -0
- package/dist/workflows/ScheduleManager.js +473 -0
- package/dist/workflows/ScheduleManager.js.map +1 -0
- package/dist/workflows/StepDOBridge.js +149 -0
- package/dist/workflows/StepDOBridge.js.map +1 -0
- package/dist/workflows/StepResultStorage.js +232 -0
- package/dist/workflows/StepResultStorage.js.map +1 -0
- package/dist/workflows/WaitForEventManager.js +461 -0
- package/dist/workflows/WaitForEventManager.js.map +1 -0
- package/dist/workflows/analyzer.js +332 -0
- package/dist/workflows/analyzer.js.map +1 -0
- package/dist/workflows/compat/activity-router.js +484 -0
- package/dist/workflows/compat/activity-router.js.map +1 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
- package/dist/workflows/compat/backends/index.js +14 -0
- package/dist/workflows/compat/backends/index.js.map +1 -0
- package/dist/workflows/compat/errors/index.js +375 -0
- package/dist/workflows/compat/errors/index.js.map +1 -0
- package/dist/workflows/compat/index.js +79 -0
- package/dist/workflows/compat/index.js.map +1 -0
- package/dist/workflows/compat/inngest/index.js +989 -0
- package/dist/workflows/compat/inngest/index.js.map +1 -0
- package/dist/workflows/compat/qstash/index.js +1263 -0
- package/dist/workflows/compat/qstash/index.js.map +1 -0
- package/dist/workflows/compat/temporal/activities.js +739 -0
- package/dist/workflows/compat/temporal/activities.js.map +1 -0
- package/dist/workflows/compat/temporal/child-workflows.js +154 -0
- package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
- package/dist/workflows/compat/temporal/client.js +381 -0
- package/dist/workflows/compat/temporal/client.js.map +1 -0
- package/dist/workflows/compat/temporal/context.js +309 -0
- package/dist/workflows/compat/temporal/context.js.map +1 -0
- package/dist/workflows/compat/temporal/determinism.js +216 -0
- package/dist/workflows/compat/temporal/determinism.js.map +1 -0
- package/dist/workflows/compat/temporal/errors.js +128 -0
- package/dist/workflows/compat/temporal/errors.js.map +1 -0
- package/dist/workflows/compat/temporal/index.js +2464 -0
- package/dist/workflows/compat/temporal/index.js.map +1 -0
- package/dist/workflows/compat/temporal/saga.js +504 -0
- package/dist/workflows/compat/temporal/saga.js.map +1 -0
- package/dist/workflows/compat/temporal/signals.js +364 -0
- package/dist/workflows/compat/temporal/signals.js.map +1 -0
- package/dist/workflows/compat/temporal/storage.js +271 -0
- package/dist/workflows/compat/temporal/storage.js.map +1 -0
- package/dist/workflows/compat/temporal/timers.js +347 -0
- package/dist/workflows/compat/temporal/timers.js.map +1 -0
- package/dist/workflows/compat/temporal/types.js +7 -0
- package/dist/workflows/compat/temporal/types.js.map +1 -0
- package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
- package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
- package/dist/workflows/compat/trigger/index.js +468 -0
- package/dist/workflows/compat/trigger/index.js.map +1 -0
- package/dist/workflows/compat/utils/index.js +69 -0
- package/dist/workflows/compat/utils/index.js.map +1 -0
- package/dist/workflows/context/correlation-capability.js +266 -0
- package/dist/workflows/context/correlation-capability.js.map +1 -0
- package/dist/workflows/context/correlation.js +484 -0
- package/dist/workflows/context/correlation.js.map +1 -0
- package/dist/workflows/context/experiment.js +289 -0
- package/dist/workflows/context/experiment.js.map +1 -0
- package/dist/workflows/context/flag.js +244 -0
- package/dist/workflows/context/flag.js.map +1 -0
- package/dist/workflows/context/foundation.js +648 -0
- package/dist/workflows/context/foundation.js.map +1 -0
- package/dist/workflows/context/human-base.js +106 -0
- package/dist/workflows/context/human-base.js.map +1 -0
- package/dist/workflows/context/human.js +368 -0
- package/dist/workflows/context/human.js.map +1 -0
- package/dist/workflows/context/measure.js +354 -0
- package/dist/workflows/context/measure.js.map +1 -0
- package/dist/workflows/context/rate-limit.js +358 -0
- package/dist/workflows/context/rate-limit.js.map +1 -0
- package/dist/workflows/context/user.js +117 -0
- package/dist/workflows/context/user.js.map +1 -0
- package/dist/workflows/context/vault.js +360 -0
- package/dist/workflows/context/vault.js.map +1 -0
- package/dist/workflows/data/entity-events/entity-events.js +489 -0
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
- package/dist/workflows/data/experiment/index.js +599 -0
- package/dist/workflows/data/experiment/index.js.map +1 -0
- package/dist/workflows/data/goal/context.js +558 -0
- package/dist/workflows/data/goal/context.js.map +1 -0
- package/dist/workflows/data/goal/index.js +32 -0
- package/dist/workflows/data/goal/index.js.map +1 -0
- package/dist/workflows/data/measure/index.js +840 -0
- package/dist/workflows/data/measure/index.js.map +1 -0
- package/dist/workflows/data/stream/index.js +1149 -0
- package/dist/workflows/data/stream/index.js.map +1 -0
- package/dist/workflows/data/track/context.js +883 -0
- package/dist/workflows/data/track/context.js.map +1 -0
- package/dist/workflows/data/track/index.js +15 -0
- package/dist/workflows/data/track/index.js.map +1 -0
- package/dist/workflows/data/view/context.js +864 -0
- package/dist/workflows/data/view/context.js.map +1 -0
- package/dist/workflows/domain.js +93 -0
- package/dist/workflows/domain.js.map +1 -0
- package/dist/workflows/flag.js +176 -0
- package/dist/workflows/flag.js.map +1 -0
- package/dist/workflows/flags.js +217 -0
- package/dist/workflows/flags.js.map +1 -0
- package/dist/workflows/hash.js +209 -0
- package/dist/workflows/hash.js.map +1 -0
- package/dist/workflows/index.js +50 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/on.js +378 -0
- package/dist/workflows/on.js.map +1 -0
- package/dist/workflows/pipeline-promise.js +481 -0
- package/dist/workflows/pipeline-promise.js.map +1 -0
- package/dist/workflows/pipeline-types.js +20 -0
- package/dist/workflows/pipeline-types.js.map +1 -0
- package/dist/workflows/proxy.js +76 -0
- package/dist/workflows/proxy.js.map +1 -0
- package/dist/workflows/runtime.js +310 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/schedule-builder.js +327 -0
- package/dist/workflows/schedule-builder.js.map +1 -0
- package/dist/workflows/visibility/index.js +146 -0
- package/dist/workflows/visibility/index.js.map +1 -0
- package/dist/workflows/visibility/query-parser.js +150 -0
- package/dist/workflows/visibility/query-parser.js.map +1 -0
- package/dist/workflows/visibility/store.js +223 -0
- package/dist/workflows/visibility/store.js.map +1 -0
- package/dist/workflows/visibility/types.js +30 -0
- package/dist/workflows/visibility/types.js.map +1 -0
- package/dist/workflows/workflow.js +53 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/package.json +294 -46
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track Context - Event tracking API implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides the $.track API for event tracking, analysis, and subscriptions.
|
|
5
|
+
*
|
|
6
|
+
* @module workflows/data/track/context
|
|
7
|
+
*/
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// UTILITY FUNCTIONS
|
|
10
|
+
// =============================================================================
|
|
11
|
+
let idCounter = 0;
|
|
12
|
+
function generateId() {
|
|
13
|
+
return `evt_${Date.now()}_${++idCounter}_${Math.random().toString(36).substring(2, 9)}`;
|
|
14
|
+
}
|
|
15
|
+
function generateSubscriptionId() {
|
|
16
|
+
return `sub_${Date.now()}_${++idCounter}_${Math.random().toString(36).substring(2, 9)}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse duration string to milliseconds.
|
|
20
|
+
* Returns the actual timestamp if given a number (for since(timestamp) calls).
|
|
21
|
+
*/
|
|
22
|
+
function parseDuration(duration) {
|
|
23
|
+
if (typeof duration === 'number') {
|
|
24
|
+
// If it's a large number (> 1 year in ms), treat it as a timestamp
|
|
25
|
+
// Otherwise treat it as milliseconds duration
|
|
26
|
+
const oneYearMs = 365 * 24 * 60 * 60 * 1000;
|
|
27
|
+
if (duration > oneYearMs) {
|
|
28
|
+
return { isTimestamp: true, value: duration };
|
|
29
|
+
}
|
|
30
|
+
return { isTimestamp: false, value: duration };
|
|
31
|
+
}
|
|
32
|
+
const match = duration.match(/^(\d+)([smhdwMy]|month|quarter|year|week)?$/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
// Handle special cases
|
|
35
|
+
if (duration === 'month')
|
|
36
|
+
return { isTimestamp: false, value: 30 * 24 * 60 * 60 * 1000 };
|
|
37
|
+
if (duration === 'quarter')
|
|
38
|
+
return { isTimestamp: false, value: 90 * 24 * 60 * 60 * 1000 };
|
|
39
|
+
if (duration === 'year')
|
|
40
|
+
return { isTimestamp: false, value: 365 * 24 * 60 * 60 * 1000 };
|
|
41
|
+
if (duration === 'week')
|
|
42
|
+
return { isTimestamp: false, value: 7 * 24 * 60 * 60 * 1000 };
|
|
43
|
+
throw new Error(`Invalid duration: ${duration}`);
|
|
44
|
+
}
|
|
45
|
+
const value = parseInt(match[1], 10);
|
|
46
|
+
const unit = match[2] || 's';
|
|
47
|
+
let ms;
|
|
48
|
+
switch (unit) {
|
|
49
|
+
case 's':
|
|
50
|
+
ms = value * 1000;
|
|
51
|
+
break;
|
|
52
|
+
case 'm':
|
|
53
|
+
ms = value * 60 * 1000;
|
|
54
|
+
break;
|
|
55
|
+
case 'h':
|
|
56
|
+
ms = value * 60 * 60 * 1000;
|
|
57
|
+
break;
|
|
58
|
+
case 'd':
|
|
59
|
+
ms = value * 24 * 60 * 60 * 1000;
|
|
60
|
+
break;
|
|
61
|
+
case 'w':
|
|
62
|
+
case 'week':
|
|
63
|
+
ms = value * 7 * 24 * 60 * 60 * 1000;
|
|
64
|
+
break;
|
|
65
|
+
case 'M':
|
|
66
|
+
case 'month':
|
|
67
|
+
ms = value * 30 * 24 * 60 * 60 * 1000;
|
|
68
|
+
break;
|
|
69
|
+
case 'quarter':
|
|
70
|
+
ms = value * 90 * 24 * 60 * 60 * 1000;
|
|
71
|
+
break;
|
|
72
|
+
case 'y':
|
|
73
|
+
case 'year':
|
|
74
|
+
ms = value * 365 * 24 * 60 * 60 * 1000;
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
ms = value * 1000;
|
|
78
|
+
}
|
|
79
|
+
return { isTimestamp: false, value: ms };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the "since" timestamp from duration or timestamp
|
|
83
|
+
*/
|
|
84
|
+
function getSinceTimestamp(duration) {
|
|
85
|
+
const parsed = parseDuration(duration);
|
|
86
|
+
if (parsed.isTimestamp) {
|
|
87
|
+
return parsed.value;
|
|
88
|
+
}
|
|
89
|
+
return Date.now() - parsed.value;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get period start for a timestamp based on granularity
|
|
93
|
+
*/
|
|
94
|
+
function getPeriodStart(timestamp, granularity) {
|
|
95
|
+
const date = new Date(timestamp);
|
|
96
|
+
switch (granularity) {
|
|
97
|
+
case 'minute':
|
|
98
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()).getTime();
|
|
99
|
+
case 'hour':
|
|
100
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()).getTime();
|
|
101
|
+
case 'day':
|
|
102
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
103
|
+
case 'week':
|
|
104
|
+
const day = date.getDay();
|
|
105
|
+
const diff = date.getDate() - day;
|
|
106
|
+
return new Date(date.getFullYear(), date.getMonth(), diff).getTime();
|
|
107
|
+
case 'month':
|
|
108
|
+
return new Date(date.getFullYear(), date.getMonth(), 1).getTime();
|
|
109
|
+
default:
|
|
110
|
+
return timestamp;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the period number from anchor timestamp
|
|
115
|
+
*/
|
|
116
|
+
function getPeriodNumber(anchorTimestamp, eventTimestamp, granularity) {
|
|
117
|
+
const diff = eventTimestamp - anchorTimestamp;
|
|
118
|
+
switch (granularity) {
|
|
119
|
+
case 'day':
|
|
120
|
+
return Math.floor(diff / (24 * 60 * 60 * 1000));
|
|
121
|
+
case 'week':
|
|
122
|
+
return Math.floor(diff / (7 * 24 * 60 * 60 * 1000));
|
|
123
|
+
case 'month':
|
|
124
|
+
return Math.floor(diff / (30 * 24 * 60 * 60 * 1000));
|
|
125
|
+
default:
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if event matches filter
|
|
131
|
+
*/
|
|
132
|
+
function matchesFilter(event, filter) {
|
|
133
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
134
|
+
// Handle experiment and variant as top-level properties
|
|
135
|
+
if (key === 'experiment') {
|
|
136
|
+
if (event.experiment !== value)
|
|
137
|
+
return false;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (key === 'variant') {
|
|
141
|
+
if (event.variant !== value)
|
|
142
|
+
return false;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const eventValue = event.data[key];
|
|
146
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
147
|
+
const ops = value;
|
|
148
|
+
if (ops.gte !== undefined && (typeof eventValue !== 'number' || eventValue < ops.gte))
|
|
149
|
+
return false;
|
|
150
|
+
if (ops.gt !== undefined && (typeof eventValue !== 'number' || eventValue <= ops.gt))
|
|
151
|
+
return false;
|
|
152
|
+
if (ops.lte !== undefined && (typeof eventValue !== 'number' || eventValue > ops.lte))
|
|
153
|
+
return false;
|
|
154
|
+
if (ops.lt !== undefined && (typeof eventValue !== 'number' || eventValue >= ops.lt))
|
|
155
|
+
return false;
|
|
156
|
+
if (ops.ne !== undefined && eventValue === ops.ne)
|
|
157
|
+
return false;
|
|
158
|
+
if (ops.in !== undefined && !ops.in.includes(eventValue))
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
if (eventValue !== value)
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
function createStorage() {
|
|
169
|
+
return {
|
|
170
|
+
events: [],
|
|
171
|
+
subscriptions: new Map(),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// EVENT PROXY
|
|
176
|
+
// =============================================================================
|
|
177
|
+
function createEventProxy(eventType, storage) {
|
|
178
|
+
const getEvents = (since) => {
|
|
179
|
+
return storage.events.filter((e) => {
|
|
180
|
+
if (e.type !== eventType)
|
|
181
|
+
return false;
|
|
182
|
+
if (since !== undefined && e.timestamp < since)
|
|
183
|
+
return false;
|
|
184
|
+
return true;
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const getFilteredEvents = (filter, since) => {
|
|
188
|
+
return storage.events.filter((e) => {
|
|
189
|
+
if (e.type !== eventType)
|
|
190
|
+
return false;
|
|
191
|
+
if (since !== undefined && e.timestamp < since)
|
|
192
|
+
return false;
|
|
193
|
+
return matchesFilter(e, filter);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const proxy = async (data, options) => {
|
|
197
|
+
const event = {
|
|
198
|
+
id: generateId(),
|
|
199
|
+
type: eventType,
|
|
200
|
+
data,
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
...(options?.experiment && { experiment: options.experiment }),
|
|
203
|
+
...(options?.variant && { variant: options.variant }),
|
|
204
|
+
};
|
|
205
|
+
storage.events.push(event);
|
|
206
|
+
// Notify subscribers
|
|
207
|
+
const subs = storage.subscriptions.get(eventType) || [];
|
|
208
|
+
for (const sub of subs) {
|
|
209
|
+
if (!sub.filter || matchesFilter(event, sub.filter)) {
|
|
210
|
+
sub.handler(event);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return event;
|
|
214
|
+
};
|
|
215
|
+
// count()
|
|
216
|
+
proxy.count = () => {
|
|
217
|
+
let sinceTime;
|
|
218
|
+
// Create a thenable that also has methods
|
|
219
|
+
const countPromise = Promise.resolve().then(async () => {
|
|
220
|
+
const events = getEvents(sinceTime);
|
|
221
|
+
return events.length;
|
|
222
|
+
});
|
|
223
|
+
// Add chainable methods
|
|
224
|
+
const countFn = countPromise;
|
|
225
|
+
countFn.since = (duration) => {
|
|
226
|
+
sinceTime = getSinceTimestamp(duration);
|
|
227
|
+
// Return a new thenable with the updated sinceTime
|
|
228
|
+
const newPromise = Promise.resolve().then(async () => {
|
|
229
|
+
const events = getEvents(sinceTime);
|
|
230
|
+
return events.length;
|
|
231
|
+
});
|
|
232
|
+
const newFn = newPromise;
|
|
233
|
+
newFn.by = async (field) => {
|
|
234
|
+
const events = getEvents(sinceTime);
|
|
235
|
+
const result = {};
|
|
236
|
+
for (const event of events) {
|
|
237
|
+
const value = field === 'variant' ? event.variant : event.data[field];
|
|
238
|
+
if (value !== undefined) {
|
|
239
|
+
const key = String(value);
|
|
240
|
+
result[key] = (result[key] || 0) + 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
};
|
|
245
|
+
newFn.groupBy = async (granularity) => {
|
|
246
|
+
const events = getEvents(sinceTime);
|
|
247
|
+
const buckets = new Map();
|
|
248
|
+
for (const event of events) {
|
|
249
|
+
const periodStart = getPeriodStart(event.timestamp, granularity);
|
|
250
|
+
buckets.set(periodStart, (buckets.get(periodStart) || 0) + 1);
|
|
251
|
+
}
|
|
252
|
+
return Array.from(buckets.entries())
|
|
253
|
+
.map(([timestamp, count]) => ({ timestamp, count }))
|
|
254
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
255
|
+
};
|
|
256
|
+
return newFn;
|
|
257
|
+
};
|
|
258
|
+
countFn.by = async (field) => {
|
|
259
|
+
const events = getEvents(sinceTime);
|
|
260
|
+
const result = {};
|
|
261
|
+
for (const event of events) {
|
|
262
|
+
const value = field === 'variant' ? event.variant : event.data[field];
|
|
263
|
+
if (value !== undefined) {
|
|
264
|
+
const key = String(value);
|
|
265
|
+
result[key] = (result[key] || 0) + 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
};
|
|
270
|
+
countFn.groupBy = async (granularity) => {
|
|
271
|
+
const events = getEvents(sinceTime);
|
|
272
|
+
const buckets = new Map();
|
|
273
|
+
for (const event of events) {
|
|
274
|
+
const periodStart = getPeriodStart(event.timestamp, granularity);
|
|
275
|
+
buckets.set(periodStart, (buckets.get(periodStart) || 0) + 1);
|
|
276
|
+
}
|
|
277
|
+
return Array.from(buckets.entries())
|
|
278
|
+
.map(([timestamp, count]) => ({ timestamp, count }))
|
|
279
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
280
|
+
};
|
|
281
|
+
return countFn;
|
|
282
|
+
};
|
|
283
|
+
// events()
|
|
284
|
+
proxy.events = () => {
|
|
285
|
+
let sinceTime;
|
|
286
|
+
const eventsQuery = {
|
|
287
|
+
limit: async (n) => {
|
|
288
|
+
const events = getEvents(sinceTime);
|
|
289
|
+
// Get events with their original indices for stable sorting
|
|
290
|
+
const eventsWithIndex = events.map((e, i) => ({ event: e, originalIndex: i }));
|
|
291
|
+
// Return in reverse chronological order (most recent first)
|
|
292
|
+
// Use original index as tiebreaker when timestamps are equal (stable sort)
|
|
293
|
+
eventsWithIndex.sort((a, b) => {
|
|
294
|
+
const timeDiff = b.event.timestamp - a.event.timestamp;
|
|
295
|
+
if (timeDiff !== 0)
|
|
296
|
+
return timeDiff;
|
|
297
|
+
// Same timestamp: later in array = more recent
|
|
298
|
+
return b.originalIndex - a.originalIndex;
|
|
299
|
+
});
|
|
300
|
+
return eventsWithIndex.slice(0, n).map((e) => e.event);
|
|
301
|
+
},
|
|
302
|
+
since: (duration) => {
|
|
303
|
+
sinceTime = getSinceTimestamp(duration);
|
|
304
|
+
return eventsQuery;
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
return eventsQuery;
|
|
308
|
+
};
|
|
309
|
+
// where()
|
|
310
|
+
proxy.where = (filter) => {
|
|
311
|
+
const whereQuery = {
|
|
312
|
+
count: () => {
|
|
313
|
+
let sinceTime;
|
|
314
|
+
const countPromise = Promise.resolve().then(async () => {
|
|
315
|
+
return getFilteredEvents(filter, sinceTime).length;
|
|
316
|
+
});
|
|
317
|
+
const countFn = countPromise;
|
|
318
|
+
countFn.since = (duration) => {
|
|
319
|
+
sinceTime = getSinceTimestamp(duration);
|
|
320
|
+
const newPromise = Promise.resolve().then(async () => {
|
|
321
|
+
return getFilteredEvents(filter, sinceTime).length;
|
|
322
|
+
});
|
|
323
|
+
const newFn = newPromise;
|
|
324
|
+
newFn.by = async (field) => {
|
|
325
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
326
|
+
const result = {};
|
|
327
|
+
for (const event of events) {
|
|
328
|
+
const value = field === 'variant' ? event.variant : event.data[field];
|
|
329
|
+
if (value !== undefined) {
|
|
330
|
+
const key = String(value);
|
|
331
|
+
result[key] = (result[key] || 0) + 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
};
|
|
336
|
+
return newFn;
|
|
337
|
+
};
|
|
338
|
+
countFn.by = async (field) => {
|
|
339
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
340
|
+
const result = {};
|
|
341
|
+
for (const event of events) {
|
|
342
|
+
const value = field === 'variant' ? event.variant : event.data[field];
|
|
343
|
+
if (value !== undefined) {
|
|
344
|
+
const key = String(value);
|
|
345
|
+
result[key] = (result[key] || 0) + 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
};
|
|
350
|
+
return countFn;
|
|
351
|
+
},
|
|
352
|
+
sum: (field) => {
|
|
353
|
+
let sinceTime;
|
|
354
|
+
const sumPromise = Promise.resolve().then(async () => {
|
|
355
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
356
|
+
return events.reduce((sum, e) => {
|
|
357
|
+
const val = e.data[field];
|
|
358
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
359
|
+
}, 0);
|
|
360
|
+
});
|
|
361
|
+
const sumFn = sumPromise;
|
|
362
|
+
sumFn.since = (duration) => {
|
|
363
|
+
sinceTime = getSinceTimestamp(duration);
|
|
364
|
+
return Promise.resolve().then(async () => {
|
|
365
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
366
|
+
return events.reduce((sum, e) => {
|
|
367
|
+
const val = e.data[field];
|
|
368
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
369
|
+
}, 0);
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
return sumFn;
|
|
373
|
+
},
|
|
374
|
+
avg: async (field) => {
|
|
375
|
+
const events = getFilteredEvents(filter);
|
|
376
|
+
if (events.length === 0)
|
|
377
|
+
return null;
|
|
378
|
+
const sum = events.reduce((sum, e) => {
|
|
379
|
+
const val = e.data[field];
|
|
380
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
381
|
+
}, 0);
|
|
382
|
+
return sum / events.length;
|
|
383
|
+
},
|
|
384
|
+
countUnique: (field) => {
|
|
385
|
+
let sinceTime;
|
|
386
|
+
const uniquePromise = Promise.resolve().then(async () => {
|
|
387
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
388
|
+
const unique = new Set(events.map((e) => e.data[field]));
|
|
389
|
+
return unique.size;
|
|
390
|
+
});
|
|
391
|
+
const uniqueFn = uniquePromise;
|
|
392
|
+
uniqueFn.since = (duration) => {
|
|
393
|
+
sinceTime = getSinceTimestamp(duration);
|
|
394
|
+
return Promise.resolve().then(async () => {
|
|
395
|
+
const events = getFilteredEvents(filter, sinceTime);
|
|
396
|
+
const unique = new Set(events.map((e) => e.data[field]));
|
|
397
|
+
return unique.size;
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
return uniqueFn;
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
return whereQuery;
|
|
404
|
+
};
|
|
405
|
+
// sum()
|
|
406
|
+
proxy.sum = (field) => {
|
|
407
|
+
let sinceTime;
|
|
408
|
+
const sumPromise = Promise.resolve().then(async () => {
|
|
409
|
+
const events = getEvents(sinceTime);
|
|
410
|
+
return events.reduce((sum, e) => {
|
|
411
|
+
const val = e.data[field];
|
|
412
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
413
|
+
}, 0);
|
|
414
|
+
});
|
|
415
|
+
const sumFn = sumPromise;
|
|
416
|
+
sumFn.since = (duration) => {
|
|
417
|
+
sinceTime = getSinceTimestamp(duration);
|
|
418
|
+
return Promise.resolve().then(async () => {
|
|
419
|
+
const events = getEvents(sinceTime);
|
|
420
|
+
return events.reduce((sum, e) => {
|
|
421
|
+
const val = e.data[field];
|
|
422
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
423
|
+
}, 0);
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
return sumFn;
|
|
427
|
+
};
|
|
428
|
+
// avg()
|
|
429
|
+
proxy.avg = async (field) => {
|
|
430
|
+
const events = getEvents();
|
|
431
|
+
if (events.length === 0)
|
|
432
|
+
return null;
|
|
433
|
+
const sum = events.reduce((sum, e) => {
|
|
434
|
+
const val = e.data[field];
|
|
435
|
+
return sum + (typeof val === 'number' ? val : 0);
|
|
436
|
+
}, 0);
|
|
437
|
+
return sum / events.length;
|
|
438
|
+
};
|
|
439
|
+
// min()
|
|
440
|
+
proxy.min = async (field) => {
|
|
441
|
+
const events = getEvents();
|
|
442
|
+
if (events.length === 0)
|
|
443
|
+
return null;
|
|
444
|
+
let min = null;
|
|
445
|
+
for (const e of events) {
|
|
446
|
+
const val = e.data[field];
|
|
447
|
+
if (typeof val === 'number') {
|
|
448
|
+
if (min === null || val < min)
|
|
449
|
+
min = val;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return min;
|
|
453
|
+
};
|
|
454
|
+
// max()
|
|
455
|
+
proxy.max = async (field) => {
|
|
456
|
+
const events = getEvents();
|
|
457
|
+
if (events.length === 0)
|
|
458
|
+
return null;
|
|
459
|
+
let max = null;
|
|
460
|
+
for (const e of events) {
|
|
461
|
+
const val = e.data[field];
|
|
462
|
+
if (typeof val === 'number') {
|
|
463
|
+
if (max === null || val > max)
|
|
464
|
+
max = val;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return max;
|
|
468
|
+
};
|
|
469
|
+
// countUnique()
|
|
470
|
+
proxy.countUnique = (field) => {
|
|
471
|
+
let sinceTime;
|
|
472
|
+
const uniquePromise = Promise.resolve().then(async () => {
|
|
473
|
+
const events = getEvents(sinceTime);
|
|
474
|
+
const unique = new Set(events.map((e) => e.data[field]));
|
|
475
|
+
return unique.size;
|
|
476
|
+
});
|
|
477
|
+
const uniqueFn = uniquePromise;
|
|
478
|
+
uniqueFn.since = (duration) => {
|
|
479
|
+
sinceTime = getSinceTimestamp(duration);
|
|
480
|
+
return Promise.resolve().then(async () => {
|
|
481
|
+
const events = getEvents(sinceTime);
|
|
482
|
+
const unique = new Set(events.map((e) => e.data[field]));
|
|
483
|
+
return unique.size;
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
return uniqueFn;
|
|
487
|
+
};
|
|
488
|
+
// subscribe()
|
|
489
|
+
proxy.subscribe = (handler, options) => {
|
|
490
|
+
const id = generateSubscriptionId();
|
|
491
|
+
if (!storage.subscriptions.has(eventType)) {
|
|
492
|
+
storage.subscriptions.set(eventType, []);
|
|
493
|
+
}
|
|
494
|
+
storage.subscriptions.get(eventType).push({
|
|
495
|
+
id,
|
|
496
|
+
handler,
|
|
497
|
+
filter: options?.where,
|
|
498
|
+
});
|
|
499
|
+
return {
|
|
500
|
+
id,
|
|
501
|
+
unsubscribe: () => {
|
|
502
|
+
const subs = storage.subscriptions.get(eventType);
|
|
503
|
+
if (subs) {
|
|
504
|
+
const idx = subs.findIndex((s) => s.id === id);
|
|
505
|
+
if (idx !== -1)
|
|
506
|
+
subs.splice(idx, 1);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
};
|
|
511
|
+
return proxy;
|
|
512
|
+
}
|
|
513
|
+
// =============================================================================
|
|
514
|
+
// FUNNEL IMPLEMENTATION
|
|
515
|
+
// =============================================================================
|
|
516
|
+
function createFunnelQuery(steps, storage) {
|
|
517
|
+
return {
|
|
518
|
+
within: (duration) => {
|
|
519
|
+
const parsed = parseDuration(duration);
|
|
520
|
+
const windowMs = parsed.value;
|
|
521
|
+
let isStrict = false;
|
|
522
|
+
let whereFilter;
|
|
523
|
+
const calculateFunnel = (events) => {
|
|
524
|
+
if (steps.length === 0) {
|
|
525
|
+
return { steps: [], overallConversionRate: 0 };
|
|
526
|
+
}
|
|
527
|
+
// Group events by userId
|
|
528
|
+
const userEvents = new Map();
|
|
529
|
+
for (const event of events) {
|
|
530
|
+
const userId = event.data.userId;
|
|
531
|
+
if (!userId)
|
|
532
|
+
continue;
|
|
533
|
+
if (!userEvents.has(userId))
|
|
534
|
+
userEvents.set(userId, []);
|
|
535
|
+
userEvents.get(userId).push(event);
|
|
536
|
+
}
|
|
537
|
+
// Sort each user's events by timestamp
|
|
538
|
+
for (const events of userEvents.values()) {
|
|
539
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
540
|
+
}
|
|
541
|
+
const stepResults = [];
|
|
542
|
+
const usersInStep = [];
|
|
543
|
+
const stepTimes = [];
|
|
544
|
+
for (let i = 0; i < steps.length; i++) {
|
|
545
|
+
usersInStep.push(new Set());
|
|
546
|
+
stepTimes.push(new Map());
|
|
547
|
+
}
|
|
548
|
+
// For each user, check if they complete the funnel
|
|
549
|
+
for (const [userId, events] of userEvents) {
|
|
550
|
+
let lastStepTime = -Infinity;
|
|
551
|
+
let lastStepIdx = -1;
|
|
552
|
+
for (const event of events) {
|
|
553
|
+
// Find which step this event matches
|
|
554
|
+
for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
|
|
555
|
+
const step = steps[stepIdx];
|
|
556
|
+
if (event.type !== step.eventType)
|
|
557
|
+
continue;
|
|
558
|
+
if (step.filter && !matchesFilter(event, step.filter))
|
|
559
|
+
continue;
|
|
560
|
+
// Check if this is the next expected step
|
|
561
|
+
if (stepIdx === 0) {
|
|
562
|
+
// First step - always valid
|
|
563
|
+
usersInStep[0].add(userId);
|
|
564
|
+
stepTimes[0].set(userId, event.timestamp);
|
|
565
|
+
lastStepTime = event.timestamp;
|
|
566
|
+
lastStepIdx = 0;
|
|
567
|
+
}
|
|
568
|
+
else if (stepIdx === lastStepIdx + 1) {
|
|
569
|
+
// Next step in sequence
|
|
570
|
+
const prevTime = stepTimes[stepIdx - 1].get(userId);
|
|
571
|
+
if (prevTime === undefined)
|
|
572
|
+
continue;
|
|
573
|
+
// Check time window
|
|
574
|
+
if (event.timestamp - prevTime > windowMs)
|
|
575
|
+
continue;
|
|
576
|
+
// For strict mode, ensure this step happens after the previous
|
|
577
|
+
if (isStrict && event.timestamp <= lastStepTime)
|
|
578
|
+
continue;
|
|
579
|
+
usersInStep[stepIdx].add(userId);
|
|
580
|
+
stepTimes[stepIdx].set(userId, event.timestamp);
|
|
581
|
+
lastStepTime = event.timestamp;
|
|
582
|
+
lastStepIdx = stepIdx;
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Build results
|
|
589
|
+
for (let i = 0; i < steps.length; i++) {
|
|
590
|
+
const count = usersInStep[i].size;
|
|
591
|
+
const prevCount = i === 0 ? count : usersInStep[i - 1].size;
|
|
592
|
+
// Calculate average time from previous step
|
|
593
|
+
let avgTimeFromPrevious;
|
|
594
|
+
if (i > 0) {
|
|
595
|
+
const times = [];
|
|
596
|
+
for (const userId of usersInStep[i]) {
|
|
597
|
+
const currTime = stepTimes[i].get(userId);
|
|
598
|
+
const prevTime = stepTimes[i - 1].get(userId);
|
|
599
|
+
if (currTime !== undefined && prevTime !== undefined) {
|
|
600
|
+
times.push(currTime - prevTime);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (times.length > 0) {
|
|
604
|
+
avgTimeFromPrevious = times.reduce((a, b) => a + b, 0) / times.length;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
stepResults.push({
|
|
608
|
+
count,
|
|
609
|
+
conversionRate: prevCount > 0 ? count / prevCount : i === 0 ? 1 : 0,
|
|
610
|
+
...(avgTimeFromPrevious !== undefined && { avgTimeFromPrevious }),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
const firstCount = stepResults[0]?.count || 0;
|
|
614
|
+
const lastCount = stepResults[stepResults.length - 1]?.count || 0;
|
|
615
|
+
return {
|
|
616
|
+
steps: stepResults,
|
|
617
|
+
overallConversionRate: firstCount > 0 ? lastCount / firstCount : 0,
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
const getEventsForFunnel = () => {
|
|
621
|
+
let events = storage.events;
|
|
622
|
+
if (whereFilter) {
|
|
623
|
+
events = events.filter((e) => matchesFilter(e, whereFilter));
|
|
624
|
+
}
|
|
625
|
+
return events;
|
|
626
|
+
};
|
|
627
|
+
// Create a thenable that is directly awaitable
|
|
628
|
+
const withinPromise = Promise.resolve().then(async () => {
|
|
629
|
+
return calculateFunnel(getEventsForFunnel());
|
|
630
|
+
});
|
|
631
|
+
const withinQuery = withinPromise;
|
|
632
|
+
withinQuery.by = async (field) => {
|
|
633
|
+
const events = getEventsForFunnel();
|
|
634
|
+
const groups = new Map();
|
|
635
|
+
for (const event of events) {
|
|
636
|
+
const value = event.data[field];
|
|
637
|
+
if (value !== undefined) {
|
|
638
|
+
const key = String(value);
|
|
639
|
+
if (!groups.has(key))
|
|
640
|
+
groups.set(key, []);
|
|
641
|
+
groups.get(key).push(event);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const result = {};
|
|
645
|
+
for (const [key, groupEvents] of groups) {
|
|
646
|
+
result[key] = calculateFunnel(groupEvents);
|
|
647
|
+
}
|
|
648
|
+
return result;
|
|
649
|
+
};
|
|
650
|
+
withinQuery.strict = () => {
|
|
651
|
+
isStrict = true;
|
|
652
|
+
// Return a new thenable with strict mode enabled
|
|
653
|
+
const strictPromise = Promise.resolve().then(async () => {
|
|
654
|
+
return calculateFunnel(getEventsForFunnel());
|
|
655
|
+
});
|
|
656
|
+
return strictPromise;
|
|
657
|
+
};
|
|
658
|
+
withinQuery.where = (filter) => {
|
|
659
|
+
whereFilter = filter;
|
|
660
|
+
return {
|
|
661
|
+
by: async (field) => {
|
|
662
|
+
const events = getEventsForFunnel();
|
|
663
|
+
const groups = new Map();
|
|
664
|
+
for (const event of events) {
|
|
665
|
+
const value = field === 'variant' ? event.variant : event.data[field];
|
|
666
|
+
if (value !== undefined) {
|
|
667
|
+
const key = String(value);
|
|
668
|
+
if (!groups.has(key))
|
|
669
|
+
groups.set(key, []);
|
|
670
|
+
groups.get(key).push(event);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const result = {};
|
|
674
|
+
for (const [key, groupEvents] of groups) {
|
|
675
|
+
result[key] = calculateFunnel(groupEvents);
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
678
|
+
},
|
|
679
|
+
};
|
|
680
|
+
};
|
|
681
|
+
return withinQuery;
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
// =============================================================================
|
|
686
|
+
// COHORT IMPLEMENTATION
|
|
687
|
+
// =============================================================================
|
|
688
|
+
function createCohortQuery(config, storage) {
|
|
689
|
+
const calculateCohort = (events) => {
|
|
690
|
+
const { anchor, activity, periods, granularity } = config;
|
|
691
|
+
// Find anchor events (e.g., Signup)
|
|
692
|
+
const anchorEvents = events.filter((e) => e.type === anchor);
|
|
693
|
+
const activityEvents = events.filter((e) => e.type === activity);
|
|
694
|
+
// Group anchor events by period and user
|
|
695
|
+
const periodMs = granularity === 'day' ? 24 * 60 * 60 * 1000 : granularity === 'week' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;
|
|
696
|
+
// Group by cohort period
|
|
697
|
+
const cohortGroups = new Map(); // periodStart -> (userId -> anchorTime)
|
|
698
|
+
for (const event of anchorEvents) {
|
|
699
|
+
const userId = event.data.userId;
|
|
700
|
+
if (!userId)
|
|
701
|
+
continue;
|
|
702
|
+
const periodStart = getPeriodStart(event.timestamp, granularity);
|
|
703
|
+
if (!cohortGroups.has(periodStart)) {
|
|
704
|
+
cohortGroups.set(periodStart, new Map());
|
|
705
|
+
}
|
|
706
|
+
const cohort = cohortGroups.get(periodStart);
|
|
707
|
+
if (!cohort.has(userId)) {
|
|
708
|
+
cohort.set(userId, event.timestamp);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Build activity lookup by user
|
|
712
|
+
const userActivities = new Map();
|
|
713
|
+
for (const event of activityEvents) {
|
|
714
|
+
const userId = event.data.userId;
|
|
715
|
+
if (!userId)
|
|
716
|
+
continue;
|
|
717
|
+
if (!userActivities.has(userId)) {
|
|
718
|
+
userActivities.set(userId, []);
|
|
719
|
+
}
|
|
720
|
+
userActivities.get(userId).push(event.timestamp);
|
|
721
|
+
}
|
|
722
|
+
// Calculate retention for each cohort
|
|
723
|
+
const cohorts = [];
|
|
724
|
+
for (const [periodStart, users] of cohortGroups) {
|
|
725
|
+
const size = users.size;
|
|
726
|
+
const retention = [];
|
|
727
|
+
for (let period = 1; period < periods; period++) {
|
|
728
|
+
let retainedCount = 0;
|
|
729
|
+
for (const [userId, anchorTime] of users) {
|
|
730
|
+
const activities = userActivities.get(userId) || [];
|
|
731
|
+
// Check if user had activity in this period
|
|
732
|
+
// retention[0] = period 1 = activity in 1st week after signup (anchorTime+week to anchorTime+2*week)
|
|
733
|
+
const periodStartTime = anchorTime + period * periodMs;
|
|
734
|
+
const periodEndTime = anchorTime + (period + 1) * periodMs;
|
|
735
|
+
const hasActivity = activities.some((t) => t >= periodStartTime && t < periodEndTime);
|
|
736
|
+
if (hasActivity)
|
|
737
|
+
retainedCount++;
|
|
738
|
+
}
|
|
739
|
+
retention.push(size > 0 ? retainedCount / size : 0);
|
|
740
|
+
}
|
|
741
|
+
cohorts.push({ startDate: periodStart, size, retention });
|
|
742
|
+
}
|
|
743
|
+
// Sort by start date
|
|
744
|
+
cohorts.sort((a, b) => a.startDate - b.startDate);
|
|
745
|
+
return { cohorts, granularity };
|
|
746
|
+
};
|
|
747
|
+
// Create a thenable that can be awaited directly
|
|
748
|
+
const cohortPromise = Promise.resolve().then(async () => {
|
|
749
|
+
return calculateCohort(storage.events);
|
|
750
|
+
});
|
|
751
|
+
const cohortQuery = cohortPromise;
|
|
752
|
+
cohortQuery.by = async (field) => {
|
|
753
|
+
const groups = new Map();
|
|
754
|
+
for (const event of storage.events) {
|
|
755
|
+
const value = event.data[field];
|
|
756
|
+
if (value !== undefined) {
|
|
757
|
+
const key = String(value);
|
|
758
|
+
if (!groups.has(key))
|
|
759
|
+
groups.set(key, []);
|
|
760
|
+
groups.get(key).push(event);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const result = {};
|
|
764
|
+
for (const [key, groupEvents] of groups) {
|
|
765
|
+
result[key] = calculateCohort(groupEvents);
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
768
|
+
};
|
|
769
|
+
return cohortQuery;
|
|
770
|
+
}
|
|
771
|
+
// =============================================================================
|
|
772
|
+
// STEP HELPER
|
|
773
|
+
// =============================================================================
|
|
774
|
+
/**
|
|
775
|
+
* Create a funnel step definition
|
|
776
|
+
*/
|
|
777
|
+
export function $step(eventType) {
|
|
778
|
+
const step = { eventType };
|
|
779
|
+
step.where = (filter) => {
|
|
780
|
+
return { eventType, filter };
|
|
781
|
+
};
|
|
782
|
+
return step;
|
|
783
|
+
}
|
|
784
|
+
// =============================================================================
|
|
785
|
+
// TRACK PROXY
|
|
786
|
+
// =============================================================================
|
|
787
|
+
function createTrackProxy(storage) {
|
|
788
|
+
const cache = new Map();
|
|
789
|
+
const proxy = new Proxy({}, {
|
|
790
|
+
get(target, prop) {
|
|
791
|
+
if (prop === 'batch') {
|
|
792
|
+
return async (events) => {
|
|
793
|
+
const results = [];
|
|
794
|
+
const baseTime = Date.now();
|
|
795
|
+
for (let i = 0; i < events.length; i++) {
|
|
796
|
+
const { event: eventType, data, options } = events[i];
|
|
797
|
+
const trackedEvent = {
|
|
798
|
+
id: generateId(),
|
|
799
|
+
type: eventType,
|
|
800
|
+
data,
|
|
801
|
+
timestamp: baseTime + i, // Sequential timestamps
|
|
802
|
+
...(options?.experiment && { experiment: options.experiment }),
|
|
803
|
+
...(options?.variant && { variant: options.variant }),
|
|
804
|
+
};
|
|
805
|
+
storage.events.push(trackedEvent);
|
|
806
|
+
results.push(trackedEvent);
|
|
807
|
+
// Notify subscribers
|
|
808
|
+
const subs = storage.subscriptions.get(eventType) || [];
|
|
809
|
+
for (const sub of subs) {
|
|
810
|
+
if (!sub.filter || matchesFilter(trackedEvent, sub.filter)) {
|
|
811
|
+
sub.handler(trackedEvent);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return results;
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (prop === 'funnel') {
|
|
819
|
+
return (steps) => {
|
|
820
|
+
return createFunnelQuery(steps, storage);
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
if (prop === 'cohort') {
|
|
824
|
+
return (config) => {
|
|
825
|
+
return createCohortQuery(config, storage);
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
if (prop === 'ratio') {
|
|
829
|
+
return async (numerator, denominator) => {
|
|
830
|
+
// Parse denominator - may include filter like "PageView:/pricing"
|
|
831
|
+
let denomEventType = denominator;
|
|
832
|
+
let denomFilter;
|
|
833
|
+
if (denominator.includes(':')) {
|
|
834
|
+
const [type, path] = denominator.split(':');
|
|
835
|
+
denomEventType = type;
|
|
836
|
+
denomFilter = { path };
|
|
837
|
+
}
|
|
838
|
+
const numeratorEvents = storage.events.filter((e) => e.type === numerator);
|
|
839
|
+
const denominatorEvents = storage.events.filter((e) => {
|
|
840
|
+
if (e.type !== denomEventType)
|
|
841
|
+
return false;
|
|
842
|
+
if (denomFilter && !matchesFilter(e, denomFilter))
|
|
843
|
+
return false;
|
|
844
|
+
return true;
|
|
845
|
+
});
|
|
846
|
+
if (denominatorEvents.length === 0)
|
|
847
|
+
return 0;
|
|
848
|
+
return numeratorEvents.length / denominatorEvents.length;
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
// Return cached event proxy or create new one
|
|
852
|
+
if (!cache.has(prop)) {
|
|
853
|
+
cache.set(prop, createEventProxy(prop, storage));
|
|
854
|
+
}
|
|
855
|
+
return cache.get(prop);
|
|
856
|
+
},
|
|
857
|
+
});
|
|
858
|
+
return proxy;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Create a track context for event tracking
|
|
862
|
+
*/
|
|
863
|
+
export function createTrackContext() {
|
|
864
|
+
const storage = createStorage();
|
|
865
|
+
return {
|
|
866
|
+
track: createTrackProxy(storage),
|
|
867
|
+
_storage: {
|
|
868
|
+
put: async (type, data, timestamp) => {
|
|
869
|
+
const event = {
|
|
870
|
+
id: generateId(),
|
|
871
|
+
type,
|
|
872
|
+
data,
|
|
873
|
+
timestamp,
|
|
874
|
+
};
|
|
875
|
+
storage.events.push(event);
|
|
876
|
+
},
|
|
877
|
+
clear: async () => {
|
|
878
|
+
storage.events.length = 0;
|
|
879
|
+
},
|
|
880
|
+
},
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
//# sourceMappingURL=context.js.map
|