@elevasis/core 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/dist/index.d.ts +435 -0
- package/dist/index.js +403 -0
- package/dist/organization-model/index.d.ts +435 -0
- package/dist/organization-model/index.js +403 -0
- package/package.json +62 -0
- package/src/README.md +34 -0
- package/src/__tests__/observability-exports.test.ts +36 -0
- package/src/__tests__/publish.test.ts +18 -0
- package/src/__tests__/template-foundations-compatibility.test.ts +34 -0
- package/src/auth/index.ts +8 -0
- package/src/auth/multi-tenancy/credentials/README.md +38 -0
- package/src/auth/multi-tenancy/credentials/__tests__/encryption.test.ts +216 -0
- package/src/auth/multi-tenancy/credentials/__tests__/service.test.ts +174 -0
- package/src/auth/multi-tenancy/credentials/index.ts +6 -0
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +39 -0
- package/src/auth/multi-tenancy/credentials/server/service.ts +60 -0
- package/src/auth/multi-tenancy/index.ts +17 -0
- package/src/auth/multi-tenancy/invitations/__tests__/invitation.test.ts +237 -0
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +107 -0
- package/src/auth/multi-tenancy/invitations/index.ts +38 -0
- package/src/auth/multi-tenancy/invitations/invitation.ts +86 -0
- package/src/auth/multi-tenancy/invitations/server/index.ts +25 -0
- package/src/auth/multi-tenancy/invitations/server/transforms.ts +24 -0
- package/src/auth/multi-tenancy/invitations/server/workos.ts +24 -0
- package/src/auth/multi-tenancy/invitations/supabase.ts +50 -0
- package/src/auth/multi-tenancy/memberships/__tests__/membership.test.ts +227 -0
- package/src/auth/multi-tenancy/memberships/__tests__/supabase-transforms.test.ts +88 -0
- package/src/auth/multi-tenancy/memberships/__tests__/workos-transforms.test.ts +139 -0
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +126 -0
- package/src/auth/multi-tenancy/memberships/index.ts +22 -0
- package/src/auth/multi-tenancy/memberships/membership.ts +138 -0
- package/src/auth/multi-tenancy/memberships/server/index.ts +15 -0
- package/src/auth/multi-tenancy/memberships/server/transforms.ts +32 -0
- package/src/auth/multi-tenancy/memberships/server/workos.ts +21 -0
- package/src/auth/multi-tenancy/memberships/supabase.ts +46 -0
- package/src/auth/multi-tenancy/organizations/__tests__/organization.test.ts +249 -0
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +128 -0
- package/src/auth/multi-tenancy/organizations/index.ts +23 -0
- package/src/auth/multi-tenancy/organizations/organization.ts +25 -0
- package/src/auth/multi-tenancy/organizations/server/index.ts +10 -0
- package/src/auth/multi-tenancy/organizations/server/transforms.ts +35 -0
- package/src/auth/multi-tenancy/organizations/server/workos.ts +20 -0
- package/src/auth/multi-tenancy/types.ts +89 -0
- package/src/auth/multi-tenancy/users/__tests__/user.test.ts +208 -0
- package/src/auth/multi-tenancy/users/api-schemas.ts +194 -0
- package/src/auth/multi-tenancy/users/index.ts +28 -0
- package/src/auth/multi-tenancy/users/server/index.ts +19 -0
- package/src/auth/multi-tenancy/users/server/transforms.ts +21 -0
- package/src/auth/multi-tenancy/users/server/workos.ts +16 -0
- package/src/auth/multi-tenancy/users/user.ts +65 -0
- package/src/business/acquisition/api-schemas.ts +759 -0
- package/src/business/acquisition/index.ts +109 -0
- package/src/business/acquisition/types.ts +400 -0
- package/src/business/crm/api-schemas.ts +75 -0
- package/src/business/delivery/index.ts +1 -0
- package/src/business/delivery/types.ts +89 -0
- package/src/business/index.ts +12 -0
- package/src/business/pdf/assets/ElevasisLogo.png +0 -0
- package/src/business/pdf/browser/image-utils.ts +74 -0
- package/src/business/pdf/browser/index.ts +16 -0
- package/src/business/pdf/browser/pdfmake-browser.ts +229 -0
- package/src/business/pdf/index.ts +10 -0
- package/src/business/pdf/sections/acceptance.ts +112 -0
- package/src/business/pdf/sections/automation.ts +56 -0
- package/src/business/pdf/sections/cover.ts +51 -0
- package/src/business/pdf/sections/index.ts +57 -0
- package/src/business/pdf/sections/investment.ts +69 -0
- package/src/business/pdf/sections/proposal-document.ts +200 -0
- package/src/business/pdf/sections/summary-investment.ts +124 -0
- package/src/business/pdf/sections/summary.ts +55 -0
- package/src/business/pdf/sections/table-summary.ts +59 -0
- package/src/business/pdf/sections/types.ts +124 -0
- package/src/business/pdf/server/__tests__/pdfmake-test.ts +219 -0
- package/src/business/pdf/server/index.ts +21 -0
- package/src/business/pdf/server/pdfmake-service.ts +237 -0
- package/src/business/pdf/server/themes/default.ts +8 -0
- package/src/business/pdf/server/themes/index.ts +9 -0
- package/src/business/pdf/server/themes/types.ts +8 -0
- package/src/business/pdf/shared/convert.ts +514 -0
- package/src/business/pdf/shared/index.ts +12 -0
- package/src/business/pdf/themes.ts +78 -0
- package/src/business/pdf/types.ts +272 -0
- package/src/business/seo/__tests__/linking.test.ts +549 -0
- package/src/business/seo/__tests__/types.test.ts +404 -0
- package/src/business/seo/index.ts +2 -0
- package/src/business/seo/linking.ts +281 -0
- package/src/business/seo/types.ts +199 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/queue/index.ts +3 -0
- package/src/commands/queue/schemas.test.ts +593 -0
- package/src/commands/queue/schemas.ts +125 -0
- package/src/commands/queue/sse-events.ts +61 -0
- package/src/commands/queue/types/action.ts +52 -0
- package/src/commands/queue/types/checkpoint.ts +44 -0
- package/src/commands/queue/types/index.ts +7 -0
- package/src/commands/queue/types/task.ts +116 -0
- package/src/commands/queue/types.ts +14 -0
- package/src/content/distribution-metadata.ts +61 -0
- package/src/content/index.ts +10 -0
- package/src/deployments/index.ts +22 -0
- package/src/execution/calibration/__tests__/schemas.test.ts +320 -0
- package/src/execution/calibration/index.ts +3 -0
- package/src/execution/calibration/schemas.ts +121 -0
- package/src/execution/calibration/sse-events.ts +125 -0
- package/src/execution/calibration/types.ts +190 -0
- package/src/execution/core/__tests__/api-schemas.test.ts +667 -0
- package/src/execution/core/__tests__/archived-logs.test.ts +72 -0
- package/src/execution/core/api-schemas.ts +312 -0
- package/src/execution/core/index.ts +11 -0
- package/src/execution/core/resource-validator.test.ts +63 -0
- package/src/execution/core/runner-types.ts +80 -0
- package/src/execution/core/server/environment.ts +31 -0
- package/src/execution/core/sse-executions.ts +119 -0
- package/src/execution/core/types.ts +29 -0
- package/src/execution/engine/__tests__/fixtures/index.ts +2 -0
- package/src/execution/engine/__tests__/fixtures/mock-scenarios.ts +60 -0
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +85 -0
- package/src/execution/engine/__tests__/integration/agent-framework.integration.test.ts +1031 -0
- package/src/execution/engine/__tests__/timeout.test.ts +565 -0
- package/src/execution/engine/agent/__tests__/errors.test.ts +508 -0
- package/src/execution/engine/agent/actions/__tests__/processor.test.ts +531 -0
- package/src/execution/engine/agent/actions/executor.ts +205 -0
- package/src/execution/engine/agent/actions/navigate-knowledge-executor.ts +230 -0
- package/src/execution/engine/agent/actions/processor.ts +116 -0
- package/src/execution/engine/agent/actions/types.ts +70 -0
- package/src/execution/engine/agent/core/__tests__/agent.test.ts +614 -0
- package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +134 -0
- package/src/execution/engine/agent/core/agent.ts +810 -0
- package/src/execution/engine/agent/core/types.ts +155 -0
- package/src/execution/engine/agent/errors.ts +251 -0
- package/src/execution/engine/agent/index.ts +78 -0
- package/src/execution/engine/agent/knowledge-map/__tests__/navigate-knowledge-executor.test.ts +580 -0
- package/src/execution/engine/agent/knowledge-map/__tests__/utils.test.ts +622 -0
- package/src/execution/engine/agent/knowledge-map/types.ts +106 -0
- package/src/execution/engine/agent/knowledge-map/utils.ts +101 -0
- package/src/execution/engine/agent/memory/__tests__/domains.test.ts +72 -0
- package/src/execution/engine/agent/memory/__tests__/manager.test.ts +754 -0
- package/src/execution/engine/agent/memory/__tests__/utils.test.ts +285 -0
- package/src/execution/engine/agent/memory/domains.ts +99 -0
- package/src/execution/engine/agent/memory/manager.ts +365 -0
- package/src/execution/engine/agent/memory/processor.ts +66 -0
- package/src/execution/engine/agent/memory/types.ts +90 -0
- package/src/execution/engine/agent/memory/utils.ts +134 -0
- package/src/execution/engine/agent/observability/logging.ts +467 -0
- package/src/execution/engine/agent/observability/types.ts +64 -0
- package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +209 -0
- package/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts +349 -0
- package/src/execution/engine/agent/reasoning/processor.ts +92 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts +134 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/completion.ts +49 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/knowledge-map.ts +93 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/memory.ts +65 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/security.ts +32 -0
- package/src/execution/engine/agent/reasoning/prompt-sections/tools.ts +44 -0
- package/src/execution/engine/agent/reasoning/request-builder.ts +169 -0
- package/src/execution/engine/agent/reasoning/types.ts +18 -0
- package/src/execution/engine/base/__tests__/errors.test.ts +246 -0
- package/src/execution/engine/base/__tests__/serialization.test.ts +670 -0
- package/src/execution/engine/base/__tests__/utils.test.ts +45 -0
- package/src/execution/engine/base/errors.ts +118 -0
- package/src/execution/engine/base/index.ts +2 -0
- package/src/execution/engine/base/logging.ts +31 -0
- package/src/execution/engine/base/serialization.ts +324 -0
- package/src/execution/engine/base/types.ts +126 -0
- package/src/execution/engine/base/utils.ts +41 -0
- package/src/execution/engine/index.ts +440 -0
- package/src/execution/engine/interface/index.ts +1 -0
- package/src/execution/engine/interface/types.ts +62 -0
- package/src/execution/engine/llm/__tests__/errors.test.ts +318 -0
- package/src/execution/engine/llm/__tests__/input-sanitizer.test.ts +286 -0
- package/src/execution/engine/llm/__tests__/model-info.test.ts +50 -0
- package/src/execution/engine/llm/__tests__/model-validation.test.ts +321 -0
- package/src/execution/engine/llm/__tests__/response-schema-validator.test.ts +115 -0
- package/src/execution/engine/llm/adapters/__tests__/adapter-factory.test.ts +375 -0
- package/src/execution/engine/llm/adapters/__tests__/anthropic-adapter.test.ts +463 -0
- package/src/execution/engine/llm/adapters/__tests__/anthropic.integration.test.ts +177 -0
- package/src/execution/engine/llm/adapters/__tests__/circuit-breaker-error.test.ts +94 -0
- package/src/execution/engine/llm/adapters/__tests__/google-adapter.test.ts +722 -0
- package/src/execution/engine/llm/adapters/__tests__/google.integration.test.ts +376 -0
- package/src/execution/engine/llm/adapters/__tests__/mock-adapter.test.ts +432 -0
- package/src/execution/engine/llm/adapters/__tests__/openai-adapter.test.ts +551 -0
- package/src/execution/engine/llm/adapters/__tests__/openrouter-adapter.test.ts +563 -0
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +105 -0
- package/src/execution/engine/llm/adapters/__tests__/universal-adapter.test.ts +537 -0
- package/src/execution/engine/llm/adapters/circuit-breaker.ts +147 -0
- package/src/execution/engine/llm/adapters/index.ts +17 -0
- package/src/execution/engine/llm/adapters/mock-adapter.ts +116 -0
- package/src/execution/engine/llm/adapters/server/adapter-factory.ts +130 -0
- package/src/execution/engine/llm/adapters/server/anthropic.ts +137 -0
- package/src/execution/engine/llm/adapters/server/compose-signal.ts +18 -0
- package/src/execution/engine/llm/adapters/server/google.ts +283 -0
- package/src/execution/engine/llm/adapters/server/index.ts +12 -0
- package/src/execution/engine/llm/adapters/server/openai.ts +206 -0
- package/src/execution/engine/llm/adapters/server/openrouter.ts +235 -0
- package/src/execution/engine/llm/adapters/universal-adapter.ts +230 -0
- package/src/execution/engine/llm/errors.ts +186 -0
- package/src/execution/engine/llm/input-sanitizer.ts +129 -0
- package/src/execution/engine/llm/model-info.ts +332 -0
- package/src/execution/engine/llm/response-schema-validator.ts +113 -0
- package/src/execution/engine/llm/types.ts +86 -0
- package/src/execution/engine/test-utils/index.ts +6 -0
- package/src/execution/engine/test-utils/mocks.ts +56 -0
- package/src/execution/engine/tools/__tests__/tooling-error.test.ts +265 -0
- package/src/execution/engine/tools/__tests__/types.test.ts +47 -0
- package/src/execution/engine/tools/integration/base-integration-adapter.ts +50 -0
- package/src/execution/engine/tools/integration/index.ts +53 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-adapter.ts +73 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-tools.ts +209 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-company-email/index.ts +82 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-decision-maker-email/index.ts +122 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-person-email/index.ts +89 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/verify-email/index.ts +84 -0
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/index.ts +16 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +293 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +100 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-tools.ts +217 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/get-dataset-items/index.ts +92 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/run-actor/index.ts +218 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/start-actor/index.ts +87 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/index.ts +11 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +362 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-adapter.ts +162 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-tools.ts +594 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/README.md +632 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-attribute/index.ts +214 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-note/index.ts +152 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-record/index.ts +141 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-note/index.ts +86 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-record/index.ts +105 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +186 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.ts +118 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-attributes/index.ts +165 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-notes/index.ts +96 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-objects/index.ts +104 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +338 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.ts +156 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-attribute/index.ts +220 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-record/index.ts +140 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/utils/types.ts +147 -0
- package/src/execution/engine/tools/integration/server/adapters/attio/index.ts +31 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/__tests__/dropbox-adapter.test.ts +409 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/dropbox-adapter.ts +281 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/dropbox-tools.ts +106 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/fetch/create-folder/__tests__/index.test.ts +451 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/fetch/create-folder/index.ts +114 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/fetch/upload-file/__tests__/index.test.ts +415 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/fetch/upload-file/index.ts +111 -0
- package/src/execution/engine/tools/integration/server/adapters/dropbox/index.ts +25 -0
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +210 -0
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +104 -0
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -0
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-adapter.ts +1189 -0
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-tools.ts +641 -0
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/index.ts +18 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/activate-campaign/index.ts +86 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/__tests__/index.test.ts +289 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/index.ts +154 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/__tests__/index.test.ts +325 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/index.ts +153 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-delete-leads/index.ts +84 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-campaign/index.ts +125 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-inbox-test/index.ts +107 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/delete-campaign/index.ts +85 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-account-health/index.ts +91 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign/index.ts +92 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/__tests__/index.test.ts +195 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/index.ts +113 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-daily-campaign-analytics/index.ts +104 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-emails/index.ts +155 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/__tests__/index.test.ts +196 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/index.ts +102 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/__tests__/index.test.ts +189 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/index.ts +87 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-leads/index.ts +112 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/patch-lead/index.ts +76 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/pause-campaign/index.ts +86 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/remove-from-subsequence/index.ts +98 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/send-reply/index.ts +126 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/__tests__/index.test.ts +193 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/index.ts +99 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/__tests__/index.test.ts +621 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/index.ts +125 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/index.ts +29 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-adapter.ts +178 -0
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1473 -0
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/check-credits/index.ts +59 -0
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/verify-email/index.ts +102 -0
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/index.ts +17 -0
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-adapter.ts +80 -0
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +102 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/get-email/index.ts +102 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +134 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +75 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/index.ts +27 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-adapter.ts +108 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-tools.ts +132 -0
- package/src/execution/engine/tools/integration/server/adapters/resend/types.ts +44 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/create-envelope/index.ts +274 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/download-document/index.ts +230 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/get-envelope/index.ts +133 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/utils/types.ts +246 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/void-envelope/index.ts +90 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/index.ts +38 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-adapter.ts +87 -0
- package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-tools.ts +179 -0
- package/src/execution/engine/tools/integration/server/adapters/stripe/fetch/utils/types.ts +210 -0
- package/src/execution/engine/tools/integration/server/adapters/stripe/index.ts +44 -0
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-adapter.ts +517 -0
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +309 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/domain-search/index.ts +133 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-finder/index.ts +122 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-verifier/index.ts +111 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/index.ts +11 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-adapter.ts +78 -0
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-tools.ts +222 -0
- package/src/execution/engine/tools/integration/server/index.ts +61 -0
- package/src/execution/engine/tools/integration/service.ts +161 -0
- package/src/execution/engine/tools/integration/tool.ts +253 -0
- package/src/execution/engine/tools/integration/types/anymailfinder.ts +74 -0
- package/src/execution/engine/tools/integration/types/apify.ts +92 -0
- package/src/execution/engine/tools/integration/types/attio.ts +354 -0
- package/src/execution/engine/tools/integration/types/dropbox.ts +64 -0
- package/src/execution/engine/tools/integration/types/gmail.ts +35 -0
- package/src/execution/engine/tools/integration/types/google-sheets.ts +303 -0
- package/src/execution/engine/tools/integration/types/index.ts +19 -0
- package/src/execution/engine/tools/integration/types/instantly.ts +557 -0
- package/src/execution/engine/tools/integration/types/millionverifier.ts +56 -0
- package/src/execution/engine/tools/integration/types/resend.ts +63 -0
- package/src/execution/engine/tools/integration/types/signature-api.ts +164 -0
- package/src/execution/engine/tools/integration/types/stripe.ts +162 -0
- package/src/execution/engine/tools/integration/types/tomba.ts +94 -0
- package/src/execution/engine/tools/lead-service-types.ts +884 -0
- package/src/execution/engine/tools/llm/index.ts +11 -0
- package/src/execution/engine/tools/llm/server/index.ts +8 -0
- package/src/execution/engine/tools/llm/server/llm-call-tool.ts +118 -0
- package/src/execution/engine/tools/platform/__tests__/approval.test.ts +242 -0
- package/src/execution/engine/tools/platform/__tests__/email.test.ts +482 -0
- package/src/execution/engine/tools/platform/__tests__/hitl-cancel.test.ts +97 -0
- package/src/execution/engine/tools/platform/__tests__/notification.test.ts +208 -0
- package/src/execution/engine/tools/platform/__tests__/pdf.test.ts +441 -0
- package/src/execution/engine/tools/platform/__tests__/scheduler.test.ts +189 -0
- package/src/execution/engine/tools/platform/__tests__/schedules.test.ts +336 -0
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +248 -0
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +319 -0
- package/src/execution/engine/tools/platform/acquisition/index.ts +43 -0
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +148 -0
- package/src/execution/engine/tools/platform/acquisition/types.ts +260 -0
- package/src/execution/engine/tools/platform/approval/cancel-by-metadata.ts +65 -0
- package/src/execution/engine/tools/platform/approval/index.ts +4 -0
- package/src/execution/engine/tools/platform/approval/tool.ts +99 -0
- package/src/execution/engine/tools/platform/email/index.ts +122 -0
- package/src/execution/engine/tools/platform/email/types.ts +96 -0
- package/src/execution/engine/tools/platform/index.ts +181 -0
- package/src/execution/engine/tools/platform/notification.ts +81 -0
- package/src/execution/engine/tools/platform/pdf/index.ts +110 -0
- package/src/execution/engine/tools/platform/pdf/types.ts +77 -0
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/edge-cases.test.ts +507 -0
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/resource-invocation-service.test.ts +500 -0
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/tool.test.ts +555 -0
- package/src/execution/engine/tools/platform/resource-invocation/dynamic-tool.ts +94 -0
- package/src/execution/engine/tools/platform/resource-invocation/index.ts +14 -0
- package/src/execution/engine/tools/platform/resource-invocation/resource-invocation-service.ts +147 -0
- package/src/execution/engine/tools/platform/resource-invocation/tool.ts +115 -0
- package/src/execution/engine/tools/platform/resource-invocation/types.ts +31 -0
- package/src/execution/engine/tools/platform/scheduler.ts +87 -0
- package/src/execution/engine/tools/platform/schedules/cancel-by-key-tool.ts +48 -0
- package/src/execution/engine/tools/platform/schedules/cancel-by-metadata-tool.ts +42 -0
- package/src/execution/engine/tools/platform/schedules/delete-by-key-tool.ts +43 -0
- package/src/execution/engine/tools/platform/schedules/index.ts +13 -0
- package/src/execution/engine/tools/platform/schedules/list-tool.ts +56 -0
- package/src/execution/engine/tools/platform/schedules/types.ts +88 -0
- package/src/execution/engine/tools/platform/storage/__tests__/storage.test.ts +998 -0
- package/src/execution/engine/tools/platform/storage/index.ts +370 -0
- package/src/execution/engine/tools/platform/storage/types.ts +128 -0
- package/src/execution/engine/tools/platform/types.ts +148 -0
- package/src/execution/engine/tools/registry.ts +590 -0
- package/src/execution/engine/tools/tool-maps.ts +694 -0
- package/src/execution/engine/tools/types.ts +233 -0
- package/src/execution/engine/workflow/__tests__/errors.test.ts +139 -0
- package/src/execution/engine/workflow/__tests__/utils.test.ts +645 -0
- package/src/execution/engine/workflow/__tests__/workflow.test.ts +818 -0
- package/src/execution/engine/workflow/errors.ts +63 -0
- package/src/execution/engine/workflow/helpers/index.ts +11 -0
- package/src/execution/engine/workflow/helpers/server/index.ts +8 -0
- package/src/execution/engine/workflow/helpers/server/llm-call.ts +93 -0
- package/src/execution/engine/workflow/index.ts +19 -0
- package/src/execution/engine/workflow/log-truncate.ts +26 -0
- package/src/execution/engine/workflow/logging.ts +191 -0
- package/src/execution/engine/workflow/types.ts +183 -0
- package/src/execution/engine/workflow/utils.ts +280 -0
- package/src/execution/engine/workflow/workflow.ts +168 -0
- package/src/execution/index.ts +20 -0
- package/src/execution/scheduler/__tests__/api-schemas.test.ts +733 -0
- package/src/execution/scheduler/__tests__/retry.test.ts +37 -0
- package/src/execution/scheduler/__tests__/utils.test.ts +1009 -0
- package/src/execution/scheduler/api-schemas.ts +296 -0
- package/src/execution/scheduler/index.ts +50 -0
- package/src/execution/scheduler/schemas.ts +264 -0
- package/src/execution/scheduler/types.ts +111 -0
- package/src/execution/scheduler/utils.ts +364 -0
- package/src/forms/index.ts +7 -0
- package/src/forms/schemas.test.ts +113 -0
- package/src/forms/schemas.ts +69 -0
- package/src/forms/types.ts +70 -0
- package/src/index.ts +54 -0
- package/src/integrations/credentials/__tests__/api-schemas.test.ts +496 -0
- package/src/integrations/credentials/__tests__/schemas.test.ts +82 -0
- package/src/integrations/credentials/__tests__/utils.test.ts +144 -0
- package/src/integrations/credentials/api-schemas.ts +143 -0
- package/src/integrations/credentials/index.ts +32 -0
- package/src/integrations/credentials/schemas.ts +164 -0
- package/src/integrations/credentials/utils.ts +59 -0
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +59 -0
- package/src/integrations/oauth/api-schemas.ts +92 -0
- package/src/integrations/oauth/index.ts +19 -0
- package/src/integrations/oauth/provider-registry.ts +61 -0
- package/src/integrations/oauth/server/__tests__/refresh-concurrent.test.ts +183 -0
- package/src/integrations/oauth/server/__tests__/refresh.integration.test.ts +257 -0
- package/src/integrations/oauth/server/__tests__/refresh.test.ts +577 -0
- package/src/integrations/oauth/server/credentials.ts +39 -0
- package/src/integrations/oauth/server/refresh.ts +214 -0
- package/src/integrations/oauth/types.ts +34 -0
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +318 -0
- package/src/integrations/webhook-endpoints/api-schemas.ts +102 -0
- package/src/integrations/webhook-endpoints/index.ts +28 -0
- package/src/integrations/webhook-endpoints/types.ts +51 -0
- package/src/operations/activities/api-schemas.ts +79 -0
- package/src/operations/activities/index.ts +9 -0
- package/src/operations/activities/sse-events.ts +30 -0
- package/src/operations/activities/types.ts +63 -0
- package/src/operations/debug-logs/client.ts +60 -0
- package/src/operations/debug-logs/debug-logger.ts +83 -0
- package/src/operations/debug-logs/index.ts +8 -0
- package/src/operations/debug-logs/server.ts +19 -0
- package/src/operations/debug-logs/types.ts +33 -0
- package/src/operations/index.ts +50 -0
- package/src/operations/notifications/__tests__/api-schemas.test.ts +216 -0
- package/src/operations/notifications/api-schemas.ts +91 -0
- package/src/operations/notifications/index.ts +3 -0
- package/src/operations/notifications/sse-events.ts +21 -0
- package/src/operations/notifications/types.ts +47 -0
- package/src/operations/observability/__tests__/openrouter-cost-flow.test.ts +297 -0
- package/src/operations/observability/__tests__/schemas.test.ts +151 -0
- package/src/operations/observability/__tests__/types.test.ts +109 -0
- package/src/operations/observability/__tests__/utils.test.ts +54 -0
- package/src/operations/observability/ai-usage-collector.ts +64 -0
- package/src/operations/observability/index.ts +13 -0
- package/src/operations/observability/metrics-collector.ts +49 -0
- package/src/operations/observability/schemas.ts +39 -0
- package/src/operations/observability/types.ts +463 -0
- package/src/operations/observability/utils.ts +77 -0
- package/src/operations/sessions/__tests__/api-schemas.test.ts +361 -0
- package/src/operations/sessions/__tests__/manager.test.ts +821 -0
- package/src/operations/sessions/api-schemas.ts +166 -0
- package/src/operations/sessions/index.ts +26 -0
- package/src/operations/sessions/server/manager.ts +90 -0
- package/src/operations/sessions/server/session.ts +180 -0
- package/src/operations/sessions/types.ts +98 -0
- package/src/operations/triggers/index.ts +12 -0
- package/src/operations/triggers/webhook/definitions/__tests__/instantly-reply-received.test.ts +72 -0
- package/src/operations/triggers/webhook/definitions/instantly-account-error.ts +44 -0
- package/src/operations/triggers/webhook/definitions/instantly-auto-reply-received.ts +51 -0
- package/src/operations/triggers/webhook/definitions/instantly-campaign-completed.ts +45 -0
- package/src/operations/triggers/webhook/definitions/instantly-email-bounced.ts +49 -0
- package/src/operations/triggers/webhook/definitions/instantly-lead-unsubscribed.ts +45 -0
- package/src/operations/triggers/webhook/definitions/instantly-reply-received.ts +54 -0
- package/src/operations/triggers/webhook/index.ts +35 -0
- package/src/operations/triggers/webhook/types.ts +74 -0
- package/src/organization-model/README.md +79 -0
- package/src/organization-model/__tests__/graph.test.ts +250 -0
- package/src/organization-model/__tests__/resolve.test.ts +47 -0
- package/src/organization-model/defaults.ts +60 -0
- package/src/organization-model/domains/branding.ts +22 -0
- package/src/organization-model/domains/crm.ts +46 -0
- package/src/organization-model/domains/delivery.ts +48 -0
- package/src/organization-model/domains/features.ts +57 -0
- package/src/organization-model/domains/lead-gen.ts +33 -0
- package/src/organization-model/domains/navigation.ts +103 -0
- package/src/organization-model/domains/shared.ts +42 -0
- package/src/organization-model/graph/build.ts +432 -0
- package/src/organization-model/graph/index.ts +4 -0
- package/src/organization-model/graph/schema.ts +50 -0
- package/src/organization-model/graph/types.ts +52 -0
- package/src/organization-model/index.ts +11 -0
- package/src/organization-model/published.ts +18 -0
- package/src/organization-model/resolve.ts +42 -0
- package/src/organization-model/schema.ts +21 -0
- package/src/organization-model/types.ts +27 -0
- package/src/platform/api/index.ts +1 -0
- package/src/platform/api/types.ts +35 -0
- package/src/platform/constants/http.ts +37 -0
- package/src/platform/constants/index.ts +5 -0
- package/src/platform/constants/limits.ts +32 -0
- package/src/platform/constants/resilience.ts +51 -0
- package/src/platform/constants/timeouts.ts +20 -0
- package/src/platform/constants/versions.ts +3 -0
- package/src/platform/index.ts +27 -0
- package/src/platform/registry/__tests__/command-view.test.ts +410 -0
- package/src/platform/registry/__tests__/resource-registry-static.test.ts +347 -0
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +1004 -0
- package/src/platform/registry/__tests__/resource-registry.list-executable.test.ts +393 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +1942 -0
- package/src/platform/registry/__tests__/serialization.test.ts +1127 -0
- package/src/platform/registry/__tests__/validation.test.ts +1086 -0
- package/src/platform/registry/command-view.ts +180 -0
- package/src/platform/registry/domains.ts +165 -0
- package/src/platform/registry/index.ts +93 -0
- package/src/platform/registry/reserved.ts +24 -0
- package/src/platform/registry/resource-metadata.ts +59 -0
- package/src/platform/registry/resource-registry.command-queue-groups.test.ts +129 -0
- package/src/platform/registry/resource-registry.ts +788 -0
- package/src/platform/registry/serialization.ts +273 -0
- package/src/platform/registry/serialized-types.ts +231 -0
- package/src/platform/registry/stats-types.ts +66 -0
- package/src/platform/registry/types.ts +404 -0
- package/src/platform/registry/validation.ts +513 -0
- package/src/platform/resilience/__tests__/circuit-breaker.test.ts +291 -0
- package/src/platform/resilience/__tests__/http-error-mapper.test.ts +173 -0
- package/src/platform/resilience/__tests__/rate-limiter.test.ts +471 -0
- package/src/platform/resilience/__tests__/retry.test.ts +380 -0
- package/src/platform/resilience/__tests__/timeout.test.ts +219 -0
- package/src/platform/resilience/circuit-breaker.ts +164 -0
- package/src/platform/resilience/errors.ts +68 -0
- package/src/platform/resilience/http-error-mapper.ts +129 -0
- package/src/platform/resilience/index.ts +93 -0
- package/src/platform/resilience/rate-limiter-types.ts +46 -0
- package/src/platform/resilience/rate-limiter.ts +140 -0
- package/src/platform/resilience/retry.ts +89 -0
- package/src/platform/resilience/timeout.ts +63 -0
- package/src/platform/sse/events.ts +67 -0
- package/src/platform/sse/index.ts +7 -0
- package/src/platform/utils/__tests__/currency.test.ts +77 -0
- package/src/platform/utils/__tests__/validation.test.ts +1083 -0
- package/src/platform/utils/currency.ts +96 -0
- package/src/platform/utils/debounce.ts +52 -0
- package/src/platform/utils/error.ts +42 -0
- package/src/platform/utils/hmac.test.ts +97 -0
- package/src/platform/utils/index.ts +32 -0
- package/src/platform/utils/server/betterstack-logger.ts +210 -0
- package/src/platform/utils/server/hmac.ts +44 -0
- package/src/platform/utils/server/unsubscribe.ts +111 -0
- package/src/platform/utils/token-counter.ts +96 -0
- package/src/platform/utils/validation.ts +425 -0
- package/src/projects/api-schemas.ts +265 -0
- package/src/published.ts +1 -0
- package/src/server.ts +273 -0
- package/src/supabase/__tests__/helpers.test.ts +51 -0
- package/src/supabase/database.types.ts +2674 -0
- package/src/supabase/helpers.ts +20 -0
- package/src/supabase/index.ts +52 -0
- package/src/supabase/server/client.ts +58 -0
- package/src/test-utils/README.md +150 -0
- package/src/test-utils/browser-mocks.ts +54 -0
- package/src/test-utils/fixtures/api-keys.ts +52 -0
- package/src/test-utils/fixtures/index.ts +4 -0
- package/src/test-utils/fixtures/memberships.ts +80 -0
- package/src/test-utils/fixtures/organizations.ts +69 -0
- package/src/test-utils/fixtures/users.ts +79 -0
- package/src/test-utils/index.ts +11 -0
- package/src/test-utils/mocks/index.ts +2 -0
- package/src/test-utils/mocks/supabase.ts +142 -0
- package/src/test-utils/mocks/workos.ts +108 -0
- package/src/test-utils/rls/RLSTestContext.ts +586 -0
- package/src/test-utils/rls/index.ts +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* These utilities handle formatting of USD currency values for display.
|
|
5
|
+
* Database stores values as NUMERIC (exact decimal precision), JavaScript
|
|
6
|
+
* uses number (IEEE 754 floating point).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format USD amount for display
|
|
11
|
+
*
|
|
12
|
+
* @param amountUsd - Amount in USD (e.g., 1.375 = $1.38)
|
|
13
|
+
* @param options - Formatting options
|
|
14
|
+
* @returns Formatted string (e.g., "$1.38")
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* formatCurrency(1.375) // "$1.38"
|
|
18
|
+
* formatCurrency(0.001) // "$0.00"
|
|
19
|
+
* formatCurrency(1234.56) // "$1,234.56"
|
|
20
|
+
* formatCurrency(1.5, { minimumFractionDigits: 4 }) // "$1.5000"
|
|
21
|
+
*/
|
|
22
|
+
export function formatCurrency(
|
|
23
|
+
amountUsd: number,
|
|
24
|
+
options: {
|
|
25
|
+
minimumFractionDigits?: number
|
|
26
|
+
maximumFractionDigits?: number
|
|
27
|
+
} = {}
|
|
28
|
+
): string {
|
|
29
|
+
const {
|
|
30
|
+
minimumFractionDigits = 2,
|
|
31
|
+
maximumFractionDigits = 2
|
|
32
|
+
} = options
|
|
33
|
+
|
|
34
|
+
return new Intl.NumberFormat('en-US', {
|
|
35
|
+
style: 'currency',
|
|
36
|
+
currency: 'USD',
|
|
37
|
+
minimumFractionDigits,
|
|
38
|
+
maximumFractionDigits
|
|
39
|
+
}).format(amountUsd)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format USD amount with high precision (for debugging/technical displays)
|
|
44
|
+
*
|
|
45
|
+
* @param amountUsd - Amount in USD
|
|
46
|
+
* @returns Formatted string with 6 decimal places (e.g., "$0.001375")
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* formatCurrencyPrecise(0.001375) // "$0.001375"
|
|
50
|
+
* formatCurrencyPrecise(1.5) // "$1.500000"
|
|
51
|
+
*/
|
|
52
|
+
export function formatCurrencyPrecise(amountUsd: number): string {
|
|
53
|
+
return formatCurrency(amountUsd, {
|
|
54
|
+
minimumFractionDigits: 6,
|
|
55
|
+
maximumFractionDigits: 6
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format USD amount as compact notation (for charts/dashboards)
|
|
61
|
+
*
|
|
62
|
+
* @param amountUsd - Amount in USD
|
|
63
|
+
* @returns Compact formatted string (e.g., "$1.2K", "$3.5M")
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* formatCurrencyCompact(1234) // "$1.2K"
|
|
67
|
+
* formatCurrencyCompact(1500000) // "$1.5M"
|
|
68
|
+
* formatCurrencyCompact(5) // "$5"
|
|
69
|
+
*/
|
|
70
|
+
export function formatCurrencyCompact(amountUsd: number): string {
|
|
71
|
+
return new Intl.NumberFormat('en-US', {
|
|
72
|
+
style: 'currency',
|
|
73
|
+
currency: 'USD',
|
|
74
|
+
notation: 'compact',
|
|
75
|
+
minimumFractionDigits: 0,
|
|
76
|
+
maximumFractionDigits: 1
|
|
77
|
+
}).format(amountUsd)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse string USD amount to number (for form inputs)
|
|
82
|
+
*
|
|
83
|
+
* @param value - String value (e.g., "$1,234.56" or "1234.56")
|
|
84
|
+
* @returns Parsed number or null if invalid
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* parseCurrency("$1,234.56") // 1234.56
|
|
88
|
+
* parseCurrency("1234.56") // 1234.56
|
|
89
|
+
* parseCurrency("invalid") // null
|
|
90
|
+
*/
|
|
91
|
+
export function parseCurrency(value: string): number | null {
|
|
92
|
+
// Remove currency symbol and commas
|
|
93
|
+
const cleaned = value.replace(/[$,]/g, '').trim()
|
|
94
|
+
const parsed = parseFloat(cleaned)
|
|
95
|
+
return isNaN(parsed) ? null : parsed
|
|
96
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debounce utility for delaying function execution
|
|
3
|
+
*
|
|
4
|
+
* Creates a debounced version of a function that delays invoking until
|
|
5
|
+
* after `wait` milliseconds have elapsed since the last time it was invoked.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a debounced function that delays invoking `fn` until after `wait`
|
|
10
|
+
* milliseconds have elapsed since the last time the debounced function was invoked.
|
|
11
|
+
*
|
|
12
|
+
* @param fn - The function to debounce
|
|
13
|
+
* @param wait - The number of milliseconds to delay
|
|
14
|
+
* @returns A debounced version of the function with a `cancel` method
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const debouncedSearch = debounce((query: string) => {
|
|
18
|
+
* console.log('Searching for:', query)
|
|
19
|
+
* }, 300)
|
|
20
|
+
*
|
|
21
|
+
* debouncedSearch('a')
|
|
22
|
+
* debouncedSearch('ab')
|
|
23
|
+
* debouncedSearch('abc') // Only this one executes after 300ms
|
|
24
|
+
*
|
|
25
|
+
* // Cancel pending execution
|
|
26
|
+
* debouncedSearch.cancel()
|
|
27
|
+
*/
|
|
28
|
+
export function debounce<T extends (...args: Parameters<T>) => void>(
|
|
29
|
+
fn: T,
|
|
30
|
+
wait: number
|
|
31
|
+
): ((...args: Parameters<T>) => void) & { cancel: () => void } {
|
|
32
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
33
|
+
|
|
34
|
+
const debounced = (...args: Parameters<T>) => {
|
|
35
|
+
if (timeoutId !== null) {
|
|
36
|
+
clearTimeout(timeoutId)
|
|
37
|
+
}
|
|
38
|
+
timeoutId = setTimeout(() => {
|
|
39
|
+
fn(...args)
|
|
40
|
+
timeoutId = null
|
|
41
|
+
}, wait)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
debounced.cancel = () => {
|
|
45
|
+
if (timeoutId !== null) {
|
|
46
|
+
clearTimeout(timeoutId)
|
|
47
|
+
timeoutId = null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return debounced
|
|
52
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ZodError } from 'zod'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert unknown error to string message
|
|
9
|
+
* Handles Error objects and other types safely
|
|
10
|
+
* Includes special handling for Zod validation errors
|
|
11
|
+
*/
|
|
12
|
+
export function errorToString(error: unknown): string {
|
|
13
|
+
if (error instanceof ZodError) {
|
|
14
|
+
// Format Zod validation errors with field path and details
|
|
15
|
+
return JSON.stringify(error.issues, null, 2)
|
|
16
|
+
}
|
|
17
|
+
return error instanceof Error ? error.message : String(error)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract structured error details for logging
|
|
22
|
+
* Includes validation errors, stack traces, and context
|
|
23
|
+
*/
|
|
24
|
+
export function getErrorDetails(error: unknown): Record<string, unknown> {
|
|
25
|
+
const details: Record<string, unknown> = {
|
|
26
|
+
message: errorToString(error),
|
|
27
|
+
type: error instanceof Error ? error.constructor.name : typeof error
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Add Zod validation details
|
|
31
|
+
if (error instanceof ZodError) {
|
|
32
|
+
details.validationErrors = error.issues
|
|
33
|
+
details.isValidationError = true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add stack trace for Error objects
|
|
37
|
+
if (error instanceof Error && error.stack) {
|
|
38
|
+
details.stack = error.stack
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return details
|
|
42
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generateHmacToken, verifyHmacToken, generateBrochureUrl } from './server/hmac'
|
|
3
|
+
|
|
4
|
+
describe('HMAC Token Utilities', () => {
|
|
5
|
+
const TEST_SECRET = 'test-secret-key-12345'
|
|
6
|
+
const TEST_CONTACT_ID = '550e8400-e29b-41d4-a716-446655440000'
|
|
7
|
+
|
|
8
|
+
describe('generateHmacToken', () => {
|
|
9
|
+
it('returns 16-character hex token', () => {
|
|
10
|
+
const token = generateHmacToken('test-data', TEST_SECRET)
|
|
11
|
+
expect(token).toHaveLength(16)
|
|
12
|
+
expect(token).toMatch(/^[0-9a-f]{16}$/)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('produces consistent tokens for same input', () => {
|
|
16
|
+
const token1 = generateHmacToken('test@example.com', TEST_SECRET)
|
|
17
|
+
const token2 = generateHmacToken('test@example.com', TEST_SECRET)
|
|
18
|
+
expect(token1).toBe(token2)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('normalizes input (lowercase and trim)', () => {
|
|
22
|
+
const token1 = generateHmacToken('TEST@EXAMPLE.COM', TEST_SECRET)
|
|
23
|
+
const token2 = generateHmacToken(' test@example.com ', TEST_SECRET)
|
|
24
|
+
const token3 = generateHmacToken('test@example.com', TEST_SECRET)
|
|
25
|
+
expect(token1).toBe(token2)
|
|
26
|
+
expect(token2).toBe(token3)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('produces different tokens for different secrets', () => {
|
|
30
|
+
const token1 = generateHmacToken('data', 'secret1')
|
|
31
|
+
const token2 = generateHmacToken('data', 'secret2')
|
|
32
|
+
expect(token1).not.toBe(token2)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('produces different tokens for different data', () => {
|
|
36
|
+
const token1 = generateHmacToken('data1', TEST_SECRET)
|
|
37
|
+
const token2 = generateHmacToken('data2', TEST_SECRET)
|
|
38
|
+
expect(token1).not.toBe(token2)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('verifyHmacToken', () => {
|
|
43
|
+
it('returns true for valid token', () => {
|
|
44
|
+
const token = generateHmacToken(TEST_CONTACT_ID, TEST_SECRET)
|
|
45
|
+
const isValid = verifyHmacToken(TEST_CONTACT_ID, token, TEST_SECRET)
|
|
46
|
+
expect(isValid).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns false for invalid token', () => {
|
|
50
|
+
const isValid = verifyHmacToken(TEST_CONTACT_ID, 'invalid-token-00', TEST_SECRET)
|
|
51
|
+
expect(isValid).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns false for wrong length token', () => {
|
|
55
|
+
const isValid = verifyHmacToken(TEST_CONTACT_ID, 'short', TEST_SECRET)
|
|
56
|
+
expect(isValid).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('returns false for wrong secret', () => {
|
|
60
|
+
const token = generateHmacToken(TEST_CONTACT_ID, TEST_SECRET)
|
|
61
|
+
const isValid = verifyHmacToken(TEST_CONTACT_ID, token, 'wrong-secret')
|
|
62
|
+
expect(isValid).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('verifies with normalized input', () => {
|
|
66
|
+
const token = generateHmacToken('test@example.com', TEST_SECRET)
|
|
67
|
+
const isValid = verifyHmacToken(' TEST@EXAMPLE.COM ', token, TEST_SECRET)
|
|
68
|
+
expect(isValid).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe('generateBrochureUrl', () => {
|
|
73
|
+
it('generates URL with default base', () => {
|
|
74
|
+
const url = generateBrochureUrl(TEST_CONTACT_ID, TEST_SECRET)
|
|
75
|
+
expect(url).toMatch(/^https:\/\/brochure\.elevasis\.io\//)
|
|
76
|
+
expect(url).toContain(TEST_CONTACT_ID)
|
|
77
|
+
expect(url).toMatch(/\?t=[0-9a-f]{16}$/)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('generates URL with custom base', () => {
|
|
81
|
+
const customBase = 'https://custom.example.com/brochure'
|
|
82
|
+
const url = generateBrochureUrl(TEST_CONTACT_ID, TEST_SECRET, customBase)
|
|
83
|
+
expect(url).toMatch(/^https:\/\/custom\.example\.com\/brochure\//)
|
|
84
|
+
expect(url).toContain(TEST_CONTACT_ID)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('generates verifiable token in URL', () => {
|
|
88
|
+
const url = generateBrochureUrl(TEST_CONTACT_ID, TEST_SECRET)
|
|
89
|
+
const tokenMatch = url.match(/\?t=([0-9a-f]{16})$/)
|
|
90
|
+
expect(tokenMatch).not.toBeNull()
|
|
91
|
+
|
|
92
|
+
const extractedToken = tokenMatch![1]
|
|
93
|
+
const isValid = verifyHmacToken(TEST_CONTACT_ID, extractedToken, TEST_SECRET)
|
|
94
|
+
expect(isValid).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { debounce } from './debounce'
|
|
6
|
+
export { errorToString, getErrorDetails } from './error'
|
|
7
|
+
export { formatCurrency, formatCurrencyPrecise, formatCurrencyCompact, parseCurrency } from './currency'
|
|
8
|
+
export { estimateTokens, validateTokenLimit, TOKEN_LIMITS } from './token-counter'
|
|
9
|
+
export type { TokenCountResult, TokenLimits } from './token-counter'
|
|
10
|
+
export {
|
|
11
|
+
UuidSchema,
|
|
12
|
+
NonEmptyStringSchema,
|
|
13
|
+
EmailSchema,
|
|
14
|
+
UrlSchema,
|
|
15
|
+
PaginationSchema,
|
|
16
|
+
TimestampSchema,
|
|
17
|
+
DateRangeSchema,
|
|
18
|
+
ResourceTypeSchema,
|
|
19
|
+
OriginResourceTypeSchema,
|
|
20
|
+
CredentialNameSchema,
|
|
21
|
+
OrganizationIdSchema,
|
|
22
|
+
OAuthProviderSchema,
|
|
23
|
+
OAuthCodeSchema,
|
|
24
|
+
OAuthStateParamSchema,
|
|
25
|
+
SanitizedStringSchema,
|
|
26
|
+
createEnumSchema,
|
|
27
|
+
createStringSchema,
|
|
28
|
+
createArraySchema,
|
|
29
|
+
createPayloadSizeValidator,
|
|
30
|
+
formatZodValidationError
|
|
31
|
+
} from './validation'
|
|
32
|
+
export type { PaginationParams, DateRange, ValidationErrorResponse, PaginatedResponse } from './validation'
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { ExecutionError } from '../../../execution/engine/base/errors'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Better Stack Logger (Simplified)
|
|
5
|
+
*
|
|
6
|
+
* Production-only error logging using Sentry SDK pointing to Better Stack.
|
|
7
|
+
* Provides domain-specific context that global error handler cannot infer.
|
|
8
|
+
*
|
|
9
|
+
* **Design Philosophy:**
|
|
10
|
+
* - Global error handler (setup-error-handler.ts) catches ALL errors automatically
|
|
11
|
+
* - bslog functions only add value when they provide context the global handler can't capture
|
|
12
|
+
* - WebSocket, SSE, Database, and Critical errors are already handled by global error handler
|
|
13
|
+
* - Only use these functions for external services and execution failures that need special context
|
|
14
|
+
*
|
|
15
|
+
* **Naming Convention:** Prefixed with "bslog" to distinguish from other logging systems:
|
|
16
|
+
* - Fastify Logger: request.log.info/error (HTTP lifecycle)
|
|
17
|
+
* - Execution Logger: executionLogger.logError (AI execution tracking)
|
|
18
|
+
* - Better Stack Logger: bslog*() (production error tracking & alerts)
|
|
19
|
+
*
|
|
20
|
+
* **When to Use:**
|
|
21
|
+
* - External service failures (OpenAI, WorkOS, OAuth) - global handler can't infer service name
|
|
22
|
+
* - Execution failures - global handler can't infer execution phase/resource type
|
|
23
|
+
* - System errors only (use isSystemError() to filter 5xx vs 4xx)
|
|
24
|
+
*
|
|
25
|
+
* **When NOT to Use:**
|
|
26
|
+
* - User errors (4xx validation, auth checks) - too noisy
|
|
27
|
+
* - WebSocket/SSE/Database errors - global handler already logs these
|
|
28
|
+
* - Generic errors - global handler already logs these
|
|
29
|
+
*
|
|
30
|
+
* All functions are no-ops unless NODE_ENV === 'production' AND API_BETTERSTACK_ERRORS_DSN is set.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Log external service failures (OpenAI, WorkOS, OAuth providers)
|
|
35
|
+
*
|
|
36
|
+
* Use this when external API calls fail to add service-specific context
|
|
37
|
+
* that the global error handler cannot infer.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* try {
|
|
42
|
+
* const response = await openai.chat.completions.create(params)
|
|
43
|
+
* } catch (error) {
|
|
44
|
+
* bslogExternalServiceError('openai', 'chat.completions.create', error, {
|
|
45
|
+
* model: params.model,
|
|
46
|
+
* tokens: params.max_tokens,
|
|
47
|
+
* organizationId: executionContext.organizationId,
|
|
48
|
+
* statusCode: (error as any).status
|
|
49
|
+
* })
|
|
50
|
+
* throw error
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export async function bslogExternalServiceError(
|
|
55
|
+
service: 'openai' | 'workos' | 'google-oauth' | 'notion-oauth' | 'attio-oauth' | string,
|
|
56
|
+
operation: string,
|
|
57
|
+
error: Error,
|
|
58
|
+
context?: {
|
|
59
|
+
statusCode?: number
|
|
60
|
+
response?: string
|
|
61
|
+
endpoint?: string
|
|
62
|
+
organizationId?: string
|
|
63
|
+
userId?: string
|
|
64
|
+
[key: string]: unknown
|
|
65
|
+
}
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
if (process.env.NODE_ENV === 'production' && process.env.API_BETTERSTACK_ERRORS_DSN) {
|
|
68
|
+
// Sentry is optional - only loaded in production
|
|
69
|
+
try {
|
|
70
|
+
const Sentry = await import('@sentry/node')
|
|
71
|
+
Sentry.captureException(error, {
|
|
72
|
+
tags: {
|
|
73
|
+
layer: 'integration',
|
|
74
|
+
service,
|
|
75
|
+
operation,
|
|
76
|
+
...(context?.statusCode && { status_code: context.statusCode })
|
|
77
|
+
},
|
|
78
|
+
user: context?.userId
|
|
79
|
+
? {
|
|
80
|
+
id: context.userId
|
|
81
|
+
}
|
|
82
|
+
: undefined,
|
|
83
|
+
extra: {
|
|
84
|
+
service,
|
|
85
|
+
operation,
|
|
86
|
+
organizationId: context?.organizationId,
|
|
87
|
+
endpoint: context?.endpoint,
|
|
88
|
+
response: context?.response,
|
|
89
|
+
...context
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
} catch {
|
|
93
|
+
// Sentry not available - skip logging
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Log workflow/agent execution failures
|
|
100
|
+
*
|
|
101
|
+
* Use this for critical business logic failures to add execution-specific
|
|
102
|
+
* context that the global error handler cannot infer.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* try {
|
|
107
|
+
* return await this.runExecution(params)
|
|
108
|
+
* } catch (error) {
|
|
109
|
+
* bslogExecutionFailure({
|
|
110
|
+
* executionId: execution.id,
|
|
111
|
+
* resourceType: params.resourceType,
|
|
112
|
+
* resourceId: params.resourceId,
|
|
113
|
+
* organizationId: params.organizationId,
|
|
114
|
+
* userId: params.userId,
|
|
115
|
+
* inputType: typeof params.input,
|
|
116
|
+
* errorPhase: 'execution'
|
|
117
|
+
* }, error as Error)
|
|
118
|
+
* throw error
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export async function bslogExecutionFailure(
|
|
123
|
+
executionContext: {
|
|
124
|
+
executionId?: string
|
|
125
|
+
resourceType: 'workflow' | 'agent'
|
|
126
|
+
resourceId: string
|
|
127
|
+
organizationId: string
|
|
128
|
+
userId?: string
|
|
129
|
+
inputType?: string
|
|
130
|
+
errorPhase?: 'initialization' | 'execution' | 'completion' | 'cleanup'
|
|
131
|
+
},
|
|
132
|
+
error: Error
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
if (process.env.NODE_ENV === 'production' && process.env.API_BETTERSTACK_ERRORS_DSN) {
|
|
135
|
+
try {
|
|
136
|
+
const Sentry = await import('@sentry/node')
|
|
137
|
+
const domainTags =
|
|
138
|
+
error instanceof ExecutionError
|
|
139
|
+
? {
|
|
140
|
+
error_type_domain: error.type,
|
|
141
|
+
error_severity: error.severity,
|
|
142
|
+
error_category: error.category,
|
|
143
|
+
is_retryable: String(error.isRetryable())
|
|
144
|
+
}
|
|
145
|
+
: {}
|
|
146
|
+
|
|
147
|
+
Sentry.captureException(error, {
|
|
148
|
+
tags: {
|
|
149
|
+
layer: 'execution',
|
|
150
|
+
resource_type: executionContext.resourceType,
|
|
151
|
+
organization_id: executionContext.organizationId,
|
|
152
|
+
error_phase: executionContext.errorPhase || 'execution',
|
|
153
|
+
...domainTags
|
|
154
|
+
},
|
|
155
|
+
user: executionContext.userId
|
|
156
|
+
? {
|
|
157
|
+
id: executionContext.userId
|
|
158
|
+
}
|
|
159
|
+
: undefined,
|
|
160
|
+
extra: {
|
|
161
|
+
executionId: executionContext.executionId,
|
|
162
|
+
resourceType: executionContext.resourceType,
|
|
163
|
+
resourceId: executionContext.resourceId,
|
|
164
|
+
organizationId: executionContext.organizationId,
|
|
165
|
+
inputType: executionContext.inputType,
|
|
166
|
+
errorPhase: executionContext.errorPhase
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
} catch {
|
|
170
|
+
// Sentry not available - skip logging
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Helper: Check if error should be logged to Better Stack
|
|
177
|
+
*
|
|
178
|
+
* Returns true for system errors (5xx), false for user errors (4xx)
|
|
179
|
+
*
|
|
180
|
+
* Use this to filter out validation errors and auth failures that
|
|
181
|
+
* would create noise in Better Stack.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* catch (error) {
|
|
186
|
+
* if (isSystemError(error)) {
|
|
187
|
+
* bslogExternalServiceError('openai', 'completion', error, { ... })
|
|
188
|
+
* }
|
|
189
|
+
* throw error
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export function isSystemError(error: unknown): boolean {
|
|
194
|
+
// Check if error has a status code property (common in HTTP errors)
|
|
195
|
+
if (error && typeof error === 'object') {
|
|
196
|
+
const statusCode =
|
|
197
|
+
('statusCode' in error && typeof error.statusCode === 'number' && error.statusCode) ||
|
|
198
|
+
('status' in error && typeof error.status === 'number' && error.status) ||
|
|
199
|
+
('code' in error && typeof error.code === 'number' && error.code)
|
|
200
|
+
|
|
201
|
+
if (statusCode) {
|
|
202
|
+
// 5xx = system error (log it)
|
|
203
|
+
// 4xx = user error (don't log)
|
|
204
|
+
return statusCode >= 500
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// If no status code, assume it's a system error (better to over-log than under-log)
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC Token Utilities
|
|
3
|
+
*
|
|
4
|
+
* Generic HMAC-based token generation and verification.
|
|
5
|
+
* Used for unsubscribe links, brochure personalization, and other marketing URLs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import crypto from 'crypto'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate HMAC-SHA256 token for any data
|
|
12
|
+
*
|
|
13
|
+
* @param data - Data to sign (email, contactId, etc.)
|
|
14
|
+
* @param secret - HMAC secret key (HMAC_SECRET)
|
|
15
|
+
* @returns 16-character hex token
|
|
16
|
+
*/
|
|
17
|
+
export function generateHmacToken(data: string, secret: string): string {
|
|
18
|
+
return crypto
|
|
19
|
+
.createHmac('sha256', secret)
|
|
20
|
+
.update(data.toLowerCase().trim())
|
|
21
|
+
.digest('hex')
|
|
22
|
+
.slice(0, 16)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Verify HMAC token using timing-safe comparison
|
|
27
|
+
*/
|
|
28
|
+
export function verifyHmacToken(data: string, token: string, secret: string): boolean {
|
|
29
|
+
const expected = generateHmacToken(data, secret)
|
|
30
|
+
if (token.length !== expected.length) return false
|
|
31
|
+
return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expected))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate brochure URL with token
|
|
36
|
+
*/
|
|
37
|
+
export function generateBrochureUrl(
|
|
38
|
+
contactId: string,
|
|
39
|
+
secret: string,
|
|
40
|
+
baseUrl = 'https://brochure.elevasis.io'
|
|
41
|
+
): string {
|
|
42
|
+
const token = generateHmacToken(contactId, secret)
|
|
43
|
+
return `${baseUrl}/${contactId}?t=${token}`
|
|
44
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unsubscribe Token Utilities
|
|
3
|
+
*
|
|
4
|
+
* HMAC-based token generation and verification for one-click unsubscribe
|
|
5
|
+
* compliance with RFC 8058 (Gmail/Yahoo/Microsoft requirements).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { generateUnsubscribeToken, verifyUnsubscribeToken } from '@repo/core/server'
|
|
10
|
+
*
|
|
11
|
+
* // Generate token for email link
|
|
12
|
+
* const token = generateUnsubscribeToken('john@example.com', process.env.UNSUBSCRIBE_SECRET!)
|
|
13
|
+
* const url = `https://api.elevasis.io/api/unsubscribe?email=john@example.com&token=${token}`
|
|
14
|
+
*
|
|
15
|
+
* // Verify token on POST request
|
|
16
|
+
* const isValid = verifyUnsubscribeToken('john@example.com', token, process.env.UNSUBSCRIBE_SECRET!)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import crypto from 'crypto'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate HMAC-SHA256 token for unsubscribe verification
|
|
24
|
+
*
|
|
25
|
+
* Uses first 16 characters of the hash for URL-friendly tokens.
|
|
26
|
+
*
|
|
27
|
+
* @param email - Email address to generate token for
|
|
28
|
+
* @param secret - HMAC secret key (HMAC_SECRET)
|
|
29
|
+
* @returns 16-character hex token
|
|
30
|
+
*/
|
|
31
|
+
export function generateUnsubscribeToken(email: string, secret: string): string {
|
|
32
|
+
return crypto.createHmac('sha256', secret).update(email.toLowerCase().trim()).digest('hex').slice(0, 16)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Verify unsubscribe token using timing-safe comparison
|
|
37
|
+
*
|
|
38
|
+
* Prevents timing attacks by using constant-time comparison.
|
|
39
|
+
*
|
|
40
|
+
* @param email - Email address to verify
|
|
41
|
+
* @param token - Token from request
|
|
42
|
+
* @param secret - HMAC secret key (HMAC_SECRET)
|
|
43
|
+
* @returns true if token is valid
|
|
44
|
+
*/
|
|
45
|
+
export function verifyUnsubscribeToken(email: string, token: string, secret: string): boolean {
|
|
46
|
+
const expected = generateUnsubscribeToken(email, secret)
|
|
47
|
+
|
|
48
|
+
// Ensure both are same length before comparison
|
|
49
|
+
if (token.length !== expected.length) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tokenBuf = Buffer.from(token)
|
|
54
|
+
const expectedBuf = Buffer.from(expected)
|
|
55
|
+
|
|
56
|
+
return crypto.timingSafeEqual(tokenBuf, expectedBuf)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate unsubscribe URL with token
|
|
61
|
+
*
|
|
62
|
+
* @param email - Email address
|
|
63
|
+
* @param secret - HMAC secret key
|
|
64
|
+
* @param baseUrl - Base URL for unsubscribe endpoint (default: production URL)
|
|
65
|
+
* @returns Full unsubscribe URL with email and token query params
|
|
66
|
+
*/
|
|
67
|
+
export function generateUnsubscribeUrl(
|
|
68
|
+
email: string,
|
|
69
|
+
secret: string,
|
|
70
|
+
baseUrl = 'https://api.elevasis.io/api/unsubscribe'
|
|
71
|
+
): string {
|
|
72
|
+
const token = generateUnsubscribeToken(email, secret)
|
|
73
|
+
const encodedEmail = encodeURIComponent(email.toLowerCase().trim())
|
|
74
|
+
return `${baseUrl}?email=${encodedEmail}&token=${token}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate List-Unsubscribe headers for RFC 8058 compliance
|
|
79
|
+
*
|
|
80
|
+
* Returns headers object to include in Resend email headers parameter.
|
|
81
|
+
*
|
|
82
|
+
* @param email - Recipient email address
|
|
83
|
+
* @param secret - HMAC secret key
|
|
84
|
+
* @param baseUrl - Base URL for unsubscribe endpoint
|
|
85
|
+
* @returns Headers object with List-Unsubscribe and List-Unsubscribe-Post
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const headers = generateUnsubscribeHeaders('john@example.com', secret)
|
|
90
|
+
* // Use in Resend:
|
|
91
|
+
* await resend.emails.send({
|
|
92
|
+
* from: 'sender@domain.com',
|
|
93
|
+
* to: 'john@example.com',
|
|
94
|
+
* subject: 'Subject',
|
|
95
|
+
* html: '<p>Content</p>',
|
|
96
|
+
* headers
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function generateUnsubscribeHeaders(
|
|
101
|
+
email: string,
|
|
102
|
+
secret: string,
|
|
103
|
+
baseUrl = 'https://api.elevasis.io/api/unsubscribe'
|
|
104
|
+
): Record<string, string> {
|
|
105
|
+
const url = generateUnsubscribeUrl(email, secret, baseUrl)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
'List-Unsubscribe': `<${url}>`,
|
|
109
|
+
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
|
|
110
|
+
}
|
|
111
|
+
}
|