@stacksjs/defaults 0.75.231 → 0.75.236
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/ai/skills/stacks-dashboard/SKILL.md +2 -2
- package/ai/skills/stacks-database/SKILL.md +1 -1
- package/ai/skills/stacks-deploy/SKILL.md +3 -2
- package/ai/skills/stacks-migrations/SKILL.md +2 -2
- package/ai/skills/stacks-native/CATALOG.md +10 -10
- package/ai/skills/stacks-realtime/SKILL.md +4 -2
- package/ai/skills/stacks-storage/SKILL.md +43 -0
- package/ai/skills/stacks-testing/SKILL.md +38 -10
- package/app/Actions/Monitoring/error-provider.ts +129 -12
- package/app/Models/Error.ts +21 -0
- package/functions/dashboard-form.test.ts +41 -0
- package/functions/dashboard-form.ts +30 -0
- package/functions/dashboard-list.test.ts +228 -0
- package/functions/dashboard-list.ts +257 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/storage/framework/types/browser-composables.d.ts +56 -0
- package/resources/components/Dashboard/Auth/AuthCard.stx +29 -0
- package/resources/components/Dashboard/Auth/ForgotPassword.stx +65 -79
- package/resources/components/Dashboard/Auth/Login.stx +73 -82
- package/resources/components/Dashboard/Auth/Register.stx +121 -135
- package/resources/components/Dashboard/Auth/ResetPassword.stx +88 -102
- package/resources/components/Dashboard/Buddy/BuddyAssistant.stx +11 -15
- package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceManufacturersDashboard.stx +13 -24
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceOverviewDashboard.stx +17 -21
- package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +7 -19
- package/resources/components/Dashboard/Commerce/CommercePosDashboard.stx +4 -16
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +7 -18
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +13 -24
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceUnitsDashboard.stx +13 -24
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/CommerceVariantsDashboard.stx +7 -18
- package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +52 -105
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryOverviewDashboard.stx +12 -16
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +53 -105
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveriesDashboard.stx +7 -13
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +53 -105
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsDashboard.stx +7 -13
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +51 -103
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +52 -104
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +10 -27
- package/resources/components/Dashboard/Commerce/PrintLogsDashboard.stx +7 -19
- package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +12 -16
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +2 -4
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +12 -16
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +2 -4
- package/resources/components/Dashboard/Deployments/DeploymentList.stx +17 -21
- package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +8 -19
- package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +8 -19
- package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +11 -22
- package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +8 -19
- package/resources/components/Dashboard/Queue/QueueDashboard.stx +12 -16
- package/resources/components/Dashboard/Realtime/RealtimeDashboard.stx +6 -10
- package/resources/components/Dashboard/Releases/ReleaseDashboard.stx +6 -10
- package/resources/components/Dashboard/UI/MetricCards.stx +32 -0
- package/resources/components/Dashboard/UI/PageHeader.stx +25 -66
- package/resources/components/Dashboard/UI/Table.stx +79 -6
- package/resources/plugins/preloader.ts +9 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-dashboard
|
|
3
|
-
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the
|
|
3
|
+
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 414 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Dashboard
|
|
10
10
|
|
|
11
|
-
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views,
|
|
11
|
+
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 414 components, and a multi-section layout.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
|
|
@@ -12,7 +12,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
12
12
|
- Database package: `storage/framework/core/database/src/`
|
|
13
13
|
- Configuration: `config/database.ts`
|
|
14
14
|
- QB config: `config/query-builder.ts`
|
|
15
|
-
- Migrations: `database/migrations/` (
|
|
15
|
+
- Migrations: `database/migrations/` (239 migration files, `.sql` format)
|
|
16
16
|
- QB state: `.qb/`
|
|
17
17
|
- ORM: `storage/framework/orm/`
|
|
18
18
|
|
|
@@ -53,8 +53,9 @@ a versioned `STACKS_DEPLOY_PREVIEW_JSON=` line for API and tool consumers.
|
|
|
53
53
|
|
|
54
54
|
Automated production deployments on GitHub Actions and ReviewOS require a full provider
|
|
55
55
|
commit SHA and branch ref. Before prerequisite setup, Buddy verifies the checkout against
|
|
56
|
-
that SHA
|
|
57
|
-
|
|
56
|
+
that SHA and accepts an unchanged origin head directly. If the head moved, Buddy fetches
|
|
57
|
+
the branch from `origin` (FETCH_HEAD only), completes shallow history and requires the
|
|
58
|
+
commit to be on it. Unavailable or still-shallow history stops deployment. Before shipping over SSH it
|
|
58
59
|
reads the release each site's `current` link serves and stops only when production
|
|
59
60
|
already runs a commit that landed after this one, so a deploy never goes backwards. A
|
|
60
61
|
verified commit newer than production deploys even when the head has moved on: requiring
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-migrations
|
|
3
|
-
description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the
|
|
3
|
+
description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the 239 built-in migration files. For the database API itself (queries, connections, SQL helpers), see stacks-database.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript, SQLite >= 3.47.2
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -203,6 +203,6 @@ lossless widenings; reports everything destructive), then
|
|
|
203
203
|
- Do not commit a second snapshot under `.qb/`; that indicates a missing `snapshotDir` configuration
|
|
204
204
|
- If a generated SQLite migration rebuilds tables, test it against a copy of the current database and run `PRAGMA integrity_check` plus `PRAGMA foreign_key_check`
|
|
205
205
|
- `--seed` flag after `migrate:fresh` seeds the database with factory data
|
|
206
|
-
-
|
|
206
|
+
- 239 migration files exist by default for all framework models
|
|
207
207
|
- SQLite >= 3.47.2 is required (system requirement)
|
|
208
208
|
- For the database API (queries, connections), see the `stacks-database` skill
|
|
@@ -35,7 +35,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
35
35
|
| `@stacksjs/composables` | [`stacks-composables`](../stacks-composables/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/composables/src/index.ts) | 18 |
|
|
36
36
|
| `@stacksjs/config` | [`stacks-config`](../stacks-config/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/config/src/index.ts) | 16 |
|
|
37
37
|
| `@stacksjs/cron` | [`stacks-cron`](../stacks-cron/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cron/src/index.ts) | 4 |
|
|
38
|
-
| `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) |
|
|
38
|
+
| `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 125 |
|
|
39
39
|
| `@stacksjs/datetime` | [`stacks-datetime`](../stacks-datetime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/datetime/src/index.ts) | 13 |
|
|
40
40
|
| `@stacksjs/defaults` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/defaults/package.json) | 1 |
|
|
41
41
|
| `@stacksjs/deploy` | [`stacks-deploy`](../stacks-deploy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/deploy/package.json) | 1 |
|
|
@@ -75,7 +75,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
75
75
|
| `@stacksjs/realtime` | [`stacks-realtime`](../stacks-realtime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/realtime/src/index.ts) | 11 |
|
|
76
76
|
| `@stacksjs/registry` | [`stacks-registry`](../stacks-registry/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/registry/src/index.ts) | 1 |
|
|
77
77
|
| `@stacksjs/repl` | [`stacks-repl`](../stacks-repl/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/repl/src/index.ts) | 2 |
|
|
78
|
-
| `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) |
|
|
78
|
+
| `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 91 |
|
|
79
79
|
| `@stacksjs/scheduler` | [`stacks-scheduler`](../stacks-scheduler/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/index.ts) | 13 |
|
|
80
80
|
| `@stacksjs/search-engine` | [`stacks-search-engine`](../stacks-search-engine/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/search-engine/src/index.ts) | 10 |
|
|
81
81
|
| `@stacksjs/security` | [`stacks-security`](../stacks-security/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/security/src/index.ts) | 11 |
|
|
@@ -86,9 +86,9 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
86
86
|
| `@stacksjs/slug` | [`stacks-slug`](../stacks-slug/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/slug/src/index.ts) | 4 |
|
|
87
87
|
| `@stacksjs/sms` | [`stacks-sms`](../stacks-sms/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/index.ts) | 5 |
|
|
88
88
|
| `@stacksjs/socials` | [`stacks-socials`](../stacks-socials/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/socials/src/index.ts) | 17 |
|
|
89
|
-
| `@stacksjs/storage` | [`stacks-storage`](../stacks-storage/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts) |
|
|
89
|
+
| `@stacksjs/storage` | [`stacks-storage`](../stacks-storage/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/index.ts) | 42 |
|
|
90
90
|
| `@stacksjs/strings` | [`stacks-strings`](../stacks-strings/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/strings/src/index.ts) | 7 |
|
|
91
|
-
| `@stacksjs/testing` | [`stacks-testing`](../stacks-testing/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/index.ts) |
|
|
91
|
+
| `@stacksjs/testing` | [`stacks-testing`](../stacks-testing/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/index.ts) | 16 |
|
|
92
92
|
| `@stacksjs/tinker` | [`stacks-repl`](../stacks-repl/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/tinker/src/index.ts) | 1 |
|
|
93
93
|
| `@stacksjs/tunnel` | [`stacks-tunnel`](../stacks-tunnel/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/tunnel/src/index.ts) | 1 |
|
|
94
94
|
| `@stacksjs/types` | [`stacks-types`](../stacks-types/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/types/src/index.ts) | 2 |
|
|
@@ -351,7 +351,7 @@ Declared runtime exports: `BUN_BROKEN_MYSQL_POOL_MESSAGE`, `BUN_BROKEN_POSTGRES_
|
|
|
351
351
|
|
|
352
352
|
Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/broken-pool.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/broken-pool.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-names.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-names.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
|
|
353
353
|
|
|
354
|
-
Evidence directory: [
|
|
354
|
+
Evidence directory: [125 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
355
355
|
|
|
356
356
|
### @stacksjs/datetime
|
|
357
357
|
|
|
@@ -719,11 +719,11 @@ Evidence directory: [2 retained tests](https://github.com/stacksjs/stacks/blob/m
|
|
|
719
719
|
|
|
720
720
|
Guidance: [`stacks-router`](../stacks-router/SKILL.md). Source: [storage/framework/core/router/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts).
|
|
721
721
|
|
|
722
|
-
Declared runtime exports: `DASHBOARD_ROUTE_GROUPS`, `DEFAULT_ROUTE_BUNDLE_FEATURES`, `DEFAULT_ROUTE_REGISTRY`, `DatabaseSessionStore`, `EncryptedSessionStore`, `FileSessionStore`, `JSON_CONTENT_TYPE`, `MemorySessionStore`, `Middleware`, `PathParamError`, `RedisSessionStore`, `RequestBodyError`, `appRouteRegistry`, `assertRouteMiddlewareResolvable`, `bundleMounts`, `cacheRedisConnection`, `cacheRequestQuery`, `checkApplicationHealth`, `clearCsrfModuleCache`, `clearCurrentRequest`, `clearMiddlewareCache`, `clearRateLimit`, `clearRouteModelBindings`, `clearTrackedQueries`, `clientAddress`, `configureViewDirectories`, `createErrorResponse`, `createMiddlewareErrorResponse`, `createNotFoundResponse`, `createSessionStore`, `createStacksRouter`, `createStacksSessionStore`, `createTypedRouter`, `createValidationErrorResponse`, `dashboardGroupMiddleware`, `defineMiddleware`, `defineRouteModelBinding`, `describeUnknownDashboardGroups`, `disableViewRouting`, `enhanceRequest`, `fallbackStorage`, `findUnresolvableRouteMiddleware`, `getCurrentRequest`, `getQueryShapeCounts`, `getTraceId`, `installMiddlewareHotReload`, `isApiRequest`, `isRouterAction`, `listNamedRoutes`, `listRegisteredRoutes`, `listRootMountedAppRoutes`, `loadMiddlewareHandlers`, `loadRoutes`, `mountedDefaultRouteBundles`, `rateLimit`, `rateLimitScale`, `rateLimitStatus`, `readJsonBody`, `readTextBody`, `request`, `resetBootHooks`, `resetRateLimiters`, `resetSharedRateLimitStorage`, `resolveDashboardAccess`, `resolveRouteModel`, `route`, `routeModelBindings`, `routeParams`, `runBootHooks`, `runHealthProbes`, `runWithRequest`, `safePathParam`, `sanitizePathParam`, `scaledLimit`, `serve`, `serverResponse`, `setCurrentRequest`, `setRouteModelFallback`, `sharedRateLimitStorage`, `signUrl`, `signedUrl`, `stream`, `trackQuery`, `trustedProxies`, `url`, `useRateLimitStorage`, `verifySignedUrl`, `verifySignedUrlMiddleware`, `warnOnMultipleRouterInstances`, `withTraceId`, `wrapAction`.
|
|
722
|
+
Declared runtime exports: `DASHBOARD_ROUTE_GROUPS`, `DEFAULT_ROUTE_BUNDLE_FEATURES`, `DEFAULT_ROUTE_REGISTRY`, `DatabaseSessionStore`, `EncryptedSessionStore`, `FileSessionStore`, `JSON_CONTENT_TYPE`, `MemorySessionStore`, `Middleware`, `PathParamError`, `RedisSessionStore`, `RequestBodyError`, `appRouteRegistry`, `assertRouteMiddlewareResolvable`, `bundleMounts`, `cacheRedisConnection`, `cacheRequestQuery`, `checkApplicationHealth`, `clearCsrfModuleCache`, `clearCurrentRequest`, `clearMiddlewareCache`, `clearRateLimit`, `clearRouteModelBindings`, `clearTrackedQueries`, `clientAddress`, `configureViewDirectories`, `createErrorResponse`, `createMiddlewareErrorResponse`, `createNotFoundResponse`, `createSessionStore`, `createStacksRouter`, `createStacksSessionStore`, `createTypedRouter`, `createValidationErrorResponse`, `dashboardGroupMiddleware`, `defineMiddleware`, `defineRouteModelBinding`, `describeUnknownDashboardGroups`, `disableViewRouting`, `enhanceRequest`, `fallbackStorage`, `findUnresolvableRouteMiddleware`, `getCurrentRequest`, `getQueryShapeCounts`, `getTraceId`, `installMiddlewareHotReload`, `isApiRequest`, `isRouterAction`, `listNamedRoutes`, `listRegisteredRoutes`, `listRootMountedAppRoutes`, `loadMiddlewareHandlers`, `loadRoutes`, `mountedDefaultRouteBundles`, `parseJsonText`, `rateLimit`, `rateLimitScale`, `rateLimitStatus`, `readJsonBody`, `readTextBody`, `request`, `resetBootHooks`, `resetRateLimiters`, `resetSharedRateLimitStorage`, `resolveDashboardAccess`, `resolveRouteModel`, `route`, `routeModelBindings`, `routeParams`, `runBootHooks`, `runHealthProbes`, `runWithRequest`, `safePathParam`, `sanitizePathParam`, `scaledLimit`, `serve`, `serverResponse`, `setCurrentRequest`, `setRouteModelFallback`, `sharedRateLimitStorage`, `signUrl`, `signedUrl`, `stream`, `trackQuery`, `trustedProxies`, `url`, `useRateLimitStorage`, `verifySignedUrl`, `verifySignedUrlMiddleware`, `warnOnMultipleRouterInstances`, `withTraceId`, `wrapAction`.
|
|
723
723
|
|
|
724
|
-
Local source modules: [src/api-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/api-shape.ts), [src/body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/body.ts), [src/dashboard-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/dashboard-access.ts), [src/encrypted-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/encrypted-session-store.ts), [src/error-handler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/error-handler.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/health.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/middleware.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/path-sanitize.ts), [src/rate-limit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/rate-limit.ts), [src/request-augmentation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-augmentation.ts), [src/request-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-context.ts), [src/route-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-loader.ts), [src/route-model-binding.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-model-binding.ts), [src/security-settings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/security-settings.ts), [src/session-factory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/session-factory.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/signed-url.ts), [src/stacks-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/stacks-router.ts), [src/typed-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/typed-router.ts).
|
|
724
|
+
Local source modules: [src/api-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/api-shape.ts), [src/body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/body.ts), [src/dashboard-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/dashboard-access.ts), [src/encrypted-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/encrypted-session-store.ts), [src/error-handler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/error-handler.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/health.ts), [src/json.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/json.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/middleware.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/path-sanitize.ts), [src/rate-limit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/rate-limit.ts), [src/request-augmentation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-augmentation.ts), [src/request-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-context.ts), [src/route-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-loader.ts), [src/route-model-binding.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-model-binding.ts), [src/security-settings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/security-settings.ts), [src/session-factory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/session-factory.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/signed-url.ts), [src/stacks-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/stacks-router.ts), [src/typed-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/typed-router.ts).
|
|
725
725
|
|
|
726
|
-
Evidence directory: [
|
|
726
|
+
Evidence directory: [91 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
727
727
|
|
|
728
728
|
### @stacksjs/scheduler
|
|
729
729
|
|
|
@@ -831,7 +831,7 @@ Declared runtime exports: `PathSanitizeError`, `Storage`, `StorageManager`, `Upl
|
|
|
831
831
|
|
|
832
832
|
Local source modules: [src/adapters/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/adapters/index.ts), [src/bounded-text-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/bounded-text-file.ts), [src/completed-artifact-directory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/completed-artifact-directory.ts), [src/copy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/copy.ts), [src/delete.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/delete.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/drivers/index.ts), [src/facade.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/facade.ts), [src/file-info.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/file-info.ts), [src/files.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/files.ts), [src/folders.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/folders.ts), [src/fs.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/fs.ts), [src/glob.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/glob.ts), [src/hash.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/hash.ts), [src/helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/helpers.ts), [src/mime-verify.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/mime-verify.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/path-sanitize.ts), [src/provision.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/provision.ts), [src/s3-presigned-post.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/s3-presigned-post.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/signed-url.ts), [src/static-serve.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/static-serve.ts), [src/storage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/storage.ts), [src/temporary-workspaces.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/temporary-workspaces.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types.ts), [src/types/filesystem.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/types/filesystem.ts), [src/uploaded-file.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/uploaded-file.ts), [src/zip.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/src/zip.ts).
|
|
833
833
|
|
|
834
|
-
Evidence directory: [
|
|
834
|
+
Evidence directory: [42 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/storage/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
835
835
|
|
|
836
836
|
### @stacksjs/strings
|
|
837
837
|
|
|
@@ -851,7 +851,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
|
|
|
851
851
|
|
|
852
852
|
Local source modules: [src/console.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/console.ts), [src/disposable-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/disposable-database.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/events.ts), [src/feature.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/feature.ts), [src/isolated.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/isolated.ts), [src/mail.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/mail.ts), [src/time.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/src/time.ts).
|
|
853
853
|
|
|
854
|
-
Evidence directory: [
|
|
854
|
+
Evidence directory: [16 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/testing/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
855
855
|
|
|
856
856
|
### @stacksjs/tinker
|
|
857
857
|
|
|
@@ -385,8 +385,10 @@ single-process HTTP transport; it does not initialize Redis or a cluster.
|
|
|
385
385
|
|
|
386
386
|
`createBroadcastResponse({ channels, authorize, signal })` returns an SSE Response.
|
|
387
387
|
Select channel names on the server. Authorization is mandatory and runs before
|
|
388
|
-
subscription, before each event, and at every heartbeat.
|
|
389
|
-
|
|
388
|
+
subscription, before each event, and at every heartbeat. The callback retains the
|
|
389
|
+
subscriber's async context even when another request broadcasts. It must still
|
|
390
|
+
resolve current credentials, grants and expiry on every call; context binding
|
|
391
|
+
does not freeze authorization facts. Pass the request's abort signal. Cancellation, failed authorization and slow consumers remove the hook.
|
|
390
392
|
Streams bound both queued events and pending authorization work (64 by default).
|
|
391
393
|
|
|
392
394
|
STX's `useBroadcastStream({ request, endpoint, onMessage, onConnected })` makes
|
|
@@ -85,6 +85,36 @@ parent-directory authorization and domain validation. This is a local hard-link
|
|
|
85
85
|
and directory-fsync protocol, not a cloud-adapter transaction or tested hardware
|
|
86
86
|
power-loss guarantee.
|
|
87
87
|
|
|
88
|
+
For a durable ownership checkpoint before private bytes, use
|
|
89
|
+
`prepareCompletedArtifactDirectorySync(directory, entries, manifestJson, options)`
|
|
90
|
+
from the same subpath or package root. Its deeply frozen handle contains only
|
|
91
|
+
`receipt` and single-use `publishSync()`. Preparation exclusively creates and
|
|
92
|
+
syncs a private empty tree, then closes every descriptor. Persist the receipt
|
|
93
|
+
before calling the same live handle's publication method. The receipt records
|
|
94
|
+
canonical parent/tree/file identities and planned lengths/SHA-256; observed
|
|
95
|
+
file lengths are zero and `manifest.json` is absent. Planned metadata is not
|
|
96
|
+
completed content or a deletion receipt.
|
|
97
|
+
|
|
98
|
+
Pass plain data records/lists and unshared Uint8Array/Buffer views. Record/list
|
|
99
|
+
validation refuses proxies, getters, holes and hidden/symbol aliases without
|
|
100
|
+
executing caller code; byte views use intrinsic offsets rather than overrides.
|
|
101
|
+
Publication rechecks the empty tree and identities, writes/syncs
|
|
102
|
+
detached bytes, verifies readback hashes and exclusively links the registered
|
|
103
|
+
pending-manifest inode to completion. Every attempt consumes the handle, even
|
|
104
|
+
on error. Errors preserve state; JSON cannot rehydrate a writer. There is no
|
|
105
|
+
overwrite, arbitrary existing-directory writer or implicit cleanup.
|
|
106
|
+
|
|
107
|
+
Read the storage README's prepared-artifact section before integrating an
|
|
108
|
+
ownership ledger. It defines decimal-string identities, planned byte semantics,
|
|
109
|
+
birth-time availability, prepublication-only change time and legitimate link
|
|
110
|
+
transitions. Parent paths must already exist, belong to the operator and remain
|
|
111
|
+
controlled. Canonical ancestor aliases are allowed; final targets are exclusive.
|
|
112
|
+
Both calls block the isolate, including while holding any caller transaction.
|
|
113
|
+
Caps bound bytes/files rather than I/O duration; database contention and SQL/FS
|
|
114
|
+
atomicity remain caller concerns. The existing async writer keeps asynchronous
|
|
115
|
+
filesystem execution. Real process-interruption tests do not establish hardware
|
|
116
|
+
power-loss, hostile same-user race, deletion or media-erasure guarantees.
|
|
117
|
+
|
|
88
118
|
### Recoverable temporary workspaces
|
|
89
119
|
|
|
90
120
|
Use `TemporaryWorkspaceJournal` for workers that recover temporary directories after a process interruption. Keep its private journal outside job-controlled checkouts and separate it by stable worker and controller identity. `create(key)` returns a frozen live ownership handle; stop its resources, then call `complete(handle)` for normal disposal. A restarted instance cannot forge this capability from a listed record.
|
|
@@ -106,6 +136,19 @@ await journal.retire(recovered.id, record => controllerConfirmsFinished(record.k
|
|
|
106
136
|
|
|
107
137
|
The `Storage` singleton is a pre-instantiated `StorageManager`. It lazily builds its config from `@stacksjs/config` (which reads `config/filesystems.ts` and env vars).
|
|
108
138
|
|
|
139
|
+
### Directory mounts without retirement
|
|
140
|
+
|
|
141
|
+
Use `inspectDirectoryMounts(canonicalDirectory)` for read-only current-namespace
|
|
142
|
+
mount checks where legitimate readers may stay open. Supply an existing,
|
|
143
|
+
checked canonical directory. An unmounted result means only that the native
|
|
144
|
+
mount table did not reference that directory or a descendant. Unavailable,
|
|
145
|
+
unsupported, aliased or missing paths never return `unmounted: true`.
|
|
146
|
+
|
|
147
|
+
Keep `inspectTemporaryWorkspace` and journal retirement for deletion: they also
|
|
148
|
+
check process references and inspection permissions. Mount inspection alone
|
|
149
|
+
grants no ownership, quiescence or retirement authority and does not inspect
|
|
150
|
+
other namespaces or prevent later host changes.
|
|
151
|
+
|
|
109
152
|
### Basic Operations
|
|
110
153
|
|
|
111
154
|
```typescript
|
|
@@ -169,7 +169,8 @@ expect(result.exitCode).toBe(0)
|
|
|
169
169
|
|
|
170
170
|
Retained implementation evidence lives under `core/testing/tests/`:
|
|
171
171
|
`http-client`, `acting-as`, `database-assertions`, `events`, `mail`, `time`,
|
|
172
|
-
`console`, `isolated`, `disposable-database` and `
|
|
172
|
+
`console`, `isolated`, `disposable-database`, `browser` and `declarations` (what dtsx
|
|
173
|
+
publishes for `browser.ts` / `isolated.ts`: no `unknown` fields) test files. Fixture mechanics live in `core/testing/src/`;
|
|
173
174
|
`package.json` declares the root and database/DynamoDB/browser export boundaries.
|
|
174
175
|
|
|
175
176
|
## Isolated processes and disposable databases
|
|
@@ -191,7 +192,7 @@ for (const dialect of ['sqlite', 'postgres'] as const) {
|
|
|
191
192
|
env: { APP_ENV: 'test', ...database.env },
|
|
192
193
|
timeoutMs: 45_000,
|
|
193
194
|
envFile: false, // --no-env-file
|
|
194
|
-
preload: [], // replace bunfig preloads (none)
|
|
195
|
+
preload: [], // replace bunfig preloads (none); or bunfig: 'tests/isolated/bunfig.toml'
|
|
195
196
|
})
|
|
196
197
|
assertIsolatedOk(result, 'checkout flow OK') // throws with stdout + stderr otherwise
|
|
197
198
|
})
|
|
@@ -199,10 +200,14 @@ for (const dialect of ['sqlite', 'postgres'] as const) {
|
|
|
199
200
|
}
|
|
200
201
|
```
|
|
201
202
|
|
|
202
|
-
- `runIsolated(file, { env, timeoutMs = 60_000, args, cwd, mode, preload, envFile, inheritEnv, stdin })`
|
|
203
|
+
- `runIsolated(file, { env, timeoutMs = 60_000, args, cwd, mode, preload, bunfig, envFile, inheritEnv, stdin })`
|
|
203
204
|
resolves `{ code, signal, stdout, stderr, timedOut, durationMs, command }`. `mode: 'auto'` runs
|
|
204
205
|
`*.test.*` / `*.spec.*` with `bun test`, anything else as a script; `env` values of `undefined`
|
|
205
206
|
remove a variable. The watchdog sends SIGTERM at the timeout and SIGKILL 2 s later.
|
|
207
|
+
- `bunfig: 'path/bunfig.toml'` runs the child with that config (`--config=`), resolved against
|
|
208
|
+
`cwd`; it must exist. `preload` instead generates a config holding only those preloads. They are
|
|
209
|
+
exclusive (a TypeError): list the preloads in your bunfig. Bun resolves a bunfig's relative
|
|
210
|
+
`preload` paths against the child's cwd, not the bunfig's directory.
|
|
206
211
|
- `withDisposableDatabase(dialect, fn, { url?, prefix? })`: SQLite is a file in a fresh temp
|
|
207
212
|
directory; Postgres is `CREATE DATABASE` on `url` or `STACKS_TEST_POSTGRES_URL`, dropped with
|
|
208
213
|
`DROP DATABASE ... WITH (FORCE)` after `fn`, even when it throws (the test's error wins over a
|
|
@@ -236,18 +241,41 @@ expect(pageAuditClean(report)).toBe(true) // no duplicate /api/ requests, failed
|
|
|
236
241
|
```
|
|
237
242
|
|
|
238
243
|
- `Cdp`: `send`, `evaluate`, `on(method, fn)` (any number of listeners per method; returns an
|
|
239
|
-
unsubscribe), `off`, `once(method, match?, timeoutMs?)`, `
|
|
240
|
-
|
|
241
|
-
(every pending command fails at
|
|
244
|
+
unsubscribe), `off`, `once(method, match?, timeoutMs?)`, `intercept(patterns, handler)`,
|
|
245
|
+
`errors`, `inFlight: Map<requestId, 'METHOD url'>` (event streams leave it when their response
|
|
246
|
+
starts, so a page with live updates can still settle), `closed` (every pending command fails at
|
|
247
|
+
once when Chrome goes away).
|
|
242
248
|
- `launch(options)` enables Page/Runtime/Network/Log, sets a 1440x1000 viewport, answers stx's
|
|
243
249
|
live-reload stream (`*/_stx/hmr*`) with a day-long retry so a save mid-run cannot reload the
|
|
244
250
|
page, accepts JavaScript dialogs and records them in `errors`, removes "HeadlessChrome" from the
|
|
245
251
|
user agent, and passes `--use-mock-keychain`. `profile` keeps one profile across relaunches.
|
|
252
|
+
- **Pausing requests next to live reload.** Chrome keeps one `Fetch.enable` pattern set per
|
|
253
|
+
session (a second call replaces it, `Fetch.disable` drops all) and sends every paused request to
|
|
254
|
+
every listener, so the session layers them. `await cdp.intercept([{ urlPattern: '*/api/slow*' }],
|
|
255
|
+
(paused, cdp) => ...)` hands a handler only what its own patterns match and resolves with an
|
|
256
|
+
async remover; `quietLiveReload(cdp, patterns?)` is one (what `launch()` runs). Your own
|
|
257
|
+
`cdp.send('Fetch.enable' | 'Fetch.disable')` sets one more layer and the session enables the
|
|
258
|
+
union, so a journey that holds its own request needs no `quietLiveReload: false`.
|
|
259
|
+
`on('Fetch.requestPaused')` listeners see the requests your patterns match, not the live-reload
|
|
260
|
+
stream. The first answer wins: a second `continueRequest`/`fulfillRequest`/`failRequest` for one
|
|
261
|
+
pause resolves without reaching Chrome. A pause nobody could answer is continued; one your own
|
|
262
|
+
pattern holds with no listener stays held (a hanging request on purpose).
|
|
263
|
+
`matchesUrlPattern(pattern, url)` and `pausedRequestMatches(pattern, paused)` are Chrome's
|
|
264
|
+
matching rules (`*`, `?`, `\` escape; stage and resource type).
|
|
246
265
|
- `settle(cdp, { quietMs, timeoutMs })` resolves false (not throws) when a page never goes quiet.
|
|
247
|
-
- `auditPage(url, { cdp?, launch?, viewport?, isApiRequest?, beforeNavigate?, quietMs, timeoutMs })`
|
|
248
|
-
returns `{ apiRequests, duplicates, failed, errors, settled, durationMs }`
|
|
249
|
-
page problem. `beforeNavigate` is where to seed a token
|
|
250
|
-
`formatPageAudit(report)` prints one line
|
|
266
|
+
- `auditPage(url, { cdp?, launch?, viewport?, isApiRequest?, beforeNavigate?, pageLoads?, quietMs, timeoutMs })`
|
|
267
|
+
returns `{ url, finalUrl, apiRequests, duplicates, loads, failed, errors, settled, durationMs }`
|
|
268
|
+
and never throws for a page problem. `beforeNavigate` is where to seed a token
|
|
269
|
+
(`Page.addScriptToEvaluateOnNewDocument`). `formatPageAudit(report)` prints one line, with
|
|
270
|
+
`/today -> /home` when the page landed elsewhere.
|
|
271
|
+
- Duplicates are counted **per page load**: a page that sends you on (`/today` replacing itself with
|
|
272
|
+
`/home`) is two loads, so `/api/me` once in each is not a duplicate. A load starts with each new
|
|
273
|
+
main-frame document after the first and each same-document (history API) navigation to a
|
|
274
|
+
different path; a query or hash change stays in the load. `loads` lists them
|
|
275
|
+
(`{ url, apiRequests, duplicates }`), `duplicates` is the highest count any one load reached,
|
|
276
|
+
`finalUrl` is `location.href` once settled. `pageLoads: 'visit'` counts the whole visit as one
|
|
277
|
+
load (the old behaviour). `splitPageLoads(url, events, mode)` and `loadDuplicates(loads)` are the
|
|
278
|
+
pure logic. `finalUrl` and `loads` are optional on the type, so hand-built reports still compile.
|
|
251
279
|
- **macOS: the installed Chrome quits about 30 s in.** GoogleUpdater SIGTERMs every process with
|
|
252
280
|
Chrome's bundle id while an update is pending; to a test that looks like a hung page. When
|
|
253
281
|
`CHROME_PATH` is not set, `launch()` runs `prepareMacChrome()`: a `cp -cR` clone in the temp
|
|
@@ -20,16 +20,78 @@ import { HQ_READ_UNAVAILABLE, readThroughProvider, resolveDashboardDriver } from
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* The
|
|
23
|
+
* The shapes the Errors section renders.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* These used to be derived from the local store:
|
|
26
|
+
*
|
|
27
|
+
* export type DashboardErrorRecord = NonNullable<Awaited<ReturnType<typeof errors.fetchById>>>
|
|
28
|
+
*
|
|
29
|
+
* which made the contract whatever `@stacksjs/commerce`'s ORM row happened to
|
|
30
|
+
* be. That is fine while the only provider IS the commerce store, and wrong
|
|
31
|
+
* the moment a second one exists: a remote provider has to be told what to
|
|
32
|
+
* serve, and "whatever a shopping package's table looks like this week" is
|
|
33
|
+
* not something anyone can be told (stacksjs/stacks#2921).
|
|
34
|
+
*
|
|
35
|
+
* Declared here instead, so the section's contract is a decision rather than
|
|
36
|
+
* a consequence. Field names are deliberately the ones already on the wire -
|
|
37
|
+
* these actions serialize straight to JSON and the dashboard reads
|
|
38
|
+
* `first_seen`, `last_seen` and `last_24h` today, so renaming them to
|
|
39
|
+
* camelCase would be a breaking change to a shipped response rather than the
|
|
40
|
+
* decoupling this is for.
|
|
29
41
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export type
|
|
42
|
+
|
|
43
|
+
/** Where a grouped error sits in the triage workflow. */
|
|
44
|
+
export type DashboardErrorStatus = 'unresolved' | 'resolved' | 'ignored'
|
|
45
|
+
|
|
46
|
+
export interface DashboardErrorRecord {
|
|
47
|
+
/**
|
|
48
|
+
* Opaque. A number from the local store's autoincrement, a string from a
|
|
49
|
+
* hosted one - bughq's issue ids are varchar (stacksjs/stacks#2915).
|
|
50
|
+
* Compare with `String(a.id) === String(b.id)` rather than `===`.
|
|
51
|
+
*/
|
|
52
|
+
id: string | number
|
|
53
|
+
type: string
|
|
54
|
+
message: string
|
|
55
|
+
stack: string | null
|
|
56
|
+
/**
|
|
57
|
+
* The HTTP status the error reported, and only that.
|
|
58
|
+
*
|
|
59
|
+
* It used to be overloaded: `resolveError` and `ignoreError` wrote 1 and 2
|
|
60
|
+
* into this column to mean resolved and ignored, which destroyed the status
|
|
61
|
+
* and meant a seeded error could never read as resolved. Split in
|
|
62
|
+
* stacksjs/stacks#2923; triage is `resolution` below.
|
|
63
|
+
*/
|
|
64
|
+
status: number | null
|
|
65
|
+
/** Where this error sits in triage. */
|
|
66
|
+
resolution: DashboardErrorStatus
|
|
67
|
+
/** Free text. Nothing in the dashboard reads it; it is carried, not shown. */
|
|
68
|
+
additional_info: string | null
|
|
69
|
+
created_at: string
|
|
70
|
+
updated_at: string | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DashboardGroupedError {
|
|
74
|
+
/** Opaque, as on {@link DashboardErrorRecord}. */
|
|
75
|
+
id: string | number
|
|
76
|
+
type: string
|
|
77
|
+
message: string
|
|
78
|
+
stack: string | null
|
|
79
|
+
/** The triage state, unlike `status` on a single record. See above. */
|
|
80
|
+
status: DashboardErrorStatus
|
|
81
|
+
count: number
|
|
82
|
+
first_seen: string
|
|
83
|
+
last_seen: string
|
|
84
|
+
error_ids: number[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DashboardErrorStats {
|
|
88
|
+
total: number
|
|
89
|
+
unresolved: number
|
|
90
|
+
resolved: number
|
|
91
|
+
ignored: number
|
|
92
|
+
last_24h: number
|
|
93
|
+
trend: number
|
|
94
|
+
}
|
|
33
95
|
|
|
34
96
|
export interface DashboardErrorTimelinePoint {
|
|
35
97
|
hour: string
|
|
@@ -41,7 +103,7 @@ export interface DashboardErrorsProvider {
|
|
|
41
103
|
stats: () => Promise<DashboardErrorStats>
|
|
42
104
|
timeline: () => Promise<DashboardErrorTimelinePoint[]>
|
|
43
105
|
byGroup: (type: string, message: string) => Promise<DashboardErrorRecord[]>
|
|
44
|
-
byId: (id: number) => Promise<DashboardErrorRecord | undefined>
|
|
106
|
+
byId: (id: string | number) => Promise<DashboardErrorRecord | undefined>
|
|
45
107
|
}
|
|
46
108
|
|
|
47
109
|
/** Wraps a payload with the reason its section is empty. */
|
|
@@ -54,12 +116,67 @@ export type DashboardErrorsEnvelope<T> = { data: T } & Partial<DashboardProvider
|
|
|
54
116
|
* adding one here would convert today's error response into a misleading empty
|
|
55
117
|
* list, which is a behaviour change rather than the refactor this is meant to be.
|
|
56
118
|
*/
|
|
119
|
+
const ERROR_STATUSES: readonly DashboardErrorStatus[] = ['unresolved', 'resolved', 'ignored']
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Narrow the store's `status` onto the three states the section renders.
|
|
123
|
+
*
|
|
124
|
+
* `fetchGrouped` types this as `string` while deriving it from exactly those
|
|
125
|
+
* three labels, so the widening is incidental. An unknown value reads as
|
|
126
|
+
* unresolved, which is the store's own default for a status it cannot
|
|
127
|
+
* interpret, and is the safe direction: an error wrongly shown as needing
|
|
128
|
+
* attention is noticed, one wrongly shown as handled is not.
|
|
129
|
+
*/
|
|
130
|
+
function toErrorStatus(value: string): DashboardErrorStatus {
|
|
131
|
+
return ERROR_STATUSES.includes(value as DashboardErrorStatus)
|
|
132
|
+
? value as DashboardErrorStatus
|
|
133
|
+
: 'unresolved'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The commerce row, as the section's contract.
|
|
138
|
+
*
|
|
139
|
+
* Written out rather than passed through. Every field here is currently an
|
|
140
|
+
* identity, and saying so explicitly is the point: when the store's shape
|
|
141
|
+
* next moves, this file stops compiling instead of quietly changing what a
|
|
142
|
+
* remote provider is required to match.
|
|
143
|
+
*/
|
|
144
|
+
function toGroupedError(row: Awaited<ReturnType<typeof errors.fetchGrouped>>[number]): DashboardGroupedError {
|
|
145
|
+
return {
|
|
146
|
+
id: row.id,
|
|
147
|
+
type: row.type,
|
|
148
|
+
message: row.message,
|
|
149
|
+
stack: row.stack,
|
|
150
|
+
status: toErrorStatus(row.status),
|
|
151
|
+
count: row.count,
|
|
152
|
+
first_seen: row.first_seen,
|
|
153
|
+
last_seen: row.last_seen,
|
|
154
|
+
error_ids: row.error_ids,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The local store's id for a requested one, or undefined when it cannot have it.
|
|
160
|
+
*
|
|
161
|
+
* Its ids are an autoincrement, so a non-integer id is not one of its rows
|
|
162
|
+
* rather than a bad request, and the caller answers 404. Route params arrive
|
|
163
|
+
* as strings even when the id is a number, which is why a numeric string is
|
|
164
|
+
* accepted (stacksjs/stacks#2915).
|
|
165
|
+
*/
|
|
166
|
+
export function localErrorId(id: string | number): number | undefined {
|
|
167
|
+
const numeric = typeof id === 'number' ? id : Number.parseInt(String(id), 10)
|
|
168
|
+
return Number.isInteger(numeric) ? numeric : undefined
|
|
169
|
+
}
|
|
170
|
+
|
|
57
171
|
export const localErrorsProvider: DashboardErrorsProvider = {
|
|
58
|
-
grouped: () => errors.fetchGrouped(),
|
|
172
|
+
grouped: async () => (await errors.fetchGrouped()).map(toGroupedError),
|
|
59
173
|
stats: () => errors.fetchStats(),
|
|
60
174
|
timeline: () => errors.fetchTimeline(),
|
|
61
175
|
byGroup: (type, message) => errors.fetchByGroup(type, message),
|
|
62
|
-
byId: id =>
|
|
176
|
+
byId: async (id) => {
|
|
177
|
+
const numeric = localErrorId(id)
|
|
178
|
+
return numeric === undefined ? undefined : errors.fetchById(numeric)
|
|
179
|
+
},
|
|
63
180
|
}
|
|
64
181
|
|
|
65
182
|
/**
|
|
@@ -125,6 +242,6 @@ export function readErrorsByGroup(type: string, message: string): Promise<Dashbo
|
|
|
125
242
|
return readErrors([], provider => provider.byGroup(type, message), Array.isArray)
|
|
126
243
|
}
|
|
127
244
|
|
|
128
|
-
export function readErrorById(id: number): Promise<DashboardErrorsEnvelope<DashboardErrorRecord | undefined>> {
|
|
245
|
+
export function readErrorById(id: string | number): Promise<DashboardErrorsEnvelope<DashboardErrorRecord | undefined>> {
|
|
129
246
|
return readErrors<DashboardErrorRecord | undefined>(undefined, provider => provider.byId(id), () => true)
|
|
130
247
|
}
|
package/app/Models/Error.ts
CHANGED
|
@@ -52,6 +52,16 @@ export default defineModel({
|
|
|
52
52
|
factory: faker => faker.system.filePath(),
|
|
53
53
|
},
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The HTTP status the error carried, and only that.
|
|
57
|
+
*
|
|
58
|
+
* It used to mean two things: the factory seeds an HTTP status while
|
|
59
|
+
* `resolveGroup` wrote 1 and `ignoreGroup` wrote 2 into the same column
|
|
60
|
+
* to mean resolved and ignored. So resolving an error destroyed its
|
|
61
|
+
* status, and a seeded error could never read as resolved because no
|
|
62
|
+
* factory row is ever 1 (stacksjs/stacks#2923). Triage now lives in
|
|
63
|
+
* `resolution`.
|
|
64
|
+
*/
|
|
55
65
|
status: {
|
|
56
66
|
fillable: true,
|
|
57
67
|
validation: {
|
|
@@ -63,6 +73,17 @@ export default defineModel({
|
|
|
63
73
|
factory: faker => faker.number.int({ min: 400, max: 500 }),
|
|
64
74
|
},
|
|
65
75
|
|
|
76
|
+
/** Where this error sits in triage. Separate from `status`, see above. */
|
|
77
|
+
resolution: {
|
|
78
|
+
required: true,
|
|
79
|
+
fillable: true,
|
|
80
|
+
default: 'unresolved',
|
|
81
|
+
validation: {
|
|
82
|
+
rule: schema.enum(['unresolved', 'resolved', 'ignored']),
|
|
83
|
+
},
|
|
84
|
+
factory: () => 'unresolved',
|
|
85
|
+
},
|
|
86
|
+
|
|
66
87
|
additional_info: {
|
|
67
88
|
fillable: true,
|
|
68
89
|
validation: {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { formErrorMessages } from './dashboard-form'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* stacksjs/stacks#2918 — this was duplicated across fourteen dialogs. The
|
|
6
|
+
* cases below are the formatting those copies encoded; they are pinned here
|
|
7
|
+
* so the single definition cannot quietly change what a user reads.
|
|
8
|
+
*/
|
|
9
|
+
describe('formErrorMessages', () => {
|
|
10
|
+
it('turns a snake_case field into a sentence-case label', () => {
|
|
11
|
+
expect(formErrorMessages({ first_name: 'is required' })).toEqual(['First name: is required'])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('splits camelCase, but not to the same result', () => {
|
|
15
|
+
// Pinning a wart, not endorsing it. The split inserts a space without
|
|
16
|
+
// lowering the second word, so `firstName` and `first_name` do not read
|
|
17
|
+
// alike. Changing that is a product call; this test exists so the change
|
|
18
|
+
// is deliberate when it happens.
|
|
19
|
+
expect(formErrorMessages({ firstName: 'is required' })).toEqual(['First Name: is required'])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('splits every word, not just the first', () => {
|
|
23
|
+
expect(formErrorMessages({ shipping_zone_name: 'is taken' })).toEqual(['Shipping zone name: is taken'])
|
|
24
|
+
expect(formErrorMessages({ taxRateCode: 'is invalid' })).toEqual(['Tax Rate Code: is invalid'])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('keeps one line per field, in the order given', () => {
|
|
28
|
+
expect(formErrorMessages({ name: 'is required', code: 'is taken' })).toEqual([
|
|
29
|
+
'Name: is required',
|
|
30
|
+
'Code: is taken',
|
|
31
|
+
])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('has nothing to say about an empty map', () => {
|
|
35
|
+
expect(formErrorMessages({})).toEqual([])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('leaves an already-capitalised single word alone', () => {
|
|
39
|
+
expect(formErrorMessages({ Name: 'is required' })).toEqual(['Name: is required'])
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form helpers shared by the dashboard's editor dialogs.
|
|
3
|
+
*
|
|
4
|
+
* `formErrorMessages` was copied verbatim into fourteen Commerce dialogs
|
|
5
|
+
* (stacksjs/stacks#2918). It is a pure function of a validation-error map with
|
|
6
|
+
* no reactive dependency and no markup, so a component was the wrong home for
|
|
7
|
+
* it: the label formatting it encodes had fourteen definitions, and a change
|
|
8
|
+
* to how a field name becomes a label had to be made in all of them or drift.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Turn a map of field errors into lines a person can read.
|
|
13
|
+
*
|
|
14
|
+
* Behaviour is the fourteen copies' behaviour, unchanged, including one wart
|
|
15
|
+
* worth naming rather than silently fixing: the two spellings do not read
|
|
16
|
+
* alike. `first_name` becomes "First name", while `firstName` becomes
|
|
17
|
+
* "First Name", because the split inserts a space without lowering the second
|
|
18
|
+
* word. A server reporting snake_case and a client reporting camelCase
|
|
19
|
+
* therefore render differently for the same field.
|
|
20
|
+
*
|
|
21
|
+
* Left as-is here because extracting and changing at once makes a regression
|
|
22
|
+
* impossible to attribute. Fixing it is a one-line change in one place now,
|
|
23
|
+
* which is the point of the extraction.
|
|
24
|
+
*/
|
|
25
|
+
export function formErrorMessages(errors: Record<string, string>): string[] {
|
|
26
|
+
return Object.entries(errors).map(([field, message]) => {
|
|
27
|
+
const label = field.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ')
|
|
28
|
+
return `${label.charAt(0).toUpperCase()}${label.slice(1)}: ${message}`
|
|
29
|
+
})
|
|
30
|
+
}
|