@stacksjs/defaults 0.75.261 → 0.75.263

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.
@@ -800,10 +800,11 @@ Models with `traits.useApi` automatically get CRUD routes via ORM actions:
800
800
  | DELETE | `/api/{model}/{id}` | Generated destroy | 204 |
801
801
 
802
802
  Which `useApi` models get these routes is `security.api.models` in
803
- `config/security.ts` (`'all'` by default, `'own'`, `'none'`, or a list of model
804
- names that may include `'own'`), overridden by `STACKS_MODEL_APIS`. `'all'`
805
- includes the ~80 framework models, so an app that does not use commerce or
806
- CMS should set `'own'`. The OpenAPI schemas follow the same selection.
803
+ `config/security.ts` (`'own'` by default, `'all'`, `'none'`, or a list of model
804
+ names that may include `'own'`), overridden by `STACKS_MODEL_APIS`. The ~80
805
+ framework models publish nothing by default (#2954); an app that uses one
806
+ names it, `['own', 'Product']`, or sets `'all'`. The OpenAPI schemas follow
807
+ the same selection.
807
808
 
808
809
  Generated store and update handlers accept declared `fillable` attributes and
809
810
  the foreign-key attributes derived from `belongsTo`. For example,
@@ -47,7 +47,9 @@ so `MAIL_MAILER` must be one of `smtp`, `ses`, `sendgrid`, `mailgun`,
47
47
  ```typescript
48
48
  import { mail } from '@stacksjs/email'
49
49
 
50
- await mail.send({
50
+ // Throws EmailDeliveryError when it was not delivered. The default choice:
51
+ // a try/catch around it means what it looks like.
52
+ await mail.sendOrFail({
51
53
  from: { name: 'App', address: 'noreply@app.com' },
52
54
  to: 'user@example.com',
53
55
  subject: 'Welcome',
@@ -55,19 +57,21 @@ await mail.send({
55
57
  text: 'Hello!'
56
58
  })
57
59
 
58
- // Use this when later state depends on successful provider delivery.
59
- await mail.sendOrFail({
60
- to: 'user@example.com',
61
- subject: 'Your invitation',
62
- text: 'Open the invitation link.'
63
- })
60
+ // send() does NOT throw on a failed delivery; it resolves with success: false.
61
+ // For callers that aggregate outcomes, and they must check it.
62
+ const result = await mail.send({ to: 'user@example.com', subject: 'Digest', text: '...' })
63
+ if (!result.success)
64
+ failed.push(result.message)
64
65
 
65
66
  // Switch driver
66
67
  const sendgridMail = mail.use('sendgrid')
67
68
  await sendgridMail.send(message)
68
69
  ```
69
70
 
70
- `mail.send()` always returns an `EmailResult`. Provider rejection is represented
71
+ `mail.send()` always returns an `EmailResult`, typed as `EmailSent | EmailFailed`,
72
+ and `sendOrFail()` resolves with `EmailSent`. Never wrap `send()` in `try`/`catch`
73
+ and count the absence of an exception as delivered: a newsletter was recorded as
74
+ sent while its SMTP host was unreachable that way (#2930). Provider rejection is represented
71
75
  as `{ success: false, message, provider }`, which is useful for campaign jobs
72
76
  that aggregate individual outcomes. It may still throw for invalid framework
73
77
  configuration. `mail.sendOrFail()` returns the same successful result and throws
@@ -224,7 +224,7 @@ const decrypted = decryptValue(encrypted, keypair.privateKey)
224
224
 
225
225
  ```bash
226
226
  buddy env:get APP_NAME # get single var
227
- buddy env:get --all --format json # all vars as JSON
227
+ buddy env:get --all --format json # every var in the file as JSON (never the process env)
228
228
  buddy env:set APP_NAME "My App" # set var
229
229
  buddy env:encrypt # encrypt .env file
230
230
  buddy env:decrypt # decrypt .env file
@@ -19,6 +19,15 @@ it does not wrap ESLint. ESLint-style disable comments are compatibility syntax.
19
19
  - `./buddy format`: write formatting; `./buddy format --check` checks without writing.
20
20
  - `./buddy lint --stx`: run the separate stx conformance checks from `config/lint.ts`.
21
21
 
22
+ All four take paths and lint only what you name: `./buddy lint:fix app/Jobs/Foo.ts`
23
+ fixes that file, `./buddy lint app/Jobs docs` covers those two directories, and
24
+ naming none covers the project. Scope `lint:fix` this way whenever you are
25
+ fixing something specific, because its project-wide run also applies rules you
26
+ are not working on (class ordering across `resources/`, markdown in `docs/`),
27
+ and those land in your diff. A path that matches nothing is an error rather
28
+ than a silent fallback to the project, so a typo cannot widen the run.
29
+ `--stx` grades the project against the config baselines and refuses paths.
30
+
22
31
  For direct CLI use, run `bunx --bun pickier .`, adding `--fix` for supported
23
32
  fixes. Re-run without `--fix` to confirm remaining findings. Only apply fixes
24
33
  that preserve the source behavior; investigate false positives at Pickier's source.
@@ -81,7 +81,7 @@ export async function sendSubscriptionConfirmation({ to, subscriberUuid }: Optio
81
81
  }
82
82
  })
83
83
 
84
- await mail.send({
84
+ await mail.sendOrFail({
85
85
  from: { name: config.app.name, address: config.email.from.address },
86
86
  to,
87
87
  subject: 'Confirm your subscription',
@@ -18,7 +18,9 @@ Schema change management via migration files.
18
18
  ## CLI Commands
19
19
 
20
20
  ```bash
21
- buddy migrate # run pending migrations
21
+ buddy migrate # run pending migrations (and generate new ones from the models, in an interactive terminal only)
22
+ buddy migrate --generate # generate from the models even when not interactive
23
+ buddy migrate --no-generate # apply committed migrations only, even in a terminal
22
24
  buddy migrate --diff # show SQL without running
23
25
  buddy migrate --auth # include auth tables
24
26
  buddy migrate:fresh # drop ALL tables and re-migrate
@@ -26,12 +28,33 @@ buddy migrate:fresh --seed # drop, migrate, then seed
26
28
  buddy make:migration <name> # create new migration file
27
29
  buddy seed # seed database
28
30
  buddy generate:migrations # generate migrations from model diffs
31
+ buddy generate:migrations --dialect postgres # into another dialect's corpus, no Postgres needed
32
+ buddy generate:migrations --dialect all # the connected dialect and every other corpus
29
33
  buddy migrate:regenerate <dialect> # rebuild the whole corpus from the models
30
34
  buddy migrate:status # corpus vs ledger vs live schema; lists hand-written files
31
35
  buddy migrate:align # plan repairs of a database that drifted from the models
32
36
  buddy migrate:align --apply # rename legacy indexes, add missing indexes/FKs/columns, widen losslessly
33
37
  ```
34
38
 
39
+ An app that develops on one dialect and deploys on another keeps a corpus per
40
+ dialect (`database/migrations/<dialect>/`, each with its own snapshot). A plain
41
+ `generate:migrations` moves only the connected dialect and then warns, naming
42
+ any other corpus the models have left behind; `--dialect <name>` or `--dialect
43
+ all` brings it up to date from the models and that dialect's snapshot, without a
44
+ database of that dialect (#2949).
45
+
46
+ `buddy migrate` never writes the committed migration files. What it adapts for
47
+ the connected database (idempotent ALTERs on Postgres, statements SQLite cannot
48
+ run, gated features) happens to a copy of the corpus made for that run, so a
49
+ SQLite app's migrations stay SQLite after a migrate against Postgres (#2929).
50
+
51
+ A non-interactive `buddy migrate` (CI, a deploy step) generates nothing unless
52
+ given `--generate` (#2948). Generating there authored a migration nobody
53
+ reviewed and applied it in the same run, and on a deploy box the file then
54
+ vanished with the release directory. Generate locally, commit the files, and let
55
+ the deploy apply them; add `--strict` in CI to fail when the models have drifted
56
+ from the committed migrations.
57
+
35
58
  ## Creating a Schema Migration
36
59
 
37
60
  ```bash
@@ -62,6 +85,14 @@ schema and excludes framework demo tables. Set
62
85
  wants the merged registry; application models override matching framework names.
63
86
  Discovered package model roots also contribute their declared models.
64
87
 
88
+ The framework's default routes follow the same exclusion. At boot, a default
89
+ route whose framework action uses an excluded model is not registered when the
90
+ database has no table for that model, and one warning names each such route
91
+ (#2943). Before this, those routes answered 500. If the table exists (for
92
+ example, the app created it before the exclusion), the route registers as usual.
93
+ Publish the model (`buddy publish model <Name>`) to serve the route from the
94
+ app's own schema.
95
+
65
96
  The generator diffs the resolved registry against the committed dialect snapshot.
66
97
  An application does not need an `app/Models/` directory for fallback framework
67
98
  model migrations to be discovered. Inspect the resolved scope with verbose output
@@ -376,6 +376,26 @@ traits: {
376
376
  },
377
377
  ```
378
378
 
379
+ A factory receives `faker`, the attributes generated above it, and a context
380
+ whose `pick(model, column = 'id')` resolves to a value from a row that already
381
+ exists. Use it for a column whose valid values another row decides, such as a
382
+ polymorphic `subject_id`, which `belongsTo` cannot express because its foreign
383
+ key would forbid most of the values. List every model a factory picks from in
384
+ `useSeeder.after` so it seeds first:
385
+
386
+ ```ts
387
+ traits: { useSeeder: { count: 30, after: ['Issue', 'PullRequest'] } },
388
+ attributes: {
389
+ subjectType: { required: true, factory: faker => faker.helpers.arrayElement(['Issue', 'PullRequest']) },
390
+ subjectId: { required: true, factory: (faker, { subjectType }, { pick }) => pick(subjectType) },
391
+ },
392
+ ```
393
+
394
+ A pick that cannot be satisfied (no rows yet, or the model seeds later and is
395
+ missing from `after`) fails that model with the fix in its message, and so does
396
+ a factory that leaves a required column `null`. Neither invents a value
397
+ (#2951).
398
+
379
399
  `buddy seed` walks every model carrying the trait and fills its table from the
380
400
  attribute factories. Model fixture data needs no separate seeder or registration.
381
401
  Idempotent application bootstrap work can live in `database/seeders` as a
@@ -11,7 +11,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
11
11
  | Package | Task guidance | Source entry | Retained tests |
12
12
  |---|---|---|---|
13
13
  | `@stacksjs/action-runner` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/action-runner/src/index.ts) | 1 |
14
- | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 82 |
14
+ | `@stacksjs/actions` | [`stacks-actions`](../stacks-actions/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/index.ts) | 84 |
15
15
  | `@stacksjs/ai` | [`stacks-ai`](../stacks-ai/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/ai/src/index.ts) | 17 |
16
16
  | `@stacksjs/alias` | [`stacks-alias`](../stacks-alias/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/alias/src/index.ts) | 1 |
17
17
  | `@stacksjs/analytics` | [`stacks-analytics`](../stacks-analytics/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/analytics/src/index.ts) | 6 |
@@ -21,7 +21,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
21
21
  | `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 119 |
22
22
  | `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
23
23
  | `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
24
- | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 159 |
24
+ | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 161 |
25
25
  | `@stacksjs/build` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/build/src/index.ts) | 3 |
26
26
  | `@stacksjs/cache` | [`stacks-cache`](../stacks-cache/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cache/src/index.ts) | 14 |
27
27
  | `@stacksjs/calendar-api` | [`stacks-calendar`](../stacks-calendar/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/calendar-api/src/index.ts) | 2 |
@@ -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) | 126 |
38
+ | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 130 |
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) | 2 |
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 |
@@ -65,21 +65,21 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
65
65
  | `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
66
66
  | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 16 |
67
67
  | `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
68
- | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 69 |
68
+ | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 71 |
69
69
  | `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
70
70
  | `@stacksjs/path` | [`stacks-path`](../stacks-path/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/path/src/index.ts) | 13 |
71
71
  | `@stacksjs/payments` | [`stacks-payments`](../stacks-payments/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/payments/src/index.ts) | 28 |
72
72
  | `@stacksjs/push` | [`stacks-push`](../stacks-push/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/push/src/index.ts) | 6 |
73
73
  | `@stacksjs/query-builder` | [`stacks-query-builder`](../stacks-query-builder/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/query-builder/src/index.ts) | 6 |
74
- | `@stacksjs/queue` | [`stacks-queue`](../stacks-queue/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/index.ts) | 48 |
74
+ | `@stacksjs/queue` | [`stacks-queue`](../stacks-queue/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/index.ts) | 49 |
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) | 12 |
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) | 94 |
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 |
78
+ | `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 96 |
79
+ | `@stacksjs/scheduler` | [`stacks-scheduler`](../stacks-scheduler/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/index.ts) | 14 |
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 |
82
- | `@stacksjs/server` | [`stacks-server`](../stacks-server/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/index.ts) | 24 |
82
+ | `@stacksjs/server` | [`stacks-server`](../stacks-server/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/index.ts) | 25 |
83
83
  | `@stacksjs/shell` | [`stacks-shell`](../stacks-shell/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/shell/src/index.ts) | 1 |
84
84
  | `@stacksjs/sites` | [`stacks-sites`](../stacks-sites/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sites/src/index.ts) | 3 |
85
85
  | `@stacksjs/skills` | [`stacks-skills`](../stacks-skills/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/skills/src/index.ts) | 1 |
@@ -119,7 +119,7 @@ Declared runtime exports: `BlogAdminError`, `DEFAULT_IOS_DEPENDENCIES`, `DEFAULT
119
119
 
120
120
  Local source modules: [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/action.ts), [src/add.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/add.ts), [src/blog-admin.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/blog-admin.ts), [src/build/ios-toolchain.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/build/ios-toolchain.ts), [src/commit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/commit.ts), [src/dev/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/dev/index.ts), [src/discover-packages.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/discover-packages.ts), [src/generate/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/generate/index.ts), [src/github-release.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/github-release.ts), [src/helpers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/helpers/index.ts), [src/library/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/library/index.ts), [src/lint/lint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/lint.ts), [src/lint/stx-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/lint/stx-gate.ts), [src/make-command.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-command.ts), [src/make-job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-job.ts), [src/make-policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-policy.ts), [src/make-resource.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make-resource.ts), [src/make.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/make.ts), [src/publication-preflight.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/publication-preflight.ts), [src/release-evidence.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/release-evidence.ts), [src/scaffold-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/scaffold-crud.ts), [src/setup/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/setup/index.ts), [src/stacks.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stacks.ts), [src/stx-source.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/stx-source.ts), [src/upgrade/package-project.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/package-project.ts), [src/upgrade/route-actions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/src/upgrade/route-actions.ts).
121
121
 
122
- Evidence directory: [82 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
122
+ Evidence directory: [84 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/actions/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
123
123
 
124
124
  ### @stacksjs/ai
125
125
 
@@ -215,7 +215,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
215
215
 
216
216
  Local source modules: [src/commands/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/commands/index.ts).
217
217
 
218
- Evidence directory: [159 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
218
+ Evidence directory: [161 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
219
219
 
220
220
  ### @stacksjs/build
221
221
 
@@ -347,11 +347,11 @@ Evidence directory: [4 retained tests](https://github.com/stacksjs/stacks/blob/m
347
347
 
348
348
  Guidance: [`stacks-database`](../stacks-database/SKILL.md). Source: [storage/framework/core/database/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts).
349
349
 
350
- Declared runtime exports: `BUN_BROKEN_MYSQL_POOL_MESSAGE`, `BUN_BROKEN_POSTGRES_POOL_MESSAGE`, `Database`, `EntityQueryBuilder`, `__flushAfterCommitNow`, `__pendingAfterCommitCount`, `addColumnSafely`, `auditForeignKeys`, `auditSchemaDrift`, `auditUniqueIndexes`, `backfillInBatches`, `buildKey`, `classifyDeclaredFKs`, `comparableIndexKey`, `createDatabase`, `createDynamo`, `createMysqlDatabase`, `createPostgresDatabase`, `createQueryBuilder`, `createSqliteDatabase`, `createTursoDatabase`, `describeIgnoreRule`, `detectDriver`, `driverDefaults`, `dynamo`, `enqueueAfterCommit`, `ensureNotificationForeignKeys`, `findFkOrphans`, `findIgnoredMigrations`, `fkKey`, `formatIgnoredMigrations`, `formatSchemaDrift`, `generateKeyPattern`, `getBrokenDatabasePools`, `getConfigFromEnv`, `getConnectionString`, `getDeclaredFKs`, `getDeclaredUniques`, `getLiveFKs`, `getLiveTables`, `getLiveUniqueIndexes`, `isBrokenBunPoolError`, `isInTransaction`, `logQuery`, `marshall`, `matchedRows`, `mergeWithDefaults`, `migrateNotificationTables`, `migrateRbacTables`, `mutationCount`, `parseKeyPattern`, `postgresIndexStatement`, `renameColumnSafely`, `runInTransactionScope`, `setConfig`, `setQueryTracker`, `trackIgnoredGeneratedMigrations`, `unmarshall`, `validateDriverConfig`.
350
+ Declared runtime exports: `BUN_BROKEN_MYSQL_POOL_MESSAGE`, `BUN_BROKEN_POSTGRES_POOL_MESSAGE`, `Database`, `EntityQueryBuilder`, `__flushAfterCommitNow`, `__pendingAfterCommitCount`, `addColumnSafely`, `auditForeignKeys`, `auditSchemaDrift`, `auditUniqueIndexes`, `backfillInBatches`, `buildKey`, `classifyDeclaredFKs`, `comparableIndexKey`, `createDatabase`, `createDynamo`, `createMysqlDatabase`, `createPostgresDatabase`, `createQueryBuilder`, `createSqliteDatabase`, `createTursoDatabase`, `describeIgnoreRule`, `detectDriver`, `driverDefaults`, `dynamo`, `enqueueAfterCommit`, `ensureNotificationForeignKeys`, `findFkOrphans`, `findIgnoredMigrations`, `fkKey`, `formatIgnoredMigrations`, `formatSchemaDrift`, `frameworkRouteAdmission`, `generateKeyPattern`, `getBrokenDatabasePools`, `getConfigFromEnv`, `getConnectionString`, `getDeclaredFKs`, `getDeclaredUniques`, `getLiveFKs`, `getLiveTables`, `getLiveUniqueIndexes`, `isBrokenBunPoolError`, `isInTransaction`, `logQuery`, `marshall`, `matchedRows`, `mergeWithDefaults`, `migrateNotificationTables`, `migrateRbacTables`, `mutationCount`, `parseKeyPattern`, `postgresIndexStatement`, `renameColumnSafely`, `runInTransactionScope`, `setConfig`, `setQueryTracker`, `trackIgnoredGeneratedMigrations`, `unmarshall`, `validateDriverConfig`.
351
351
 
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).
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/framework-route-admission.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-route-admission.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: [126 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.
354
+ Evidence directory: [130 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
 
@@ -633,7 +633,7 @@ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`
633
633
 
634
634
  Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
635
635
 
636
- Evidence directory: [69 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
636
+ Evidence directory: [71 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
637
637
 
638
638
  ### @stacksjs/pagination
639
639
 
@@ -687,7 +687,7 @@ Declared runtime exports: `Batch`, `DispatchedBatch`, `FailedJobNotifier`, `JOB_
687
687
 
688
688
  Local source modules: [src/action-runner.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action-runner.ts), [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action.ts), [src/batch.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/batch.ts), [src/bun-queue.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/bun-queue.ts), [src/circuit-breaker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/circuit-breaker.ts), [src/dead-letter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/dead-letter.ts), [src/discovery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/discovery.ts), [src/drivers/redis.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/drivers/redis.ts), [src/envelope.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/envelope.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/events.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/health.ts), [src/idempotency.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/idempotency.ts), [src/job-progress.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job-progress.ts), [src/job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job.ts), [src/notifications.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/notifications.ts), [src/poison.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/poison.ts), [src/scheduler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/scheduler.ts), [src/testing.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/testing.ts), [src/worker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/worker.ts).
689
689
 
690
- Evidence directory: [48 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
690
+ Evidence directory: [49 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
691
691
 
692
692
  ### @stacksjs/realtime
693
693
 
@@ -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`, `listRedirectRoutes`, `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`.
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`, `listRedirectRoutes`, `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`, `withRouteAdmission`, `withTraceId`, `wrapAction`.
723
723
 
724
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: [94 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.
726
+ Evidence directory: [96 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
 
@@ -733,7 +733,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
733
733
 
734
734
  Local source modules: [src/run.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/run.ts), [src/schedule.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/schedule.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/types.ts).
735
735
 
736
- Evidence directory: [13 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
736
+ Evidence directory: [14 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
737
737
 
738
738
  ### @stacksjs/search-engine
739
739
 
@@ -763,7 +763,7 @@ Declared runtime exports: `server`.
763
763
 
764
764
  Local source modules: [src/config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/config.ts), [src/controllers/base.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/controllers/base.ts), [src/imports.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/imports.ts), [src/maintenance.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/maintenance.ts), [src/proxy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/proxy.ts), [src/redirects.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/redirects.ts), [src/rewrites.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/rewrites.ts), [src/security-headers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/security-headers.ts), [src/shadowed-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/src/shadowed-defaults.ts).
765
765
 
766
- Evidence directory: [24 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
766
+ Evidence directory: [25 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/server/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
767
767
 
768
768
  ### @stacksjs/shell
769
769
 
@@ -81,9 +81,20 @@ Each sets an internal cron pattern. Once a timing method is called, the schedule
81
81
  | `weekly()` | `0 0 * * 0` | Sunday at midnight |
82
82
  | `monthly()` | `0 0 1 * *` | 1st of month at midnight |
83
83
  | `yearly()` / `annually()` | `0 0 1 1 *` | Jan 1 at midnight |
84
+ | `weekdays()` | `0 0 * * 1-5` | Monday to Friday |
84
85
  | `onDays(days: number[])` | `0 0 * * {days}` | e.g. `onDays([1,3,5])` => `0 0 * * 1,3,5` |
85
86
  | `at(time: string)` | `{min} {hr} * * *` | e.g. `at('14:30')` => `30 14 * * *` |
86
87
 
88
+ The patterns above are what each method answers on its own. Chained, they
89
+ compose on two independent axes and the order does not matter: the time of day
90
+ (`at`, `hourly`, `every*Minutes`) and the days it runs on (`daily`, `weekly`,
91
+ `monthly`, `yearly`, `weekdays`, `onDays`). Each method writes its own axis and
92
+ keeps the other, so `.at('09:00').onDays([2])` and `.onDays([2]).at('09:00')`
93
+ are both `0 9 * * 2`. Two calls on the same axis means the last one wins, so
94
+ `.monthly().weekly()` is weekly. A `cron()` expression replaces both axes, and
95
+ `everySecond()` leaves cron entirely (any cadence method after it returns to a
96
+ cron pattern).
97
+
87
98
  ### Configuration Methods (chainable, returns `this`)
88
99
 
89
100
  ```typescript
@@ -297,6 +297,8 @@ expect(pageAuditClean(report)).toBe(true) // no duplicate /api/ requests, failed
297
297
  buddy test # run all tests
298
298
  buddy test --unit # unit tests only
299
299
  buddy test --feature # feature tests only
300
+ buddy test tests/unit/auth.test.ts # just that file
301
+ buddy test storage/framework/core/scheduler/tests # just that directory
300
302
  bun run test # via npm script
301
303
  bun run test:ui # UI tests
302
304
  bun run test:coverage # with coverage
@@ -305,6 +307,18 @@ buddy test:page-audit --url /,/dashboard --base http://localhost:3000 # duplica
305
307
  buddy test:page-audit --url http://localhost:3000/pricing --mobile --json
306
308
  ```
307
309
 
310
+ Name a path to run only what is under it, file or directory, anywhere in the
311
+ repo. A path matching no test file is an error rather than a fallback to the
312
+ whole suite, which matters because the fallback used to report `0 fail` and
313
+ exit 0 for a file it never opened. `test:unit` and `test:feature` narrow a
314
+ named path to their own suite and say so when it holds none of their tests.
315
+
316
+ Running `bun test` directly needs the `./` prefix: bunfig sets
317
+ `[test] root = "tests"`, so `bun test storage/.../foo.test.ts` from the repo
318
+ root matches nothing and reports a clean run over no files, while
319
+ `bun test ./storage/.../foo.test.ts` works. `buddy test` adds the prefix for
320
+ you, so prefer it.
321
+
308
322
  `test:page-audit` exits 1 when any page has duplicates, failed same-origin requests or errors, or
309
323
  never settles.
310
324
 
@@ -23,9 +23,9 @@ export default defineModel({
23
23
  useUuid: true,
24
24
  useTimestamps: true, // defaults to true, `timestampable` used as an alias
25
25
  useSearch: {
26
- displayable: ['id', 'name', 'email'], // the fields to become d (defaults to all fields)
27
- searchable: ['name', 'email'], // the fields to become searchable (defaults to all fields)
28
- sortable: ['created_at', 'updated_at'], // the fields to become sortable (defaults to all fields)
26
+ displayable: ['id', 'name', 'email'], // the fields a hit returns
27
+ searchable: ['name', 'email'], // the fields a query matches against
28
+ sortable: ['created_at', 'updated_at'], // the fields results can be ordered by
29
29
  filterable: [],
30
30
  },
31
31
 
@@ -41,10 +41,10 @@ export default defineModel({
41
41
  useTimestamps: true, // defaults to true, `timestampable` used as an alias
42
42
  useSocials: ['github'],
43
43
  useSearch: {
44
- displayable: ['id', 'name', 'email'], // the fields to become d (defaults to all fields)
45
- searchable: ['name', 'email'], // the fields to become searchable (defaults to all fields)
46
- sortable: ['created_at', 'updated_at'], // the fields to become sortable (defaults to all fields)
47
- filterable: [], // the fields to become filterable (defaults to all fields)
44
+ displayable: ['id', 'name', 'email'], // the fields a hit returns
45
+ searchable: ['name', 'email'], // the fields a query matches against
46
+ sortable: ['created_at', 'updated_at'], // the fields results can be ordered by
47
+ filterable: [], // the fields results can be narrowed by
48
48
  // options: {}, // you may pass options to the search engine
49
49
  },
50
50
 
package/bootstrap.ts CHANGED
@@ -23,7 +23,8 @@
23
23
 
24
24
  import { config, feature } from '@stacksjs/config'
25
25
  import { frameworkPath } from '@stacksjs/path'
26
- import { bundleMounts, DEFAULT_ROUTE_BUNDLE_FEATURES, route } from '@stacksjs/router'
26
+ import { log } from '@stacksjs/logging/runtime'
27
+ import { bundleMounts, DEFAULT_ROUTE_BUNDLE_FEATURES, route, withRouteAdmission } from '@stacksjs/router'
27
28
  import MaintenanceMiddleware from './app/Middleware/Maintenance'
28
29
 
29
30
  // Global maintenance / coming-soon gate. Registered first so the
@@ -71,116 +72,135 @@ function mounts(bundle: keyof typeof DEFAULT_ROUTE_BUNDLE_FEATURES): boolean {
71
72
  return bundleMounts(selection, bundle, feature(DEFAULT_ROUTE_BUNDLE_FEATURES[bundle]))
72
73
  }
73
74
 
74
- // Auth: login, registration, logout, refresh/revoke, passkeys, TOTP 2FA and
75
- // password reset. Split out of dashboard.ts so it can be mounted on its own
76
- // (stacksjs/stacks#2229) — previously the only way to get `/login` was to
77
- // activate `dashboard` and take the storefront, reviews, AI and voice surface
78
- // with it.
79
- //
80
- // Registered BEFORE dashboard.ts, which is where these lived, so the
81
- // first-registration-wins order among framework routes is unchanged.
82
- //
83
- // Deliberately NOT gated on `feature('auth')`, despite the issue asking for
84
- // it: `config/auth.ts` ships in every app with `enabled: true`, so that gate
85
- // is true everywhere and would mount the auth surface — including
86
- // `/generate-two-factor-secret`, `/logout-all` and `/auth/tokens` — in apps
87
- // currently running with `dashboard` off. Widening an app's public surface on
88
- // upgrade is not something a refactor gets to do silently.
89
- if (mounts('auth'))
90
- await route.register(frameworkPath('defaults/routes/auth.ts'))
91
-
92
- // The rest of dashboard.ts: email subscribe, storefront cart/checkout,
93
- // reviews, sitemap, AI, voice, and the admin dashboard's REST surface. Still
94
- // one file and still one gate — splitting auth out was the case with a
95
- // reporter behind it; marketing.ts / commerce.ts / monitoring.ts remain the
96
- // obvious next cuts.
97
- //
98
- // Apps that need only a slice can also define the routes they want directly
99
- // in `routes/api.ts` — first-registration-wins means the user version takes
100
- // priority.
101
- if (mounts('dashboard')) {
102
- await route.register(frameworkPath('defaults/routes/dashboard.ts'))
103
- // JSON endpoints for the dev dashboard UI. Kept separate from the view
104
- // routes above so the data layer is one obvious file to grep.
105
- await route.register(frameworkPath('defaults/routes/dashboard-api.ts'))
106
- // The dev dashboard boots through this file rather than the application
107
- // router's importRoutes() path, so load model-declared useApi routes here too.
75
+ /**
76
+ * Every default bundle this app mounts, registered in one place so the
77
+ * admission check below covers all of them.
78
+ */
79
+ async function registerDefaultBundles(): Promise<void> {
80
+ // Auth: login, registration, logout, refresh/revoke, passkeys, TOTP 2FA and
81
+ // password reset. Split out of dashboard.ts so it can be mounted on its own
82
+ // (stacksjs/stacks#2229) — previously the only way to get `/login` was to
83
+ // activate `dashboard` and take the storefront, reviews, AI and voice surface
84
+ // with it.
85
+ //
86
+ // Registered BEFORE dashboard.ts, which is where these lived, so the
87
+ // first-registration-wins order among framework routes is unchanged.
108
88
  //
109
- // Package first, vendored second, matching importRoutes() and start.ts. This
110
- // line used to import the vendored copy only, and nothing re-vendors that
111
- // file: `@stacksjs/orm` publishes `dist/routes.js` and no `routes.ts`, so an
112
- // app kept whatever generator its copy froze at however often it upgraded.
113
- // An old enough copy compares route paths literally, so a generated
114
- // `PATCH /api/sites/{id}` does not recognise a hand-written
115
- // `/api/sites/{siteId}` as the same endpoint and registers alongside it,
116
- // and the hand-written handler's authorization check stops running
117
- // (stacksjs/stacks#2364).
89
+ // Deliberately NOT gated on `feature('auth')`, despite the issue asking for
90
+ // it: `config/auth.ts` ships in every app with `enabled: true`, so that gate
91
+ // is true everywhere and would mount the auth surface — including
92
+ // `/generate-two-factor-secret`, `/logout-all` and `/auth/tokens` — in apps
93
+ // currently running with `dashboard` off. Widening an app's public surface on
94
+ // upgrade is not something a refactor gets to do silently.
95
+ if (mounts('auth'))
96
+ await route.register(frameworkPath('defaults/routes/auth.ts'))
97
+
98
+ // The rest of dashboard.ts: email subscribe, storefront cart/checkout,
99
+ // reviews, sitemap, AI, voice, and the admin dashboard's REST surface. Still
100
+ // one file and still one gate — splitting auth out was the case with a
101
+ // reporter behind it; marketing.ts / commerce.ts / monitoring.ts remain the
102
+ // obvious next cuts.
118
103
  //
119
- // Held in a variable so the specifier resolves at runtime rather than while
120
- // transpiling, where an unresolvable literal would fail this module instead
121
- // of throwing where it can be caught.
122
- const ormRoutesPackage = '@stacksjs/orm/routes'
123
- try {
124
- await import(ormRoutesPackage)
104
+ // Apps that need only a slice can also define the routes they want directly
105
+ // in `routes/api.ts` — first-registration-wins means the user version takes
106
+ // priority.
107
+ if (mounts('dashboard')) {
108
+ await route.register(frameworkPath('defaults/routes/dashboard.ts'))
109
+ // JSON endpoints for the dev dashboard UI. Kept separate from the view
110
+ // routes above so the data layer is one obvious file to grep.
111
+ await route.register(frameworkPath('defaults/routes/dashboard-api.ts'))
112
+ // The dev dashboard boots through this file rather than the application
113
+ // router's importRoutes() path, so load model-declared useApi routes here too.
114
+ //
115
+ // Package first, vendored second, matching importRoutes() and start.ts. This
116
+ // line used to import the vendored copy only, and nothing re-vendors that
117
+ // file: `@stacksjs/orm` publishes `dist/routes.js` and no `routes.ts`, so an
118
+ // app kept whatever generator its copy froze at however often it upgraded.
119
+ // An old enough copy compares route paths literally, so a generated
120
+ // `PATCH /api/sites/{id}` does not recognise a hand-written
121
+ // `/api/sites/{siteId}` as the same endpoint and registers alongside it,
122
+ // and the hand-written handler's authorization check stops running
123
+ // (stacksjs/stacks#2364).
124
+ //
125
+ // Held in a variable so the specifier resolves at runtime rather than while
126
+ // transpiling, where an unresolvable literal would fail this module instead
127
+ // of throwing where it can be caught.
128
+ const ormRoutesPackage = '@stacksjs/orm/routes'
129
+ try {
130
+ await import(ormRoutesPackage)
131
+ }
132
+ catch {
133
+ await import(frameworkPath('orm/routes.ts'))
134
+ }
125
135
  }
126
- catch {
127
- await import(frameworkPath('orm/routes.ts'))
136
+
137
+ // Email webhook + unsubscribe routes (stacksjs/stacks#1881, #1880).
138
+ // Always mounted when the `email` feature is on — the underlying
139
+ // handlers self-disable when their provider credentials aren't
140
+ // configured (they 401 rather than processing). Apps that need a
141
+ // non-default mount path register their own routes in `routes/api.ts`
142
+ // and the framework's mount silently no-ops since user routes
143
+ // register first.
144
+ if (mounts('email')) {
145
+ await route.register(frameworkPath('defaults/routes/email.ts'))
128
146
  }
129
- }
130
147
 
131
- // Email webhook + unsubscribe routes (stacksjs/stacks#1881, #1880).
132
- // Always mounted when the `email` feature is on — the underlying
133
- // handlers self-disable when their provider credentials aren't
134
- // configured (they 401 rather than processing). Apps that need a
135
- // non-default mount path register their own routes in `routes/api.ts`
136
- // and the framework's mount silently no-ops since user routes
137
- // register first.
138
- if (mounts('email')) {
139
- await route.register(frameworkPath('defaults/routes/email.ts'))
140
- }
148
+ // Form-builder public endpoints (`@stacksjs/forms`). Gated on the `forms`
149
+ // feature, which defaults OFF - an app opts in with config/forms.ts
150
+ // (`buddy forms:install`) and only then do the public submit routes exist.
151
+ if (mounts('forms')) {
152
+ await route.register(frameworkPath('defaults/routes/forms.ts'))
153
+ }
141
154
 
142
- // Form-builder public endpoints (`@stacksjs/forms`). Gated on the `forms`
143
- // feature, which defaults OFF - an app opts in with config/forms.ts
144
- // (`buddy forms:install`) and only then do the public submit routes exist.
145
- if (mounts('forms')) {
146
- await route.register(frameworkPath('defaults/routes/forms.ts'))
147
- }
155
+ // Courier delivery endpoints: position ingest and the stop/route lifecycle a
156
+ // courier's device drives. Gated with the rest of the commerce surface, since
157
+ // they are meaningless without the delivery models behind them.
158
+ if (mounts('delivery')) {
159
+ await route.register(frameworkPath('defaults/routes/delivery.ts'))
160
+ }
148
161
 
149
- // Courier delivery endpoints: position ingest and the stop/route lifecycle a
150
- // courier's device drives. Gated with the rest of the commerce surface, since
151
- // they are meaningless without the delivery models behind them.
152
- if (mounts('delivery')) {
153
- await route.register(frameworkPath('defaults/routes/delivery.ts'))
154
- }
162
+ // The payment provider's webhook, which applies payments and refunds to
163
+ // orders. Gated with the commerce surface; it answers 401 until the driver's
164
+ // webhook secret is configured.
165
+ if (mounts('payments')) {
166
+ await route.register(frameworkPath('defaults/routes/payments.ts'))
167
+ }
155
168
 
156
- // The payment provider's webhook, which applies payments and refunds to
157
- // orders. Gated with the commerce surface; it answers 401 until the driver's
158
- // webhook secret is configured.
159
- if (mounts('payments')) {
160
- await route.register(frameworkPath('defaults/routes/payments.ts'))
161
- }
169
+ // The page a Paddle payment link opens: Paddle has no hosted checkout, so
170
+ // its default payment link has to point at a page of the app's that loads
171
+ // Paddle.js. Mounted while the app pays through Paddle, as part of the
172
+ // `payments` bundle when the app names its bundles.
173
+ const paymentDriverName = (config as { payment?: { driver?: string } }).payment?.driver
174
+ if (paymentDriverName === 'paddle' && (selection?.explicit ? selection.bundles.has('payments') : true)) {
175
+ await route.register(frameworkPath('defaults/routes/paddle.ts'))
176
+ }
162
177
 
163
- // The page a Paddle payment link opens: Paddle has no hosted checkout, so
164
- // its default payment link has to point at a page of the app's that loads
165
- // Paddle.js. Mounted while the app pays through Paddle, as part of the
166
- // `payments` bundle when the app names its bundles.
167
- const paymentDriverName = (config as { payment?: { driver?: string } }).payment?.driver
168
- if (paymentDriverName === 'paddle' && (selection?.explicit ? selection.bundles.has('payments') : true)) {
169
- await route.register(frameworkPath('defaults/routes/paddle.ts'))
178
+ // Social sign-in: `/auth/{provider}` + `/auth/{provider}/callback`
179
+ // (stacksjs/stacks#2276). An opt-in bundle, NOT part of the implicit default
180
+ // set or `all` — OAuth callback URLs in an app that configured no provider
181
+ // are surface for nothing. So `mounts()` does not apply here: an app that
182
+ // NAMED its bundles decides outright (`social` listed → on, absent → off),
183
+ // and an app that said nothing gets it exactly when a provider is actually
184
+ // configured in config/services.ts — configuring GitHub and finding
185
+ // /auth/github dead would be the puzzle, not the mount.
186
+ const { configuredSocialProviders } = await import('@stacksjs/socials')
187
+ const mountSocial = selection?.explicit
188
+ ? selection.bundles.has('social')
189
+ : configuredSocialProviders().length > 0
190
+ if (mountSocial) {
191
+ await route.register(frameworkPath('defaults/routes/socials.ts'))
192
+ }
170
193
  }
171
194
 
172
- // Social sign-in: `/auth/{provider}` + `/auth/{provider}/callback`
173
- // (stacksjs/stacks#2276). An opt-in bundle, NOT part of the implicit default
174
- // set or `all` — OAuth callback URLs in an app that configured no provider
175
- // are surface for nothing. So `mounts()` does not apply here: an app that
176
- // NAMED its bundles decides outright (`social` listed → on, absent → off),
177
- // and an app that said nothing gets it exactly when a provider is actually
178
- // configured in config/services.ts — configuring GitHub and finding
179
- // /auth/github dead would be the puzzle, not the mount.
180
- const { configuredSocialProviders } = await import('@stacksjs/socials')
181
- const mountSocial = selection?.explicit
182
- ? selection.bundles.has('social')
183
- : configuredSocialProviders().length > 0
184
- if (mountSocial) {
185
- await route.register(frameworkPath('defaults/routes/socials.ts'))
195
+ // Register the bundles, withholding any route whose framework action depends
196
+ // on a model this app excluded from its schema and whose table its database
197
+ // does not have. Those routes could only answer 500 (stacksjs/stacks#2943); now
198
+ // they are absent, and boot says which and why. When the table list cannot be
199
+ // read, nothing is withheld.
200
+ const { frameworkRouteAdmission } = await import('@stacksjs/database')
201
+ const withheld = await withRouteAdmission(await frameworkRouteAdmission(), registerDefaultBundles)
202
+ if (withheld.length > 0) {
203
+ const shown = withheld.slice(0, 5).map(r => `${r.method} ${r.path} -> ${r.handler} needs ${r.reason}`)
204
+ const more = withheld.length > shown.length ? `\n ... and ${withheld.length - shown.length} more` : ''
205
+ log.warn(`[Routes] Not registering ${withheld.length} framework route(s) whose models are not in this app's schema (database.models.includeFrameworkDefaults is false) and whose tables do not exist:\n ${shown.join('\n ')}${more}\nPublish a model with \`buddy publish model <Name>\` or set includeFrameworkDefaults to serve them.`)
186
206
  }
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.261",
5
+ "version": "0.75.263",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.261",
5
+ "version": "0.75.263",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,9 +45,9 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.261",
49
- "@stacksjs/sanitizer": "0.2.656",
50
- "@stacksjs/stx": "0.2.656",
48
+ "@stacksjs/mobile": "0.75.263",
49
+ "@stacksjs/sanitizer": "0.2.661",
50
+ "@stacksjs/stx": "0.2.661",
51
51
  "@xterm/addon-fit": "^0.11.0",
52
52
  "@xterm/xterm": "^6.0.0",
53
53
  "ts-qr-codes": "^0.1.8"