@stacksjs/defaults 0.75.460 → 0.75.463

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.
@@ -20,7 +20,7 @@ Server actions are reusable business logic units invoked from routes, events, or
20
20
 
21
21
  ```typescript
22
22
  // app/Actions/CreateWidget.ts
23
- import { Action } from '@stacksjs/actions'
23
+ import { Action } from '@stacksjs/actions/runtime'
24
24
  import { toSnakeCaseKeys } from '@stacksjs/orm'
25
25
  import { response } from '@stacksjs/router'
26
26
 
@@ -57,7 +57,7 @@ the rules used by an explicit `request.validate()` call; do not assume attaching
57
57
  a model means every custom handler has already validated all its input.
58
58
 
59
59
  ```ts
60
- import { Action } from '@stacksjs/actions'
60
+ import { Action } from '@stacksjs/actions/runtime'
61
61
  import { schema } from '@stacksjs/validation'
62
62
 
63
63
  export default new Action({
@@ -222,17 +222,20 @@ response.json({ message: 'OK' })
222
222
  response.json(data, { status: 200, headers: { 'X-Custom': 'value' }, pretty: true })
223
223
 
224
224
  // Status shortcuts
225
- response.created({ id: 1 }) // 201
225
+ response.success(data, 'Saved') // 200, { success: true, data, message }
226
+ response.created({ id: 1 }) // 201, { success: true, data }
226
227
  response.noContent() // 204
227
- response.badRequest({ error: 'Bad' }) // 400
228
- response.unauthorized() // 401 (default: { error: 'Unauthorized' })
229
- response.forbidden() // 403 (default: { error: 'Forbidden' })
230
- response.notFound() // 404 (default: { error: 'Not Found' })
231
- response.error() // 500 (default: { error: 'Internal Server Error' })
228
+ response.badRequest('Bad', errors?) // 400
229
+ response.unauthorized() // 401
230
+ response.forbidden() // 403
231
+ response.notFound() // 404
232
+ response.validationError(errors) // 422
233
+ response.error('Conflict', 409) // (message, status = 400, errors?)
234
+ response.serverError() // 500
232
235
 
233
236
  // Other content types
234
- response.text('Hello', { status: 200 })
235
- response.html('<h1>Hello</h1>')
237
+ response.text('Hello', 200)
238
+ response.view('<h1>Hello</h1>') // HTML; there is no response.html()
236
239
  response.redirect('/new-url', 302) // 301 | 302 | 303 | 307 | 308
237
240
  ```
238
241
 
@@ -102,7 +102,7 @@ cms.posts.fetchById(id: number): Promise<PostJsonResponse | undefined>
102
102
  cms.posts.fetchAll(): Promise<PostJsonResponse[]>
103
103
  cms.posts.fetchByStatus(status: 'published' | 'draft' | 'archived'): Promise<PostJsonResponse[]>
104
104
  cms.posts.fetchByCategory(category: string): Promise<PostJsonResponse[]>
105
- cms.posts.fetchByAuthor(author: string): Promise<PostJsonResponse[]>
105
+ cms.posts.fetchByAuthor(authorId: number): Promise<PostJsonResponse[]>
106
106
  cms.posts.fetchByMinViews(minViews: number): Promise<PostJsonResponse[]>
107
107
  cms.posts.fetchPublishedAfter(timestamp: number): Promise<PostJsonResponse[]>
108
108
 
@@ -128,8 +128,10 @@ cms.authors.findByName(name: string): Promise<AuthorJsonResponse | undefined>
128
128
  cms.authors.findByUserId(userId: number): Promise<AuthorJsonResponse | undefined>
129
129
  cms.authors.fetchAll(): Promise<AuthorJsonResponse[]>
130
130
  cms.authors.fetchById(id: number): Promise<AuthorJsonResponse | undefined>
131
- cms.authors.update(id, data): Promise<AuthorJsonResponse>
132
- cms.authors.destroy(id): Promise<boolean>
131
+ cms.authors.findByUuid(uuid: string): Promise<AuthorJsonResponse | undefined>
132
+ cms.authors.update(id, data): Promise<AuthorJsonResponse | undefined>
133
+ cms.authors.destroy(id): Promise<AuthorJsonResponse>
134
+ cms.authors.destroyMany(ids: number[]): Promise<number>
133
135
  ```
134
136
 
135
137
  ## Tags API
@@ -92,7 +92,7 @@ buddy/src/commands/
92
92
  - `buddy dev:system-tray` (alias: `dev:tray`) -- System tray dev server
93
93
 
94
94
  ### Build Commands
95
- - `buddy build` -- Build stacks framework (default when no option specified)
95
+ - `buddy build` -- Interactive target picker in a terminal; with no TTY (CI) or no pick it builds the frontend views. The framework builds only with `--stacks` / `build:stacks`
96
96
  - `buddy build components` -- Build component libraries
97
97
  - `buddy build docs` -- Build documentation site
98
98
  - `buddy build server` (alias: `build:docker`) -- Build Docker server image
@@ -132,7 +132,7 @@ buddy queue # queue management
132
132
  - Prefer `default new Job({...})` for dispatch methods. Discovery also supports default objects with handle and class exports with a handle method
133
133
  - The `handle()` method receives the payload passed to `dispatch()`
134
134
  - `dispatchNow()` runs immediately in the current process - no queue involved
135
- - Default queue driver is `sync` - jobs run immediately unless changed to `database` or `redis`
135
+ - Default queue driver is `sync` locally and `database` when `APP_ENV=production` (`QUEUE_DRIVER` overrides both). On `sync`, `dispatchAfter` blocks the caller for the delay
136
136
  - Jobs with `rate` are auto-discovered by the scheduler
137
137
  - Backoff array `[10, 30, 60]` means: retry after 10s, then 30s, then 60s
138
138
  - Jobs should be idempotent - safe to retry on failure
@@ -148,6 +148,9 @@ driver. `.withIdempotencyKey(key)` claims a persistent dispatch key.
148
148
  Inside `db.transaction()` dispatch defers until commit by default;
149
149
  `.afterCommit()` states that intent, and `.withoutCommit()` explicitly opts
150
150
  out. Outside a transaction, afterCommit warns and dispatches immediately.
151
+ Only the fluent builder is transaction-aware: `SomeJob.dispatch()` on a
152
+ `new Job(...)` instance sends at once, even inside a transaction, so use
153
+ `job(name, payload)` there.
151
154
  See `stacks-queue` for the complete envelope and batch lifecycle.
152
155
 
153
156
  Scheduling a named job through `schedule.job()` runs it in the scheduler by
@@ -1,83 +1,503 @@
1
1
  ---
2
2
  name: stacks-native
3
- description: Use when planning or implementing a Stacks feature, choosing a native API, or checking which framework capability already solves a task. Covers the complete native package catalog, model-driven workflows, feature gates, driver evidence, and cross-package gotchas.
3
+ description: Use when planning or implementing a Stacks feature, choosing a native API, or checking which framework capability already solves a task. A verified cheat sheet of the idiomatic call for every subsystem (models, queries, actions, routes, auth, jobs, events, mail, cache, storage, search, CMS, commerce, AI, stx, CLI, tests, deploy) plus the native package catalog, driver evidence and cross-package gotchas.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
7
7
  ---
8
8
 
9
- # Native Stacks capabilities
10
-
11
- Start with the native feature that already owns the behavior. Read the selected
12
- subsystem skill before implementing its API. [CATALOG.md](CATALOG.md) maps every
13
- core package to task guidance and its current source entrypoint; browse the
14
- relevant section rather than loading the whole reference.
15
-
16
- ## Choose a workflow
17
-
18
- | Need | Native workflow |
19
- |---|---|
20
- | Persist a new entity and expose CRUD | `stacks-models`, `stacks-migrations`, `stacks-orm`, `stacks-actions`, `stacks-router` |
21
- | Extend a built-in model without forking it | `extendModel`, then model-driven migration generation |
22
- | Typed requests and an inferred API client | `Action.validations`, directly registered actions, `createTypedRouter` and `createTypedClient` |
23
- | Authenticate or authorize rows | `stacks-auth`, model API ownership, middleware, policies and gates |
24
- | Render interactive pages and validate forms | `stacks-stx`, `stacks-composables`, `stacks-forms`, `stacks-ui` |
25
- | Queue work or run it later | `stacks-jobs`, `stacks-queue`, `stacks-scheduler` |
26
- | Send mail, notifications, SMS or push | `stacks-mail`, `stacks-email`, `stacks-notifications`, `stacks-sms`, `stacks-push` |
27
- | Publish CMS pages or run commerce | `stacks-cms`, `stacks-commerce`, `stacks-payments` |
28
- | Build sites, newsletters, charts or media | `stacks-sites`, `stacks-newsletter`, `stacks-charts`, `stacks-image`, `stacks-audio`, `stacks-video` |
29
- | Paginate, cache, search or store files | `stacks-pagination`, `stacks-cache`, `stacks-search-engine`, `stacks-storage` |
30
- | Enable an optional bundle or run an experiment | `stacks-features` for framework bundles, `stacks-feature-flags` for user-targeted flags |
31
- | Build, deploy or operate an app | `stacks-build`, `stacks-deploy`, `stacks-cloud`, `stacks-server`, `stacks-buddy` |
32
-
33
- ## Model-first implementation
34
-
35
- Declare schema, validators, fillable fields, relationships, traits, and ownership
36
- in `app/Models/`. `defineModel()` produces the query surface immediately;
37
- `useApi` registers CRUD at runtime. It does not require generated ORM classes
38
- or generated action files. Add a custom Action only when the behavior differs
39
- from the native CRUD contract.
40
-
41
- Run `./buddy generate:migrations`, review the SQL and dialect snapshot, then
42
- run it again to confirm no further diff. Apply with `./buddy migrate`.
43
- Keep models, generated migrations, and the snapshot together in review.
44
- `migrate:fresh` destroys tables and is a separate development operation.
45
-
46
- See [RECIPES.md](RECIPES.md) for an end-to-end native tracer bullet and
47
- the cases that require an action, job, policy, or explicit route.
48
-
49
- ## Important boundaries
50
-
51
- - Before adding imports in app code, consult the context-specific names in
52
- `storage/framework/auto-imports/RUNTIME.md` at the project root. Use listed
53
- runtime helpers directly; read `stacks-auto-imports` for initialization and
54
- module boundaries. Generation refreshes the inventory for this app.
55
- - `app/` overrides framework defaults. Use `extendModel` for additive model
56
- changes; an ordinary override replaces the definition.
57
- - Generated CRUD defaults to authenticated routes. Ownership and policies
58
- govern rows separately; unscoped writes may be withheld by the default
59
- row-scoping policy. Do not weaken that policy to make an example work.
60
- - Transactional queries use the supplied `tx` handle. Model calls do not
61
- automatically join that transaction's connection.
62
- - Optional bundles control model/migration/view selection. Route mounting has
63
- its own registry and default-bundle selection; a package being installed
64
- does not mean every route is public or mounted.
65
- - Browser declarations, stx eager bindings, demand-inline bindings, and
66
- explicit imported composables are different surfaces. Read
67
- `stacks-auto-imports` before using an unfamiliar bare name.
68
- - Use request-time subpath exports when available. Tooling barrels may load
69
- migrations, config discovery, or native modules that a handler does not need.
70
- - A typed configuration or exported adapter is not proof of provider support.
71
- [CAPABILITIES.md](CAPABILITIES.md) is generated from the driver evidence
72
- registry, including partial and experimental limits.
73
-
74
- ## Completion and freshness
75
-
76
- Use the subsystem's actual signatures and retained tests, and verify the
77
- requested behavior. Run pickier through buddy, relevant tests, and the proper
78
- typecheck. UI changes also complete the design preflight.
79
-
80
- Within the framework repository, the native catalog and driver reference are generated by
81
- `./buddy docs:native-skills`; `./buddy docs:native-skills:check` detects missing
82
- package guidance and stale references. That check establishes coverage and
83
- source freshness, not semantic correctness of every prose sentence.
9
+ # Stacks, natively
10
+
11
+ Stacks already ships most of what an app needs. Find the row below, use that
12
+ idiom, and read the named skill before going past it. Each snippet was checked
13
+ against source. The **Gotcha** is the mistake that section most often causes.
14
+
15
+ Companion files, loaded only when needed:
16
+ [RECIPES.md](RECIPES.md) has end-to-end tracer bullets.
17
+ [CATALOG.md](CATALOG.md) maps every core package to its skill, entrypoint and
18
+ tests (browse one section, don't load all of it).
19
+ [CAPABILITIES.md](CAPABILITIES.md) records which drivers are supported, partial
20
+ or experimental.
21
+
22
+ ## Ten rules
23
+
24
+ 1. **Model first.** The schema, validation, relations, traits and factory all
25
+ live in one `defineModel()`. Migrations are generated from models, never
26
+ hand-written first.
27
+ 2. **`useApi` before a custom action.** It registers authenticated CRUD at
28
+ runtime. Write an Action only when the behavior differs from CRUD.
29
+ 3. **Override, don't fork.** A file at the same path under `app/` replaces
30
+ the default. Use `extendModel()` to add fields to a built-in model.
31
+ 4. **Don't import what Stacks auto-imports.** Actions, routes, jobs,
32
+ listeners, middleware, controllers, app commands and stx scripts use the
33
+ injected names bare (see [Auto-imports](#auto-imports)). Import only names
34
+ that aren't injected, and in the few files that load without a booted app.
35
+ 5. **When you do import, prefer runtime subpaths** such as
36
+ `@stacksjs/database/runtime` over full barrels. The barrels load tooling
37
+ that a request does not need.
38
+ 6. **Money is integer minor units** (cents), everywhere.
39
+ 7. **Writes in a transaction go through `tx`.** Model calls inside
40
+ `transaction()` do not join it.
41
+ 8. **Failures can be silent.** `mail.send`, `notify` and `dispatch` resolve
42
+ on failure, and every `log.*` call is async. Check results, use the
43
+ `OrFail`/`AndCollect` variants, and `await log.flush()` before exiting.
44
+ 9. **A config type is not driver support.** Check CAPABILITIES.md before
45
+ promising a provider.
46
+ 10. **Prove it.** Run `./buddy lint`, the relevant `./buddy test`, and
47
+ `./buddy typecheck`. UI work also has to pass the `stacks-design-taste`
48
+ preflight.
49
+
50
+ ## Auto-imports
51
+
52
+ The full list for this app is `storage/framework/auto-imports/RUNTIME.md`
53
+ (regenerate it with `./buddy generate:auto-imports`). The snippets below
54
+ follow it, so **a missing import is deliberate.**
55
+
56
+ - **Server, after boot:** every model (`User`, `Post`, ...) and every job
57
+ (`SendWelcomeEmail`, ...); `Action`, `route`, `response`, `request`,
58
+ `Middleware`, `url`, `schema`, `db`, `sql`, `log`, `config`, `path`, `HttpError`,
59
+ `Auth`, `dispatch`, `listen`, `mail`, `template`, `notify`, `emit`,
60
+ `emitToUser`, `createChannel`, `schedule`, `Feature`, `collect`, `t`,
61
+ `setLocale`, `makeHash`, `verifyHash`, `defineModel`; plus the exports of
62
+ `resources/functions/`.
63
+ - **`<script client>`:** signals (`state`, `derived`, `effect`, `watch`),
64
+ lifecycle (`onMount`, `onDestroy`), `navigate`, `toast`, `useFetch`,
65
+ `useQuery`, `useMutation`, `useColorMode`, `useDark`, `keptState`, and the
66
+ macros (`defineProps`, ...). On-demand helpers such as `useForm`,
67
+ `useScroll` and `copyToClipboard` are bundled when you call them.
68
+ - **Still import:**
69
+ - Names not in the list (`cache`, `env`, `Storage`, `transaction`, `job`,
70
+ `validate`, `defineEvents`, `defineGates`, `defineCommand`, ...).
71
+ - Type-only names, with `import type`.
72
+ - `Job` from `@stacksjs/queue`, because the global `Job` is the jobs-table
73
+ model.
74
+ - The `Error` and `Request` models, which are never made global.
75
+ - **Files that load without globals, so they import everything:**
76
+ - Model files, because `generate:migrations`, `migrate` and `seed` read
77
+ them without booting.
78
+ - Standalone scripts and framework packages.
79
+ - `.ts` modules imported by a client script.
80
+ - **Tests get the globals too:** `tests/setup.ts` runs
81
+ `await bootTestEnvironment()`, so bare-style actions and routes load under
82
+ `bun test`. An older app whose setup still calls `setupTestEnvironment()`
83
+ gets "Action is not defined" until it switches.
84
+
85
+ ## Data
86
+
87
+ ### Models (`stacks-models`, `stacks-orm`)
88
+
89
+ ```ts
90
+ // app/Models/Post.ts: imports stay, because migrate/generate/seed load models without globals
91
+ import { defineModel } from '@stacksjs/orm'
92
+ import { schema } from '@stacksjs/validation'
93
+
94
+ export default defineModel({
95
+ name: 'Post',
96
+ table: 'posts',
97
+ belongsTo: ['User'], // adds user_id; generated CRUD derives the owner from the session
98
+ hasMany: { comments: 'Comment' }, // the object form names the relation explicitly
99
+ traits: {
100
+ useUuid: true,
101
+ useSoftDeletes: true, // useTimestamps is on unless set to false
102
+ useApi: { uri: 'posts', routes: ['index', 'store', 'show', 'update', 'destroy'] },
103
+ useSearch: { searchable: ['title'], filterable: ['status'] },
104
+ useSeeder: { count: 20 },
105
+ observe: true, // emits post:created / post:updated / post:deleted
106
+ taggable: true,
107
+ commentable: true,
108
+ },
109
+ attributes: {
110
+ title: { fillable: true, required: true, validation: { rule: schema.string().max(120) }, factory: f => f.lorem.words(3) },
111
+ status: { fillable: true, default: 'draft', validation: { rule: schema.enum(['draft', 'published']) } },
112
+ secret: { hidden: true, guarded: true, encrypted: true },
113
+ },
114
+ casts: { meta: 'json' }, // casts sit at model level; attributes have no `cast` key
115
+ } as const)
116
+ ```
117
+
118
+ - **Other attribute keys:** `unique`, `nullable`, `type`, `order`, `foreignKey`, `personal`.
119
+ - **Other traits:** `useAuth`, `billable`, `likeable`, `categorizable`,
120
+ `useAudit`, `useActivityLog`, `prunable`, `gdpr`, `broadcastOn`/`broadcastWith`.
121
+ - **Add fields to a built-in model:**
122
+ `export default extendModel(Order, { attributes: { note: {...} } })`.
123
+ - **Gotcha:** a misspelled trait only logs a warning and is otherwise ignored.
124
+ Event names come from the lowercased model name with no separator, so
125
+ `StatusReport` emits `statusreport:created`.
126
+
127
+ ### Queries, transactions, pagination (`stacks-query-builder`, `stacks-database`, `stacks-pagination`)
128
+
129
+ ```ts
130
+ const post = await Post.find(id) // undefined if missing; findOrFail throws
131
+ const rows = await Post.where('status', 'published').with('user').orderBy('id', 'desc').get()
132
+ const created = await Post.create({ title })
133
+ await created.update({ title: 'New' }); await created.delete()
134
+ await Post.update(id, data); await Post.delete(id) // static forms: row | undefined, boolean
135
+ const page = await Post.where('status', 'published').paginate(20, 2) // (perPage, page)
136
+
137
+ import { transaction } from '@stacksjs/orm'
138
+ await transaction(async tx => tx.insertInto('posts').values({ title }).execute(), { retries: 2 })
139
+
140
+ await db.updateTable('posts').set({ status: 'archived' }).where('id', '=', id).execute() // raw builder (db is a global)
141
+ ```
142
+
143
+ - **More helpers:** `first`, `count`, `exists`, `pluck`, `whereIn`, `latest`,
144
+ `firstOrCreate`, `updateOrCreate`, `createMany`, `simplePaginate`,
145
+ `cursorPaginate(perPage, cursor, ['created_at', 'id'])`, `withoutEvents`.
146
+ - **Pagination defaults:** with no arguments, `paginate()` reads
147
+ `?page&per_page`, uses 15 per page, and caps `per_page` at 100.
148
+ - **Gotcha:** `paginate` takes `(perPage, page)`, the reverse of upstream.
149
+ `.where(...).update()` and `.delete()` on a builder return a count, not rows.
150
+
151
+ ### Migrations and seeding (`stacks-migrations`)
152
+
153
+ ```sh
154
+ ./buddy generate:migrations # diff models against the snapshot and write SQL; review it, then run again to confirm no diff
155
+ ./buddy migrate # apply (--diff previews; non-interactive runs need --generate to also generate)
156
+ ./buddy migrate:fresh --seed # dev only: drops everything
157
+ ./buddy seed # skips tables that already have rows unless --fresh
158
+ ./buddy migrate:status
159
+ ```
160
+
161
+ **Gotcha:** commit the model, its generated migration and the
162
+ `model-snapshot.<dialect>.json` together. A stale snapshot fails the next deploy.
163
+
164
+ ## HTTP
165
+
166
+ ### Actions (`stacks-actions`)
167
+
168
+ ```ts
169
+ // app/Actions/PublishPostAction.ts: Action, schema, response and Post are all globals
170
+ export default new Action({
171
+ name: 'Publish Post',
172
+ method: 'POST',
173
+ validations: { note: { rule: schema.string().max(200) } }, // validated before authorize/handle
174
+ async authorize(request) { return request.can('update', await Post.findOrFail(request.getParam('id'))) },
175
+ async handle(request) {
176
+ const post = await Post.findOrFail(request.getParam('id'))
177
+ await post.update({ status: 'published' })
178
+ return response.success(post)
179
+ },
180
+ })
181
+ ```
182
+
183
+ **Gotcha:** `model: Post` only feeds rules to an explicit
184
+ `await request.validate()`. It validates nothing by itself. An `authorize`
185
+ that returns `false` produces a 403.
186
+
187
+ ### Routes, middleware, responses (`stacks-router`, `stacks-middleware`, `stacks-api`)
188
+
189
+ ```ts
190
+ // routes/api.ts (mounted at /api automatically)
191
+ route.post('/posts/{id}/publish', 'Actions/PublishPostAction').middleware('auth')
192
+ route.group({ prefix: '/v1', middleware: ['auth'] }, () => {
193
+ route.get('/me', 'Actions/Auth/GetMeAction').name('me').rateLimit(60, 'minute')
194
+ })
195
+ route.resource('tags', 'Tag', { only: ['index', 'show'] })
196
+
197
+ // Typed end to end: register imported actions, infer the client
198
+ import { createTypedClient, createTypedRouter } from '@stacksjs/router'
199
+ export const api = createTypedRouter().get('/v1/posts', PostIndexAction).post('/v1/posts', PostStoreAction, { middleware: 'auth' })
200
+ const client = createTypedClient<typeof api>({ baseUrl })
201
+ ```
202
+
203
+ ```ts
204
+ // app/Middleware/EnsureBeta.ts (Middleware is a global): return nothing to continue, throw to stop
205
+ export default new Middleware({ name: 'EnsureBeta', priority: 5, async handle(request) { /* ... */ } })
206
+ // app/Middleware.ts
207
+ import { defineMiddleware } from '@stacksjs/router'
208
+ export default defineMiddleware({ beta: 'EnsureBeta' }) // merged over the built-in aliases
209
+ ```
210
+
211
+ - **Built-in middleware aliases:** `auth`, `guest`, `can:ability,model`,
212
+ `throttle`, `verified`, `role`, `permission`, `abilities`, `signed`, `csrf`,
213
+ `cors`, `site`, `env:production`, `maintenance`.
214
+ - **Responses:** `response.json(data, { status })`, `success(data, msg?)`,
215
+ `created(data)` (both wrap the payload as `{ success, data }`), `noContent()`,
216
+ `notFound(msg?)`, `forbidden()`, `unauthorized()`,
217
+ `validationError(errors)`, `error(msg, status = 400)`, `serverError()`,
218
+ `redirect(url)`, `text(s, status?)`, `view(html)`, `download(path)`.
219
+ Returning a plain object gives raw JSON.
220
+ - **Gotcha:** there is no `route.prefix()` (use `group({ prefix })`) and no
221
+ `response.html()` (use `view`). String action paths load lazily and don't
222
+ infer response types; pass the imported action when a typed client needs them.
223
+
224
+ ### Validation and errors (`stacks-validation`, `stacks-error-handling`)
225
+
226
+ ```ts
227
+ import { validate } from '@stacksjs/validation' // schema is a global, validate is not
228
+ const data = await validate(request, { email: schema.string().email().required() }) // 422 HttpError on failure
229
+
230
+ import { fromPromise } from '@stacksjs/error-handling'
231
+ const r = await fromPromise(fetchThing()); if (r.isErr) log.warn(r.error) // isOk / isErr are properties
232
+ throw new HttpError(409, 'Already exists') // HttpError is a global
233
+ ```
234
+
235
+ **Gotcha:** `validate()` returns only the declared fields that were sent.
236
+ Unknown fields are dropped silently.
237
+
238
+ ## Auth and access (`stacks-auth`)
239
+
240
+ ```ts
241
+ const session = await Auth.login({ email, password }) // { user, token, ... } | null
242
+ const user = await request.user() // set by the 'auth' middleware; undefined on public routes
243
+ const viewer = await Auth.user() // resolves the bearer token or cookie itself
244
+ const token = await Auth.createToken(user, 'cli', ['posts:read'])
245
+
246
+ // app/Gates.ts
247
+ import { defineGates } from '@stacksjs/auth'
248
+ export default defineGates({ gates: { 'edit-settings': user => user.isAdmin }, policies: { Post: 'PostPolicy' } })
249
+ // in an action
250
+ await request.authorize('update', post) // throws a 403
251
+ ```
252
+
253
+ - **2FA:** `generateTwoFactorSetup`, `enableTwoFactor`, `verifyTwoFactorLoginCode`.
254
+ - **Passkeys:** `beginPasskeyRegistration` / `finishPasskeyAuthentication`. In the
255
+ browser, import from `@stacksjs/auth/passkeys-browser`.
256
+ - **Gotcha:** an ability with no gate and no policy method is denied. Don't
257
+ loosen the default row-scoping policy to make an example work. Declare an
258
+ owner instead (see RECIPES.md).
259
+
260
+ ## Background work
261
+
262
+ ### Jobs and queues (`stacks-jobs`, `stacks-queue`)
263
+
264
+ ```ts
265
+ // app/Jobs/SendWelcomeEmail.ts: import Job, because the global Job is the jobs-table model
266
+ import { Job } from '@stacksjs/queue'
267
+ export default new Job({ name: 'SendWelcomeEmail', queue: 'emails', tries: 3, backoff: [10, 30, 60], async handle({ email }: { email: string }) { /* ... */ } })
268
+
269
+ await SendWelcomeEmail.dispatch({ email }) // global; also dispatchAfter(60, p), dispatchNow(p)
270
+ import { job } from '@stacksjs/queue'
271
+ await job('SendWelcomeEmail', { email }).delay(60).afterCommit().dispatch() // the transaction-aware builder
272
+ ```
273
+
274
+ **Gotcha:** only `job(...)` waits for a transaction to commit. Inside
275
+ `transaction()`, `Job.dispatch()` sends immediately. The driver is `sync`
276
+ locally and `database` in production, and durable drivers need a worker.
277
+
278
+ ### Scheduler (`stacks-scheduler`)
279
+
280
+ ```ts
281
+ // app/Scheduler.ts
282
+ export default function () {
283
+ schedule.job('Inspire').hourly().onQueue('default') // without onQueue it runs in the scheduler process
284
+ schedule.action('CleanupTempFiles').everyFiveMinutes().withoutOverlapping()
285
+ schedule.command('echo hi').dailyAt('02:00').onOneServer()
286
+ }
287
+ ```
288
+
289
+ **Gotcha:** keep each chain synchronous, because configuration applies in a
290
+ microtask.
291
+
292
+ ### Events and listeners (`stacks-events`, `stacks-listeners`)
293
+
294
+ ```ts
295
+ // app/Events.ts: values resolve against app/Listeners, then app/Actions
296
+ import { defineEvents } from '@stacksjs/events'
297
+ export default defineEvents({ 'user:registered': ['SendWelcomeEmail'] })
298
+ // app/Listeners/NotifyOps.ts
299
+ import { defineListener } from '@stacksjs/events'
300
+ export default defineListener({ listensTo: 'invoice:settled', handle: async (payload) => { /* ... */ } })
301
+
302
+ dispatch('invoice:settled', { id }) // fire and forget (global)
303
+ import { dispatchAndCollect } from '@stacksjs/events'
304
+ const results = await dispatchAndCollect('invoice:settled', { id }) // when you need listener failures
305
+ ```
306
+
307
+ **Gotcha:** the event function is `dispatch`. The `emit` global is realtime
308
+ broadcasting, not events.
309
+
310
+ ## Messaging
311
+
312
+ ```ts
313
+ // Mail (stacks-mail, stacks-email): mail, template, notify and emit are globals
314
+ const { html, text } = await template('welcome', { variables: { name }, subject: 'Hi' })
315
+ await mail.sendOrFail({ to: [email], subject: 'Hi', html, text }) // send() resolves on failure
316
+ const r = await new WelcomeMail(user).send(); if (!r.success) await log.error(r.message)
317
+
318
+ // Every channel at once (stacks-notifications)
319
+ const results = await notify({ userId, email, phone }, { subject: 'Shipped', body }, ['email', 'sms', 'database'])
320
+
321
+ // Single channels (stacks-sms, stacks-push, stacks-chat)
322
+ import { sms } from '@stacksjs/sms'; await sms().to('+15551234567').body('Code 1234').send()
323
+ import { send as push } from '@stacksjs/push'; await push(token, { title, body }, { driver: 'fcm' })
324
+ import { send as chat } from '@stacksjs/chat'; await chat({ content: 'Deployed' }, { driver: 'slack' })
325
+
326
+ // Realtime (stacks-realtime)
327
+ emit('orders', 'created', { id }) // emitToUser(42, 'notice', data) targets private-user.42
328
+ ```
329
+
330
+ **Gotcha:** `notify` resolves even when a channel failed, so check each
331
+ `result.success`. Only `notify` honors SMS STOP opt-outs. `emit` reaches only a
332
+ broadcast server running in the same process; otherwise it warns and does
333
+ nothing.
334
+
335
+ ## Platform services
336
+
337
+ ```ts
338
+ // Cache (stacks-cache): not a global. TTL in seconds, 0 = forever, omitted = config.cache.ttl
339
+ import { cache } from '@stacksjs/cache'
340
+ await cache.remember(`post:${id}`, 300, () => Post.find(id)); await cache.del(`post:${id}`)
341
+
342
+ // Storage (stacks-storage)
343
+ import { Storage } from '@stacksjs/storage' // the global `storage` is a lower-level namespace, not this facade
344
+ const saved = await Storage.put(file, { disk: 'public', dir: 'avatars' }) // { path, url, ... }
345
+ await Storage.disk('s3').write('x.txt', bytes) // disk() returns an adapter: write/read/deleteFile/publicUrl
346
+
347
+ // Search (stacks-search-engine), from the useSearch trait
348
+ const hits = await Post.search('widget').where({ status: 'published' }).paginate(20, 1)
349
+
350
+ // Logging, env, config (stacks-logging, stacks-env, stacks-config)
351
+ await log.info('Order created', { orderId }); await log.error('Charge failed', err, { orderId })
352
+ import { env } from '@stacksjs/env'
353
+ const debug = env.DEBUG; const name = config.app.name // config and log are globals; declare env keys in config/env.ts
354
+
355
+ // i18n (stacks-i18n): t is a global; strings live in locales/*.yml with {name} interpolation
356
+ t('greeting', { name })
357
+
358
+ // Feature flags vs bundles (stacks-feature-flags, stacks-features)
359
+ if (await Feature.for(user.id).active('new-checkout')) { /* user-scoped rollout, async */ }
360
+ import { feature } from '@stacksjs/config'; if (feature('commerce')) { /* framework bundle, sync */ }
361
+ ```
362
+
363
+ **Gotcha:** `cache` has no `forget()`; use `del`. `Storage.disk()` has no
364
+ `put`/`get`. The search index holds only `id` plus the listed fields. Read
365
+ `config` after `await overridesReady` in boot-time code. `setLocale` is
366
+ process-global; use `applyRequestLocale(req)` per request.
367
+
368
+ ## Domains
369
+
370
+ ```ts
371
+ // CMS (stacks-cms): built-in Post, Author, Page, Menu, Redirect, Comment, Tag models
372
+ import { authors, posts } from '@stacksjs/cms'
373
+ const published = await posts.fetchByStatus('published'); await posts.fetchByAuthor(authorId)
374
+ // override a CMS endpoint: create app/Actions/Cms/PostIndexAction.ts
375
+
376
+ // Payments and commerce (stacks-payments, stacks-commerce): integer cents
377
+ import { Payment } from '@stacksjs/payments'
378
+ const session = await Payment.checkout(user, { mode: 'payment', lines: [{ price: 'price_x', quantity: 1 }], successUrl, cancelUrl })
379
+ await Payment.subscribe(user, 'premium-monthly')
380
+ import { commerce } from '@stacksjs/commerce'; await commerce.orders.placeOrder({ order, items, payment, idempotencyKey })
381
+
382
+ // AI (stacks-ai): provider comes from config ai.default, a provider name or model id
383
+ import { createAIClient, createEmbedding } from '@stacksjs/ai'
384
+ const { content } = await createAIClient(config.ai).generate([{ role: 'user', content: 'Hi' }], { system: 'Be brief.' })
385
+ ```
386
+
387
+ - **Sites:** `@stacksjs/sites` provides `siteResolver`, `currentSite()` and
388
+ `forSite(query, column, siteId)`. Queries are not scoped to a site automatically.
389
+ - **Newsletter:** `newsletter.subscribe(email, { list })` from `@stacksjs/newsletter`.
390
+ - **Analytics:** render `getAnalyticsHead(analytics)` in your layout. Nothing
391
+ is injected for you.
392
+ - **Images:** `image(src).widths([320, 640]).formats(['avif', 'webp']).generate()`.
393
+ - **Charts:** `new Chart(canvas, { type, data })` from `@stacksjs/charts`.
394
+ - **Gotcha:** the default `User` is not `billable`. Publish it and enable the
395
+ trait before calling `user.checkout()`. `ask()`/`summarize()` always use
396
+ Bedrock, and `createEmbedding` defaults to OpenAI.
397
+
398
+ ## Utilities (`stacks-strings`, `stacks-arrays`, `stacks-collections`, `stacks-datetime`, `stacks-slug`)
399
+
400
+ ```ts
401
+ import { camelCase, plural, slug, truncate } from '@stacksjs/strings' // slug('Hello World') -> 'hello-world'
402
+ import { chunk, groupBy, sortBy, unique } from '@stacksjs/arrays'
403
+ collect(users).pluck('name').toArray() // collect is a server global
404
+ import { now } from '@stacksjs/datetime'; now().addDays(3).format('YYYY-MM-DD') // immutable, months 1-based
405
+ import { uniqueSlug } from '@stacksjs/slug'; await uniqueSlug(title, { table: 'posts', column: 'slug' })
406
+ ```
407
+
408
+ **Gotcha:** `uniqueSlug` checks first and writes later, so keep a UNIQUE
409
+ index. `arrays.range()` is a statistic (max minus min), not a sequence.
410
+
411
+ ## Frontend (`stacks-stx`, `stacks-composables`, `stacks-ts-css`, `stacks-ui`)
412
+
413
+ ```stx
414
+ @extends('layouts/default')
415
+ <script server>
416
+ const post = await Post.where('slug', String(params?.slug || '')).first() // resources/views/posts/[slug].stx
417
+ if (!post) notFound()
418
+ </script>
419
+ <script client>
420
+ const likes = state(0)
421
+ const label = derived(() => `${likes()} likes`)
422
+ const { data: comments, loading } = useFetch('/api/comments')
423
+ </script>
424
+ @section('content')
425
+ <h1 class="text-2xl font-semibold dark:text-white">{{ post.title }}</h1>
426
+ @foreach (post.tags as tag) <span>{{ tag }}</span> @endforeach
427
+ <button @click="likes.update(n => n + 1)"><i class="i-hugeicons-favourite"></i> {{ label() }}</button>
428
+ <Card :title="post.title" />
429
+ @endsection
430
+ ```
431
+
432
+ - **Signals:** read with `count()`, write with `.set()` or `.update()`.
433
+ There is no `.value`.
434
+ - **Composables in client scripts:** `useFetch`, `useQuery`, `useMutation`,
435
+ `navigate`, `toast.success()`, `useColorMode`, `useDark`, `keptState`.
436
+ `useForm` is delivered on demand.
437
+ - **Gotchas:**
438
+ - No `var`, `document` or `window` in stx scripts.
439
+ - `@foreach` takes no `$` sigil.
440
+ - Class names must be complete static strings, or added to `safelist`.
441
+ - The bare stx `useForm(schema, initial)` and the imported
442
+ `@stacksjs/composables` `useForm({ ... })` are different APIs.
443
+ - Imported `.ts` modules get no auto-imports.
444
+
445
+ ## CLI, tests, dev, deploy
446
+
447
+ ```ts
448
+ // app/Commands/Greet.ts, auto-discovered (scaffold with: buddy make:command)
449
+ import { defineCommand } from '@stacksjs/cli'
450
+ export default defineCommand({
451
+ name: 'greet <who>', description: 'Greet a user',
452
+ options: { '--loud, -l': { description: 'Shout', default: false } },
453
+ handle(options, who) { console.log(options.loud ? who.toUpperCase() : who) }, // options come first
454
+ })
455
+ ```
456
+
457
+ ```ts
458
+ // tests/feature/posts.test.ts (stacks-testing)
459
+ import { actingAs, describe, expect, test } from '@stacksjs/testing'
460
+ import { assertDatabaseHas, factory, refreshDatabase } from '@stacksjs/testing/database' // DB helpers come from /database
461
+ test('creates a post', async () => {
462
+ await refreshDatabase()
463
+ const user = await factory('User').create() // actingAs needs a persisted user
464
+ await actingAs(user).post('/api/posts', { title: 'Hi' })
465
+ await assertDatabaseHas('posts', { title: 'Hi' })
466
+ })
467
+ ```
468
+
469
+ ```sh
470
+ ./buddy dev # frontend :3000, api :3008, dashboard :3002, docs :3006 (also dev:api, dev:dashboard)
471
+ ./buddy test # --feature / --unit / a path
472
+ ./buddy build # interactive picker; without a TTY it builds the frontend views
473
+ ./buddy deploy --dry-run # preview first; CI needs --yes. Undo with buddy deploy:rollback
474
+ ./buddy tinker # REPL with the app booted
475
+ ```
476
+
477
+ **Dashboard, desktop, mobile:** override a dashboard page at
478
+ `resources/views/dashboard/<path>.stx` (`stacks-dashboard`).
479
+ `./buddy dev:desktop` runs the app in a native window (`stacks-desktop`). The
480
+ mobile build reuses the stx views (`stacks-mobile`).
481
+
482
+ ## Cross-cutting boundaries
483
+
484
+ - **Bundles and routes are separate.** Optional bundles control which models,
485
+ migrations and views are selected. Route mounting has its own registry, so
486
+ an installed package does not mean its routes are mounted or public.
487
+ - **Four kinds of browser names.** Browser declarations, stx eager bindings,
488
+ demand-inline bindings and imported composables are different surfaces.
489
+ Read `stacks-auto-imports` before using an unfamiliar bare name.
490
+ - **Ownership.** Generated CRUD derives the owner from the authenticated user.
491
+ Test it with real requests and another user's rows, not a source grep.
492
+ - **Event dispatch.** Ordered dispatch is not the same as awaiting listeners.
493
+ Use the result-bearing API when the caller needs outcomes.
494
+ - **Skill vs source.** If a skill and the source disagree, trust the source and
495
+ fix the skill in the same change.
496
+
497
+ ## Keeping this current
498
+
499
+ Inside the framework repository, `./buddy docs:native-skills` regenerates
500
+ CATALOG.md and CAPABILITIES.md. `./buddy docs:native-skills:check` reports a
501
+ package without guidance or a stale reference. That check proves coverage, not
502
+ that every sentence is right, so verify an idiom against its source entrypoint
503
+ before relying on an edge case.
@@ -34,6 +34,16 @@ BigInts as decimal string entries inside a JSON array. Keep those tokens intact
34
34
  through query parameters; the ORM parser restores tuples and SQL bindings
35
35
  accept decimal strings without a lossy Number conversion.
36
36
 
37
+ ## Call order
38
+
39
+ Stacks model pagination takes `(perPage, page)`, the reverse of upstream
40
+ bun-query-builder: `Post.where('status', 'published').paginate(20, 2)`. With
41
+ no arguments it reads `?page` and `?per_page` from the request, defaults to 15
42
+ per page, and caps a query-string `per_page` at 100 (an explicit perPage is not
43
+ capped). `simplePaginate(perPage, page)` and
44
+ `cursorPaginate(perPage, cursor, columns, direction)` follow the same order.
45
+ The ORM paginator does not read the query builder's `pagination.defaultPerPage`.
46
+
37
47
  ## HTTP and UI
38
48
 
39
49
  ORM request-aware pagination can derive page/limit and attach URLs from the
@@ -44,7 +44,7 @@ await db.insertInto('users')
44
44
  .execute()
45
45
 
46
46
  // Update
47
- await db.update('users')
47
+ await db.updateTable('users')
48
48
  .set({ active: false })
49
49
  .where('id', '=', 1)
50
50
  .execute()
@@ -555,7 +555,7 @@ await SendWelcomeEmail.dispatchNow({ email, name }) // bypass queue
555
555
  ## config/queue.ts
556
556
  ```typescript
557
557
  {
558
- default: 'sync', // implemented dispatch drivers: sync | database | redis
558
+ default: env.QUEUE_DRIVER || (env.APP_ENV === 'production' ? 'database' : 'sync'), // sync | database | redis
559
559
  connections: {
560
560
  sync: { driver: 'sync' },
561
561
  database: { driver: 'database', table: 'jobs', queue: 'default', retryAfter: 90 },
@@ -312,16 +312,19 @@ await req.tokenCant('delete-users')
312
312
  import { response } from '@stacksjs/router'
313
313
 
314
314
  response.json(data, { status: 200 })
315
- response.created(data) // 201
316
- response.noContent() // 204
317
- response.badRequest(data) // 400
318
- response.unauthorized() // 401
319
- response.forbidden() // 403
320
- response.notFound() // 404
321
- response.error() // 500
315
+ response.success(data, 'Saved') // 200, { success: true, data, message }
316
+ response.created(data, location?) // 201, { success: true, data }
317
+ response.noContent() // 204
318
+ response.badRequest(message?, errors?) // 400
319
+ response.unauthorized(message?) // 401
320
+ response.forbidden(message?) // 403
321
+ response.notFound(message?) // 404
322
+ response.validationError(errors) // 422
323
+ response.error(message, status?) // defaults to 400, not 500
324
+ response.serverError(message?) // 500
322
325
  response.redirect(url, 302)
323
- response.text('hello')
324
- response.html('<h1>Hi</h1>')
326
+ response.text('hello', 200)
327
+ response.view('<h1>Hi</h1>') // HTML; there is no response.html()
325
328
  ```
326
329
 
327
330
  ## Error Responses
@@ -367,7 +370,7 @@ route.use(logger)
367
370
  ```
368
371
 
369
372
  ### Available Middleware Aliases (from app/Middleware.ts)
370
- maintenance, auth, guest, api, team, logger, abilities, can, throttle, local, development, staging, production, env.local, env.development, env.staging, env.production, role, permission, verified
373
+ maintenance, cors, auth, guest, api, team, site, logger, abilities, can, throttle, signed, env, env:local, env:development, env:dev, env:staging, env:production, env:prod, role, permission, verified, csrf, compress
371
374
 
372
375
  ## Query Tracking
373
376
 
@@ -44,7 +44,10 @@ await search.addDocuments('products', [
44
44
  ])
45
45
 
46
46
  // Search
47
- const results = await search.search('products', 'widget')
47
+ const results = await search.search('products', { q: 'widget', limit: 20, filter: { status: 'active' } })
48
+
49
+ // From a model with the useSearch trait (the usual call)
50
+ const hits = await Product.search('widget').where({ status: 'active' }).get()
48
51
 
49
52
  // List indexes
50
53
  const indexes = await search.listAllIndexes()
@@ -38,14 +38,16 @@ const slug = await uniqueSlug('Product Name', {
38
38
  ## Basic Slugify (No Database Check)
39
39
 
40
40
  ```typescript
41
- import { slugify } from '@stacksjs/slug'
41
+ import { slug } from '@stacksjs/strings'
42
42
 
43
- slugify('Hello World!') // 'hello-world'
44
- slugify('Ünïcödé Têxt') // 'unicode-text'
45
- slugify(' Extra Spaces ') // 'extra-spaces'
43
+ slug('Hello World!') // 'hello-world'
44
+ slug('Ünïcödé Têxt') // 'unicode-text'
45
+ slug(' Extra Spaces ') // 'extra-spaces'
46
46
  ```
47
47
 
48
- Re-exported from `ts-slug` - handles Unicode transliteration.
48
+ `@stacksjs/slug` exports only `uniqueSlug`. The sync helpers live in
49
+ `@stacksjs/strings`: `slug()` lowercases and strips punctuation, while the
50
+ lower-level `slugify()` defaults to `lower: false` and keeps case.
49
51
 
50
52
  ## Model Usage
51
53
 
@@ -77,14 +79,14 @@ interface SlugifyOptions {
77
79
  ## Gotchas
78
80
  - Omitting either table or column returns the base slug with no query.
79
81
  - A title with no letters or digits at all (`!!!`, an emoji) gets a random
80
- eight-character slug from `uniqueSlug()`, never `""`; `slugify()` still returns `""`.
82
+ eight-character slug from `uniqueSlug()`, never `""`; `slug()` still returns `""`.
81
83
  - The check and insert are separate: keep a UNIQUE index and handle/retry an
82
84
  insert conflict. A helper cannot reserve the returned slug against a race.
83
85
  - Collision attempts are bounded; after repeated conflicts the helper returns
84
86
  a cryptographically randomized suffix instead of looping forever.
85
87
  - Declaring a slug attribute does not itself populate it from the title.
86
88
  - `uniqueSlug()` is async - it queries the database
87
- - `slugify()` is sync - no database interaction
89
+ - `slug()` is sync - no database interaction
88
90
  - Collision detection appends `-2`, `-3`, etc. (not random suffixes)
89
91
  - Unicode characters are transliterated (u → u, e → e, etc.) where ts-slug's charmap
90
92
  romanizes the script (Latin, Greek, Cyrillic, Hebrew, Arabic, Devanagari, Georgian);
@@ -24,11 +24,21 @@ before deciding what to test, and this one for how.
24
24
  ## Test Setup
25
25
 
26
26
  ```typescript
27
- import { setupTestEnvironment } from '@stacksjs/testing'
28
- import { refreshDatabase, setupDatabase, truncateMysql, truncateSqlite } from '@stacksjs/testing/database'
27
+ // tests/setup.ts, the bunfig [test] preload
28
+ import { bootTestEnvironment } from '@stacksjs/testing'
29
+
30
+ // NODE_ENV/APP_ENV = 'test', then the server auto-imports (models, jobs,
31
+ // Action, route, response, ...), app listeners and gates
32
+ await bootTestEnvironment()
33
+ ```
29
34
 
30
- // Set NODE_ENV and APP_ENV to 'test'
31
- setupTestEnvironment()
35
+ `bun test` never runs the framework preloader, so this is what makes the
36
+ auto-imported names exist in a test. Without it, loading an action written
37
+ without imports throws "Action is not defined". `setupTestEnvironment()` still
38
+ exists and only sets the env.
39
+
40
+ ```typescript
41
+ import { refreshDatabase, setupDatabase, truncateMysql, truncateSqlite } from '@stacksjs/testing/database'
32
42
 
33
43
  // MySQL database setup; this helper does not initialize SQLite
34
44
  await setupDatabase()
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.460",
5
+ "version": "0.75.463",
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.460",
5
+ "version": "0.75.463",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.460",
48
+ "@stacksjs/mobile": "0.75.463",
49
49
  "@stacksjs/sanitizer": "0.2.798",
50
50
  "@stacksjs/stx": "0.2.798",
51
51
  "@xterm/addon-fit": "^0.11.0",