@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,738 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stacks-api
|
|
3
|
+
description: Use when building, modifying, or debugging API endpoints in a Stacks application — defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
|
+
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stacks API
|
|
10
|
+
|
|
11
|
+
The Stacks API system comprises two packages: the `@stacksjs/api` utilities package (HTTP client, API resources, OpenAPI generation) and the `stacks-api` server (Bun HTTP server with hot-reload). Routing is delegated to `@stacksjs/router` which wraps `@stacksjs/bun-router`.
|
|
12
|
+
|
|
13
|
+
## Key Paths
|
|
14
|
+
- Core utilities (package): `storage/framework/core/api/src/`
|
|
15
|
+
- API server: `storage/framework/api/`
|
|
16
|
+
- Router package: `storage/framework/core/router/src/`
|
|
17
|
+
- Route definitions: `routes/`
|
|
18
|
+
- Route registry: `app/Routes.ts` (re-exports `storage/framework/defaults/app/Routes.ts`)
|
|
19
|
+
- Port configuration: `config/ports.ts`
|
|
20
|
+
- Generated OpenAPI spec: `storage/framework/api/openapi.json`
|
|
21
|
+
- Generated API types: `storage/framework/api/api-types.ts`
|
|
22
|
+
- Actions directory: `app/Actions/`
|
|
23
|
+
- Controllers directory: `app/Controllers/`
|
|
24
|
+
- Middleware directory: `app/Middleware/`
|
|
25
|
+
- Middleware alias map: `app/Middleware.ts`
|
|
26
|
+
- Package: `@stacksjs/api`
|
|
27
|
+
|
|
28
|
+
## Source Files
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
core/api/src/
|
|
32
|
+
├── index.ts # Re-exports fetcher, generate-openapi, resource
|
|
33
|
+
├── fetcher.ts # Fetcher HTTP client class (fluent API)
|
|
34
|
+
├── generate-openapi.ts # OpenAPI 3.0 spec generator from route definitions
|
|
35
|
+
├── resource.ts # JsonResource, ResourceCollection, PaginatedResourceCollection
|
|
36
|
+
└── types.ts # FetcherResponse, QueryParams, BodyData, FileAttachment
|
|
37
|
+
|
|
38
|
+
framework/api/
|
|
39
|
+
├── dev.ts # API server entry point (Bun --hot, file watchers)
|
|
40
|
+
├── build.ts # Bun.build config for production
|
|
41
|
+
├── api-types.ts # Auto-generated TypeScript types from OpenAPI spec
|
|
42
|
+
├── openapi.json # Generated OpenAPI 3.0 specification
|
|
43
|
+
├── package.json # Dependencies: @stacksjs/router, queue, storage, logging
|
|
44
|
+
└── tsconfig.json
|
|
45
|
+
|
|
46
|
+
core/router/src/
|
|
47
|
+
├── index.ts # Re-exports bun-router + stacks-specific additions
|
|
48
|
+
├── stacks-router.ts # Route object, serve(), url(), action/controller resolution
|
|
49
|
+
├── response.ts # Response factory (json, text, html, redirect, etc.)
|
|
50
|
+
├── middleware.ts # Middleware class definition
|
|
51
|
+
├── request-context.ts # AsyncLocalStorage-based request context (request proxy)
|
|
52
|
+
├── route-loader.ts # Loads route files from registry with auto-prefixing
|
|
53
|
+
├── error-handler.ts # Ignition-style error pages, query tracking
|
|
54
|
+
└── action-paths.ts # StacksActionPath type definitions
|
|
55
|
+
|
|
56
|
+
routes/
|
|
57
|
+
├── api.ts # Main API routes (auto-prefixed with /api; see #1835)
|
|
58
|
+
├── v1.ts # Versioned routes (loaded at /v1/*)
|
|
59
|
+
├── buddy.ts # Buddy CLI routes (loaded at /buddy/*)
|
|
60
|
+
└── users.ts # Example user routes (commented out)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API Server (dev.ts)
|
|
64
|
+
|
|
65
|
+
The API server runs on Bun's HTTP server with hot-reload (`bun --hot run dev.ts`).
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { serve } from '@stacksjs/router'
|
|
69
|
+
|
|
70
|
+
serve({
|
|
71
|
+
port: ports.api || 3008, // Configurable via config/ports.ts or PORT_API env
|
|
72
|
+
timezone: app.timezone || 'UTC',
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Hot Reload Watchers
|
|
77
|
+
The dev server watches these directories for changes and auto-reloads:
|
|
78
|
+
- `storage/framework/core/*/src/` -- rebuilds core packages on change
|
|
79
|
+
- `routes/` -- detects route file changes
|
|
80
|
+
- `app/Actions/` -- invalidates module cache, hot-reloads actions
|
|
81
|
+
- `app/Controllers/` -- invalidates module cache, hot-reloads controllers
|
|
82
|
+
- `app/Middleware/` -- invalidates module cache, hot-reloads middleware
|
|
83
|
+
- `app/Models/` (user models) -- triggers `buddy generate:model-files`
|
|
84
|
+
|
|
85
|
+
Production build: `bun build.ts` bundles `dev.ts` to `dist/dev.js`.
|
|
86
|
+
|
|
87
|
+
## Route Definitions
|
|
88
|
+
|
|
89
|
+
### Route Registry (app/Routes.ts)
|
|
90
|
+
|
|
91
|
+
Routes are organized in `routes/*.ts` files and registered in `app/Routes.ts`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export interface RouteDefinition {
|
|
95
|
+
path: string // Route file path relative to routes/
|
|
96
|
+
prefix?: string // URL prefix (overrides key-based prefix)
|
|
97
|
+
middleware?: string | string[] // Middleware for all routes in file
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type RouteRegistry = Record<string, string | RouteDefinition>
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
'api': 'api', // Auto-prefixed with /api (see #1835)
|
|
104
|
+
'v1': { path: 'v1', prefix: 'v1' }, // routes at /v1/*
|
|
105
|
+
// 'admin': { path: 'admin', middleware: ['auth'] },
|
|
106
|
+
} satisfies RouteRegistry
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`'web'` is the only key that loads at root `/` with no prefix; all other keys (including `'api'`) auto-prefix with `/<key>`. The `'api'` auto-prefix lines up with the rpx proxy forward path so a user route registered as `route.get('/cart/add', ...)` in `routes/api.ts` resolves at `https://<domain>/api/cart/add` through the dev proxy.
|
|
110
|
+
|
|
111
|
+
### Route Methods
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { route, response } from '@stacksjs/router'
|
|
115
|
+
|
|
116
|
+
// Inline handler
|
|
117
|
+
route.get('/hello', () => response.json({ message: 'Hello' }))
|
|
118
|
+
route.post('/users', () => response.created({ id: 1 }))
|
|
119
|
+
|
|
120
|
+
// String-based handler (Action)
|
|
121
|
+
route.get('/subscribers', 'Actions/SubscriberIndexOrmAction')
|
|
122
|
+
route.post('/login', 'Actions/Auth/LoginAction')
|
|
123
|
+
|
|
124
|
+
// String-based handler (Controller@method)
|
|
125
|
+
route.get('/coming-soon', 'Controllers/ComingSoonController@index')
|
|
126
|
+
route.get('/stats', 'Controllers/QueryController@getStats')
|
|
127
|
+
|
|
128
|
+
// Route with path parameters
|
|
129
|
+
route.get('/foo/bar/{id}', () => response.text('hello'))
|
|
130
|
+
route.get('/users/{id}/posts/{postId}', handler)
|
|
131
|
+
|
|
132
|
+
// Route chaining: middleware + naming
|
|
133
|
+
route.get('/tokens', 'Actions/Auth/ListTokensAction').middleware('auth')
|
|
134
|
+
route.post('/email/subscribe', 'Actions/SubscriberEmailAction').name('email.subscribe')
|
|
135
|
+
route.delete('/tokens/{id}', 'Actions/Auth/RevokeTokenAction').middleware('auth').name('token.revoke')
|
|
136
|
+
|
|
137
|
+
// Route groups (prefix + middleware)
|
|
138
|
+
route.group({ prefix: '/auth' }, () => {
|
|
139
|
+
route.post('/refresh', 'Actions/Auth/RefreshTokenAction')
|
|
140
|
+
route.get('/tokens', 'Actions/Auth/ListTokensAction').middleware('auth')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
route.group({ middleware: 'auth' }, () => {
|
|
144
|
+
route.get('/me', 'Actions/Auth/AuthUserAction')
|
|
145
|
+
route.post('/logout', 'Actions/Auth/LogoutAction')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Health check (adds GET /health)
|
|
149
|
+
route.health()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Named Routes & URL Generation
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { url } from '@stacksjs/router'
|
|
156
|
+
|
|
157
|
+
// Define named routes
|
|
158
|
+
route.get('/api/email/unsubscribe', 'Actions/UnsubscribeAction').name('email.unsubscribe')
|
|
159
|
+
route.get('/users/{id}/posts/{postId}', handler).name('user.post')
|
|
160
|
+
|
|
161
|
+
// Generate URLs
|
|
162
|
+
url('email.unsubscribe', { token: 'abc-123' })
|
|
163
|
+
// => https://stacksjs.com/api/email/unsubscribe?token=abc-123
|
|
164
|
+
|
|
165
|
+
url('user.post', { id: 42, postId: 7 })
|
|
166
|
+
// => https://stacksjs.com/users/42/posts/7
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Path parameters (`{id}`) are substituted; remaining params become query string. Uses `APP_URL` env var as base (defaults to `https://localhost`).
|
|
170
|
+
|
|
171
|
+
## Action Resolution
|
|
172
|
+
|
|
173
|
+
String-based route handlers are resolved at runtime:
|
|
174
|
+
|
|
175
|
+
1. **Actions** (`'Actions/MyAction'`): checks `app/Actions/MyAction.ts` first, falls back to `storage/framework/defaults/app/Actions/MyAction.ts`
|
|
176
|
+
2. **ORM Actions** (`'Actions/UserIndexOrmAction'`): loaded from `storage/framework/actions/src/`
|
|
177
|
+
3. **Controllers** (`'Controllers/MyController@method'`): checks `app/Controllers/` first, falls back to defaults
|
|
178
|
+
|
|
179
|
+
Action modules must export a default object with a `handle(req: EnhancedRequest)` method:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// app/Actions/MyAction.ts
|
|
183
|
+
export default {
|
|
184
|
+
handle(request: EnhancedRequest): Response {
|
|
185
|
+
const name = request.get('name', 'world')
|
|
186
|
+
return Response.json({ message: `Hello ${name}` })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Actions can also define `validations` for automatic input validation:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
export default {
|
|
195
|
+
validations: {
|
|
196
|
+
email: {
|
|
197
|
+
rule: { validate: (value) => ({ valid: typeof value === 'string' && value.includes('@') }) },
|
|
198
|
+
message: 'A valid email is required',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
handle(request: EnhancedRequest): Response {
|
|
202
|
+
// Request is validated before reaching handle()
|
|
203
|
+
return Response.json({ success: true })
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Validation failures return a `422` response:
|
|
209
|
+
```json
|
|
210
|
+
{ "error": "Validation failed", "errors": { "email": ["A valid email is required"] } }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Response Factory
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { response } from '@stacksjs/router'
|
|
217
|
+
|
|
218
|
+
// JSON (200)
|
|
219
|
+
response.json({ message: 'OK' })
|
|
220
|
+
response.json(data, { status: 200, headers: { 'X-Custom': 'value' }, pretty: true })
|
|
221
|
+
|
|
222
|
+
// Status shortcuts
|
|
223
|
+
response.created({ id: 1 }) // 201
|
|
224
|
+
response.noContent() // 204
|
|
225
|
+
response.badRequest({ error: 'Bad' }) // 400
|
|
226
|
+
response.unauthorized() // 401 (default: { error: 'Unauthorized' })
|
|
227
|
+
response.forbidden() // 403 (default: { error: 'Forbidden' })
|
|
228
|
+
response.notFound() // 404 (default: { error: 'Not Found' })
|
|
229
|
+
response.error() // 500 (default: { error: 'Internal Server Error' })
|
|
230
|
+
|
|
231
|
+
// Other content types
|
|
232
|
+
response.text('Hello', { status: 200 })
|
|
233
|
+
response.html('<h1>Hello</h1>')
|
|
234
|
+
response.redirect('/new-url', 302) // 301 | 302 | 303 | 307 | 308
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
All methods return a standard `Response` object.
|
|
238
|
+
|
|
239
|
+
## Enhanced Request (EnhancedRequest)
|
|
240
|
+
|
|
241
|
+
Route handlers receive an `EnhancedRequest` (extends `Request`) with Laravel-style helper methods:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Input access (merges query params + JSON body + form body + route params)
|
|
245
|
+
request.get<T>(key: string, defaultValue?: T): T
|
|
246
|
+
request.input<T>(key: string, defaultValue?: T): T
|
|
247
|
+
request.all(): Record<string, unknown>
|
|
248
|
+
request.only<T>(keys: string[]): T
|
|
249
|
+
request.except<T>(keys: string[]): T
|
|
250
|
+
|
|
251
|
+
// Presence checks
|
|
252
|
+
request.has(key: string | string[]): boolean
|
|
253
|
+
request.hasAny(keys: string[]): boolean
|
|
254
|
+
request.filled(key: string | string[]): boolean
|
|
255
|
+
request.missing(key: string | string[]): boolean
|
|
256
|
+
|
|
257
|
+
// Typed getters
|
|
258
|
+
request.string(key: string, defaultValue?: string): string
|
|
259
|
+
request.integer(key: string, defaultValue?: number): number
|
|
260
|
+
request.float(key: string, defaultValue?: number): number
|
|
261
|
+
request.boolean(key: string, defaultValue?: boolean): boolean
|
|
262
|
+
request.array<T>(key: string): T[]
|
|
263
|
+
|
|
264
|
+
// File handling (returns UploadedFile with store/storeAs methods)
|
|
265
|
+
request.file(key: string): UploadedFile | null
|
|
266
|
+
request.getFiles(key: string): UploadedFile[]
|
|
267
|
+
request.hasFile(key: string): boolean
|
|
268
|
+
request.allFiles(): Record<string, UploadedFile | UploadedFile[]>
|
|
269
|
+
|
|
270
|
+
// Authentication (set by auth middleware)
|
|
271
|
+
await request.user(): Promise<any>
|
|
272
|
+
await request.userToken(): Promise<any>
|
|
273
|
+
await request.tokenCan(ability: string): Promise<boolean>
|
|
274
|
+
await request.tokenCant(ability: string): Promise<boolean>
|
|
275
|
+
request.bearerToken(): string | null
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Request Context (AsyncLocalStorage)
|
|
279
|
+
|
|
280
|
+
Access the current request from anywhere using the `request` proxy:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { request, getCurrentRequest, runWithRequest, setCurrentRequest } from '@stacksjs/router'
|
|
284
|
+
|
|
285
|
+
// The `request` export is a Proxy that reads from AsyncLocalStorage
|
|
286
|
+
const token = request.bearerToken()
|
|
287
|
+
const user = await request.user()
|
|
288
|
+
|
|
289
|
+
// Manually run code in a request context
|
|
290
|
+
runWithRequest(enhancedReq, () => {
|
|
291
|
+
// request proxy works here
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Middleware
|
|
296
|
+
|
|
297
|
+
### Defining Middleware
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// app/Middleware/Auth.ts
|
|
301
|
+
import { Middleware } from '@stacksjs/router'
|
|
302
|
+
|
|
303
|
+
export default new Middleware({
|
|
304
|
+
name: 'Auth',
|
|
305
|
+
priority: 1, // Lower numbers run first (default: 10)
|
|
306
|
+
async handle(request) {
|
|
307
|
+
const token = request.bearerToken()
|
|
308
|
+
if (!token) {
|
|
309
|
+
const error = new Error('Unauthorized') as Error & { statusCode: number }
|
|
310
|
+
error.statusCode = 401
|
|
311
|
+
throw error
|
|
312
|
+
}
|
|
313
|
+
// Set authenticated user on request
|
|
314
|
+
;(request as any)._authenticatedUser = user
|
|
315
|
+
},
|
|
316
|
+
})
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Middleware Alias Map
|
|
320
|
+
|
|
321
|
+
Define shorthand names in `app/Middleware.ts`:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
export default {
|
|
325
|
+
auth: 'Auth', // maps to app/Middleware/Auth.ts
|
|
326
|
+
verified: 'EnsureEmailIsVerified',
|
|
327
|
+
abilities: 'CheckAbilities', // supports params: 'abilities:read,write'
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Middleware Resolution Order
|
|
332
|
+
1. Parse alias via `app/Middleware.ts` (e.g., `'auth'` -> `'Auth'`)
|
|
333
|
+
2. Load from `app/Middleware/Auth.ts` (user override)
|
|
334
|
+
3. Fall back to `storage/framework/defaults/app/Middleware/Auth.ts`
|
|
335
|
+
|
|
336
|
+
### Middleware with Parameters
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
route.get('/admin', handler).middleware('abilities:admin,write')
|
|
340
|
+
// Params accessible in middleware via: (request as any)._middlewareParams.abilities === 'admin,write'
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Fetcher (HTTP Client)
|
|
344
|
+
|
|
345
|
+
A fluent HTTP client for making API requests:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { fetcher } from '@stacksjs/api'
|
|
349
|
+
|
|
350
|
+
// GET request
|
|
351
|
+
const res = await fetcher.get<UserResponse>('/api/users')
|
|
352
|
+
res.data // UserResponse
|
|
353
|
+
res.status // 200
|
|
354
|
+
res.isOk // true
|
|
355
|
+
res.ok() // true (status === 200)
|
|
356
|
+
res.notFound() // false
|
|
357
|
+
|
|
358
|
+
// POST with JSON body
|
|
359
|
+
const res = await fetcher
|
|
360
|
+
.withBody({ name: 'John', email: 'john@example.com' })
|
|
361
|
+
.post<User>('/api/users')
|
|
362
|
+
|
|
363
|
+
// PUT/PATCH/DELETE
|
|
364
|
+
await fetcher.withBody(data).put<User>('/api/users/1')
|
|
365
|
+
await fetcher.withBody(data).patch<User>('/api/users/1')
|
|
366
|
+
await fetcher.delete('/api/users/1')
|
|
367
|
+
|
|
368
|
+
// Query parameters
|
|
369
|
+
await fetcher.withQueryParams({ page: 1, limit: 25 }).get('/api/users')
|
|
370
|
+
|
|
371
|
+
// Custom headers
|
|
372
|
+
await fetcher.withHeaders({ 'X-Custom': 'value' }).get('/api/users')
|
|
373
|
+
|
|
374
|
+
// Authentication
|
|
375
|
+
await fetcher.withToken('bearer-token-here').get('/api/me')
|
|
376
|
+
await fetcher.withBasicAuth('user', 'pass').get('/api/protected')
|
|
377
|
+
await fetcher.withDigestAuth('user', 'pass').get('/api/digest-protected')
|
|
378
|
+
|
|
379
|
+
// Form data
|
|
380
|
+
await fetcher.asForm().withBody({ email: 'test@test.com' }).post('/subscribe')
|
|
381
|
+
|
|
382
|
+
// File uploads (multipart/form-data)
|
|
383
|
+
await fetcher
|
|
384
|
+
.attach('avatar', file, 'photo.jpg')
|
|
385
|
+
.withBody({ name: 'John' })
|
|
386
|
+
.post('/api/upload')
|
|
387
|
+
|
|
388
|
+
// Accept header
|
|
389
|
+
await fetcher.acceptJson().get('/api/data')
|
|
390
|
+
await fetcher.accept('text/xml').get('/api/feed')
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### FetcherResponse<T>
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
interface FetcherResponse<T = any> {
|
|
397
|
+
data: T
|
|
398
|
+
status: number
|
|
399
|
+
headers: Headers
|
|
400
|
+
isOk: boolean
|
|
401
|
+
|
|
402
|
+
// Status checkers
|
|
403
|
+
ok(): boolean // 200
|
|
404
|
+
created(): boolean // 201
|
|
405
|
+
accepted(): boolean // 202
|
|
406
|
+
noContent(): boolean // 204
|
|
407
|
+
movedPermanently(): boolean // 301
|
|
408
|
+
found(): boolean // 302
|
|
409
|
+
badRequest(): boolean // 400
|
|
410
|
+
unauthorized(): boolean // 401
|
|
411
|
+
paymentRequired(): boolean // 402
|
|
412
|
+
forbidden(): boolean // 403
|
|
413
|
+
notFound(): boolean // 404
|
|
414
|
+
requestTimeout(): boolean // 408
|
|
415
|
+
conflict(): boolean // 409
|
|
416
|
+
unprocessableEntity(): boolean // 422
|
|
417
|
+
tooManyRequests(): boolean // 429
|
|
418
|
+
serverError(): boolean // 500
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Fetcher Types
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
type QueryParams = Record<string, string | number | boolean | null | undefined>
|
|
426
|
+
type BodyData = Record<string, any>
|
|
427
|
+
|
|
428
|
+
interface FileAttachment {
|
|
429
|
+
name: string
|
|
430
|
+
content: Blob
|
|
431
|
+
filename?: string
|
|
432
|
+
headers?: Record<string, string>
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## API Resources (Laravel-style)
|
|
437
|
+
|
|
438
|
+
### JsonResource
|
|
439
|
+
|
|
440
|
+
Transform models into JSON responses with conditional field inclusion:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { JsonResource } from '@stacksjs/api'
|
|
444
|
+
|
|
445
|
+
class UserResource extends JsonResource<User> {
|
|
446
|
+
toArray() {
|
|
447
|
+
return {
|
|
448
|
+
id: this.resource.id,
|
|
449
|
+
name: this.resource.name,
|
|
450
|
+
email: this.resource.email,
|
|
451
|
+
// Conditional inclusion
|
|
452
|
+
secret: this.when(isAdmin, this.resource.secret),
|
|
453
|
+
avatar: this.whenNotNull(this.resource.avatar),
|
|
454
|
+
// Relationship loading
|
|
455
|
+
posts: this.whenLoaded('posts', () => PostResource.collection(this.resource.posts)),
|
|
456
|
+
posts_count: this.whenCounted('posts'),
|
|
457
|
+
// Conditional merge
|
|
458
|
+
...this.mergeWhen(isAdmin, { admin_notes: this.resource.adminNotes }),
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Single resource
|
|
464
|
+
const response = new UserResource(user).toResponse()
|
|
465
|
+
// => { data: { id: 1, name: 'John', email: 'john@...' } }
|
|
466
|
+
|
|
467
|
+
// With additional data
|
|
468
|
+
new UserResource(user).withAdditional({ meta: { version: 1 } }).toResponse()
|
|
469
|
+
|
|
470
|
+
// JSON string
|
|
471
|
+
new UserResource(user).toJson()
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### JsonResource Methods
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
abstract class JsonResource<T> {
|
|
478
|
+
resource: T
|
|
479
|
+
additional: Record<string, any>
|
|
480
|
+
static wrap: string | null = 'data' // Wrap key, null to disable
|
|
481
|
+
|
|
482
|
+
abstract toArray(request?: Request): Record<string, any>
|
|
483
|
+
|
|
484
|
+
// Instance methods
|
|
485
|
+
withRequest(request: Request): this
|
|
486
|
+
withAdditional(data: Record<string, any>): this
|
|
487
|
+
resolve(request?: Request): Record<string, any>
|
|
488
|
+
toResponse(request?: Request): Record<string, any>
|
|
489
|
+
toJson(request?: Request): string
|
|
490
|
+
|
|
491
|
+
// Static methods
|
|
492
|
+
static collection<T, R>(resources: T[]): ResourceCollection<T, R>
|
|
493
|
+
static withoutWrapping(): void // Set wrap = null for this class
|
|
494
|
+
static wrapWith(key: string): void // Set custom wrap key
|
|
495
|
+
|
|
496
|
+
// Conditional helpers (protected, use inside toArray)
|
|
497
|
+
protected when<V>(condition: boolean | (() => boolean), value: V | (() => V), defaultValue?: any): V | MissingValue
|
|
498
|
+
protected whenNotNull<V>(value: V | null | undefined, transform?: (v: V) => any): any | MissingValue
|
|
499
|
+
protected whenLoaded<V>(relationship: string, value?: V | (() => V), defaultValue?: any): V | any[] | MissingValue
|
|
500
|
+
protected whenCounted(relationship: string, defaultValue?: number): number | MissingValue
|
|
501
|
+
protected merge(data: Record<string, any>): MergeValue
|
|
502
|
+
protected mergeWhen(condition: boolean | (() => boolean), data: Record<string, any> | (() => Record<string, any>)): MergeValue | MissingValue
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### ResourceCollection
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
class ResourceCollection<T, R extends JsonResource<T>> {
|
|
510
|
+
resources: T[]
|
|
511
|
+
static wrap: string | null = 'data'
|
|
512
|
+
|
|
513
|
+
withRequest(request: Request): this
|
|
514
|
+
withAdditional(data: Record<string, any>): this
|
|
515
|
+
resolve(request?: Request): Record<string, any>[]
|
|
516
|
+
toResponse(request?: Request): Record<string, any>
|
|
517
|
+
toJson(request?: Request): string
|
|
518
|
+
count(): number
|
|
519
|
+
isEmpty(): boolean
|
|
520
|
+
isNotEmpty(): boolean
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Usage
|
|
524
|
+
const collection = UserResource.collection(users)
|
|
525
|
+
collection.toResponse()
|
|
526
|
+
// => { data: [{ id: 1, ... }, { id: 2, ... }] }
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### PaginatedResourceCollection
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
class PaginatedResourceCollection<T, R extends JsonResource<T>> extends ResourceCollection<T, R> {
|
|
533
|
+
meta: PaginationMeta
|
|
534
|
+
links: PaginationLinks
|
|
535
|
+
|
|
536
|
+
// Create from raw pagination data
|
|
537
|
+
static fromPagination<T, R>(
|
|
538
|
+
data: T[],
|
|
539
|
+
ResourceClass: new (resource: T) => R,
|
|
540
|
+
options: { currentPage: number, perPage: number, total: number, baseUrl?: string },
|
|
541
|
+
): PaginatedResourceCollection<T, R>
|
|
542
|
+
|
|
543
|
+
toResponse(): Record<string, any>
|
|
544
|
+
// => { data: [...], meta: { current_page, from, last_page, per_page, to, total }, links: { first, last, prev, next } }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
interface PaginationMeta {
|
|
548
|
+
current_page: number
|
|
549
|
+
from: number | null
|
|
550
|
+
last_page: number
|
|
551
|
+
per_page: number
|
|
552
|
+
to: number | null
|
|
553
|
+
total: number
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
interface PaginationLinks {
|
|
557
|
+
first: string | null
|
|
558
|
+
last: string | null
|
|
559
|
+
prev: string | null
|
|
560
|
+
next: string | null
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Anonymous Resources
|
|
565
|
+
|
|
566
|
+
Quick inline transformations without defining a class:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import { resource, collection } from '@stacksjs/api'
|
|
570
|
+
|
|
571
|
+
// Single resource
|
|
572
|
+
resource(user, (u) => ({ id: u.id, name: u.name })).toResponse()
|
|
573
|
+
// => { data: { id: 1, name: 'John' } }
|
|
574
|
+
|
|
575
|
+
// Collection
|
|
576
|
+
collection(users, (u) => ({ id: u.id, name: u.name })).toResponse()
|
|
577
|
+
// => { data: [{ id: 1, name: 'John' }, ...] }
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Conditional Value Types
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
class MissingValue {
|
|
584
|
+
static instance: MissingValue
|
|
585
|
+
isMissing(): boolean
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
class MergeValue {
|
|
589
|
+
constructor(data: Record<string, any>)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
class ConditionalValue {
|
|
593
|
+
constructor(condition: boolean | (() => boolean), value: any, defaultValue?: any)
|
|
594
|
+
resolve(): any
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`MissingValue` entries are stripped from the final output by `filterAndResolve()` (max depth: 20).
|
|
599
|
+
|
|
600
|
+
## OpenAPI Generation
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
import { generateOpenApi } from '@stacksjs/api'
|
|
604
|
+
|
|
605
|
+
await generateOpenApi()
|
|
606
|
+
// Reads all registered routes via `route.routes()`
|
|
607
|
+
// Writes OpenAPI 3.0 spec to storage/framework/api/openapi.json
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
The generated spec includes:
|
|
611
|
+
- Path definitions from route URLs
|
|
612
|
+
- HTTP methods from route registrations
|
|
613
|
+
- Path parameters extracted from `{param}` placeholders
|
|
614
|
+
- Response/request schemas if defined on routes (`route.responseSchema`, `route.requestSchema`)
|
|
615
|
+
- Operation IDs from route callbacks (action paths)
|
|
616
|
+
|
|
617
|
+
TypeScript types can be generated from the spec:
|
|
618
|
+
```bash
|
|
619
|
+
# Defined in package.json scripts
|
|
620
|
+
bun run generate-types
|
|
621
|
+
# Runs: open-api ./../../api/openapi.json --output ./../../api/api-types.ts
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## Error Handling
|
|
625
|
+
|
|
626
|
+
### Error Response Structure
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
interface ErrorResponseBody {
|
|
630
|
+
error: string
|
|
631
|
+
message: string
|
|
632
|
+
status: number
|
|
633
|
+
timestamp: string
|
|
634
|
+
details?: Record<string, unknown>
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Development vs Production
|
|
639
|
+
- **Development**: Ignition-style HTML error pages with full stack traces, database queries, and request context
|
|
640
|
+
- **Production**: Simple JSON or HTML error responses (no stack traces)
|
|
641
|
+
- Detection: `APP_ENV !== 'production' && NODE_ENV !== 'production'`
|
|
642
|
+
- API requests (Accept: `application/json`): always get JSON error responses
|
|
643
|
+
|
|
644
|
+
### Error Functions
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
import {
|
|
648
|
+
createErrorResponse,
|
|
649
|
+
createMiddlewareErrorResponse,
|
|
650
|
+
createValidationErrorResponse,
|
|
651
|
+
createNotFoundResponse,
|
|
652
|
+
trackQuery,
|
|
653
|
+
clearTrackedQueries,
|
|
654
|
+
} from '@stacksjs/router'
|
|
655
|
+
|
|
656
|
+
// Track queries for error context (circular buffer, max 50)
|
|
657
|
+
trackQuery('SELECT * FROM users', 12.5, 'sqlite')
|
|
658
|
+
|
|
659
|
+
// Create error responses
|
|
660
|
+
await createErrorResponse(error, request, { status: 500, handlerPath: 'Actions/MyAction' })
|
|
661
|
+
await createMiddlewareErrorResponse(error, request) // Uses error.statusCode
|
|
662
|
+
createValidationErrorResponse({ email: ['Required'] }, request) // 422
|
|
663
|
+
await createNotFoundResponse('/missing-path', request) // 404
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Sensitive Data Sanitization
|
|
667
|
+
Error pages automatically redact fields matching these patterns: `password`, `secret`, `token`, `api_key`, `authorization`, `credential`, `cookie`, `session_id`, and others.
|
|
668
|
+
|
|
669
|
+
## Port Configuration (config/ports.ts)
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
{
|
|
673
|
+
frontend: env.PORT ?? 3000,
|
|
674
|
+
backend: env.PORT_BACKEND ?? 3001,
|
|
675
|
+
admin: env.PORT_ADMIN ?? 3002,
|
|
676
|
+
api: env.PORT_API ?? 3008,
|
|
677
|
+
// ... other services
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
## Route Groups in routes/api.ts
|
|
682
|
+
|
|
683
|
+
The main `routes/api.ts` file defines route groups for major feature areas:
|
|
684
|
+
|
|
685
|
+
| Prefix | Feature | Actions Prefix |
|
|
686
|
+
|--------|---------|---------------|
|
|
687
|
+
| `/auth` | Authentication & tokens | `Actions/Auth/` |
|
|
688
|
+
| `/password` | Password reset flow | `Actions/Password/` |
|
|
689
|
+
| `/payments` | Stripe payment integration | `Actions/Payment/` |
|
|
690
|
+
| `/commerce` | E-commerce (products, orders, customers, coupons, gift cards, tax rates, reviews, receipts, POS, waitlists) | `Actions/Commerce/` |
|
|
691
|
+
| `/shipping` | Shipping methods, rates, zones, delivery routes, drivers, digital delivery, license keys | `Actions/Commerce/Shipping/` |
|
|
692
|
+
| `/cms` | Blog posts, authors, categories, tags, comments, pages | `Actions/Cms/` |
|
|
693
|
+
| `/blog` | Public blog endpoints (read-only) | `Actions/Cms/` |
|
|
694
|
+
| `/monitoring` | Error tracking & resolution | `Actions/Monitoring/` |
|
|
695
|
+
| `/dashboard` | Dashboard stats & health | `Actions/Dashboard/` |
|
|
696
|
+
| `/queries` | Query log dashboard | `Controllers/QueryController@` |
|
|
697
|
+
| `/queues` | Queue management | `Actions/Queue/` |
|
|
698
|
+
| `/realtime` | WebSocket management | `Actions/Realtime/` |
|
|
699
|
+
| `/voide` | Voice AI code assistant | `Actions/Buddy/` |
|
|
700
|
+
|
|
701
|
+
## ORM-Generated CRUD Routes
|
|
702
|
+
|
|
703
|
+
Models with `traits.useApi` automatically get CRUD routes via ORM actions:
|
|
704
|
+
|
|
705
|
+
| Method | Path | Action | Status |
|
|
706
|
+
|--------|------|--------|--------|
|
|
707
|
+
| GET | `/api/{model}` | `{Model}IndexOrmAction` | 200 |
|
|
708
|
+
| POST | `/api/{model}` | `{Model}StoreOrmAction` | 201 |
|
|
709
|
+
| GET | `/api/{model}/{id}` | `{Model}ShowOrmAction` | 200 |
|
|
710
|
+
| PATCH | `/api/{model}/{id}` | `{Model}UpdateOrmAction` | 202 |
|
|
711
|
+
| DELETE | `/api/{model}/{id}` | `{Model}DestroyOrmAction` | 204 |
|
|
712
|
+
|
|
713
|
+
## CLI Commands
|
|
714
|
+
|
|
715
|
+
- `buddy dev` -- starts the API server with hot-reload
|
|
716
|
+
- `buddy generate:api-types` -- regenerates API TypeScript types from OpenAPI spec
|
|
717
|
+
|
|
718
|
+
## Gotchas
|
|
719
|
+
|
|
720
|
+
- The `fetcher` is a **singleton instance** -- state (headers, body, query params) is reset after each request via `resetState()`, but calling chain methods without executing a request will accumulate state
|
|
721
|
+
- `fetcher.withDigestAuth()` uses SHA-256 internally despite the method name `generateMD5` -- this is a misnomer in the source code
|
|
722
|
+
- `fetcher` always parses responses as JSON (`response.json()`) -- it will throw if the response is not valid JSON
|
|
723
|
+
- Only the `'web'` route file key loads at root `/` with no prefix; all other keys (including `'api'`) auto-prefix with `/<key>`. The `'api'` auto-prefix was added in stacksjs/stacks#1835 so user routes line up with the rpx proxy forward path
|
|
724
|
+
- The `route` object is a global singleton from `stacks-router.ts` -- all route definitions across files share the same router instance
|
|
725
|
+
- String-based handlers (`'Actions/MyAction'`) are resolved lazily at request time, not at registration time -- import errors surface only when the route is hit
|
|
726
|
+
- Actions must export `default` with a `handle()` method; Controllers must export `default` as a class with the specified method (defaults to `index`)
|
|
727
|
+
- The `request` proxy from `@stacksjs/router` returns safe defaults (null, undefined, empty) when accessed outside of a request context -- it warns in non-production
|
|
728
|
+
- `JsonResource.filterAndResolve()` has a max recursion depth of 20 to prevent stack overflow on circular references
|
|
729
|
+
- `ResourceCollection.toResponse()` and `JsonResource.toResponse()` both wrap data in `{ data: ... }` by default -- call `MyResource.withoutWrapping()` to disable
|
|
730
|
+
- The error handler tracks up to 50 recent queries in a circular buffer -- these are cleared after each successful response
|
|
731
|
+
- Middleware runs in registration order (not by priority) within a single route -- the `priority` field on `Middleware` is for documentation/ordering within files
|
|
732
|
+
- Middleware parameters (e.g., `'abilities:read,write'`) are stored on `request._middlewareParams` -- middleware must parse the param string itself
|
|
733
|
+
- The dev server's file watchers use Node's `fs.watch` (not `chokidar`) -- on macOS this uses FSEvents, on Linux it uses inotify
|
|
734
|
+
- `openapi.json` starts empty (`{}`) and is only populated when `generateOpenApi()` is explicitly called
|
|
735
|
+
- The `api-types.ts` file uses operations keyed by action path (e.g., `'Actions/UserIndexOrmAction'`), not by route name
|
|
736
|
+
- CORS headers (`Access-Control-Allow-Origin: http://localhost:5173`) are only added in development mode for error responses
|
|
737
|
+
- Body parsing happens once per request (tracked via `_bodyParsed` flag) -- JSON, URL-encoded, and multipart/form-data are all supported
|
|
738
|
+
- `response.json()` catches circular reference errors and returns `{ error: 'Response data could not be serialized' }` instead of throwing
|