@elevasis/core 0.7.1 → 0.8.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/package.json +1 -1
- package/src/README.md +41 -41
- package/src/__tests__/publish.test.ts +18 -18
- package/src/__tests__/{template-foundations-compatibility.test.ts → template-core-compatibility.test.ts} +99 -99
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +1135 -1131
- package/src/_gen/__tests__/scaffold-contracts.test.ts +53 -53
- package/src/_gen/scaffold-contracts.ts +45 -45
- package/src/auth/multi-tenancy/credentials/README.md +38 -38
- package/src/auth/multi-tenancy/credentials/index.ts +6 -6
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +39 -39
- package/src/auth/multi-tenancy/credentials/server/service.ts +60 -60
- package/src/auth/multi-tenancy/index.ts +17 -17
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +107 -107
- package/src/auth/multi-tenancy/invitations/index.ts +37 -37
- package/src/auth/multi-tenancy/invitations/invitation.ts +86 -86
- package/src/auth/multi-tenancy/invitations/server/index.ts +25 -25
- package/src/auth/multi-tenancy/invitations/server/transforms.ts +24 -24
- package/src/auth/multi-tenancy/invitations/server/workos.ts +24 -24
- package/src/auth/multi-tenancy/invitations/supabase.ts +50 -50
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +126 -126
- package/src/auth/multi-tenancy/memberships/index.ts +21 -21
- package/src/auth/multi-tenancy/memberships/membership.ts +138 -138
- package/src/auth/multi-tenancy/memberships/server/index.ts +15 -15
- package/src/auth/multi-tenancy/memberships/server/transforms.ts +32 -32
- package/src/auth/multi-tenancy/memberships/server/workos.ts +21 -21
- package/src/auth/multi-tenancy/memberships/supabase.ts +46 -46
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +128 -128
- package/src/auth/multi-tenancy/organizations/index.ts +23 -23
- package/src/auth/multi-tenancy/organizations/organization.ts +24 -24
- package/src/auth/multi-tenancy/organizations/server/index.ts +10 -10
- package/src/auth/multi-tenancy/organizations/server/transforms.ts +35 -35
- package/src/auth/multi-tenancy/organizations/server/workos.ts +20 -20
- package/src/auth/multi-tenancy/types.ts +83 -83
- package/src/auth/multi-tenancy/users/api-schemas.ts +194 -194
- package/src/auth/multi-tenancy/users/index.ts +27 -27
- package/src/auth/multi-tenancy/users/server/index.ts +19 -19
- package/src/auth/multi-tenancy/users/server/transforms.ts +21 -21
- package/src/auth/multi-tenancy/users/server/workos.ts +16 -16
- package/src/auth/multi-tenancy/users/user.ts +65 -65
- package/src/business/README.md +52 -52
- package/src/business/__tests__/entities-published.test.ts +33 -33
- package/src/business/acquisition/api-schemas.ts +759 -759
- package/src/business/acquisition/index.ts +109 -109
- package/src/business/acquisition/types.ts +402 -402
- package/src/business/base-entities.test.ts +481 -481
- package/src/business/base-entities.ts +241 -241
- package/src/business/entities-published.ts +24 -24
- package/src/business/index.ts +15 -15
- package/src/business/pdf/browser/pdfmake-browser.ts +229 -229
- package/src/business/pdf/index.ts +10 -10
- package/src/business/pdf/server/index.ts +21 -21
- package/src/business/pdf/server/themes/default.ts +8 -8
- package/src/business/pdf/server/themes/index.ts +9 -9
- package/src/business/pdf/server/themes/types.ts +8 -8
- package/src/business/pdf/types.ts +272 -272
- package/src/business/projects/index.ts +2 -2
- package/src/business/projects/sse-events.ts +21 -21
- package/src/business/projects/types.ts +89 -89
- package/src/business/sales/api-schemas.ts +75 -75
- package/src/business/seo/__tests__/linking.test.ts +549 -549
- package/src/business/seo/__tests__/types.test.ts +404 -404
- package/src/business/seo/index.ts +2 -2
- package/src/business/seo/linking.ts +281 -281
- package/src/business/seo/types.ts +199 -199
- package/src/commands/queue/index.ts +3 -3
- package/src/commands/queue/schemas.test.ts +593 -593
- package/src/commands/queue/schemas.ts +125 -125
- package/src/commands/queue/sse-events.ts +61 -61
- package/src/commands/queue/types/action.ts +52 -52
- package/src/commands/queue/types/checkpoint.ts +44 -44
- package/src/commands/queue/types/index.ts +7 -7
- package/src/commands/queue/types/task.ts +116 -116
- package/src/commands/queue/types.ts +14 -14
- package/src/content/distribution-metadata.ts +61 -61
- package/src/content/index.ts +10 -10
- package/src/deployments/index.ts +22 -22
- package/src/execution/core/__tests__/archived-logs.test.ts +72 -72
- package/src/execution/core/index.ts +11 -11
- package/src/execution/core/runner-types.ts +80 -80
- package/src/execution/core/server/environment.ts +31 -31
- package/src/execution/core/sse-executions.ts +119 -119
- package/src/execution/core/types.ts +29 -29
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +4 -4
- package/src/execution/engine/__tests__/timeout.test.ts +565 -565
- package/src/execution/engine/agent/__tests__/errors.test.ts +508 -508
- package/src/execution/engine/agent/actions/__tests__/processor.test.ts +531 -531
- package/src/execution/engine/agent/actions/executor.ts +205 -205
- package/src/execution/engine/agent/actions/navigate-knowledge-executor.ts +230 -230
- package/src/execution/engine/agent/actions/processor.ts +116 -116
- package/src/execution/engine/agent/actions/types.ts +70 -70
- package/src/execution/engine/agent/core/agent.ts +810 -810
- package/src/execution/engine/agent/core/types.ts +155 -155
- package/src/execution/engine/agent/errors.ts +251 -251
- package/src/execution/engine/agent/index.ts +78 -78
- package/src/execution/engine/agent/knowledge-map/types.ts +106 -106
- package/src/execution/engine/agent/knowledge-map/utils.ts +101 -101
- package/src/execution/engine/agent/memory/__tests__/manager.test.ts +754 -754
- package/src/execution/engine/agent/memory/domains.ts +99 -99
- package/src/execution/engine/agent/memory/manager.ts +365 -365
- package/src/execution/engine/agent/memory/processor.ts +66 -66
- package/src/execution/engine/agent/memory/types.ts +90 -90
- package/src/execution/engine/agent/memory/utils.ts +134 -134
- package/src/execution/engine/agent/observability/logging.ts +467 -467
- package/src/execution/engine/agent/observability/types.ts +64 -64
- package/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts +349 -349
- package/src/execution/engine/agent/reasoning/processor.ts +92 -92
- package/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts +134 -134
- package/src/execution/engine/agent/reasoning/prompt-sections/completion.ts +49 -49
- package/src/execution/engine/agent/reasoning/prompt-sections/knowledge-map.ts +93 -93
- package/src/execution/engine/agent/reasoning/prompt-sections/memory.ts +65 -65
- package/src/execution/engine/agent/reasoning/prompt-sections/tools.ts +44 -44
- package/src/execution/engine/agent/reasoning/request-builder.ts +169 -169
- package/src/execution/engine/agent/reasoning/types.ts +18 -18
- package/src/execution/engine/base/errors.ts +118 -118
- package/src/execution/engine/base/index.ts +2 -2
- package/src/execution/engine/base/logging.ts +31 -31
- package/src/execution/engine/base/serialization.ts +324 -324
- package/src/execution/engine/base/types.ts +126 -126
- package/src/execution/engine/base/utils.ts +41 -41
- package/src/execution/engine/index.ts +434 -434
- package/src/execution/engine/interface/index.ts +1 -1
- package/src/execution/engine/interface/types.ts +62 -62
- package/src/execution/engine/llm/__tests__/model-info.test.ts +50 -50
- package/src/execution/engine/llm/__tests__/model-validation.test.ts +321 -321
- package/src/execution/engine/llm/__tests__/response-schema-validator.test.ts +115 -115
- package/src/execution/engine/llm/adapters/__tests__/adapter-factory.test.ts +375 -375
- package/src/execution/engine/llm/adapters/__tests__/anthropic-adapter.test.ts +463 -463
- package/src/execution/engine/llm/adapters/__tests__/anthropic.integration.test.ts +177 -177
- package/src/execution/engine/llm/adapters/__tests__/google-adapter.test.ts +722 -722
- package/src/execution/engine/llm/adapters/__tests__/google.integration.test.ts +376 -376
- package/src/execution/engine/llm/adapters/__tests__/openai-adapter.test.ts +551 -551
- package/src/execution/engine/llm/adapters/__tests__/openrouter-adapter.test.ts +563 -563
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +105 -105
- package/src/execution/engine/llm/adapters/__tests__/universal-adapter.test.ts +537 -537
- package/src/execution/engine/llm/adapters/circuit-breaker.ts +147 -147
- package/src/execution/engine/llm/adapters/index.ts +17 -17
- package/src/execution/engine/llm/adapters/mock-adapter.ts +116 -116
- package/src/execution/engine/llm/adapters/server/adapter-factory.ts +130 -130
- package/src/execution/engine/llm/adapters/server/anthropic.ts +137 -137
- package/src/execution/engine/llm/adapters/server/google.ts +283 -283
- package/src/execution/engine/llm/adapters/server/index.ts +12 -12
- package/src/execution/engine/llm/adapters/server/openai.ts +206 -206
- package/src/execution/engine/llm/adapters/server/openrouter.ts +235 -235
- package/src/execution/engine/llm/adapters/universal-adapter.ts +230 -230
- package/src/execution/engine/llm/errors.ts +186 -186
- package/src/execution/engine/llm/model-info.ts +332 -332
- package/src/execution/engine/llm/response-schema-validator.ts +113 -113
- package/src/execution/engine/llm/types.ts +86 -86
- package/src/execution/engine/test-utils/index.ts +6 -6
- package/src/execution/engine/test-utils/mocks.ts +56 -56
- package/src/execution/engine/tools/integration/base-integration-adapter.ts +50 -50
- package/src/execution/engine/tools/integration/index.ts +53 -53
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-adapter.ts +73 -73
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-tools.ts +209 -209
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-company-email/index.ts +82 -82
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-decision-maker-email/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-person-email/index.ts +89 -89
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/verify-email/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/index.ts +16 -16
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +293 -293
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +100 -100
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-tools.ts +217 -217
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/get-dataset-items/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/run-actor/index.ts +218 -218
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/start-actor/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/apify/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +361 -361
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-adapter.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-tools.ts +594 -594
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-attribute/index.ts +214 -214
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-note/index.ts +152 -152
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-record/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-note/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-record/index.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.ts +118 -118
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-attributes/index.ts +165 -165
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-notes/index.ts +96 -96
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-objects/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.ts +156 -156
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-attribute/index.ts +220 -220
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-record/index.ts +140 -140
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/utils/types.ts +146 -146
- package/src/execution/engine/tools/integration/server/adapters/attio/index.ts +31 -31
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-adapter.ts +1189 -1189
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-tools.ts +641 -641
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/index.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/activate-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/__tests__/index.test.ts +289 -289
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/index.ts +154 -154
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/__tests__/index.test.ts +325 -325
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/index.ts +153 -153
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-delete-leads/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-campaign/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-inbox-test/index.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/delete-campaign/index.ts +85 -85
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-account-health/index.ts +91 -91
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/__tests__/index.test.ts +195 -195
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/index.ts +113 -113
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-daily-campaign-analytics/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-emails/index.ts +155 -155
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/__tests__/index.test.ts +196 -196
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/__tests__/index.test.ts +189 -189
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-leads/index.ts +112 -112
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/patch-lead/index.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/pause-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/remove-from-subsequence/index.ts +98 -98
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/send-reply/index.ts +126 -126
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/__tests__/index.test.ts +193 -193
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/index.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/__tests__/index.test.ts +621 -621
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/index.ts +29 -29
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-adapter.ts +178 -178
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1473 -1473
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/check-credits/index.ts +59 -59
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/verify-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/index.ts +17 -17
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-adapter.ts +80 -80
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/get-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +134 -134
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +75 -75
- package/src/execution/engine/tools/integration/server/adapters/resend/index.ts +27 -27
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-adapter.ts +108 -108
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-tools.ts +132 -132
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/create-envelope/index.ts +274 -274
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/download-document/index.ts +230 -230
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/get-envelope/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/void-envelope/index.ts +90 -90
- package/src/execution/engine/tools/integration/server/adapters/stripe/fetch/utils/types.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-adapter.ts +517 -517
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +309 -309
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/domain-search/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-finder/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-verifier/index.ts +111 -111
- package/src/execution/engine/tools/integration/server/adapters/tomba/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-adapter.ts +78 -78
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-tools.ts +222 -222
- package/src/execution/engine/tools/integration/server/index.ts +61 -61
- package/src/execution/engine/tools/integration/service.ts +161 -161
- package/src/execution/engine/tools/integration/tool.ts +253 -253
- package/src/execution/engine/tools/integration/types/anymailfinder.ts +74 -74
- package/src/execution/engine/tools/integration/types/apify.ts +92 -92
- package/src/execution/engine/tools/integration/types/index.ts +19 -19
- package/src/execution/engine/tools/integration/types/instantly.ts +557 -557
- package/src/execution/engine/tools/integration/types/millionverifier.ts +56 -56
- package/src/execution/engine/tools/integration/types/stripe.ts +162 -162
- package/src/execution/engine/tools/integration/types/tomba.ts +94 -94
- package/src/execution/engine/tools/lead-service-types.ts +884 -884
- package/src/execution/engine/tools/llm/index.ts +11 -11
- package/src/execution/engine/tools/llm/server/index.ts +8 -8
- package/src/execution/engine/tools/llm/server/llm-call-tool.ts +118 -118
- package/src/execution/engine/tools/platform/__tests__/pdf.test.ts +441 -441
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +248 -248
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +319 -319
- package/src/execution/engine/tools/platform/acquisition/index.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +148 -148
- package/src/execution/engine/tools/platform/acquisition/types.ts +260 -260
- package/src/execution/engine/tools/platform/email/index.ts +122 -122
- package/src/execution/engine/tools/platform/email/types.ts +96 -96
- package/src/execution/engine/tools/platform/index.ts +157 -157
- package/src/execution/engine/tools/platform/notification.ts +81 -81
- package/src/execution/engine/tools/platform/pdf/index.ts +110 -110
- package/src/execution/engine/tools/platform/pdf/types.ts +77 -77
- package/src/execution/engine/tools/platform/scheduler.ts +87 -87
- package/src/execution/engine/tools/platform/storage/index.ts +370 -370
- package/src/execution/engine/tools/platform/types.ts +148 -148
- package/src/execution/engine/tools/registry.ts +700 -700
- package/src/execution/engine/tools/tool-maps.ts +786 -786
- package/src/execution/engine/tools/types.ts +233 -233
- package/src/execution/engine/workflow/__tests__/errors.test.ts +139 -139
- package/src/execution/engine/workflow/errors.ts +63 -63
- package/src/execution/engine/workflow/helpers/index.ts +11 -11
- package/src/execution/engine/workflow/helpers/server/index.ts +8 -8
- package/src/execution/engine/workflow/helpers/server/llm-call.ts +93 -93
- package/src/execution/engine/workflow/index.ts +19 -19
- package/src/execution/engine/workflow/log-truncate.ts +26 -26
- package/src/execution/engine/workflow/logging.ts +191 -191
- package/src/execution/engine/workflow/types.ts +182 -182
- package/src/execution/engine/workflow/utils.ts +280 -280
- package/src/execution/engine/workflow/workflow.ts +168 -168
- package/src/execution/index.ts +3 -3
- package/src/execution/scheduler/__tests__/api-schemas.test.ts +733 -733
- package/src/execution/scheduler/__tests__/utils.test.ts +1009 -1009
- package/src/execution/scheduler/api-schemas.ts +296 -296
- package/src/execution/scheduler/index.ts +50 -50
- package/src/execution/scheduler/schemas.ts +264 -264
- package/src/execution/scheduler/types.ts +111 -111
- package/src/execution/scheduler/utils.ts +364 -364
- package/src/forms/index.ts +7 -7
- package/src/forms/schemas.ts +69 -69
- package/src/forms/types.ts +70 -70
- package/src/index.ts +71 -60
- package/src/integrations/credentials/__tests__/schemas.test.ts +82 -82
- package/src/integrations/credentials/__tests__/utils.test.ts +144 -144
- package/src/integrations/credentials/api-schemas.ts +143 -143
- package/src/integrations/credentials/index.ts +32 -32
- package/src/integrations/credentials/schemas.ts +164 -164
- package/src/integrations/credentials/utils.ts +59 -59
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +59 -59
- package/src/integrations/oauth/api-schemas.ts +92 -92
- package/src/integrations/oauth/index.ts +19 -19
- package/src/integrations/oauth/provider-registry.ts +61 -61
- package/src/integrations/oauth/server/__tests__/refresh-concurrent.test.ts +183 -183
- package/src/integrations/oauth/server/__tests__/refresh.test.ts +577 -577
- package/src/integrations/oauth/server/credentials.ts +39 -39
- package/src/integrations/oauth/server/refresh.ts +214 -214
- package/src/integrations/oauth/types.ts +34 -34
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +318 -318
- package/src/integrations/webhook-endpoints/api-schemas.ts +102 -102
- package/src/integrations/webhook-endpoints/index.ts +28 -28
- package/src/integrations/webhook-endpoints/types.ts +51 -51
- package/src/operations/activities/api-schemas.ts +79 -79
- package/src/operations/activities/index.ts +9 -9
- package/src/operations/activities/sse-events.ts +30 -30
- package/src/operations/activities/types.ts +63 -63
- package/src/operations/debug-logs/client.ts +60 -60
- package/src/operations/debug-logs/debug-logger.ts +83 -83
- package/src/operations/debug-logs/index.ts +8 -8
- package/src/operations/debug-logs/server.ts +19 -19
- package/src/operations/debug-logs/types.ts +33 -33
- package/src/operations/index.ts +50 -50
- package/src/operations/notifications/api-schemas.ts +91 -91
- package/src/operations/notifications/index.ts +3 -3
- package/src/operations/notifications/sse-events.ts +21 -21
- package/src/operations/notifications/types.ts +47 -47
- package/src/operations/observability/__tests__/openrouter-cost-flow.test.ts +297 -297
- package/src/operations/observability/__tests__/utils.test.ts +54 -54
- package/src/operations/observability/ai-usage-collector.ts +64 -64
- package/src/operations/observability/index.ts +13 -13
- package/src/operations/observability/metrics-collector.ts +49 -49
- package/src/operations/observability/schemas.ts +39 -39
- package/src/operations/observability/types.ts +463 -463
- package/src/operations/observability/utils.ts +77 -77
- package/src/operations/sessions/__tests__/manager.test.ts +821 -821
- package/src/operations/sessions/index.ts +26 -26
- package/src/operations/sessions/server/manager.ts +90 -90
- package/src/operations/sessions/server/session.ts +180 -180
- package/src/operations/sessions/types.ts +98 -98
- package/src/operations/triggers/index.ts +12 -12
- package/src/operations/triggers/webhook/definitions/instantly-account-error.ts +44 -44
- package/src/operations/triggers/webhook/definitions/instantly-auto-reply-received.ts +51 -51
- package/src/operations/triggers/webhook/definitions/instantly-campaign-completed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-email-bounced.ts +49 -49
- package/src/operations/triggers/webhook/definitions/instantly-lead-unsubscribed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-reply-received.ts +54 -54
- package/src/operations/triggers/webhook/index.ts +35 -35
- package/src/operations/triggers/webhook/types.ts +74 -74
- package/src/organization-model/README.md +97 -97
- package/src/organization-model/__tests__/defaults.test.ts +175 -175
- package/src/organization-model/__tests__/domains/customers.test.ts +295 -295
- package/src/organization-model/__tests__/domains/goals.test.ts +479 -479
- package/src/organization-model/__tests__/domains/identity.test.ts +279 -279
- package/src/organization-model/__tests__/domains/navigation.test.ts +212 -212
- package/src/organization-model/__tests__/domains/offerings.test.ts +419 -419
- package/src/organization-model/__tests__/domains/operations.test.ts +203 -203
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -362
- package/src/organization-model/__tests__/domains/roles.test.ts +347 -347
- package/src/organization-model/__tests__/domains/statuses.test.ts +243 -243
- package/src/organization-model/__tests__/foundation.test.ts +105 -105
- package/src/organization-model/__tests__/graph.test.ts +894 -894
- package/src/organization-model/__tests__/resolve.test.ts +690 -690
- package/src/organization-model/__tests__/schema.test.ts +407 -407
- package/src/organization-model/contracts.ts +14 -14
- package/src/organization-model/defaults.ts +148 -148
- package/src/organization-model/domains/branding.ts +22 -22
- package/src/organization-model/domains/customers.ts +75 -75
- package/src/organization-model/domains/features.ts +22 -22
- package/src/organization-model/domains/goals.ts +80 -80
- package/src/organization-model/domains/identity.ts +94 -94
- package/src/organization-model/domains/navigation.ts +391 -391
- package/src/organization-model/domains/offerings.ts +66 -66
- package/src/organization-model/domains/operations.ts +85 -85
- package/src/organization-model/domains/projects.ts +48 -48
- package/src/organization-model/domains/prospecting.ts +33 -33
- package/src/organization-model/domains/roles.ts +55 -55
- package/src/organization-model/domains/sales.ts +94 -94
- package/src/organization-model/domains/shared.ts +62 -62
- package/src/organization-model/domains/statuses.ts +130 -130
- package/src/organization-model/foundation.ts +97 -97
- package/src/organization-model/graph/build.ts +399 -399
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/schema.ts +48 -48
- package/src/organization-model/graph/types.ts +40 -40
- package/src/organization-model/index.ts +13 -13
- package/src/organization-model/organization-graph.mdx +272 -272
- package/src/organization-model/organization-model.mdx +320 -320
- package/src/organization-model/published.ts +85 -85
- package/src/organization-model/resolve.ts +66 -66
- package/src/organization-model/schema.ts +287 -287
- package/src/organization-model/types.ts +46 -46
- package/src/platform/api/index.ts +1 -1
- package/src/platform/api/types.ts +35 -35
- package/src/platform/constants/http.ts +37 -37
- package/src/platform/constants/index.ts +5 -5
- package/src/platform/constants/limits.ts +32 -32
- package/src/platform/constants/resilience.ts +51 -51
- package/src/platform/constants/timeouts.ts +20 -20
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/__tests__/resource-registry-static.test.ts +347 -347
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +1028 -1028
- package/src/platform/registry/__tests__/resource-registry.list-executable.test.ts +393 -393
- package/src/platform/registry/__tests__/resource-registry.test.ts +2005 -2005
- package/src/platform/registry/__tests__/serialization.test.ts +1127 -1127
- package/src/platform/registry/command-view.ts +180 -180
- package/src/platform/registry/domains.ts +165 -165
- package/src/platform/registry/index.ts +93 -93
- package/src/platform/registry/reserved.ts +24 -24
- package/src/platform/registry/resource-metadata.ts +59 -59
- package/src/platform/registry/resource-registry.command-queue-groups.test.ts +129 -129
- package/src/platform/registry/resource-registry.ts +876 -876
- package/src/platform/registry/serialization.ts +273 -273
- package/src/platform/registry/serialized-types.ts +231 -231
- package/src/platform/registry/stats-types.ts +66 -66
- package/src/platform/registry/types.ts +404 -404
- package/src/platform/registry/validation.ts +513 -513
- package/src/platform/resilience/__tests__/rate-limiter.test.ts +471 -471
- package/src/platform/resilience/circuit-breaker.ts +164 -164
- package/src/platform/resilience/errors.ts +68 -68
- package/src/platform/resilience/http-error-mapper.ts +129 -129
- package/src/platform/resilience/index.ts +93 -93
- package/src/platform/resilience/rate-limiter-types.ts +46 -46
- package/src/platform/resilience/rate-limiter.ts +140 -140
- package/src/platform/resilience/retry.ts +89 -89
- package/src/platform/resilience/timeout.ts +63 -63
- package/src/platform/sse/events.ts +37 -37
- package/src/platform/sse/index.ts +7 -7
- package/src/platform/utils/__tests__/validation.test.ts +1083 -1083
- package/src/platform/utils/currency.ts +96 -96
- package/src/platform/utils/debounce.ts +52 -52
- package/src/platform/utils/error.ts +41 -41
- package/src/platform/utils/hmac.test.ts +97 -97
- package/src/platform/utils/index.ts +32 -32
- package/src/platform/utils/server/betterstack-logger.ts +210 -210
- package/src/platform/utils/server/hmac.ts +44 -44
- package/src/platform/utils/server/unsubscribe.ts +111 -111
- package/src/platform/utils/token-counter.ts +96 -96
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.ts +268 -268
- package/src/published.ts +1 -1
- package/src/reference/_generated/contracts.md +607 -607
- package/src/reference/glossary.md +105 -105
- package/src/requests/__tests__/api-schemas.test.ts +277 -277
- package/src/requests/api-schemas.ts +83 -83
- package/src/requests/index.ts +1 -1
- package/src/scaffold-registry/__tests__/index.test.ts +17 -0
- package/src/scaffold-registry/__tests__/schema.test.ts +329 -230
- package/src/scaffold-registry/index.ts +205 -189
- package/src/scaffold-registry/schema.ts +196 -128
- package/src/server.ts +272 -272
- package/src/supabase/database.types.ts +2719 -2719
- package/src/supabase/helpers.ts +20 -20
- package/src/supabase/index.ts +52 -52
- package/src/supabase/server/client.ts +58 -58
- package/src/test-utils/README.md +38 -38
- package/src/test-utils/browser-mocks.ts +54 -54
- package/src/test-utils/fixtures/api-keys.ts +52 -52
- package/src/test-utils/fixtures/index.ts +4 -4
- package/src/test-utils/fixtures/memberships.ts +80 -80
- package/src/test-utils/fixtures/organizations.ts +69 -69
- package/src/test-utils/fixtures/users.ts +79 -79
- package/src/test-utils/index.ts +11 -11
- package/src/test-utils/mocks/index.ts +2 -2
- package/src/test-utils/mocks/supabase.ts +142 -142
- package/src/test-utils/mocks/workos.ts +108 -108
- package/src/test-utils/rls/RLSTestContext.ts +556 -556
- package/src/test-utils/rls/index.ts +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './types'
|
|
2
|
-
export * from './linking'
|
|
1
|
+
export * from './types'
|
|
2
|
+
export * from './linking'
|
|
@@ -1,281 +1,281 @@
|
|
|
1
|
-
import type { SeoInternalLink, SeoPage } from './types'
|
|
2
|
-
|
|
3
|
-
// =============================================================================
|
|
4
|
-
// Linking Limits
|
|
5
|
-
// =============================================================================
|
|
6
|
-
|
|
7
|
-
const LIMITS = {
|
|
8
|
-
/** Max location pages linked from a pillar page */
|
|
9
|
-
PILLAR_TO_LOCATIONS: 50,
|
|
10
|
-
/** Max cluster pages linked from a pillar page */
|
|
11
|
-
PILLAR_TO_CLUSTERS: 20,
|
|
12
|
-
/** Max other vertical pillar pages linked from a pillar page */
|
|
13
|
-
PILLAR_TO_OTHER_VERTICALS: 3,
|
|
14
|
-
/** Min sibling location pages linked from a location page */
|
|
15
|
-
LOCATION_SIBLINGS_MIN: 3,
|
|
16
|
-
/** Max sibling location pages linked from a location page */
|
|
17
|
-
LOCATION_SIBLINGS_MAX: 5,
|
|
18
|
-
/** Max cross-vertical pages linked from a location page */
|
|
19
|
-
LOCATION_CROSS_VERTICAL: 3,
|
|
20
|
-
/** Max cluster pages linked from a location page */
|
|
21
|
-
LOCATION_TO_CLUSTERS: 2,
|
|
22
|
-
/** Max location pages linked from a cluster page */
|
|
23
|
-
CLUSTER_TO_LOCATIONS: 3,
|
|
24
|
-
/** Max related cluster pages linked from a cluster page */
|
|
25
|
-
CLUSTER_TO_CLUSTERS: 3
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// =============================================================================
|
|
29
|
-
// Anchor Text Generation
|
|
30
|
-
// =============================================================================
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Maps a vertical slug to a human-readable label.
|
|
34
|
-
*/
|
|
35
|
-
const VERTICAL_LABELS: Record<string, string> = {
|
|
36
|
-
'veterinary-clinics': 'Vet Clinics',
|
|
37
|
-
'auto-repair': 'Auto Repair Shops',
|
|
38
|
-
'home-services': 'Home Services',
|
|
39
|
-
'hvac-contractors': 'HVAC Contractors',
|
|
40
|
-
'pest-control': 'Pest Control',
|
|
41
|
-
'commercial-cleaning': 'Commercial Cleaning',
|
|
42
|
-
'med-spas': 'Med Spas',
|
|
43
|
-
'hoa-property-management': 'HOA & Property Management',
|
|
44
|
-
'security-companies': 'Security Companies'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Returns a human-readable label for a vertical slug.
|
|
49
|
-
* Falls back to title-casing the slug if not found in the map.
|
|
50
|
-
*/
|
|
51
|
-
export function getVerticalLabel(vertical: string): string {
|
|
52
|
-
return (
|
|
53
|
-
VERTICAL_LABELS[vertical] ??
|
|
54
|
-
vertical
|
|
55
|
-
.split('-')
|
|
56
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
57
|
-
.join(' ')
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Returns a human-readable label for a city slug.
|
|
63
|
-
* Capitalises each word.
|
|
64
|
-
*/
|
|
65
|
-
function getCityLabel(city: string): string {
|
|
66
|
-
return city
|
|
67
|
-
.split('-')
|
|
68
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
69
|
-
.join(' ')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Generates anchor text for a given page.
|
|
74
|
-
* - Pillar: "AI Automation for Vet Clinics"
|
|
75
|
-
* - Location: "AI Automation for Vet Clinics in Irvine"
|
|
76
|
-
* - Cluster: "AI Scheduling for Vet Clinics" (use-case label + vertical)
|
|
77
|
-
* - Other: Uses the page title directly
|
|
78
|
-
*/
|
|
79
|
-
export function generateAnchorText(page: SeoPage): string {
|
|
80
|
-
const verticalLabel = getVerticalLabel(page.vertical)
|
|
81
|
-
|
|
82
|
-
if (page.pageType === 'pillar') {
|
|
83
|
-
return `AI Automation for ${verticalLabel}`
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (page.pageType === 'location' && page.city) {
|
|
87
|
-
const cityLabel = getCityLabel(page.city)
|
|
88
|
-
return `AI Automation for ${verticalLabel} in ${cityLabel}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if ((page.pageType === 'cluster' || page.pageType === 'use-case') && page.useCase) {
|
|
92
|
-
const useCaseLabel = page.useCase
|
|
93
|
-
.split('-')
|
|
94
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
95
|
-
.join(' ')
|
|
96
|
-
return `${useCaseLabel} for ${verticalLabel}`
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Fallback: use the page's own title
|
|
100
|
-
return page.title
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// =============================================================================
|
|
104
|
-
// Helpers
|
|
105
|
-
// =============================================================================
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Builds a SeoInternalLink for a target page.
|
|
109
|
-
*/
|
|
110
|
-
function linkTo(target: SeoPage): SeoInternalLink {
|
|
111
|
-
return {
|
|
112
|
-
slug: target.slug,
|
|
113
|
-
anchor_text: generateAnchorText(target)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Deterministically shuffles an array using the source page ID as a seed.
|
|
119
|
-
* This keeps output stable across multiple runs for the same source page.
|
|
120
|
-
*/
|
|
121
|
-
function seededShuffle<T>(arr: T[], seed: string): T[] {
|
|
122
|
-
// Simple numeric hash of the seed string
|
|
123
|
-
let hash = 0
|
|
124
|
-
for (let i = 0; i < seed.length; i++) {
|
|
125
|
-
hash = (hash * 31 + seed.charCodeAt(i)) >>> 0
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const copy = [...arr]
|
|
129
|
-
for (let i = copy.length - 1; i > 0; i--) {
|
|
130
|
-
hash = (hash * 1664525 + 1013904223) >>> 0
|
|
131
|
-
const j = hash % (i + 1)
|
|
132
|
-
;[copy[i], copy[j]] = [copy[j]!, copy[i]!]
|
|
133
|
-
}
|
|
134
|
-
return copy
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// =============================================================================
|
|
138
|
-
// Core Logic
|
|
139
|
-
// =============================================================================
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Generates internal links for a pillar page.
|
|
143
|
-
*
|
|
144
|
-
* Links to:
|
|
145
|
-
* - All location pages for this vertical
|
|
146
|
-
* - All cluster/use-case pages for this vertical
|
|
147
|
-
* - Up to PILLAR_TO_OTHER_VERTICALS other vertical pillar pages
|
|
148
|
-
*/
|
|
149
|
-
function generatePillarLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
150
|
-
const links: SeoInternalLink[] = []
|
|
151
|
-
|
|
152
|
-
// Location pages for this vertical
|
|
153
|
-
const locationPages = allPages.filter(
|
|
154
|
-
(p) => p.pageType === 'location' && p.vertical === page.vertical && p.slug !== page.slug
|
|
155
|
-
)
|
|
156
|
-
links.push(...locationPages.slice(0, LIMITS.PILLAR_TO_LOCATIONS).map(linkTo))
|
|
157
|
-
|
|
158
|
-
// Cluster / use-case pages for this vertical
|
|
159
|
-
const clusterPages = allPages.filter(
|
|
160
|
-
(p) =>
|
|
161
|
-
(p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical && p.slug !== page.slug
|
|
162
|
-
)
|
|
163
|
-
links.push(...clusterPages.slice(0, LIMITS.PILLAR_TO_CLUSTERS).map(linkTo))
|
|
164
|
-
|
|
165
|
-
// Other vertical pillar pages (different vertical, same page type)
|
|
166
|
-
const otherPillarPages = allPages.filter((p) => p.pageType === 'pillar' && p.vertical !== page.vertical)
|
|
167
|
-
const shuffled = seededShuffle(otherPillarPages, page.id)
|
|
168
|
-
links.push(...shuffled.slice(0, LIMITS.PILLAR_TO_OTHER_VERTICALS).map(linkTo))
|
|
169
|
-
|
|
170
|
-
return links
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Generates internal links for a location page.
|
|
175
|
-
*
|
|
176
|
-
* Links to:
|
|
177
|
-
* - Parent pillar page (same vertical)
|
|
178
|
-
* - 3–5 sibling location pages (same vertical, different city)
|
|
179
|
-
* - Up to LOCATION_CROSS_VERTICAL cross-vertical pages (different vertical, same city)
|
|
180
|
-
* - Up to LOCATION_TO_CLUSTERS cluster pages (same vertical)
|
|
181
|
-
*/
|
|
182
|
-
function generateLocationLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
183
|
-
const links: SeoInternalLink[] = []
|
|
184
|
-
|
|
185
|
-
// Parent pillar page
|
|
186
|
-
const pillar = allPages.find((p) => p.pageType === 'pillar' && p.vertical === page.vertical)
|
|
187
|
-
if (pillar) {
|
|
188
|
-
links.push(linkTo(pillar))
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Sibling location pages (same vertical, different city)
|
|
192
|
-
const siblings = allPages.filter(
|
|
193
|
-
(p) => p.pageType === 'location' && p.vertical === page.vertical && p.slug !== page.slug
|
|
194
|
-
)
|
|
195
|
-
const shuffledSiblings = seededShuffle(siblings, page.id)
|
|
196
|
-
links.push(...shuffledSiblings.slice(0, LIMITS.LOCATION_SIBLINGS_MAX).map(linkTo))
|
|
197
|
-
|
|
198
|
-
// Cross-vertical pages (different vertical, same city)
|
|
199
|
-
if (page.city) {
|
|
200
|
-
const crossVertical = allPages.filter(
|
|
201
|
-
(p) => p.pageType === 'location' && p.vertical !== page.vertical && p.city === page.city
|
|
202
|
-
)
|
|
203
|
-
const shuffledCross = seededShuffle(crossVertical, page.id + '-cross')
|
|
204
|
-
links.push(...shuffledCross.slice(0, LIMITS.LOCATION_CROSS_VERTICAL).map(linkTo))
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Cluster pages for this vertical
|
|
208
|
-
const clusterPages = allPages.filter(
|
|
209
|
-
(p) => (p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical
|
|
210
|
-
)
|
|
211
|
-
const shuffledClusters = seededShuffle(clusterPages, page.id + '-cluster')
|
|
212
|
-
links.push(...shuffledClusters.slice(0, LIMITS.LOCATION_TO_CLUSTERS).map(linkTo))
|
|
213
|
-
|
|
214
|
-
return links
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Generates internal links for a cluster/use-case page.
|
|
219
|
-
*
|
|
220
|
-
* Links to:
|
|
221
|
-
* - Parent pillar page (same vertical)
|
|
222
|
-
* - Up to CLUSTER_TO_LOCATIONS location pages (same vertical)
|
|
223
|
-
* - Up to CLUSTER_TO_CLUSTERS related cluster pages (same vertical, different use-case)
|
|
224
|
-
*/
|
|
225
|
-
function generateClusterLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
226
|
-
const links: SeoInternalLink[] = []
|
|
227
|
-
|
|
228
|
-
// Parent pillar page
|
|
229
|
-
const pillar = allPages.find((p) => p.pageType === 'pillar' && p.vertical === page.vertical)
|
|
230
|
-
if (pillar) {
|
|
231
|
-
links.push(linkTo(pillar))
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Location pages for this vertical
|
|
235
|
-
const locationPages = allPages.filter((p) => p.pageType === 'location' && p.vertical === page.vertical)
|
|
236
|
-
const shuffledLocations = seededShuffle(locationPages, page.id)
|
|
237
|
-
links.push(...shuffledLocations.slice(0, LIMITS.CLUSTER_TO_LOCATIONS).map(linkTo))
|
|
238
|
-
|
|
239
|
-
// Related cluster pages (same vertical, different slug)
|
|
240
|
-
const relatedClusters = allPages.filter(
|
|
241
|
-
(p) =>
|
|
242
|
-
(p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical && p.slug !== page.slug
|
|
243
|
-
)
|
|
244
|
-
const shuffledClusters = seededShuffle(relatedClusters, page.id + '-sibling')
|
|
245
|
-
links.push(...shuffledClusters.slice(0, LIMITS.CLUSTER_TO_CLUSTERS).map(linkTo))
|
|
246
|
-
|
|
247
|
-
return links
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// =============================================================================
|
|
251
|
-
// Public API
|
|
252
|
-
// =============================================================================
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Generates internal links for a page based on the hub-and-spoke model.
|
|
256
|
-
*
|
|
257
|
-
* Pure function — no database access. Can be tested in isolation.
|
|
258
|
-
*
|
|
259
|
-
* Rules applied per page type:
|
|
260
|
-
* - pillar: → location pages + cluster pages + other vertical pillars
|
|
261
|
-
* - location: → parent pillar + siblings + cross-vertical + clusters
|
|
262
|
-
* - cluster/use-case: → parent pillar + location pages + related clusters
|
|
263
|
-
* - comparison/other: → empty list (no hub-and-spoke rules defined)
|
|
264
|
-
*
|
|
265
|
-
* @param page - The page for which to generate links
|
|
266
|
-
* @param allPages - All published SEO pages (used to find related pages)
|
|
267
|
-
* @returns Array of internal links sorted stable (pillar-first, then alphabetical by slug)
|
|
268
|
-
*/
|
|
269
|
-
export function generateInternalLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
270
|
-
switch (page.pageType) {
|
|
271
|
-
case 'pillar':
|
|
272
|
-
return generatePillarLinks(page, allPages)
|
|
273
|
-
case 'location':
|
|
274
|
-
return generateLocationLinks(page, allPages)
|
|
275
|
-
case 'cluster':
|
|
276
|
-
case 'use-case':
|
|
277
|
-
return generateClusterLinks(page, allPages)
|
|
278
|
-
default:
|
|
279
|
-
return []
|
|
280
|
-
}
|
|
281
|
-
}
|
|
1
|
+
import type { SeoInternalLink, SeoPage } from './types'
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Linking Limits
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
const LIMITS = {
|
|
8
|
+
/** Max location pages linked from a pillar page */
|
|
9
|
+
PILLAR_TO_LOCATIONS: 50,
|
|
10
|
+
/** Max cluster pages linked from a pillar page */
|
|
11
|
+
PILLAR_TO_CLUSTERS: 20,
|
|
12
|
+
/** Max other vertical pillar pages linked from a pillar page */
|
|
13
|
+
PILLAR_TO_OTHER_VERTICALS: 3,
|
|
14
|
+
/** Min sibling location pages linked from a location page */
|
|
15
|
+
LOCATION_SIBLINGS_MIN: 3,
|
|
16
|
+
/** Max sibling location pages linked from a location page */
|
|
17
|
+
LOCATION_SIBLINGS_MAX: 5,
|
|
18
|
+
/** Max cross-vertical pages linked from a location page */
|
|
19
|
+
LOCATION_CROSS_VERTICAL: 3,
|
|
20
|
+
/** Max cluster pages linked from a location page */
|
|
21
|
+
LOCATION_TO_CLUSTERS: 2,
|
|
22
|
+
/** Max location pages linked from a cluster page */
|
|
23
|
+
CLUSTER_TO_LOCATIONS: 3,
|
|
24
|
+
/** Max related cluster pages linked from a cluster page */
|
|
25
|
+
CLUSTER_TO_CLUSTERS: 3
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Anchor Text Generation
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Maps a vertical slug to a human-readable label.
|
|
34
|
+
*/
|
|
35
|
+
const VERTICAL_LABELS: Record<string, string> = {
|
|
36
|
+
'veterinary-clinics': 'Vet Clinics',
|
|
37
|
+
'auto-repair': 'Auto Repair Shops',
|
|
38
|
+
'home-services': 'Home Services',
|
|
39
|
+
'hvac-contractors': 'HVAC Contractors',
|
|
40
|
+
'pest-control': 'Pest Control',
|
|
41
|
+
'commercial-cleaning': 'Commercial Cleaning',
|
|
42
|
+
'med-spas': 'Med Spas',
|
|
43
|
+
'hoa-property-management': 'HOA & Property Management',
|
|
44
|
+
'security-companies': 'Security Companies'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a human-readable label for a vertical slug.
|
|
49
|
+
* Falls back to title-casing the slug if not found in the map.
|
|
50
|
+
*/
|
|
51
|
+
export function getVerticalLabel(vertical: string): string {
|
|
52
|
+
return (
|
|
53
|
+
VERTICAL_LABELS[vertical] ??
|
|
54
|
+
vertical
|
|
55
|
+
.split('-')
|
|
56
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
57
|
+
.join(' ')
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a human-readable label for a city slug.
|
|
63
|
+
* Capitalises each word.
|
|
64
|
+
*/
|
|
65
|
+
function getCityLabel(city: string): string {
|
|
66
|
+
return city
|
|
67
|
+
.split('-')
|
|
68
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
69
|
+
.join(' ')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generates anchor text for a given page.
|
|
74
|
+
* - Pillar: "AI Automation for Vet Clinics"
|
|
75
|
+
* - Location: "AI Automation for Vet Clinics in Irvine"
|
|
76
|
+
* - Cluster: "AI Scheduling for Vet Clinics" (use-case label + vertical)
|
|
77
|
+
* - Other: Uses the page title directly
|
|
78
|
+
*/
|
|
79
|
+
export function generateAnchorText(page: SeoPage): string {
|
|
80
|
+
const verticalLabel = getVerticalLabel(page.vertical)
|
|
81
|
+
|
|
82
|
+
if (page.pageType === 'pillar') {
|
|
83
|
+
return `AI Automation for ${verticalLabel}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (page.pageType === 'location' && page.city) {
|
|
87
|
+
const cityLabel = getCityLabel(page.city)
|
|
88
|
+
return `AI Automation for ${verticalLabel} in ${cityLabel}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ((page.pageType === 'cluster' || page.pageType === 'use-case') && page.useCase) {
|
|
92
|
+
const useCaseLabel = page.useCase
|
|
93
|
+
.split('-')
|
|
94
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
95
|
+
.join(' ')
|
|
96
|
+
return `${useCaseLabel} for ${verticalLabel}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Fallback: use the page's own title
|
|
100
|
+
return page.title
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// =============================================================================
|
|
104
|
+
// Helpers
|
|
105
|
+
// =============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Builds a SeoInternalLink for a target page.
|
|
109
|
+
*/
|
|
110
|
+
function linkTo(target: SeoPage): SeoInternalLink {
|
|
111
|
+
return {
|
|
112
|
+
slug: target.slug,
|
|
113
|
+
anchor_text: generateAnchorText(target)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Deterministically shuffles an array using the source page ID as a seed.
|
|
119
|
+
* This keeps output stable across multiple runs for the same source page.
|
|
120
|
+
*/
|
|
121
|
+
function seededShuffle<T>(arr: T[], seed: string): T[] {
|
|
122
|
+
// Simple numeric hash of the seed string
|
|
123
|
+
let hash = 0
|
|
124
|
+
for (let i = 0; i < seed.length; i++) {
|
|
125
|
+
hash = (hash * 31 + seed.charCodeAt(i)) >>> 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const copy = [...arr]
|
|
129
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
130
|
+
hash = (hash * 1664525 + 1013904223) >>> 0
|
|
131
|
+
const j = hash % (i + 1)
|
|
132
|
+
;[copy[i], copy[j]] = [copy[j]!, copy[i]!]
|
|
133
|
+
}
|
|
134
|
+
return copy
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Core Logic
|
|
139
|
+
// =============================================================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generates internal links for a pillar page.
|
|
143
|
+
*
|
|
144
|
+
* Links to:
|
|
145
|
+
* - All location pages for this vertical
|
|
146
|
+
* - All cluster/use-case pages for this vertical
|
|
147
|
+
* - Up to PILLAR_TO_OTHER_VERTICALS other vertical pillar pages
|
|
148
|
+
*/
|
|
149
|
+
function generatePillarLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
150
|
+
const links: SeoInternalLink[] = []
|
|
151
|
+
|
|
152
|
+
// Location pages for this vertical
|
|
153
|
+
const locationPages = allPages.filter(
|
|
154
|
+
(p) => p.pageType === 'location' && p.vertical === page.vertical && p.slug !== page.slug
|
|
155
|
+
)
|
|
156
|
+
links.push(...locationPages.slice(0, LIMITS.PILLAR_TO_LOCATIONS).map(linkTo))
|
|
157
|
+
|
|
158
|
+
// Cluster / use-case pages for this vertical
|
|
159
|
+
const clusterPages = allPages.filter(
|
|
160
|
+
(p) =>
|
|
161
|
+
(p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical && p.slug !== page.slug
|
|
162
|
+
)
|
|
163
|
+
links.push(...clusterPages.slice(0, LIMITS.PILLAR_TO_CLUSTERS).map(linkTo))
|
|
164
|
+
|
|
165
|
+
// Other vertical pillar pages (different vertical, same page type)
|
|
166
|
+
const otherPillarPages = allPages.filter((p) => p.pageType === 'pillar' && p.vertical !== page.vertical)
|
|
167
|
+
const shuffled = seededShuffle(otherPillarPages, page.id)
|
|
168
|
+
links.push(...shuffled.slice(0, LIMITS.PILLAR_TO_OTHER_VERTICALS).map(linkTo))
|
|
169
|
+
|
|
170
|
+
return links
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Generates internal links for a location page.
|
|
175
|
+
*
|
|
176
|
+
* Links to:
|
|
177
|
+
* - Parent pillar page (same vertical)
|
|
178
|
+
* - 3–5 sibling location pages (same vertical, different city)
|
|
179
|
+
* - Up to LOCATION_CROSS_VERTICAL cross-vertical pages (different vertical, same city)
|
|
180
|
+
* - Up to LOCATION_TO_CLUSTERS cluster pages (same vertical)
|
|
181
|
+
*/
|
|
182
|
+
function generateLocationLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
183
|
+
const links: SeoInternalLink[] = []
|
|
184
|
+
|
|
185
|
+
// Parent pillar page
|
|
186
|
+
const pillar = allPages.find((p) => p.pageType === 'pillar' && p.vertical === page.vertical)
|
|
187
|
+
if (pillar) {
|
|
188
|
+
links.push(linkTo(pillar))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Sibling location pages (same vertical, different city)
|
|
192
|
+
const siblings = allPages.filter(
|
|
193
|
+
(p) => p.pageType === 'location' && p.vertical === page.vertical && p.slug !== page.slug
|
|
194
|
+
)
|
|
195
|
+
const shuffledSiblings = seededShuffle(siblings, page.id)
|
|
196
|
+
links.push(...shuffledSiblings.slice(0, LIMITS.LOCATION_SIBLINGS_MAX).map(linkTo))
|
|
197
|
+
|
|
198
|
+
// Cross-vertical pages (different vertical, same city)
|
|
199
|
+
if (page.city) {
|
|
200
|
+
const crossVertical = allPages.filter(
|
|
201
|
+
(p) => p.pageType === 'location' && p.vertical !== page.vertical && p.city === page.city
|
|
202
|
+
)
|
|
203
|
+
const shuffledCross = seededShuffle(crossVertical, page.id + '-cross')
|
|
204
|
+
links.push(...shuffledCross.slice(0, LIMITS.LOCATION_CROSS_VERTICAL).map(linkTo))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Cluster pages for this vertical
|
|
208
|
+
const clusterPages = allPages.filter(
|
|
209
|
+
(p) => (p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical
|
|
210
|
+
)
|
|
211
|
+
const shuffledClusters = seededShuffle(clusterPages, page.id + '-cluster')
|
|
212
|
+
links.push(...shuffledClusters.slice(0, LIMITS.LOCATION_TO_CLUSTERS).map(linkTo))
|
|
213
|
+
|
|
214
|
+
return links
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generates internal links for a cluster/use-case page.
|
|
219
|
+
*
|
|
220
|
+
* Links to:
|
|
221
|
+
* - Parent pillar page (same vertical)
|
|
222
|
+
* - Up to CLUSTER_TO_LOCATIONS location pages (same vertical)
|
|
223
|
+
* - Up to CLUSTER_TO_CLUSTERS related cluster pages (same vertical, different use-case)
|
|
224
|
+
*/
|
|
225
|
+
function generateClusterLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
226
|
+
const links: SeoInternalLink[] = []
|
|
227
|
+
|
|
228
|
+
// Parent pillar page
|
|
229
|
+
const pillar = allPages.find((p) => p.pageType === 'pillar' && p.vertical === page.vertical)
|
|
230
|
+
if (pillar) {
|
|
231
|
+
links.push(linkTo(pillar))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Location pages for this vertical
|
|
235
|
+
const locationPages = allPages.filter((p) => p.pageType === 'location' && p.vertical === page.vertical)
|
|
236
|
+
const shuffledLocations = seededShuffle(locationPages, page.id)
|
|
237
|
+
links.push(...shuffledLocations.slice(0, LIMITS.CLUSTER_TO_LOCATIONS).map(linkTo))
|
|
238
|
+
|
|
239
|
+
// Related cluster pages (same vertical, different slug)
|
|
240
|
+
const relatedClusters = allPages.filter(
|
|
241
|
+
(p) =>
|
|
242
|
+
(p.pageType === 'cluster' || p.pageType === 'use-case') && p.vertical === page.vertical && p.slug !== page.slug
|
|
243
|
+
)
|
|
244
|
+
const shuffledClusters = seededShuffle(relatedClusters, page.id + '-sibling')
|
|
245
|
+
links.push(...shuffledClusters.slice(0, LIMITS.CLUSTER_TO_CLUSTERS).map(linkTo))
|
|
246
|
+
|
|
247
|
+
return links
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// =============================================================================
|
|
251
|
+
// Public API
|
|
252
|
+
// =============================================================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Generates internal links for a page based on the hub-and-spoke model.
|
|
256
|
+
*
|
|
257
|
+
* Pure function — no database access. Can be tested in isolation.
|
|
258
|
+
*
|
|
259
|
+
* Rules applied per page type:
|
|
260
|
+
* - pillar: → location pages + cluster pages + other vertical pillars
|
|
261
|
+
* - location: → parent pillar + siblings + cross-vertical + clusters
|
|
262
|
+
* - cluster/use-case: → parent pillar + location pages + related clusters
|
|
263
|
+
* - comparison/other: → empty list (no hub-and-spoke rules defined)
|
|
264
|
+
*
|
|
265
|
+
* @param page - The page for which to generate links
|
|
266
|
+
* @param allPages - All published SEO pages (used to find related pages)
|
|
267
|
+
* @returns Array of internal links sorted stable (pillar-first, then alphabetical by slug)
|
|
268
|
+
*/
|
|
269
|
+
export function generateInternalLinks(page: SeoPage, allPages: SeoPage[]): SeoInternalLink[] {
|
|
270
|
+
switch (page.pageType) {
|
|
271
|
+
case 'pillar':
|
|
272
|
+
return generatePillarLinks(page, allPages)
|
|
273
|
+
case 'location':
|
|
274
|
+
return generateLocationLinks(page, allPages)
|
|
275
|
+
case 'cluster':
|
|
276
|
+
case 'use-case':
|
|
277
|
+
return generateClusterLinks(page, allPages)
|
|
278
|
+
default:
|
|
279
|
+
return []
|
|
280
|
+
}
|
|
281
|
+
}
|