@stacksjs/defaults 0.75.422 → 0.75.431

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.
Files changed (37) hide show
  1. package/ai/skills/stacks-alias/SKILL.md +8 -0
  2. package/ai/skills/stacks-arrays/SKILL.md +3 -1
  3. package/ai/skills/stacks-build/SKILL.md +3 -0
  4. package/ai/skills/stacks-cache/SKILL.md +13 -9
  5. package/ai/skills/stacks-calendar/SKILL.md +20 -12
  6. package/ai/skills/stacks-chat/SKILL.md +22 -9
  7. package/ai/skills/stacks-database/SKILL.md +10 -0
  8. package/ai/skills/stacks-desktop/SKILL.md +7 -0
  9. package/ai/skills/stacks-events/SKILL.md +6 -2
  10. package/ai/skills/stacks-i18n/SKILL.md +9 -10
  11. package/ai/skills/stacks-native/CATALOG.md +70 -62
  12. package/ai/skills/stacks-notifications/SKILL.md +11 -2
  13. package/ai/skills/stacks-objects/SKILL.md +6 -1
  14. package/ai/skills/stacks-payments/SKILL.md +5 -3
  15. package/ai/skills/stacks-push/SKILL.md +14 -9
  16. package/ai/skills/stacks-queue/SKILL.md +7 -0
  17. package/ai/skills/stacks-slug/SKILL.md +7 -1
  18. package/ai/skills/stacks-strings/SKILL.md +19 -10
  19. package/ai/skills/stacks-testing/SKILL.md +5 -1
  20. package/ai/skills/stacks-utils/SKILL.md +1 -1
  21. package/ai/skills/stacks-validation/SKILL.md +20 -4
  22. package/ai/skills/stacks-whois/SKILL.md +5 -4
  23. package/app/Actions/Dashboard/Content/post-input.ts +1 -1
  24. package/app/Actions/InstallAction.ts +3 -3
  25. package/app/Models/Content/Post.ts +1 -1
  26. package/app/Models/Error.ts +1 -1
  27. package/app/Models/Log.ts +1 -1
  28. package/app/Models/PaymentMethod.ts +3 -3
  29. package/app/Models/PaymentProduct.ts +1 -1
  30. package/app/Models/Subscription.ts +1 -1
  31. package/app/Models/commerce/PrintDevice.ts +1 -1
  32. package/app/Models/commerce/Receipt.ts +1 -1
  33. package/app/Models/realtime/Websocket.ts +2 -2
  34. package/ide/vscode/package.json +1 -1
  35. package/package.json +2 -2
  36. package/resources/views/cms/blocks/form.stx +31 -36
  37. package/routes/forms.ts +14 -5
@@ -41,6 +41,14 @@ into another package's unpublished types is the problem. Avoid the former rule
41
41
  that all relative imports are forbidden, since framework code itself uses them
42
42
  to keep internal modules and cycle boundaries explicit.
43
43
 
44
+ The five source tsconfigs use manifest-derived package names and public exports,
45
+ not namespace-to-directory wildcards. Run
46
+ `bun .github/scripts/generate-workspace-aliases.ts` after changing a workspace
47
+ manifest and use `--check` to verify freshness. The owning helpers and source
48
+ exceptions are documented in `core/alias/README.md`. External packages such as
49
+ STX's `@stacksjs/desktop` keep normal package resolution; `core/desktop` owns
50
+ `@stacksjs/desktop-build`.
51
+
44
52
  ## Verification
45
53
 
46
54
  Source: `core/alias/src/index.ts`, active tsconfig.json and the build/runtime
@@ -198,10 +198,12 @@ zScore([1, 2, 3, 4], 3) // 0.447...
198
198
  percentile([1, 2, 3, 4, 5], 50) // 3 -- interpolated percentile
199
199
  percentile([1, 2, 3, 4], 25) // 1.75 -- linear interpolation
200
200
  // Note: takes percentile value (0-100), uses linear interpolation
201
+ // Throws for an empty array (like median) and a RangeError outside 0-100
201
202
 
202
203
  interquartileRange([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
203
204
  // Q3 median - Q1 median
204
- // Splits array at midpoint, computes median of each half
205
+ // Sorts, splits at the midpoint, computes the median of each half
206
+ // One number gives 0; an empty array throws
205
207
 
206
208
  covariance([1, 2, 3, 4], [1, 2, 3, 4]) // 1.25 -- positive correlation
207
209
  covariance([1, 2, 3, 4], [4, 3, 2, 1]) // -1.25 -- negative correlation
@@ -86,6 +86,9 @@ buddy build --server --verbose --project [name]
86
86
 
87
87
  ## Standard Build Pattern
88
88
 
89
+ For workspace dependency ordering, failure propagation and umbrella builds, read
90
+ [the build package's workspace contract](../../../../core/build/README.md#workspace-builds).
91
+
89
92
  For a library barrel or a package with public subpaths, preserve the module
90
93
  graph with the shared transpiler. Identifier-minifying bundles have produced
91
94
  invalid re-export bindings on supported Bun versions. Separate bundles also
@@ -22,10 +22,11 @@ const products = await cache.mget<{ name: string }>(['product:42', 'product:43']
22
22
  // mget returns a record keyed by cache key, not an array.
23
23
  ~~~
24
24
 
25
- - TTL is in seconds. Zero means forever; omitted TTL uses the factory's
26
- stdTTL, which defaults to zero. The exported cache/memory singleton is a
27
- memory instance with no default expiry. Config's ttl value does not silently
28
- turn that singleton into a configured Redis instance.
25
+ - TTL is in seconds. Zero means forever; an omitted TTL uses the store's
26
+ default. For the `cache` export that is `ttl` from config/cache.ts (3600 as
27
+ shipped) on every driver, memory included, so a value set with no TTL
28
+ expires after an hour. The `memory` export is a separate in-process
29
+ instance with no default expiry.
29
30
  - `setForever` stores without expiry; `getTtl` reads remaining TTL and
30
31
  `ttl(key, seconds)` updates it. Missing values are undefined.
31
32
  - `mset([{ key, value, ttl? }])` writes entries. `del(keyOrKeys)` and
@@ -60,11 +61,14 @@ deploys, pointing `path` at a file the deploy preserves (the app's own SQLite
60
61
  database works; the table is the driver's alone). `createCache(driver,
61
62
  options?)` selects one of memory, redis, singlestore or sqlite.
62
63
 
63
- The `cache` export is the driver `config/cache.ts` selects (`driver`, with
64
- `prefix` and `ttl` applied to the non-memory drivers), resolved on first use
65
- and once the app's config has loaded. With the default `driver: 'memory'` it
66
- answers from the `memory` instance itself. `cacheFromConfig(settings?)` builds
67
- the same choice explicitly; `resetConfiguredCache()` forgets it.
64
+ The `cache` export is the driver `config/cache.ts` selects, with its `prefix`
65
+ and `ttl` applied on every driver (memory also takes `maxKeys`, `useClones`
66
+ and `drivers.memory`), resolved on first use and once the app's config has
67
+ loaded. It is the shared `memory` instance only when the config sets none of
68
+ those. The shipped config sets them, so there `cache` and `memory` are two
69
+ stores: a value written through one is not visible through the other.
70
+ `cacheFromConfig(settings?)` builds the same choice explicitly;
71
+ `resetConfiguredCache()` forgets it.
68
72
  `config/cache.ts` reads `REDIS_URL`, `REDIS_HOST`, `REDIS_PORT`,
69
73
  `REDIS_USERNAME`, `REDIS_PASSWORD` and `REDIS_DB`, as `config/queue.ts` does.
70
74
 
@@ -38,11 +38,13 @@ title, required allDay and optional description/address/timezone; the root entry
38
38
  does not re-export that type. Infer a literal or read src/types.ts when extending
39
39
  the package.
40
40
 
41
- The older single-event generator has its own timezone/time-format rules and
42
- does not emit a VTIMEZONE block. Its UID derives from event timing/title, so
43
- editing those changes the UID. Do not promise subscription update semantics
44
- from a generated single-event link. Prefer the feed builder for stable records,
45
- escaping and folded lines.
41
+ Single-event exports write a timed event as UTC instants (`...Z`), so a link
42
+ means the same on every host; `timezone` only sets the zone Google's compose
43
+ screen displays it in. All-day dates are the UTC calendar dates of from/to.
44
+ The ICS body escapes and folds its text as the feed builder does. Its UID
45
+ derives from the event's instants and title, so editing those changes the
46
+ UID. Do not promise subscription update semantics from a generated
47
+ single-event link; use the feed builder for stable records.
46
48
 
47
49
  ## Native subscribable feeds
48
50
 
@@ -59,13 +61,19 @@ control, even when the feed declares a suggested interval.
59
61
 
60
62
  ## Recurrence
61
63
 
62
- parseRRule(value) returns RecurrenceRule or null for unsupported frequency.
63
- expandRecurrence(rule, dtstart, windowStart, windowEnd) returns matching UTC
64
- start instants, capped defensively. Supported fields include DAILY/WEEKLY/
65
- MONTHLY/YEARLY, INTERVAL, COUNT, UNTIL, BYDAY and BYMONTHDAY. This is a subset;
66
- unknown fields are not a proof of full RFC conformance. Callers own timezone
67
- interpretation and DST policy. Calendar clients can expand a feed's raw rrule
68
- independently from the server subset.
64
+ parseRRule(value) returns a RecurrenceRule, or null for any rule it cannot
65
+ follow exactly: an unsupported frequency or part (BYMONTH, BYSETPOS, BYWEEKNO,
66
+ BYYEARDAY, BYHOUR...), a combination RFC 5545 rules out or this subset does
67
+ not cover, or a value it cannot read. Null means "do not expand this here",
68
+ never "expanded without the part". expandRecurrence(rule, dtstart, windowStart,
69
+ windowEnd) returns matching UTC start instants, capped at 1000.
70
+
71
+ The subset: DAILY/WEEKLY/MONTHLY/YEARLY, INTERVAL, COUNT or UNTIL, WKST, BYDAY
72
+ (a filter for DAILY and WEEKLY; within a month, `MO` is every Monday and `2TU`
73
+ or `-1FR` an ordinal) and BYMONTHDAY for DAILY or MONTHLY (`-1` is the last
74
+ day). Weeks start on WKST, Monday by default, as clients expand them. YEARLY
75
+ repeats dtstart's date, skipping years without it (Feb 29). Callers own
76
+ timezone interpretation and DST policy.
69
77
 
70
78
  ## Sources and evidence
71
79
 
@@ -95,7 +95,7 @@ abstract class BaseChatDriver implements ChatDriver {
95
95
 
96
96
  ```typescript
97
97
  interface ChatMessage {
98
- to: string | string[] // recipient (channel ID, user ID, etc.)
98
+ to?: string | string[] // a bot token's channel or user ID; omit for a webhook
99
99
  from?: { id?: string, name?: string, avatar?: string }
100
100
  subject?: string // title for rich messages
101
101
  content?: string // plain text content
@@ -181,8 +181,14 @@ interface SlackAttachment {
181
181
 
182
182
  ### Slack Sending Modes
183
183
 
184
- 1. **Webhook mode**: If `config.webhookUrl` is set, sends via `POST` to webhook URL. Payload includes `text`, `username`, `mrkdwn: true`. If `message.template` exists, builds Block Kit blocks.
185
- 2. **Bot token mode**: If `config.botToken` is set, sends via Slack API `https://slack.com/api/chat.postMessage` with `Authorization: Bearer <token>`. Returns `ts` (timestamp) as message ID.
184
+ 1. **Webhook mode**: If `config.webhookUrl` is set, sends via `POST` to webhook URL. Payload includes `text`, `username`, `mrkdwn: true`. If `message.template` exists, builds Block Kit blocks. Needs no `message.to`.
185
+ 2. **Bot token mode**: If `config.botToken` is set, sends via Slack API `https://slack.com/api/chat.postMessage` with `Authorization: Bearer <token>`, to the channel in `message.to` (required). Returns `ts` (timestamp) as message ID.
186
+
187
+ `send()` treats `content` as the plain text `ChatMessage` says it is: `&`, `<`
188
+ and `>` are escaped (`escapeSlackText`), so `<!channel>` or a disguised
189
+ `<url|label>` in user text arrives as literal text instead of pinging the
190
+ channel or hiding a link. Plain URLs still link. `sendWebhook(url, text)` is
191
+ raw, for Slack markup written on purpose.
186
192
  3. Throws `Error('Slack not configured: provide webhookUrl or botToken')` if neither is set.
187
193
 
188
194
  ### Direct Webhook Function
@@ -247,8 +253,15 @@ interface DiscordMessage {
247
253
 
248
254
  ### Discord Sending Modes
249
255
 
250
- 1. **Webhook mode**: POST to `config.webhookUrl`. Handles `204` (empty success) responses. Returns `id` from response.
251
- 2. **Bot token mode**: POST to `https://discord.com/api/v10/channels/{channelId}/messages` with `Authorization: Bot <token>`. Uses `message.to` as the channel ID.
256
+ 1. **Webhook mode**: POST to `config.webhookUrl`. Handles `204` (empty success) responses. Returns `id` from response. Needs no `message.to`.
257
+ 2. **Bot token mode**: POST to `https://discord.com/api/v10/channels/{channelId}/messages` with `Authorization: Bot <token>`. Uses `message.to` as the channel ID (required).
258
+
259
+ Every payload carries `allowed_mentions`, by default `{ parse: ['users', 'roles'] }`
260
+ (`DEFAULT_ALLOWED_MENTIONS`): people and roles named by ID ping, `@everyone`
261
+ and `@here` in content never do. `sendWebhook()`'s `allowedMentions` replaces
262
+ the default, and its camelCase options are sent under Discord's names
263
+ (`avatar_url`, `allowed_mentions`) by `discordPayload()`. With a subject or
264
+ template the body goes in the embed only, not also as `content`.
252
265
 
253
266
  ### Embed Building
254
267
  When `message.subject` or `message.template` is set, `buildEmbed()` creates an embed with:
@@ -457,10 +470,10 @@ All three drivers implement `sendWithRetry()`:
457
470
 
458
471
  Direct send returns ChatResult, with success false for a handled provider failure;
459
472
  inspect it before persisting sent state. Awaiting a send only proves the promise
460
- settled. Webhook configuration does not replace ChatMessage's to/content or
461
- template fields. The generic notify chat channel supplies a body-only shape and
462
- does not normalize every structured transport failure, so use this package's
463
- direct API when application behavior depends on valid chat delivery.
473
+ settled. A webhook send needs no `to`; a bot-token send needs the channel in
474
+ `to`. notify()'s chat channel sends the body as `content` and `chatRecipient`
475
+ as `to`, through `recipient.chatDriver` or `config.notification.chat.default`,
476
+ and a failed send is a failed NotifyResult.
464
477
 
465
478
  Settings resolve per send after app config readiness, with explicit configure
466
479
  overrides taking precedence. Retained request/config coverage lives under
@@ -384,3 +384,13 @@ canonical numeric string value, so padded numeric inputs cannot reverse lock ord
384
384
  transaction handle and choose ownership filters; reads, writes and validation
385
385
  remain on that handle. This prevents opposite input order from creating a
386
386
  lock-order cycle during a transfer, merge or multi-item stock reservation.
387
+
388
+ ## Row leases for external operations
389
+
390
+ `claimRowLease(table, ownerScope, { token, heartbeat }, ttlMs)` from
391
+ `@stacksjs/database/runtime` claims model-owned nullable token and heartbeat
392
+ columns atomically. It returns null when busy, or a lease with `renew()`,
393
+ `release()`, `assertOwned()` and `commit(work)`. Renew periodically during I/O.
394
+ Keep network work outside `commit`; commit locks and revalidates the token before
395
+ writing one bounded batch. An expired worker cannot commit or release a successor.
396
+ A lease's unique owner scope must include the resource binding where applicable.
@@ -133,6 +133,13 @@ Write `app/Desktop/launcher.ts` and `build:desktop` compiles that instead, the
133
133
  same way anything under `app/` overrides its framework default. `DESKTOP_URL`
134
134
  and `APP_URL` then become optional, because the launcher decides what to open.
135
135
 
136
+ Every launcher, the framework's or yours, compiles through
137
+ `desktopLauncherCompileArgs()` to bytecode as an ES module: parsing the bundle
138
+ was most of a launcher's start (Browser's agent reached its window host in
139
+ 379 ms as source, 146 ms as bytecode), for an executable about a quarter
140
+ larger. Pass `bytecode: false` only to compare. Compile any helper binary of
141
+ your own with the same function rather than hand-written `bun build` flags.
142
+
136
143
  ```ts
137
144
  // app/Desktop/launcher.ts - compiled to Contents/MacOS/<AppName>
138
145
  import { dirname, join } from 'node:path'
@@ -26,10 +26,14 @@ Use the native event bus for process-local application and model events. Use
26
26
  downstream work determines the response.
27
27
 
28
28
  Payloadless events are supported by the emitter. Exact handlers receive the
29
- payload; wildcard and glob handlers receive `(eventName, payload)`.
29
+ payload; wildcard and glob handlers receive `(eventName, payload)`. In a glob,
30
+ `*` is any run of characters and `.` any one character (`*.created` matches
31
+ `user:created`); every other character is literal.
30
32
 
31
33
  `emitter.on(name, handler, { priority })` sorts higher priorities first
32
- within each handler bucket. Equal priority preserves registration order.
34
+ within each handler bucket. A priority belongs to that one registration, so
35
+ one handler can run first for one event and last for another. Equal priority
36
+ preserves registration order.
33
37
  Exact handlers run before glob buckets, then `*` handlers. Snapshotting keeps
34
38
  subscription changes during a dispatch from corrupting iteration.
35
39
 
@@ -21,7 +21,7 @@ i18n/src/
21
21
  ├── translator.ts # I18n class, global API (t, tc, te, tm)
22
22
  ├── formatter.ts # Locale-aware formatting (dates, numbers, currency, lists)
23
23
  ├── loader.ts # File-based translation loader (JSON, YAML, JS/TS)
24
- └── pluralization.ts # CLDR plural rules for 25+ languages
24
+ └── pluralization.ts # CLDR plural rules, from the runtime's Intl.PluralRules
25
25
  ```
26
26
 
27
27
  ## Core Types
@@ -101,14 +101,13 @@ tc('items', 5) // '5 items'
101
101
 
102
102
  ### Supported Languages
103
103
 
104
- | Rule | Languages |
105
- |------|-----------|
106
- | One/Other | en, de, nl, es, it, pt, sv, da, no, fi, el, he, hu, tr |
107
- | French (0-1=one) | fr |
108
- | No plural | zh, ja, ko, vi, th, id, ms |
109
- | One/Few/Many/Other | ru, uk, pl |
110
- | One/Few/Other | cs, sk |
111
- | Full (zero/one/two/few/many/other) | ar, cy |
104
+ Every language the runtime's CLDR data covers, through `Intl.PluralRules`,
105
+ for the full locale (`pt` counts 0 as `one`, `pt-PT` does not). A language it
106
+ has no data for says `other` for every number; `hasPluralRule(locale)` tells
107
+ you which. `addPluralRule(locale, rule)` overrides a language. The rules were
108
+ a hand-written table of 33 languages until it turned out to leave out Catalan,
109
+ Croatian, Serbian, Bulgarian and `nb` (they said `other` for 1) and to get
110
+ Hebrew's `two` and Latvian's `zero` wrong.
112
111
 
113
112
  ```typescript
114
113
  import { getPluralCategory, addPluralRule, hasPluralRule } from '@stacksjs/i18n'
@@ -218,6 +217,6 @@ default:
218
217
  - **YAML parsed with Bun.YAML** - not a third-party parser
219
218
  - **Missing keys return the key itself** - not an error, not empty string
220
219
  - **Pluralization uses `|` separator** - not ICU MessageFormat
221
- - **CLDR rules are built-in** - no external data needed, 25+ languages hardcoded
220
+ - **CLDR rules come from the runtime** - `Intl.PluralRules`, no data shipped and no table to fall out of date
222
221
  - **HTML escaping is opt-in** - `escapeValues: true` in config
223
222
  - **Formatting uses Intl APIs** - delegates to `Intl.DateTimeFormat`, `Intl.NumberFormat`, etc.