@stacksjs/defaults 0.70.206 → 0.70.208
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/TODO.md +21 -0
- package/ai/AGENTS.md +79 -0
- package/ai/README.md +45 -0
- package/ai/claude/launch.json +12 -0
- package/ai/skills/stacks-actions/SKILL.md +128 -0
- package/ai/skills/stacks-ai/SKILL.md +276 -0
- package/ai/skills/stacks-alias/SKILL.md +94 -0
- package/ai/skills/stacks-analytics/SKILL.md +91 -0
- package/ai/skills/stacks-api/SKILL.md +738 -0
- package/ai/skills/stacks-arrays/SKILL.md +277 -0
- package/ai/skills/stacks-auth/SKILL.md +422 -0
- package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
- package/ai/skills/stacks-brandkit/SKILL.md +811 -0
- package/ai/skills/stacks-browse/SKILL.md +127 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
- package/ai/skills/stacks-browser/SKILL.md +145 -0
- package/ai/skills/stacks-buddy/SKILL.md +676 -0
- package/ai/skills/stacks-build/SKILL.md +163 -0
- package/ai/skills/stacks-cache/SKILL.md +125 -0
- package/ai/skills/stacks-calendar/SKILL.md +113 -0
- package/ai/skills/stacks-chat/SKILL.md +371 -0
- package/ai/skills/stacks-cli/SKILL.md +112 -0
- package/ai/skills/stacks-cloud/SKILL.md +192 -0
- package/ai/skills/stacks-cms/SKILL.md +217 -0
- package/ai/skills/stacks-collections/SKILL.md +82 -0
- package/ai/skills/stacks-commerce/SKILL.md +114 -0
- package/ai/skills/stacks-composables/SKILL.md +161 -0
- package/ai/skills/stacks-config/SKILL.md +144 -0
- package/ai/skills/stacks-configuration/SKILL.md +154 -0
- package/ai/skills/stacks-cron/SKILL.md +123 -0
- package/ai/skills/stacks-crosswind/SKILL.md +190 -0
- package/ai/skills/stacks-dashboard/SKILL.md +173 -0
- package/ai/skills/stacks-database/SKILL.md +286 -0
- package/ai/skills/stacks-datetime/SKILL.md +269 -0
- package/ai/skills/stacks-dependencies/SKILL.md +111 -0
- package/ai/skills/stacks-deploy/SKILL.md +102 -0
- package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
- package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
- package/ai/skills/stacks-design-output/SKILL.md +55 -0
- package/ai/skills/stacks-design-soft/SKILL.md +156 -0
- package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
- package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
- package/ai/skills/stacks-desktop/SKILL.md +78 -0
- package/ai/skills/stacks-development/SKILL.md +298 -0
- package/ai/skills/stacks-dns/SKILL.md +149 -0
- package/ai/skills/stacks-docs/SKILL.md +109 -0
- package/ai/skills/stacks-email/SKILL.md +236 -0
- package/ai/skills/stacks-enums/SKILL.md +100 -0
- package/ai/skills/stacks-env/SKILL.md +181 -0
- package/ai/skills/stacks-error-handling/SKILL.md +137 -0
- package/ai/skills/stacks-events/SKILL.md +228 -0
- package/ai/skills/stacks-faker/SKILL.md +158 -0
- package/ai/skills/stacks-git/SKILL.md +158 -0
- package/ai/skills/stacks-guard/SKILL.md +98 -0
- package/ai/skills/stacks-health/SKILL.md +75 -0
- package/ai/skills/stacks-http/SKILL.md +316 -0
- package/ai/skills/stacks-i18n/SKILL.md +198 -0
- package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
- package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
- package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
- package/ai/skills/stacks-investigate/SKILL.md +119 -0
- package/ai/skills/stacks-jobs/SKILL.md +136 -0
- package/ai/skills/stacks-lint/SKILL.md +184 -0
- package/ai/skills/stacks-listeners/SKILL.md +99 -0
- package/ai/skills/stacks-logging/SKILL.md +78 -0
- package/ai/skills/stacks-mail/SKILL.md +120 -0
- package/ai/skills/stacks-middleware/SKILL.md +249 -0
- package/ai/skills/stacks-migrations/SKILL.md +105 -0
- package/ai/skills/stacks-models/SKILL.md +267 -0
- package/ai/skills/stacks-new-feature/SKILL.md +206 -0
- package/ai/skills/stacks-notifications/SKILL.md +210 -0
- package/ai/skills/stacks-objects/SKILL.md +205 -0
- package/ai/skills/stacks-office-hours/SKILL.md +134 -0
- package/ai/skills/stacks-orm/SKILL.md +342 -0
- package/ai/skills/stacks-path/SKILL.md +118 -0
- package/ai/skills/stacks-payments/SKILL.md +373 -0
- package/ai/skills/stacks-plan-review/SKILL.md +152 -0
- package/ai/skills/stacks-plugins/SKILL.md +149 -0
- package/ai/skills/stacks-push/SKILL.md +376 -0
- package/ai/skills/stacks-query-builder/SKILL.md +128 -0
- package/ai/skills/stacks-queue/SKILL.md +559 -0
- package/ai/skills/stacks-realtime/SKILL.md +442 -0
- package/ai/skills/stacks-redesign/SKILL.md +182 -0
- package/ai/skills/stacks-registry/SKILL.md +58 -0
- package/ai/skills/stacks-repl/SKILL.md +80 -0
- package/ai/skills/stacks-retro/SKILL.md +122 -0
- package/ai/skills/stacks-review/SKILL.md +135 -0
- package/ai/skills/stacks-router/SKILL.md +231 -0
- package/ai/skills/stacks-routes/SKILL.md +105 -0
- package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
- package/ai/skills/stacks-scheduler/SKILL.md +363 -0
- package/ai/skills/stacks-search-engine/SKILL.md +112 -0
- package/ai/skills/stacks-security/SKILL.md +129 -0
- package/ai/skills/stacks-security-audit/SKILL.md +128 -0
- package/ai/skills/stacks-server/SKILL.md +458 -0
- package/ai/skills/stacks-shell/SKILL.md +62 -0
- package/ai/skills/stacks-slug/SKILL.md +87 -0
- package/ai/skills/stacks-sms/SKILL.md +385 -0
- package/ai/skills/stacks-socials/SKILL.md +303 -0
- package/ai/skills/stacks-storage/SKILL.md +576 -0
- package/ai/skills/stacks-strings/SKILL.md +416 -0
- package/ai/skills/stacks-stx/SKILL.md +176 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
- package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
- package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
- package/ai/skills/stacks-testing/SKILL.md +129 -0
- package/ai/skills/stacks-tunnel/SKILL.md +125 -0
- package/ai/skills/stacks-types/SKILL.md +178 -0
- package/ai/skills/stacks-ui/SKILL.md +246 -0
- package/ai/skills/stacks-utils/SKILL.md +444 -0
- package/ai/skills/stacks-validation/SKILL.md +371 -0
- package/ai/skills/stacks-whois/SKILL.md +315 -0
- package/assets/world-map-data.ts +121 -0
- package/bootstrap.ts +114 -0
- package/docs/api-examples.md +46 -0
- package/docs/index.md +24 -0
- package/docs/markdown-examples.md +87 -0
- package/functions/analytics.ts +97 -0
- package/functions/api.ts +143 -0
- package/functions/auth.ts +208 -0
- package/functions/billing/payments.ts +149 -0
- package/functions/blog.ts +120 -0
- package/functions/cms/categorizables.ts +107 -0
- package/functions/cms/pages.ts +108 -0
- package/functions/cms/posts.ts +100 -0
- package/functions/cms/taggables.ts +102 -0
- package/functions/commerce/coupons.ts +128 -0
- package/functions/commerce/customers.ts +128 -0
- package/functions/commerce/gift-cards.ts +119 -0
- package/functions/commerce/orders.ts +112 -0
- package/functions/commerce/payments.ts +119 -0
- package/functions/commerce/products/categories.ts +123 -0
- package/functions/commerce/products/manufacturers.ts +123 -0
- package/functions/commerce/products/products.ts +123 -0
- package/functions/commerce/products/reviews.ts +123 -0
- package/functions/commerce/products/units.ts +123 -0
- package/functions/commerce/products/variants.ts +123 -0
- package/functions/commerce/shippings/delivery-routes.ts +123 -0
- package/functions/commerce/shippings/digital-deliveries.ts +119 -0
- package/functions/commerce/shippings/drivers.ts +123 -0
- package/functions/commerce/shippings/license-keys.ts +123 -0
- package/functions/commerce/shippings/shipping-methods.ts +123 -0
- package/functions/commerce/shippings/shipping-rates.ts +119 -0
- package/functions/commerce/shippings/shipping-zones.ts +119 -0
- package/functions/commerce/taxes.ts +119 -0
- package/functions/commerce/waitlist/products.ts +123 -0
- package/functions/commerce/waitlist/restaurants.ts +123 -0
- package/functions/craft.ts +107 -0
- package/functions/dashboard.ts +153 -0
- package/functions/inbox.ts +95 -0
- package/functions/jobs.ts +86 -0
- package/functions/monitoring/errors.ts +295 -0
- package/functions/queue.ts +111 -0
- package/functions/realtime/websockets.ts +96 -0
- package/functions/realtime.ts +52 -0
- package/functions/releases.ts +49 -0
- package/functions/search.ts +126 -0
- package/functions/toasts.ts +95 -0
- package/functions/uploads.ts +216 -0
- package/ide/.gitignore +4 -0
- package/ide/cursor/rules/auto-imports.mdc +574 -0
- package/ide/cursor/rules/code-style.mdc +12 -0
- package/ide/cursor/rules/documentation.mdc +9 -0
- package/ide/cursor/rules/error-handling-validation.mdc +11 -0
- package/ide/cursor/rules/key-conventions.mdc +11 -0
- package/ide/cursor/rules/project-structure.mdc +169 -0
- package/ide/cursor/rules/readme.mdc +318 -0
- package/ide/cursor/rules/syntax-formatting.mdc +9 -0
- package/ide/cursor/rules/testing.mdc +10 -0
- package/ide/cursor/rules/typescript.mdc +9 -0
- package/ide/cursor/rules/ui-styling.mdc +0 -0
- package/ide/dictionary.txt +397 -0
- package/ide/jetbrains/.fleet/settings.json +4 -0
- package/ide/jetbrains/.idea/.gitignore +5 -0
- package/ide/jetbrains/.idea/.name +1 -0
- package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
- package/ide/jetbrains/.idea/modules.xml +8 -0
- package/ide/jetbrains/.idea/stacks.iml +22 -0
- package/ide/jetbrains/.idea/vcs.xml +6 -0
- package/ide/jetbrains/.idea/webResources.xml +14 -0
- package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
- package/ide/jetbrains/installed.txt +4 -0
- package/ide/jetbrains/options/github-copilot.xml +5 -0
- package/ide/jetbrains/options/nodejs.xml +7 -0
- package/ide/jetbrains/options/templates.xml +124 -0
- package/ide/jetbrains/templates/JavaScript.xml +6 -0
- package/ide/jetbrains/templates/Stacks.xml +8 -0
- package/ide/vscode/.vscode/README.md +23 -0
- package/ide/vscode/.vscode/extensions.json +5 -0
- package/ide/vscode/.vscode/settings.json +176 -0
- package/ide/vscode/.vscode/stacks.code-snippets +181 -0
- package/ide/vscode/LICENSE.md +21 -0
- package/ide/vscode/README.md +36 -0
- package/ide/vscode/logo.png +0 -0
- package/ide/vscode/package.json +79 -0
- package/ide/zed/.zed/settings.json +139 -0
- package/lang/README.md +28 -0
- package/lang/de.yml +14 -0
- package/lang/en.yml +15 -0
- package/package.json +19 -3
- package/project/bootstrap +161 -0
- package/project/buddy +175 -0
- package/project/storage/framework/tsconfig.app.json +50 -0
- package/project/storage/framework/tsconfig.base.json +58 -0
- package/routes/core.ts +78 -0
- package/routes/dashboard-api.ts +233 -0
- package/routes/dashboard.ts +739 -0
- package/routes/email.ts +129 -0
- package/stores/[name].stx +16 -0
- package/stores/git.ts +71 -0
- package/stores/payment.ts +474 -0
- package/stores/queue.ts +43 -0
- package/stores/user.ts +29 -0
- package/stx-components-plugin.ts +25 -0
- package/styles/design-tokens.css +597 -0
- package/types/dashboard.ts +70 -0
- package/types/defaults.ts +438 -0
- package/vcs/github/CODE_OF_CONDUCT.md +83 -0
- package/vcs/github/CONTRIBUTING.md +187 -0
- package/vcs/github/EXPLANATIONS.md +41 -0
- package/vcs/github/FUNDING.yml +1 -0
- package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
- package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
- package/vcs/github/SECURITY.md +70 -0
- package/vcs/github/renovate.json +5 -0
- package/vcs/github/stale.yml +14 -0
- package/vcs/github/workflows/README.md +16 -0
- package/vcs/github/workflows/ci.yml +114 -0
- package/vcs/github/workflows/export-size.yml +25 -0
- package/vcs/github/workflows/labeler.yml +13 -0
- package/vcs/github/workflows/release.yml +219 -0
- package/views/[...all].stx +8 -0
- package/views/about.md +3 -0
- package/views/auth/login.stx +1 -0
- package/views/auth/signup.stx +85 -0
- package/views/dashboard/.discovered-models.json +542 -0
- package/views/dashboard/AUDIT.md +904 -0
- package/views/dashboard/[...all].stx +89 -0
- package/views/dashboard/access-tokens/index.stx +177 -0
- package/views/dashboard/actions/index.stx +129 -0
- package/views/dashboard/analytics/blog/index.stx +356 -0
- package/views/dashboard/analytics/browsers/index.stx +263 -0
- package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
- package/views/dashboard/analytics/commerce/web/index.stx +379 -0
- package/views/dashboard/analytics/countries/index.stx +289 -0
- package/views/dashboard/analytics/devices/index.stx +305 -0
- package/views/dashboard/analytics/events/index.stx +536 -0
- package/views/dashboard/analytics/index.stx +88 -0
- package/views/dashboard/analytics/marketing/index.stx +315 -0
- package/views/dashboard/analytics/pages/index.stx +271 -0
- package/views/dashboard/analytics/referrers/index.stx +319 -0
- package/views/dashboard/analytics/web/index.stx +356 -0
- package/views/dashboard/buddy/index.stx +198 -0
- package/views/dashboard/ci/index.stx +462 -0
- package/views/dashboard/cloud/index.stx +452 -0
- package/views/dashboard/commands/index.stx +295 -0
- package/views/dashboard/commerce/categories/index.stx +406 -0
- package/views/dashboard/commerce/coupons/index.stx +485 -0
- package/views/dashboard/commerce/customers/index.stx +410 -0
- package/views/dashboard/commerce/dashboard/index.stx +287 -0
- package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
- package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
- package/views/dashboard/commerce/delivery/drivers.stx +397 -0
- package/views/dashboard/commerce/delivery/index.stx +18 -0
- package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
- package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
- package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
- package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
- package/views/dashboard/commerce/delivery/tabs.ts +25 -0
- package/views/dashboard/commerce/gift-cards/index.stx +561 -0
- package/views/dashboard/commerce/manufacturers/index.stx +308 -0
- package/views/dashboard/commerce/orders/index.stx +247 -0
- package/views/dashboard/commerce/payments/index.stx +535 -0
- package/views/dashboard/commerce/pos/index.stx +653 -0
- package/views/dashboard/commerce/printers/devices/index.stx +273 -0
- package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
- package/views/dashboard/commerce/products/detail.stx +563 -0
- package/views/dashboard/commerce/products/index.stx +370 -0
- package/views/dashboard/commerce/reviews/index.stx +656 -0
- package/views/dashboard/commerce/reviews/index.ts +0 -0
- package/views/dashboard/commerce/taxes/index.stx +377 -0
- package/views/dashboard/commerce/units/index.stx +317 -0
- package/views/dashboard/commerce/variants/index.stx +319 -0
- package/views/dashboard/commerce/waitlist/products.stx +942 -0
- package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
- package/views/dashboard/components/index.stx +330 -0
- package/views/dashboard/composables/index.ts +19 -0
- package/views/dashboard/composables/useChart.ts +77 -0
- package/views/dashboard/composables/useNavigation.ts +99 -0
- package/views/dashboard/composables/useRole.ts +58 -0
- package/views/dashboard/composables/useTheme.ts +74 -0
- package/views/dashboard/content/authors/index.stx +171 -0
- package/views/dashboard/content/blog/index.stx +493 -0
- package/views/dashboard/content/categories/index.stx +169 -0
- package/views/dashboard/content/comments/index.stx +189 -0
- package/views/dashboard/content/dashboard.stx +383 -0
- package/views/dashboard/content/files/index.stx +706 -0
- package/views/dashboard/content/pages/index.stx +174 -0
- package/views/dashboard/content/posts/index.stx +269 -0
- package/views/dashboard/content/seo/index.stx +240 -0
- package/views/dashboard/content/tags/index.stx +167 -0
- package/views/dashboard/data/activity/index.stx +261 -0
- package/views/dashboard/data/dashboard/index.stx +116 -0
- package/views/dashboard/data/subscribers.stx +270 -0
- package/views/dashboard/data/teams.stx +275 -0
- package/views/dashboard/data/users.stx +279 -0
- package/views/dashboard/dependencies/index.stx +178 -0
- package/views/dashboard/deployments/[id].stx +234 -0
- package/views/dashboard/deployments/index.stx +9 -0
- package/views/dashboard/dns/index.stx +163 -0
- package/views/dashboard/environment/index.stx +283 -0
- package/views/dashboard/errors/index.stx +274 -0
- package/views/dashboard/forgot-password.stx +57 -0
- package/views/dashboard/functions/index.stx +159 -0
- package/views/dashboard/health/index.stx +248 -0
- package/views/dashboard/inbox/activity.stx +350 -0
- package/views/dashboard/inbox/index.stx +452 -0
- package/views/dashboard/inbox/settings.stx +290 -0
- package/views/dashboard/index.stx +252 -0
- package/views/dashboard/insights/index.stx +529 -0
- package/views/dashboard/jobs/[id].stx +430 -0
- package/views/dashboard/jobs/history.stx +222 -0
- package/views/dashboard/jobs/index.stx +214 -0
- package/views/dashboard/kanban/[id].stx +899 -0
- package/views/dashboard/kanban/index.stx +260 -0
- package/views/dashboard/layouts/default.stx +565 -0
- package/views/dashboard/layouts/guest.stx +10 -0
- package/views/dashboard/login.stx +48 -0
- package/views/dashboard/logs/index.stx +540 -0
- package/views/dashboard/mailboxes/index.stx +149 -0
- package/views/dashboard/management/permissions/index.stx +490 -0
- package/views/dashboard/marketing/campaigns/index.stx +604 -0
- package/views/dashboard/marketing/lists/index.stx +514 -0
- package/views/dashboard/marketing/reviews/index.stx +475 -0
- package/views/dashboard/marketing/social-posts/index.stx +560 -0
- package/views/dashboard/models/[model].stx +195 -0
- package/views/dashboard/models/index.stx +159 -0
- package/views/dashboard/monitoring/errors/index.stx +415 -0
- package/views/dashboard/notifications/dashboard.stx +383 -0
- package/views/dashboard/notifications/email.stx +336 -0
- package/views/dashboard/notifications/history.stx +373 -0
- package/views/dashboard/notifications/sms.stx +332 -0
- package/views/dashboard/packages/index.stx +195 -0
- package/views/dashboard/queries/[id].stx +381 -0
- package/views/dashboard/queries/history.stx +227 -0
- package/views/dashboard/queries/index.stx +335 -0
- package/views/dashboard/queries/slow.stx +318 -0
- package/views/dashboard/queue/index.stx +427 -0
- package/views/dashboard/realtime/index.stx +429 -0
- package/views/dashboard/register.stx +46 -0
- package/views/dashboard/releases/index.stx +389 -0
- package/views/dashboard/requests/index.stx +462 -0
- package/views/dashboard/serverless/index.stx +475 -0
- package/views/dashboard/servers/[id].stx +299 -0
- package/views/dashboard/servers/index.stx +483 -0
- package/views/dashboard/settings/billing.stx +50 -0
- package/views/dashboard/settings/index.stx +256 -0
- package/views/dashboard/settings/mail.stx +252 -0
- package/views/dashboard/stores/auth.ts +123 -0
- package/views/dashboard/stores/ci.ts +342 -0
- package/views/dashboard/stores/index.ts +129 -0
- package/views/dashboard/stores/kanban.ts +723 -0
- package/views/dashboard/stores/rbac.ts +350 -0
- package/views/dashboard/stx.config.ts +34 -0
- package/views/dashboard/teams/[id].stx +170 -0
- package/views/forum/category.stx +92 -0
- package/views/forum/index.stx +209 -0
- package/views/forum/new-topic.stx +113 -0
- package/views/forum/profile.stx +1 -0
- package/views/forum/topic.stx +143 -0
- package/views/hello-world/[name].stx +47 -0
- package/views/index.stx +21 -0
- package/views/system-tray/index.stx +147 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stacks-sms
|
|
3
|
+
description: Use when implementing SMS in Stacks — sending text messages, the SmsBuilder fluent API, SMS templates, phone verification (OTP/2FA), bulk sending, Twilio/Vonage drivers, E.164 formatting, or the SMS facade. Covers @stacksjs/sms and config/sms.ts.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
|
+
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stacks SMS
|
|
10
|
+
|
|
11
|
+
Multi-driver SMS system with verification (OTP), templates, bulk sending, and a fluent builder API. Two fully implemented drivers: Twilio and Vonage.
|
|
12
|
+
|
|
13
|
+
## Key Paths
|
|
14
|
+
- Core package: `storage/framework/core/sms/src/`
|
|
15
|
+
- SMS facade and builder: `storage/framework/core/sms/src/sms.ts`
|
|
16
|
+
- Twilio driver: `storage/framework/core/sms/src/drivers/twilio.ts`
|
|
17
|
+
- Vonage driver: `storage/framework/core/sms/src/drivers/vonage.ts`
|
|
18
|
+
- Drivers index: `storage/framework/core/sms/src/drivers/index.ts`
|
|
19
|
+
- Configuration: `config/sms.ts`
|
|
20
|
+
|
|
21
|
+
## Package Exports
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Main facade and functions
|
|
25
|
+
import SMS, { sms, send, sendSms, sendBulk, sendTemplate } from '@stacksjs/sms'
|
|
26
|
+
import { startVerification, checkVerification, cancelVerification } from '@stacksjs/sms'
|
|
27
|
+
import { formatE164, isValidPhoneNumber, isEnabled, getConfig, configure, getDriver, init } from '@stacksjs/sms'
|
|
28
|
+
import { SmsBuilder } from '@stacksjs/sms'
|
|
29
|
+
|
|
30
|
+
// Drivers
|
|
31
|
+
import { TwilioDriver, createTwilioDriver } from '@stacksjs/sms'
|
|
32
|
+
import { VonageDriver, createVonageDriver } from '@stacksjs/sms'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## SMS Facade Object
|
|
36
|
+
|
|
37
|
+
The `SMS` default export aggregates all functions:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
SMS.init() // Load config from config/sms.ts
|
|
41
|
+
SMS.configure(config) // Override config at runtime
|
|
42
|
+
SMS.isEnabled() // Check if SMS is enabled (config.enabled)
|
|
43
|
+
SMS.getConfig() // Get current config (returns copy)
|
|
44
|
+
|
|
45
|
+
SMS.send(message) // Send single SMS
|
|
46
|
+
SMS.sendSms(message) // Alias for send
|
|
47
|
+
SMS.sendBulk(messages) // Send multiple SMS
|
|
48
|
+
SMS.sendTemplate(to, name, vars) // Send using template
|
|
49
|
+
|
|
50
|
+
SMS.getStatus(messageId) // Get delivery status
|
|
51
|
+
SMS.getBalance() // Get account balance
|
|
52
|
+
SMS.verifyNumber(phone) // Lookup/verify phone number
|
|
53
|
+
|
|
54
|
+
SMS.startVerification(req) // Start OTP flow
|
|
55
|
+
SMS.checkVerification(req) // Verify OTP code
|
|
56
|
+
SMS.cancelVerification(id) // Cancel pending verification
|
|
57
|
+
|
|
58
|
+
SMS.formatE164(phone, cc?) // Format to E.164
|
|
59
|
+
SMS.isValidPhoneNumber(phone) // Validate E.164 format
|
|
60
|
+
|
|
61
|
+
SMS.sms() // Create SmsBuilder instance
|
|
62
|
+
SMS.getDriver(provider?) // Get driver instance
|
|
63
|
+
SMS.getVerificationDriver(p?) // Get verification driver
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## SmsBuilder (Fluent API)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { sms } from '@stacksjs/sms'
|
|
70
|
+
|
|
71
|
+
// Basic SMS
|
|
72
|
+
await sms()
|
|
73
|
+
.to('+1234567890')
|
|
74
|
+
.body('Your code is 123456')
|
|
75
|
+
.from('+0987654321')
|
|
76
|
+
.send()
|
|
77
|
+
|
|
78
|
+
// With provider selection
|
|
79
|
+
await sms()
|
|
80
|
+
.to('+1234567890')
|
|
81
|
+
.body('Hello!')
|
|
82
|
+
.via('vonage')
|
|
83
|
+
.send()
|
|
84
|
+
|
|
85
|
+
// MMS with media and callback
|
|
86
|
+
await sms()
|
|
87
|
+
.to(['+1111111111', '+2222222222'])
|
|
88
|
+
.body('Check this out')
|
|
89
|
+
.media(['https://example.com/image.jpg'])
|
|
90
|
+
.callback('https://myapp.com/sms/status')
|
|
91
|
+
.send()
|
|
92
|
+
|
|
93
|
+
// .text() is an alias for .body()
|
|
94
|
+
await sms()
|
|
95
|
+
.to('+1234567890')
|
|
96
|
+
.text('Hello!')
|
|
97
|
+
.send()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The builder validates that `to` and `body` are set before sending. If missing, it returns a failed `SmsSendResult` without throwing. If `.via(provider)` is set, it creates a new driver for that provider; otherwise uses the default driver.
|
|
101
|
+
|
|
102
|
+
## Direct Send Functions
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { send, sendBulk } from '@stacksjs/sms'
|
|
106
|
+
|
|
107
|
+
// SmsMessage shape
|
|
108
|
+
await send({
|
|
109
|
+
to: '+1234567890', // string or string[]
|
|
110
|
+
body: 'Hello!',
|
|
111
|
+
from: '+0987654321', // optional, falls back to config
|
|
112
|
+
mediaUrls: ['url'], // optional, for MMS
|
|
113
|
+
statusCallback: 'url', // optional, webhook for status updates
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Bulk send
|
|
117
|
+
await sendBulk([msg1, msg2, msg3]) // returns SmsSendResult[]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`send()` and all sending functions call `ensureConfig()` first, which lazily loads `config/sms.ts` via dynamic import.
|
|
121
|
+
|
|
122
|
+
## Message Status & Info
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { getStatus, verifyNumber, getBalance } from '@stacksjs/sms'
|
|
126
|
+
|
|
127
|
+
// Get delivery status for a message
|
|
128
|
+
const status = await getStatus('SM_message_id')
|
|
129
|
+
// Returns SmsStatusUpdate | null (null if driver doesn't support it)
|
|
130
|
+
|
|
131
|
+
// Verify/lookup a phone number
|
|
132
|
+
const info = await verifyNumber('+1234567890')
|
|
133
|
+
// Returns { valid: boolean, carrier?: string, type?: string }
|
|
134
|
+
|
|
135
|
+
// Get account balance
|
|
136
|
+
const balance = await getBalance()
|
|
137
|
+
// Returns { balance: number, currency: string } | null
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Phone Verification (OTP/2FA)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { startVerification, checkVerification, cancelVerification } from '@stacksjs/sms'
|
|
144
|
+
|
|
145
|
+
// Start -- sends OTP code via SMS (or 'call', 'email' for Twilio; 'sms', 'whatsapp' for Vonage)
|
|
146
|
+
const result = await startVerification({
|
|
147
|
+
to: '+1234567890',
|
|
148
|
+
channel: 'sms', // default: 'sms'
|
|
149
|
+
codeLength: 6, // optional
|
|
150
|
+
locale: 'en', // optional
|
|
151
|
+
customMessage: 'Your code: {code}', // optional, Twilio only
|
|
152
|
+
})
|
|
153
|
+
// Returns { success: boolean, verificationId?: string, status: 'pending' | 'denied', error?: string }
|
|
154
|
+
|
|
155
|
+
// Check -- verify the code the user entered
|
|
156
|
+
const check = await checkVerification({
|
|
157
|
+
to: '+1234567890', // Twilio requires 'to'
|
|
158
|
+
verificationId: result.verificationId, // Vonage requires this
|
|
159
|
+
code: '123456',
|
|
160
|
+
})
|
|
161
|
+
// Returns { success: boolean, verificationId?: string, status: 'approved' | 'denied', error?: string }
|
|
162
|
+
|
|
163
|
+
// Cancel -- cancel a pending verification
|
|
164
|
+
const cancelled = await cancelVerification(result.verificationId!)
|
|
165
|
+
// Returns boolean
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Twilio** uses the Verify service (`verify.twilio.com/v2`) and requires `verifyServiceSid` passed to the TwilioDriver constructor.
|
|
169
|
+
|
|
170
|
+
**Vonage** uses the Verify V2 API (`api.nexmo.com/v2/verify`). Whatsapp channel maps to `whatsapp_interactive`.
|
|
171
|
+
|
|
172
|
+
## SMS Templates
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { sendTemplate } from '@stacksjs/sms'
|
|
176
|
+
|
|
177
|
+
await sendTemplate('+1234567890', 'order-confirmation', {
|
|
178
|
+
orderNumber: '#12345',
|
|
179
|
+
total: '$29.99',
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Templates are defined in `config/sms.ts` under `templates[]`. Each template has `name`, `body`, and optional `variables`. Variables use single-brace syntax `{variableName}` in the template body. The function finds the template by name, replaces variables, and sends via the default driver.
|
|
184
|
+
|
|
185
|
+
Returns a failed result (without throwing) if the template is not found.
|
|
186
|
+
|
|
187
|
+
## Phone Number Utilities
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { formatE164, isValidPhoneNumber } from '@stacksjs/sms'
|
|
191
|
+
|
|
192
|
+
// Normalize to E.164 format
|
|
193
|
+
formatE164('+1 (234) 567-890') // '+12345678900'
|
|
194
|
+
formatE164('2345678900', '1') // '+12345678900'
|
|
195
|
+
formatE164('002345678900') // '+2345678900' (00 prefix stripped)
|
|
196
|
+
|
|
197
|
+
// Validate E.164 format (regex: /^\+[1-9]\d{6,14}$/)
|
|
198
|
+
isValidPhoneNumber('+1234567890') // true
|
|
199
|
+
isValidPhoneNumber('1234567890') // true (formatted first, then validated)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`formatE164()` strips spaces, dashes, and parentheses. If the number starts with `+`, returns as-is. If it starts with `00`, replaces with `+`. Otherwise prepends `+` and the default country code (from `config.defaultCountryCode` or `'1'` for US).
|
|
203
|
+
|
|
204
|
+
## Twilio Driver
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { TwilioDriver, createTwilioDriver } from '@stacksjs/sms'
|
|
208
|
+
|
|
209
|
+
const driver = new TwilioDriver({
|
|
210
|
+
accountSid: 'AC...',
|
|
211
|
+
authToken: '...',
|
|
212
|
+
from: '+1234567890',
|
|
213
|
+
messagingServiceSid: 'MG...', // optional, used instead of 'from'
|
|
214
|
+
statusCallback: 'https://...', // optional, default callback URL
|
|
215
|
+
}, 'VA_verify_service_sid') // optional second arg for verification
|
|
216
|
+
|
|
217
|
+
// Or use factory
|
|
218
|
+
const driver = createTwilioDriver(config, verifyServiceSid?)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Uses REST API at `https://api.twilio.com/2010-04-01`. Auth via HTTP Basic (`accountSid:authToken`).
|
|
222
|
+
|
|
223
|
+
Features:
|
|
224
|
+
- `send(message)` -- sends via Messages API, supports MMS (mediaUrls), statusCallback, messagingServiceSid
|
|
225
|
+
- `sendBulk(messages)` -- sends in parallel via `Promise.all`
|
|
226
|
+
- `getStatus(messageId)` -- retrieves message status
|
|
227
|
+
- `verify(phoneNumber)` -- uses Twilio Lookup API v2 with line_type_intelligence
|
|
228
|
+
- `getBalance()` -- retrieves account balance
|
|
229
|
+
- `startVerification(req)` -- Twilio Verify service
|
|
230
|
+
- `checkVerification(req)` -- check OTP code
|
|
231
|
+
- `cancelVerification(id)` -- cancel pending verification
|
|
232
|
+
|
|
233
|
+
Status mapping: queued, sending, sent, delivered, undelivered -> `SmsStatus`; canceled -> 'failed'.
|
|
234
|
+
|
|
235
|
+
## Vonage Driver
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { VonageDriver, createVonageDriver } from '@stacksjs/sms'
|
|
239
|
+
|
|
240
|
+
const driver = new VonageDriver({
|
|
241
|
+
apiKey: '...',
|
|
242
|
+
apiSecret: '...',
|
|
243
|
+
from: 'MyApp',
|
|
244
|
+
applicationId: '...', // optional, for JWT auth
|
|
245
|
+
privateKey: '...', // optional, for JWT auth
|
|
246
|
+
}, true) // optional: useMessagesApi (default: false)
|
|
247
|
+
|
|
248
|
+
// Or use factory
|
|
249
|
+
const driver = createVonageDriver(config, useMessagesApi?)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Two send modes:
|
|
253
|
+
- **SMS API** (legacy, default) -- `https://rest.nexmo.com/sms/json`, uses API key/secret in body
|
|
254
|
+
- **Messages API** (newer) -- `https://api.nexmo.com/v1/messages`, uses Basic auth or JWT
|
|
255
|
+
|
|
256
|
+
Features:
|
|
257
|
+
- `send(message)` -- sends via chosen API mode
|
|
258
|
+
- `sendBulk(messages)` -- sends in parallel
|
|
259
|
+
- `getStatus(messageId)` -- Messages API only
|
|
260
|
+
- `verify(phoneNumber)` -- Vonage Number Insight API (basic)
|
|
261
|
+
- `getBalance()` -- account balance in EUR
|
|
262
|
+
- `startVerification(req)` -- Vonage Verify V2 API
|
|
263
|
+
- `checkVerification(req)` -- requires `verificationId`
|
|
264
|
+
- `cancelVerification(id)` -- DELETE request to cancel
|
|
265
|
+
|
|
266
|
+
The `nexmo` export is a legacy alias for `vonage`.
|
|
267
|
+
|
|
268
|
+
## Other Drivers (Commented Out / Placeholder)
|
|
269
|
+
|
|
270
|
+
The `drivers/` directory contains placeholder files for: `gupshup`, `nexmo` (alias for vonage), `plivo`, `sms77`, `sns`, `telnyx`, `termii`. These are all commented out and not functional. Only Twilio and Vonage are active.
|
|
271
|
+
|
|
272
|
+
## config/sms.ts
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
{
|
|
276
|
+
enabled: false, // Must explicitly enable
|
|
277
|
+
provider: 'twilio', // 'twilio' | 'vonage'
|
|
278
|
+
from: env.SMS_FROM_NUMBER || '',
|
|
279
|
+
defaultCountryCode: 'US',
|
|
280
|
+
messageType: 'TRANSACTIONAL', // TRANSACTIONAL or PROMOTIONAL
|
|
281
|
+
|
|
282
|
+
drivers: {
|
|
283
|
+
twilio: {
|
|
284
|
+
accountSid: env.TWILIO_ACCOUNT_SID || '',
|
|
285
|
+
authToken: env.TWILIO_AUTH_TOKEN || '',
|
|
286
|
+
from: env.TWILIO_FROM_NUMBER || '',
|
|
287
|
+
messagingServiceSid: env.TWILIO_MESSAGING_SERVICE_SID || '',
|
|
288
|
+
},
|
|
289
|
+
vonage: {
|
|
290
|
+
apiKey: env.VONAGE_API_KEY || '',
|
|
291
|
+
apiSecret: env.VONAGE_API_SECRET || '',
|
|
292
|
+
from: env.VONAGE_FROM_NUMBER || '',
|
|
293
|
+
},
|
|
294
|
+
pinpoint: {
|
|
295
|
+
region: env.AWS_REGION || 'us-east-1',
|
|
296
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID || '',
|
|
297
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY || '',
|
|
298
|
+
senderId: env.SMS_SENDER_ID || '',
|
|
299
|
+
originationNumber: env.SMS_ORIGINATION_NUMBER || '',
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
maxSpendPerMonth: 100,
|
|
304
|
+
optOut: {
|
|
305
|
+
enabled: true,
|
|
306
|
+
keywords: ['STOP', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT'],
|
|
307
|
+
},
|
|
308
|
+
templates: [], // Array of { name, body, variables }
|
|
309
|
+
twoWay: {
|
|
310
|
+
enabled: false,
|
|
311
|
+
},
|
|
312
|
+
} satisfies SmsConfig
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Type Interfaces (from @stacksjs/types)
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
interface SmsMessage {
|
|
319
|
+
to: string | string[]
|
|
320
|
+
body: string
|
|
321
|
+
from?: string
|
|
322
|
+
mediaUrls?: string[]
|
|
323
|
+
statusCallback?: string
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
interface SmsSendResult {
|
|
327
|
+
success: boolean
|
|
328
|
+
messageId?: string
|
|
329
|
+
status?: SmsStatus
|
|
330
|
+
to: string
|
|
331
|
+
provider: string
|
|
332
|
+
error?: string
|
|
333
|
+
segments?: number
|
|
334
|
+
price?: number
|
|
335
|
+
currency?: string
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
type SmsStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'undelivered' | 'failed' | 'unknown'
|
|
339
|
+
|
|
340
|
+
interface SmsStatusUpdate {
|
|
341
|
+
messageId: string
|
|
342
|
+
to: string
|
|
343
|
+
status: SmsStatus
|
|
344
|
+
timestamp: Date
|
|
345
|
+
errorCode?: string
|
|
346
|
+
errorMessage?: string
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
type SmsProvider = 'twilio' | 'vonage' | 'pinpoint'
|
|
350
|
+
|
|
351
|
+
interface VerificationRequest {
|
|
352
|
+
to: string
|
|
353
|
+
channel?: string // 'sms' | 'call' | 'email' | 'whatsapp'
|
|
354
|
+
codeLength?: number
|
|
355
|
+
locale?: string
|
|
356
|
+
customMessage?: string // Twilio only
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
interface VerificationCheckRequest {
|
|
360
|
+
to: string
|
|
361
|
+
code: string
|
|
362
|
+
verificationId?: string
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface VerificationResult {
|
|
366
|
+
success: boolean
|
|
367
|
+
verificationId?: string
|
|
368
|
+
status: 'pending' | 'approved' | 'denied'
|
|
369
|
+
error?: string
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Gotchas
|
|
374
|
+
- SMS is **disabled by default** (`enabled: false`) -- must set to `true` in `config/sms.ts`
|
|
375
|
+
- Provider API keys go in `.env`, not config files
|
|
376
|
+
- Config is loaded lazily via dynamic import on first `send()`/operation -- `init()` pre-loads it
|
|
377
|
+
- Only Twilio and Vonage drivers are fully implemented; other drivers (gupshup, plivo, sns, telnyx, etc.) are commented-out placeholders
|
|
378
|
+
- `pinpoint` appears in config but has no driver implementation in `getDriver()` -- using it throws `'Unsupported SMS provider: pinpoint'`
|
|
379
|
+
- The `defaultCountryCode` in config is `'US'` but `formatE164()` uses it as a numeric code prefix (the fallback is `'1'`), so the actual behavior uses the number `'1'` for US
|
|
380
|
+
- Template variables use single-brace syntax `{variableName}`, NOT double-brace
|
|
381
|
+
- `sendBulk()` sends all messages in parallel via `Promise.all` -- no rate limiting
|
|
382
|
+
- Twilio verification requires a `verifyServiceSid` passed to the driver constructor, which is not part of the standard config structure
|
|
383
|
+
- Vonage JWT authentication is a simplified placeholder -- real RS256 signing is not fully implemented
|
|
384
|
+
- `getDriver()` creates a new driver instance each time unless accessed through the cached `getDefaultDriver()`
|
|
385
|
+
- `sendTemplate()` returns a failed result (not an exception) if the template name is not found
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stacks-socials
|
|
3
|
+
description: Use when implementing social authentication in Stacks — OAuth2 flows with GitHub/Google/Facebook/Twitter providers, the AbstractProvider base class, PKCE support, state management, scope configuration, social user profiles, or token handling. Covers @stacksjs/socials.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
|
+
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stacks Socials
|
|
10
|
+
|
|
11
|
+
OAuth2-based social authentication with 4 built-in providers.
|
|
12
|
+
|
|
13
|
+
## Key Paths
|
|
14
|
+
- Core package: `storage/framework/core/socials/src/`
|
|
15
|
+
|
|
16
|
+
## Source Files
|
|
17
|
+
```
|
|
18
|
+
socials/src/
|
|
19
|
+
├── index.ts # re-exports from drivers/
|
|
20
|
+
├── abstract.ts # AbstractProvider base class
|
|
21
|
+
├── token.ts # Token class
|
|
22
|
+
├── types.ts # SocialUser, provider-specific interfaces
|
|
23
|
+
├── exceptions.ts # InvalidStateException, ConfigException
|
|
24
|
+
└── drivers/
|
|
25
|
+
├── index.ts # re-exports all 4 providers
|
|
26
|
+
├── github.ts # GitHub OAuth provider
|
|
27
|
+
├── google.ts # Google OAuth provider
|
|
28
|
+
├── facebook.ts # Facebook OAuth provider
|
|
29
|
+
└── twitter.ts # Twitter/X OAuth2 + PKCE provider
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## AbstractProvider Base Class
|
|
33
|
+
|
|
34
|
+
All providers extend `AbstractProvider` which implements `ProviderInterface`. The constructor takes a `ProviderConfig`:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface ProviderConfig {
|
|
38
|
+
clientId: string
|
|
39
|
+
clientSecret: string
|
|
40
|
+
redirectUrl: string
|
|
41
|
+
guzzle?: Record<string, any>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Properties and methods on `AbstractProvider`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
abstract class AbstractProvider implements ProviderInterface {
|
|
49
|
+
// Protected state
|
|
50
|
+
protected clientId: string
|
|
51
|
+
protected clientSecret: string
|
|
52
|
+
protected redirectUrl: string
|
|
53
|
+
protected parameters: Record<string, any> = {}
|
|
54
|
+
protected _scopes: string[] = []
|
|
55
|
+
protected scopeSeparator: string = ','
|
|
56
|
+
protected _stateless: boolean = false
|
|
57
|
+
protected _usesPKCE: boolean = false
|
|
58
|
+
protected user: SocialUser | null = null
|
|
59
|
+
|
|
60
|
+
// Abstract methods (each provider implements)
|
|
61
|
+
abstract getAuthUrl(): Promise<string> // public, returns Promise
|
|
62
|
+
protected abstract getTokenUrl(): string // protected
|
|
63
|
+
abstract getAccessToken(code: string): Promise<string>
|
|
64
|
+
abstract getUserByToken(token: string): Promise<SocialUser>
|
|
65
|
+
|
|
66
|
+
// Scope management
|
|
67
|
+
scopes(scopes: string | string[]): this // merge into existing (deduplicates via Set)
|
|
68
|
+
setScopes(scopes: string | string[]): this // replace all scopes (deduplicates via Set)
|
|
69
|
+
getScopes(): string[]
|
|
70
|
+
|
|
71
|
+
// Configuration
|
|
72
|
+
setRedirectUrl(url: string): this
|
|
73
|
+
stateless(): this // sets _stateless = true
|
|
74
|
+
enablePKCE(): this // sets _usesPKCE = true
|
|
75
|
+
with(parameters: Record<string, any>): this // replaces custom query parameters
|
|
76
|
+
|
|
77
|
+
// User retrieval
|
|
78
|
+
async userFromToken(token: string): Promise<SocialUser> // calls getUserByToken, adds token
|
|
79
|
+
|
|
80
|
+
// URL building (protected)
|
|
81
|
+
protected buildAuthUrlFromBase(url: string, state: string | null): string
|
|
82
|
+
protected getCodeFields(state: string | null = null): Record<string, any>
|
|
83
|
+
protected formatScopes(scopes: string[], scopeSeparator: string): string
|
|
84
|
+
|
|
85
|
+
// State helpers (protected)
|
|
86
|
+
protected usesState(): boolean // returns !_stateless
|
|
87
|
+
protected isStateless(): boolean // returns _stateless
|
|
88
|
+
protected getState(): string // 20 random bytes -> 40 hex chars
|
|
89
|
+
|
|
90
|
+
// PKCE helpers (protected)
|
|
91
|
+
protected usesPKCE(): boolean
|
|
92
|
+
protected getCodeVerifier(): string // 48 random bytes -> 96 hex chars
|
|
93
|
+
protected async getCodeChallenge(): Promise<string> // SHA-256 of verifier, base64url encoded
|
|
94
|
+
protected getCodeChallengeMethod(): string // always 'S256'
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `getCodeFields()` method builds the query parameters for the authorization URL:
|
|
99
|
+
- Always includes: `client_id`, `redirect_uri`, `scope` (joined by `scopeSeparator`), `response_type: 'code'`
|
|
100
|
+
- Conditionally adds `state` when `usesState()` is true
|
|
101
|
+
- Conditionally adds `code_challenge` and `code_challenge_method` when `usesPKCE()` is true
|
|
102
|
+
- Merges in any custom `parameters` set via `with()`
|
|
103
|
+
|
|
104
|
+
## Provider Implementations
|
|
105
|
+
|
|
106
|
+
### GitHub Provider
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
class GitHubProvider extends AbstractProvider {
|
|
110
|
+
protected baseUrl = 'https://github.com'
|
|
111
|
+
protected apiUrl = 'https://api.github.com'
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Config source: `config.services.github` (clientId, clientSecret, redirectUrl, scopes)
|
|
116
|
+
- Default scopes: `['read:user', 'user:email']`
|
|
117
|
+
- Auth URL: `https://github.com/login/oauth/authorize` with scopes joined by space
|
|
118
|
+
- Token URL: `https://github.com/login/oauth/access_token` (POST)
|
|
119
|
+
- User API: Fetches `/user` and `/user/emails` in parallel via `Promise.all`
|
|
120
|
+
- Email resolution: Primary email first, then verified, then first available
|
|
121
|
+
- Uses `@stacksjs/api` `fetcher` for HTTP requests
|
|
122
|
+
- Throws `ConfigException` if clientId, clientSecret, or redirectUrl missing
|
|
123
|
+
- GitHub token response: `GitHubTokenResponse { access_token, error?, error_description? }`
|
|
124
|
+
|
|
125
|
+
### Google Provider
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
class GoogleProvider extends AbstractProvider {
|
|
129
|
+
protected baseUrl = 'https://accounts.google.com'
|
|
130
|
+
protected apiUrl = 'https://www.googleapis.com'
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- Config source: `config.services.google`
|
|
135
|
+
- Default scopes: `['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email']`
|
|
136
|
+
- Auth URL: `https://accounts.google.com/o/oauth2/v2/auth` (includes `access_type: 'offline'`, `prompt: 'consent'`)
|
|
137
|
+
- Token URL: `https://accounts.google.com/oauth2/v4/token` (POST with `grant_type: 'authorization_code'`)
|
|
138
|
+
- User API: `https://www.googleapis.com/oauth2/v2/userinfo` with Bearer token
|
|
139
|
+
- Maps `given_name` to `nickname`, `picture` to `avatar`
|
|
140
|
+
- Google user type: `{ id, email, verified_email, name, given_name, family_name, picture, locale }`
|
|
141
|
+
|
|
142
|
+
### Facebook Provider
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
class FacebookProvider extends AbstractProvider {
|
|
146
|
+
protected baseUrl = 'https://www.facebook.com'
|
|
147
|
+
protected apiUrl = 'https://graph.facebook.com'
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Config source: `config.services.facebook`
|
|
152
|
+
- Default scopes: `['email', 'public_profile']`
|
|
153
|
+
- Auth URL: `https://www.facebook.com/v18.0/dialog/oauth` with scopes joined by comma
|
|
154
|
+
- Token URL: `https://graph.facebook.com/v18.0/oauth/access_token` (GET request, not POST)
|
|
155
|
+
- User API: `https://graph.facebook.com/v18.0/me?fields=id,name,email,picture`
|
|
156
|
+
- Token is passed as query parameter `access_token`, not in Authorization header
|
|
157
|
+
- Always sets `nickname: null` (Facebook does not expose usernames)
|
|
158
|
+
- Avatar extracted from `picture.data.url`
|
|
159
|
+
- Error response shape: `{ error: { message, type, code } }` (nested object, unlike other providers)
|
|
160
|
+
|
|
161
|
+
### Twitter/X Provider
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
class TwitterProvider extends AbstractProvider {
|
|
165
|
+
protected baseUrl = 'https://twitter.com'
|
|
166
|
+
protected apiUrl = 'https://api.twitter.com'
|
|
167
|
+
private codeVerifier: string | null = null
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Config source: `config.services.twitter`
|
|
172
|
+
- Default scopes: `['users.read', 'tweet.read']`
|
|
173
|
+
- Uses OAuth 2.0 with PKCE (not OAuth 1.0a)
|
|
174
|
+
- Auth URL: `https://twitter.com/i/oauth2/authorize` with PKCE code challenge
|
|
175
|
+
- Token URL: `https://api.twitter.com/2/oauth2/token` (POST with Basic auth)
|
|
176
|
+
- User API: `https://api.twitter.com/2/users/me?user.fields=profile_image_url` with Bearer token
|
|
177
|
+
- PKCE implementation uses its own `generateCodeVerifier()` and `generateCodeChallenge()` methods (using `node:crypto` `randomBytes` and `createHash`), not the base class PKCE
|
|
178
|
+
- Code verifier: `randomBytes(32)` -> base64 -> alphanumeric only -> max 128 chars
|
|
179
|
+
- Token exchange uses Basic auth: `Buffer.from(clientId:clientSecret).toString('base64')`
|
|
180
|
+
- `getAccessToken()` throws if `codeVerifier` is null -- `getAuthUrl()` must be called first
|
|
181
|
+
- Maps `username` to `nickname`, `profile_image_url` to `avatar`
|
|
182
|
+
|
|
183
|
+
## SocialUser Interface
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface SocialUser {
|
|
187
|
+
id: string
|
|
188
|
+
nickname: string | null
|
|
189
|
+
name: string
|
|
190
|
+
email: string | null
|
|
191
|
+
avatar: string | null
|
|
192
|
+
token: string
|
|
193
|
+
raw?: any // full provider API response
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## ProviderInterface
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface ProviderInterface {
|
|
201
|
+
getAuthUrl: () => Promise<string>
|
|
202
|
+
getAccessToken: (code: string) => Promise<string>
|
|
203
|
+
getUserByToken: (token: string) => Promise<SocialUser>
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Token Class
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
class Token {
|
|
211
|
+
constructor(
|
|
212
|
+
public accessToken: string,
|
|
213
|
+
public refreshToken: string | null = null,
|
|
214
|
+
public expiresIn: number | null = null,
|
|
215
|
+
public approvedScopes: string[] = [],
|
|
216
|
+
) {}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Provider-Specific Types
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// GitHub
|
|
224
|
+
interface GitHubUser { id: number, login: string, name: string | null, avatar_url: string | null, [key: string]: any }
|
|
225
|
+
interface GitHubEmail { email: string, primary: boolean, verified: boolean }
|
|
226
|
+
interface GitHubTokenResponse { access_token: string, error?: string, error_description?: string }
|
|
227
|
+
|
|
228
|
+
// Twitter/X
|
|
229
|
+
interface TwitterUser { id: string, username: string, name: string, email?: string, profile_image_url?: string }
|
|
230
|
+
interface TwitterTokenResponse { access_token: string, token_type: string, expires_in: number, scope: string, error?: string, error_description?: string }
|
|
231
|
+
|
|
232
|
+
// Google (local to google.ts)
|
|
233
|
+
interface GoogleUser { id: string, email: string, verified_email: boolean, name: string, given_name: string, family_name: string, picture: string, locale: string }
|
|
234
|
+
interface GoogleTokenResponse { access_token: string, token_type: string, expires_in: number, error?: string, error_description?: string }
|
|
235
|
+
|
|
236
|
+
// Facebook (local to facebook.ts)
|
|
237
|
+
interface FacebookUser { id: string, email?: string, name: string, picture?: { data: { url: string } } }
|
|
238
|
+
interface FacebookTokenResponse { access_token: string, token_type: string, expires_in: number, error?: { message: string, type: string, code: number } }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Exceptions
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
class InvalidStateException extends Error { name = 'InvalidStateException' }
|
|
245
|
+
class ConfigException extends Error { name = 'ConfigException' }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Providers throw `ConfigException` when `clientId`, `clientSecret`, or `redirectUrl` are missing.
|
|
249
|
+
|
|
250
|
+
## OAuth2 Flow
|
|
251
|
+
|
|
252
|
+
### 1. Get Auth URL
|
|
253
|
+
```typescript
|
|
254
|
+
const github = new GitHubProvider({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
255
|
+
const authUrl = await github.scopes(['user:email', 'read:org']).getAuthUrl()
|
|
256
|
+
// Each provider reads actual config from config.services.<provider>
|
|
257
|
+
// The constructor config is overridden by getConfig() in each provider
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 2. Handle Callback
|
|
261
|
+
```typescript
|
|
262
|
+
const token = await github.getAccessToken(code)
|
|
263
|
+
const user = await github.getUserByToken(token)
|
|
264
|
+
// user: { id, nickname, name, email, avatar, token, raw }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 3. User from Existing Token
|
|
268
|
+
```typescript
|
|
269
|
+
const user = await github.userFromToken(existingToken)
|
|
270
|
+
// Calls getUserByToken internally, adds token to result
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Configuration Source
|
|
274
|
+
|
|
275
|
+
All providers read from the Stacks config object:
|
|
276
|
+
```typescript
|
|
277
|
+
config.services.github // { clientId, clientSecret, redirectUrl, scopes }
|
|
278
|
+
config.services.google // { clientId, clientSecret, redirectUrl, scopes }
|
|
279
|
+
config.services.facebook // { clientId, clientSecret, redirectUrl, scopes }
|
|
280
|
+
config.services.twitter // { clientId, clientSecret, redirectUrl, scopes }
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Each provider's `getConfig()` method reads these and also calls `this.setScopes()` with the configured scopes.
|
|
284
|
+
|
|
285
|
+
## Dependencies
|
|
286
|
+
- `@stacksjs/api` -- `fetcher` used for all HTTP requests (provides `.withHeaders()`, `.get()`, `.post()`)
|
|
287
|
+
- `@stacksjs/config` -- provides `config.services.*` for provider credentials
|
|
288
|
+
- `node:buffer` and `node:crypto` -- used by Twitter provider for PKCE and Basic auth
|
|
289
|
+
|
|
290
|
+
## Gotchas
|
|
291
|
+
- The `ProviderConfig` constructor params are largely ignored -- each provider's `getConfig()` reads from `config.services.*` instead
|
|
292
|
+
- `getAuthUrl()` returns `Promise<string>` (not `string`) -- it is async
|
|
293
|
+
- `getTokenUrl()` is `protected` -- not part of the public `ProviderInterface`
|
|
294
|
+
- GitHub joins scopes with space, Facebook with comma, Google with space, Twitter with space
|
|
295
|
+
- Facebook uses GET for token exchange; all others use POST
|
|
296
|
+
- Facebook `nickname` is always `null`
|
|
297
|
+
- Twitter requires `getAuthUrl()` before `getAccessToken()` because the code verifier is stored as instance state
|
|
298
|
+
- Twitter uses its own PKCE implementation via `node:crypto`, not the base class `getCodeVerifier()`/`getCodeChallenge()`
|
|
299
|
+
- Google auth URL includes `access_type: 'offline'` and `prompt: 'consent'` for refresh token support
|
|
300
|
+
- The base class state token is 40 hex chars (20 bytes), not the 20 chars stated in some docs
|
|
301
|
+
- `userFromToken()` spreads the user object and adds/overwrites the `token` field
|
|
302
|
+
- All providers include the full API response in `SocialUser.raw`
|
|
303
|
+
- Errors from GitHub/Google/Twitter are flat objects with `error` + `error_description`; Facebook nests it under `error.message`
|