ferrovue 0.11.0 → 0.13.0

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 (127) hide show
  1. package/README.md +23 -20
  2. package/dist/args.d.ts +1 -1
  3. package/dist/args.js +1 -1
  4. package/dist/attrs.d.ts +4 -0
  5. package/dist/attrs.js +24 -14
  6. package/dist/blocks.js +8 -18
  7. package/dist/calls.js +150 -330
  8. package/dist/children.js +110 -30
  9. package/dist/classes.js +11 -14
  10. package/dist/cli.js +3 -1
  11. package/dist/client.d.ts +28 -7
  12. package/dist/client.js +84 -4
  13. package/dist/compiler.d.ts +11 -6
  14. package/dist/compiler.js +8 -5
  15. package/dist/component.js +85 -26
  16. package/dist/conditional.d.ts +3 -0
  17. package/dist/conditional.js +201 -0
  18. package/dist/config.d.ts +128 -0
  19. package/dist/config.js +32 -0
  20. package/dist/conformance.d.ts +6 -43
  21. package/dist/conformance.js +8 -24
  22. package/dist/constants.js +196 -22
  23. package/dist/context.d.ts +9 -126
  24. package/dist/context.js +22 -31
  25. package/dist/date-plans.d.ts +11 -0
  26. package/dist/date-plans.js +10 -0
  27. package/dist/diagnostics.d.ts +3 -3
  28. package/dist/diagnostics.js +1 -1
  29. package/dist/dynamic.js +18 -10
  30. package/dist/editor.js +3 -2
  31. package/dist/enums.d.ts +56 -5
  32. package/dist/enums.js +318 -28
  33. package/dist/error-areas.d.ts +5 -0
  34. package/dist/error-areas.js +24 -0
  35. package/dist/errors.d.ts +90 -38
  36. package/dist/errors.js +97 -56
  37. package/dist/expr.d.ts +2 -1
  38. package/dist/expr.js +258 -743
  39. package/dist/fallthrough.d.ts +8 -0
  40. package/dist/fallthrough.js +39 -31
  41. package/dist/file-routes.d.ts +2 -15
  42. package/dist/file-routes.js +2 -1
  43. package/dist/files.d.ts +3 -3
  44. package/dist/files.js +2 -1
  45. package/dist/functions.js +19 -4
  46. package/dist/globals.d.ts +7 -0
  47. package/dist/globals.js +223 -0
  48. package/dist/inputs.d.ts +1 -1
  49. package/dist/inputs.js +1 -0
  50. package/dist/link-router.d.ts +1 -2
  51. package/dist/link-router.js +1 -2
  52. package/dist/lists.d.ts +7 -2
  53. package/dist/lists.js +119 -27
  54. package/dist/loads.d.ts +6 -25
  55. package/dist/loads.js +1 -84
  56. package/dist/logical.d.ts +3 -0
  57. package/dist/logical.js +215 -0
  58. package/dist/loops.js +26 -9
  59. package/dist/members.d.ts +5 -0
  60. package/dist/members.js +343 -0
  61. package/dist/model.d.ts +89 -37
  62. package/dist/model.js +137 -41
  63. package/dist/narrowing.d.ts +19 -1
  64. package/dist/narrowing.js +55 -5
  65. package/dist/numbers.d.ts +2 -0
  66. package/dist/numbers.js +40 -3
  67. package/dist/objects.d.ts +2 -0
  68. package/dist/objects.js +30 -4
  69. package/dist/operators.d.ts +5 -0
  70. package/dist/operators.js +186 -0
  71. package/dist/page-routes.d.ts +1 -2
  72. package/dist/peer.d.ts +3 -0
  73. package/dist/peer.js +17 -0
  74. package/dist/plugin.d.ts +1 -1
  75. package/dist/plugin.js +1 -0
  76. package/dist/plugins/client-only.js +2 -2
  77. package/dist/plugins/css-modules.js +2 -1
  78. package/dist/plugins/dates.js +9 -9
  79. package/dist/plugins/head.js +34 -44
  80. package/dist/plugins/i18n.js +4 -4
  81. package/dist/plugins/loaders.d.ts +5 -0
  82. package/dist/plugins/loaders.js +136 -28
  83. package/dist/plugins/router-link.js +17 -24
  84. package/dist/plugins/router.d.ts +1 -1
  85. package/dist/plugins/router.js +14 -12
  86. package/dist/plugins/scoped.d.ts +1 -1
  87. package/dist/plugins/scoped.js +9 -15
  88. package/dist/plugins/stores.js +3 -2
  89. package/dist/plugins/teleport.js +5 -5
  90. package/dist/plugins/twins.d.ts +1 -1
  91. package/dist/plugins/twins.js +6 -4
  92. package/dist/problems.d.ts +8 -0
  93. package/dist/problems.js +25 -0
  94. package/dist/resolve.d.ts +0 -10
  95. package/dist/revive.js +6 -2
  96. package/dist/rust.d.ts +1 -1
  97. package/dist/rust.js +78 -44
  98. package/dist/schema.js +2 -1
  99. package/dist/script.d.ts +0 -1
  100. package/dist/script.js +374 -232
  101. package/dist/slots.d.ts +2 -0
  102. package/dist/slots.js +17 -5
  103. package/dist/ssr.js +3 -2
  104. package/dist/statements.js +11 -12
  105. package/dist/strings.d.ts +27 -2
  106. package/dist/strings.js +264 -23
  107. package/dist/styles.js +28 -37
  108. package/dist/template.js +111 -18
  109. package/dist/{fixture.d.ts → testing-browser.d.ts} +26 -13
  110. package/dist/testing-browser.js +206 -0
  111. package/dist/testing.d.ts +42 -5
  112. package/dist/testing.js +14 -4
  113. package/dist/types.d.ts +1 -1
  114. package/dist/typescript.d.ts +7 -0
  115. package/dist/typescript.js +171 -11
  116. package/dist/vhtml.js +1 -1
  117. package/dist/vite.d.ts +14 -4
  118. package/dist/vite.js +12 -4
  119. package/package.json +22 -6
  120. package/schema.json +13 -5
  121. package/dist/fixture.js +0 -122
  122. package/dist/hydration.d.ts +0 -26
  123. package/dist/hydration.js +0 -126
  124. package/dist/index.d.ts +0 -4
  125. package/dist/index.js +0 -3
  126. /package/dist/{routes.d.ts → route-records.d.ts} +0 -0
  127. /package/dist/{routes.js → route-records.js} +0 -0
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![npm](https://img.shields.io/npm/v/ferrovue.svg)](https://www.npmjs.com/package/ferrovue)
5
5
  [![docs.rs](https://img.shields.io/docsrs/ferrovue)](https://docs.rs/ferrovue)
6
6
  [![CI](https://github.com/Kinsmir/ferrovue/actions/workflows/ci.yml/badge.svg)](https://github.com/Kinsmir/ferrovue/actions/workflows/ci.yml)
7
+ [![Mutation score](https://img.shields.io/endpoint?url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FKinsmir%2Fferrovue%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/Kinsmir/ferrovue/main)
7
8
  [![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#licence)
8
9
 
9
10
  Documentation: [docs.rs/ferrovue](https://docs.rs/ferrovue) (API and guide) · [kinsmir.github.io/ferrovue](https://kinsmir.github.io/ferrovue/) (guide and overview)
@@ -34,7 +35,7 @@ element structure, attribute order, whitespace and fragment markers.
34
35
  | | What it is | Published as |
35
36
  |---|---|---|
36
37
  | `packages/ferrovue` | The compiler, the `ferrovue` command, the Vite plugin and the browser client (TypeScript) | npm: [`ferrovue`](https://www.npmjs.com/package/ferrovue) |
37
- | `crates/ferrovue` | The runtime the generated Rust calls: escaping, slots, islands, `<RouterLink>` matching. It re-exports `ferrovue-router`, `ferrovue-i18n` and `ferrovue-core`, released with it | crates.io: [`ferrovue`](https://crates.io/crates/ferrovue) ([API docs](https://docs.rs/ferrovue)) |
38
+ | `crates/ferrovue` | The runtime the generated Rust calls: escaping, slots, islands, `<RouterLink>` matching. It holds `ferrovue-core`, `ferrovue-router` and `ferrovue-i18n`, released with it, as its modules `core`, `router` and `i18n` | crates.io: [`ferrovue`](https://crates.io/crates/ferrovue) ([API docs](https://docs.rs/ferrovue)) |
38
39
 
39
40
  ## Quick start
40
41
 
@@ -56,7 +57,7 @@ the configuration at your project root yourself:
56
57
 
57
58
  ```json
58
59
  {
59
- "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.8.0/schema.json",
60
+ "$schema": "https://cdn.jsdelivr.net/npm/ferrovue@0.13.0/schema.json",
60
61
  "components": "client/components",
61
62
  "out": "src/generated"
62
63
  }
@@ -75,8 +76,9 @@ read `$schema`. Offline, the copy in the installed package works as well:
75
76
  | `routes` | no | JSON file listing the app's routes: each a vue-router path, or `{ "path", "name", "children" }`. Or `{ "pages": "client/pages" }`: a folder of pages, whose file names give the routes as vue-router's file-based routing reads them (`index.vue`, `[id].vue`, `[[id]].vue`, `[...path].vue`, `(group)` folders, a `name.vue` beside `name/` as its layout), each page compiled as a component. Needed for `<RouterLink>`, `<RouterView>` and `useRoute()` |
76
77
  | `router` | no | Instead of `routes`: `{ routes, base?, linkActiveClass?, linkExactActiveClass? }`, matching `createWebHistory(base)` and `createRouter`'s options |
77
78
  | `stores` | no | Directory of Pinia option stores whose state components may read |
78
- | `trustedHtml` | no | Rust type of a `TrustedHtml` prop (needed for `v-html`): `ferrovue::BasicHtml` (text with a few formatting tags, built in), `ferrovue::InlineHtml` (inline formatting tags only, built in, which makes every `TrustedHtml` prop inline HTML), `ferrovue::Sanitised` (HTML cleaned by ammonia, the `ammonia` feature), or a type of your own such as `crate::html::CleanHtml` |
79
- | `dateType` | no | Rust type of a `Date` prop: `ferrovue::JsDate` (the default), `chrono::DateTime<chrono::Utc>` (the `chrono` feature), or a type of your own that implements `ferrovue::JsTime`. See [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html#date-props) |
79
+ | `trustedHtml` | no | Rust type of a `TrustedHtml` prop (needed for `v-html`): `ferrovue::html::BasicHtml` (text with a few formatting tags, built in), `ferrovue::html::InlineHtml` (inline formatting tags only, built in, which makes every `TrustedHtml` prop inline HTML), `ferrovue::html::Sanitised` (HTML cleaned by ammonia, the `ammonia` feature), or a type of your own such as `crate::html::CleanHtml` |
80
+ | `dateType` | no | Rust type of a `Date` prop: `ferrovue::date::JsDate` (the default), `chrono::DateTime<chrono::Utc>` (the `chrono` feature), or a type of your own that implements `ferrovue::date::JsTime`. See [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html#date-props) |
81
+ | `unions` | no | Rust enums of your own for unions of string literals, by `type` alias: `{ "Tone": "crate::domain::Tone" }`. Generated code uses the enum, which implements `ferrovue::props::Union` and has the variants ferrovue would generate, and generates none for the union. See [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-mapped-to-existing-enums) |
80
82
  | `helpers` | no | `{ module, functions }`: functions a template may call, each mapped to a Rust twin |
81
83
  | `twins` | no | Components ferrovue does not compile, each rendered by a Rust function of yours: `{ "VBtn": { "rust": "crate::ui::v_btn", "props": { "label": "string" }, "slots": ["default"] } }`. See [Rust twins](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#rust-twins) |
82
84
  | `i18n` | no | vue-i18n: `{ messages, locale?, fallbackLocale? }`, the directory of locale files (`en.json`, `nl.json`), the default locale and the fallbacks |
@@ -171,7 +173,7 @@ The crate's features:
171
173
 
172
174
  - `router` and `i18n`, both on by default: vue-router and vue-i18n. An application with neither
173
175
  routes nor translations can leave them out, and build neither `ferrovue-router` nor
174
- `ferrovue-i18n`: `ferrovue = { version = "0.8", default-features = false }`.
176
+ `ferrovue-i18n`: `ferrovue = { version = "0.13", default-features = false }`.
175
177
  - `axum` and `actix-web`: `Html` is an `IntoResponse` or a `Responder`, so a handler responds with
176
178
  the component as `text/html; charset=utf-8`. `into_html(props)` and `into_island(props)` take the
177
179
  props by value, so a handler that builds them returns the page:
@@ -184,12 +186,12 @@ The crate's features:
184
186
 
185
187
  - `stream`, which `axum` and `actix-web` turn on: `HtmlStream`, a page sent in the order its parts
186
188
  are ready.
187
- - `maud`: `ferrovue::Html` implements `maud::Render`, so `(greeting::html(&props))` goes straight
189
+ - `maud`: `ferrovue::html::Html` implements `maud::Render`, so `(greeting::html(&props))` goes straight
188
190
  into a `maud::html!` page.
189
191
  - `dioxus`: `{greeting::island(&props)}` in a Dioxus 0.7 `rsx!` is the island element itself, with
190
192
  exactly the markup Vue hydrates inside it (`examples/dioxus`).
191
- - `ammonia`: `ferrovue::Sanitised`, HTML cleaned by [ammonia](https://docs.rs/ammonia), for
192
- `v-html`. `ferrovue::BasicHtml` and `ferrovue::InlineHtml` are built in; the
193
+ - `ammonia`: `ferrovue::html::Sanitised`, HTML cleaned by [ammonia](https://docs.rs/ammonia), for
194
+ `v-html`. `ferrovue::html::BasicHtml` and `ferrovue::html::InlineHtml` are built in; the
193
195
  [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html) guide describes
194
196
  all three.
195
197
 
@@ -221,20 +223,20 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
221
223
 
222
224
  | Area | Supported |
223
225
  |---|---|
224
- | Prop types | `string`, `number` (integers, `i64`, written and computed as JavaScript does, exact within ±2⁵³), `Float` from `ferrovue/types` (fractions, `f64`), `boolean`, unions of two or more string literals (`"sm" \| "md"`, an alias, or written in place), each a Rust enum generated for it (`Size::Md`, `"md".parse::<Size>()`, written and serialised as its literal; see the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-of-string-literals) guide), `T \| undefined`, `T \| null` (an `Option` written as `null`), arrays (`T[]`, `Array<T>`, `readonly T[]`), interfaces and object type aliases (recursive ones too), dictionaries (`Record<string, T>`, `{ [key: string]: T }`, `ferrovue::Record` in Rust, which keeps JavaScript's order of keys), another component's exported `Props`, `TrustedHtml`, `InlineHtml`, `Date` (a prop, optional or nullable too, and inside lists, dictionaries and interfaces: `ferrovue::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
226
+ | Prop types | `string`, `number` (integers, `i64`, written and computed as JavaScript does, exact within ±2⁵³), `Float` from `ferrovue/types` (fractions, `f64`), `boolean`, unions of two or more string literals (`"sm" \| "md"`, an alias, or written in place), each a Rust enum generated for it (`Size::Md`, `"md".parse::<Size>()`, written and serialised as its literal; see the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#unions-of-string-literals) guide), or an enum of the application's own that `unions` names for the alias; a list of such a union, a list of its enum (`Size[]` is `Vec<Size>`); `T \| undefined`, `T \| null` (an `Option` written as `null`), arrays (`T[]`, `Array<T>`, `readonly T[]`), interfaces and object type aliases (recursive ones too), object types written in place, mapped types and `Record`s over a union of names, indexed types (`Card["size"]`), tuples (`[label: string, n: number]`, a tuple struct written as an array), dictionaries (`Record<string, T>`, `{ [key: string]: T }`, `ferrovue::props::Record` in Rust, which keeps JavaScript's order of keys), another component's exported `Props`, `TrustedHtml`, `InlineHtml`, `Date` (a prop, optional or nullable too, and inside lists, dictionaries and interfaces: `ferrovue::date::JsDate`, or `chrono::DateTime<Utc>` with the `chrono` feature) |
225
227
  | Shared types | Interfaces and type aliases imported from `.ts` files (generated once, into `types.rs`; imported by a relative path or through an alias the project declares, in its tsconfig.json's `compilerOptions.paths` or, with the Vite plugin, Vite's `resolve.alias`: `@/lib/format`), from a store's file, or from another component's `.vue` file; objects of a shared type can be passed between components. A TypeScript `enum` of string members is a Rust enum, as a union of string literals is, its variants named after its members; one of numbers is a number |
226
- | Constants | Constants imported from `.ts` files, object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component), evaluated when the component is compiled: strings, numbers, booleans, `null`, arithmetic and joined strings of them as JavaScript evaluates them (`60 * MINUTE`, `` `${BASE}/feed` ``, `"#" + 1.5`, and `**` where it gives an exact integer within ±2⁵³), lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals), compiled to a `match` over the members (over the variants of a union's enum, with no catch-all arm), read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`, `kind && ICONS[kind]`); a free-text key tested with `Object.hasOwn(ROLES, key)`, against the object's own keys, which narrows the key for `ROLES[key]` in what follows; `Object.keys()` of an object constant; a constant `new Map([[key, value], …])` of string or integer keys, read with `.get(key)` (`undefined` for a key it lacks) and `.has(key)`. See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
227
- | Props | `withDefaults`, destructured props with defaults (`const { size = "md" } = defineProps<…>()`), the props object (`const props = defineProps<…>()`, then `props.label` in the template or in script code), optional booleans (Vue casts an absent one to `false`), `defineModel` (named, required, with defaults), components with no props. A generic component (`<script setup generic="T extends Item">`) renders each type parameter as its constraint, which is all the template can rely on |
228
- | Setup | `ref`/`shallowRef`, `computed` (an expression, or a body of statements: see Statements; one that may throw, through `.repeat()`, `.padStart()`, a date, `$t` or a helper, is evaluated where the render first reads it, once, as Vue does), refs that start empty, typed by their type argument (`ref<Row[]>([])`, `ref<Row>()`, `ref<string \| undefined>(undefined)`), plain `const`/`let`, `.value` in script code, helpers with Rust twins, functions (see Functions), a plain `<script>` block beside setup. Lifecycle hooks, `watch` (not `immediate`), `defineEmits`, `defineSlots`, `defineOptions`, `defineExpose` and template refs (`ref(null)`, `useTemplateRef`) are client-only, as is a function only an event handler calls. The template may name them only from event handlers, which the server drops |
228
+ | Constants | Constants imported from `.ts` files, object constants declared in `<script setup>`, and `enum`s (imported, or declared in the component, with members set to such constants or to earlier members), evaluated when the component is compiled: strings, numbers, booleans, `null`, arithmetic and joined strings of them as JavaScript evaluates them (`60 * MINUTE`, `` `${BASE}/feed` ``, `"#" + 1.5`, and `**` where it gives an exact integer within ±2⁵³), lists of them, objects of them read field by field (`LABELS.save`, `Tone.Loud`, `Rank[5]`), and lists of objects of them, which become a `const` in `types.rs` (`{ value, label }` items get a type named after the constant, `OptionsItem`, unless the constant is typed with an interface: `OPTIONS: Option[]`; a field that is `null` in some items is `T | null`); lookup tables: an object constant (`as const`, `Record<Size, T>` or untyped, keys computed from enum members too) read by a key whose type is a union of string or integer literals or an `enum` (a prop, a `computed`, a `v-for` item's field, a `?:` of literals, or built from one by `.toUpperCase()`, `.toLowerCase()` or a template literal), or by a constant number (`O[HALF]` reads `O["0.5"]`), compiled to a `match` over the members (over the variants of a union's enum, with no catch-all arm; a table of a union's members, `Record<Tone, Tone>`, reaches a union prop as a `match` from variant to variant), read on by field (`THEME[tone].fg`) in text, attributes, `:class` and `:style`, with `undefined` for a member the object lacks (`ICONS[kind] ?? "none"`, `ICONS[kind]?.label`, `v-if="ICONS[kind]"`, `kind && ICONS[kind]`); a free-text key tested with `Object.hasOwn(ROLES, key)`, against the object's own keys, which narrows the key for `ROLES[key]` in what follows; `Object.keys()` of an object constant; a constant `new Map([[key, value], …])` of string or integer keys, read with `.get(key)` (`undefined` for a key it lacks) and `.has(key)`. See the crate's [`props`](https://docs.rs/ferrovue/latest/ferrovue/guide/props/index.html#constants) guide |
229
+ | Props | Props declared in place, by an interface of the component, or by one imported from a `.ts` file by a relative path (`defineProps<CardProps>()`), extended or joined with `&`; `withDefaults`, destructured props with defaults and new names (`const { size = "md", label: text } = defineProps<…>()`), the props object (`const props = defineProps<…>()`, then `props.label` in the template or in script code), optional booleans (Vue casts an absent one to `false`), `defineModel` (named, required, with defaults), components with no props. A generic component (`<script setup generic="T extends Item">`) renders each type parameter as its constraint, which is all the template can rely on |
230
+ | Setup | `ref`/`shallowRef`, `computed` (an expression, or a body of statements: see Statements; one that may throw, through `.repeat()`, `.padStart()`, a date, `$t` or a helper, is evaluated where the render first reads it, once, as Vue does), refs that start empty, typed by their type argument (`ref<Row[]>([])`, `ref<Row>()`, `ref<string \| undefined>(undefined)`), refs of a union of string literals, held as its enum (`ref<Choice>(props.tone ?? "")`, a member, a value of the union, a lookup table's value, or a `?:` or `??` of them), plain `const`/`let`, `.value` in script code, helpers with Rust twins, functions (see Functions), a plain `<script>` block beside setup. Lifecycle hooks, `watch` (not `immediate`), `defineEmits`, `defineSlots`, `defineOptions`, `defineExpose` and template refs (`ref(null)`, `useTemplateRef`) are client-only, as is a function only an event handler calls. The template may name them only from event handlers, which the server drops |
229
231
  | Statements | in a `computed`'s body and in a list method's callback (`.map()`, `.filter()`, `.find()`, `.some()`, `.reduce()`, a `.toSorted()` comparator and the others): `const` and `let` of any value the template may compute, destructured too (`const { name, posts: count } = author`); `if` / `else if` / `else` and early `return`, narrowing what follows as TypeScript narrows it (`if (!user) return ""; return user.name`, `if (!a \|\| !a.title) return "-"`); `switch` over a string or a number, with stacked `case`s, `default` and `break`; assignments to the body's `let`s (`=`, `+=`, `-=`, `*=`, `++`, `--` and the others), with the type TypeScript gives them; `for (const x of list)` over a list, `list.entries()`, `Object.keys()` or `Object.entries()`, `for (const ch of text)` over a string by its characters (a character beyond U+FFFF is one), and counting loops `for (let i = 0; i < n; i++)` to an integer bound, with `break`, `continue`, nested loops and `return` inside them, carrying strings, numbers and booleans from one item to the next (`let total = 0; for (const r of rows) total += r.n;`, thousands separators, the largest of a list). Each value is evaluated once, where JavaScript evaluates it, and only on the path that reaches it. `while`, `do…while` and `for…in` loops, assignments to props, refs, `const`s, parameters and fields, `try`, `throw`, `var` and a path that ends without `return` are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html) guide |
230
232
  | Functions | functions the template or a `computed` calls: declared in `<script setup>` (`function f(a: T): R { … }`, `const f = (a: T) => …`) or exported from a `.ts` file the component imports, whose bodies hold what a `computed`'s may (see Statements), arrow functions of their own included (`const pad = (k: number) => …`, reading the body's names) and read props, refs, `computed`s, constants and other such functions (a `.ts` file's own constants, functions and imports); parameters by name, optional (`x?: T`), with defaults (`x = 1`, also after an earlier parameter: ``plural = `${word}s` ``) and as object patterns. Each call is translated where it is written, its arguments evaluated once and in order, with the types they have there, so one function serves an integer and a `Float`. A function imported from a `.ts` file needs no `helpers` entry. Recursion, `...rest`, `async` and generators are refused. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html#functions) guide |
231
233
  | Object literals | object literals built as the Rust struct of the interface where they go: a `computed` annotated with it (`computed<Card>(…)`, `computed((): Card => …)`, `ComputedRef<Card>`), a function's return type or parameter, a `.map()` callback's return type or the list type its result is given (`computed<Row[]>`, a child's `Row[]` prop: `:rows="items.map((x) => ({ … }))"`), a child's prop typed with an interface (`:item="{ id, label }"`, `v-bind="{ item: { … } }"`), an annotated `const`, `satisfies`; shorthand keys, computed keys the compiler knows (a constant, an enum member), nested literals and array literals of them, a spread of the same type before the other keys (`{ ...card, title }`), optional fields left out or `undefined`, `null` for a `T \| null` field, a member of a field's union of string literals as its enum's variant (`{ size: "md" }`). An untyped literal is refused with a message that says how to type it. See the crate's [`script_logic`](https://docs.rs/ferrovue/latest/ferrovue/guide/script_logic/index.html#object-literals) guide |
232
234
  | Loaders | A top-level `await` in `<script setup>` of a loader declared with `defineLoader`, whose arguments are strings, numbers and booleans and whose result is any type a prop may have, dates inside it included: the compiler writes a `Loaders` trait the Rust app implements, and the render stops where Vue's would until the result is in, starting siblings' loaders together. The results travel in the page for the client to hydrate from, inside a `<Suspense>` `mountPage` adds. See the crate's [`loaders`](https://docs.rs/ferrovue/latest/ferrovue/guide/loaders/index.html) guide |
233
235
  | Provide and inject | `provide` and `inject` resolved through the component tree, slot content seeing the providers of the component that renders it, as Vue does: keys that are string literals or `InjectionKey` symbols exported from `.ts` files; values that are props, refs, `computed`s, constants or objects of them (an interface's fields, `reactive()` unwrapping refs); defaults, factory defaults (`inject(k, () => …, true)`), object defaults built as the key's interface (`inject(k, { size: "md" })`), and keys holding functions, which only event handlers may call. A page rendered from Rust passes the generated `Provides`, empty or holding what the client app gives `app.provide`. See the crate's [`provide_inject`](https://docs.rs/ferrovue/latest/ferrovue/guide/provide_inject/index.html) guide |
234
236
  | Text | `{{ }}` of strings, integers and booleans; `+`, `-`, `*`, `/`, `%`, with integers and fractions as JavaScript computes them; `<`, `>`, `<=`, `>=` (between numbers, or between strings by UTF-16 code unit), `===`, `!==`; unary `-`, `!`; `??`, `\|\|`, `&&`, `?:` (whose branches may differ in type where the value is only written out: in `{{ }}`, a string-valued attribute and a template literal, each branch is written as JavaScript writes it, `{{ n > 0 ? n : "none" }}`); template literals and `String()` of strings, numbers, booleans, values that may be absent (`undefined` or `null`, as JavaScript writes them) and lists (their items joined by `,`); `typeof`; optional chaining `a?.b`, `a?.[i]` and chains that go on past it (`a?.b.c`, `s?.trim().length`, `list?.at(-1)?.name`), with every property and method the value has when present, `undefined` as a whole once `a` is absent; `.length`; `.toString()`, `.toFixed()`, `.toPrecision()` and `.toExponential()` (with no argument or a literal one), `Math.max`/`min` (of a spread list too, `Math.max(...xs)`)/`abs`/`round`/`floor`/`ceil`/`trunc`/`sqrt`/`sign`, the constants of `Math` and `Number`, `**` and `Math.pow()` of an integer and a whole exponent known when compiling, exact within ±2⁵³; `Number()`, `parseInt()` (no radix, 10 or 16), `parseFloat()`; `Number.isNaN()`, `Number.isFinite()`, `Number.isInteger()` and `Number.isSafeInteger()` of a number, present or optional, and `isNaN()` and `isFinite()` of a present number; `JSON.stringify()` of strings, numbers, booleans and lists of those; `encodeURIComponent()` and `encodeURI()`; array literals |
235
- | Strings | `.trim()`, `.trimStart()`, `.trimEnd()`, `.trimLeft()`, `.trimRight()`, `.toUpperCase()`, `.toLowerCase()`, `.includes()`, `.startsWith()`, `.endsWith()`, `.indexOf()` and `.lastIndexOf()`, with a position or without, `.slice()`, `.substring()`, `.substr()`, `.at()`, `.charAt()`, `.charCodeAt()`, `.codePointAt()`, `.concat()`, `.split()` with a limit or without, `.replace()` and `.replaceAll()` with string patterns (`$&`, `` $` ``, `$'`, `$$` read as JavaScript reads them), `.padStart()`, `.padEnd()`, `.repeat()`, `s[i]`; indices and lengths count UTF-16 code units, as JavaScript's do; a string known to hold ASCII alone (`String(n)`, `n.toFixed(2)`, literals and what is built of them) may be cut into characters and joined again, as a thousands separator is (`String(n).split("").reduce(…)`, slices) (see the crate's [`strings`](https://docs.rs/ferrovue/latest/ferrovue/guide/strings/index.html) guide) |
237
+ | Strings | `.trim()`, `.trimStart()`, `.trimEnd()`, `.trimLeft()`, `.trimRight()`, `.toUpperCase()`, `.toLowerCase()`, `.includes()`, `.startsWith()`, `.endsWith()`, `.indexOf()` and `.lastIndexOf()`, with a position or without, `.slice()`, `.substring()`, `.substr()`, `.at()`, `.charAt()`, `.charCodeAt()`, `.codePointAt()`, `.concat()`, `.split()` with a limit or without, `.replace()` and `.replaceAll()` with string patterns (`$&`, `` $` ``, `$'`, `$$` read as JavaScript reads them), regular expression literals of a subset (characters, classes, `^`, `$`, `\b`, quantifiers, the flags `g` and `i`) in `.replace()`, `.replaceAll()`, `.split()` and `.test()`, `.padStart()`, `.padEnd()`, `.repeat()`, `s[i]`; indices and lengths count UTF-16 code units, as JavaScript's do; a string known to hold ASCII alone (`String(n)`, `n.toFixed(2)`, literals and what is built of them) may be cut into characters and joined again, as a thousands separator is (`String(n).split("").reduce(…)`, slices) (see the crate's [`strings`](https://docs.rs/ferrovue/latest/ferrovue/guide/strings/index.html) guide) |
236
238
  | Dates | `new Date(s)` and `Date.parse(s)` of an ISO string (a date alone, or a date and time with a `Z` or an offset, as ECMAScript's date time string format specifies), `new Date(ms)`, `Date.UTC(…)` of one to seven parts, `.toISOString()`, `.toUTCString()`, `.toJSON()`, `JSON.stringify()`, `.getTime()`, `.valueOf()` and the `.getUTC…()` methods, and `<`, `<=`, `>` and `>=` between two dates, invalid dates included, as JavaScript computes them, a date's validity tested with `Number.isNaN(d.getTime())`, and dates chosen with `?:`, `??` and `||` (a date is always truthy, an invalid one too), of a date built in the template or of a `Date` prop, which an island's `data-props` carries as its ISO string and the client turns back into a `Date` before hydrating, and which may be handed to a child's `Date` prop. A date inside a list (`Date[]`, `(Date \| null)[]`), a dictionary (`Record<string, Date>`) or an interface of the props or of a loader's result (`rows: Row[]` with `at: Date`) is read as a prop's date is, walked with `v-for` and the list methods that do not compare or write dates as text, built in an object literal, and turned back into a `Date` in the browser from a plan the compiler writes beside the props (`data-dates`) and the loaders' results. Prices and formatted dates are written in Rust and passed as strings, or filled into a `$t` message. See the crate's [`dates_and_prices`](https://docs.rs/ferrovue/latest/ferrovue/guide/dates_and_prices/index.html) guide |
237
- | Conditions | `v-if` / `v-else-if` / `v-else`, `?:`, `&&` and `||`, with optional and nullable values narrowed as TypeScript narrows them: `v-if="user"`, `user !== undefined`, `user !== null`, `user != null`, `typeof user !== "undefined"`, and `!user`, `user === undefined` or `user === null` for the `v-else`; a test narrows every operand after it in a chain (`n != null && n > 1 && n < 9`, `n == null \|\| n < 0 \|\| n > 9`, `n != null && n > 1 ? n * 2 : 0`), and `a && b` gives `undefined` where `a`, whose present values are all truthy, is absent (`kind && ICONS[kind]`) |
239
+ | Conditions | `v-if` / `v-else-if` / `v-else`, `?:`, `&&` and `||`, with optional and nullable values narrowed as TypeScript narrows them: `v-if="user"`, `user !== undefined`, `user !== null`, `user != null`, `typeof user !== "undefined"`, and `!user`, `user === undefined` or `user === null` for the `v-else`; a test narrows every operand after it in a chain (`n != null && n > 1 && n < 9`, `n == null \|\| n < 0 \|\| n > 9`, `n != null && n > 1 ? n * 2 : 0`), and `a && b` gives `undefined` where `a`, whose present values are all truthy, is absent (`kind && ICONS[kind]`); a string of a union of string literals narrowed the same way, by `===` and `!==` with a literal, by its truth and by `||`, so that a `ref<Tone | "">` reaches a child's `Tone` prop as `tone \|\| undefined`, `tone === '' ? undefined : tone`, under `v-if="tone !== ''"` or through a `computed` annotated `Tone \| undefined` |
238
240
  | Lists | `v-for` over arrays (of strings, numbers, objects or child props), array literals and number ranges (`n in 5`); with an index; with destructured items (`{ id, name } in rows`); nested; on `<template>`. `.includes()` (a number looked for in a list of numbers whether either is a `number` or a `Float`, as JavaScript compares them), `.join()`, and `.filter()`, `.map()`, `.some()`, `.every()`, `.find()`, `.findIndex()`, `.findLast()`, `.findLastIndex()` with an arrow function of the item (a name, or destructured) and its index, whose body is an expression or a body of statements (see Statements), narrowing inside as outside, or with `Boolean`; `.filter(Boolean)` of a list or an array literal whose items may be `null` or `undefined`, which keeps the ones present and truthy as a list of the values (`[name, title, city].filter(Boolean).join(" · ")`); `Boolean(x)`; `.slice()`; an item by its index, `list[i]` with an integer `i` and `list.at(i)` (from the end when negative), `undefined` outside the list and narrowed as other optional values are (`v-if="rows[0]"`); `.indexOf()` and `.lastIndexOf()` of a string, number or boolean, compared with `===` (`NaN` found nowhere, `-0` found as `0`); `.concat()` of lists of one item type (numbers whether `number` or `Float`); `.reduce()` and `.reduceRight()` with an initial value, into a string, a number or a boolean; `.toReversed()`, and `.toSorted()` by `String(x)` as JavaScript sorts without a comparator (`[10, 9, 1]` to `[1, 10, 9]`) or by a comparator `(a, b) => a - b`, `a.k - b.k` of integers or `?:` over `<` and `>` of integers or strings, stable as JavaScript's; `[...list].sort(…)` and `[...list].reverse()`; spread in array literals (`[...a, x, ...b]`) of one item type; `.flat()` of lists of lists, to a literal depth or `Infinity`, and `.flatMap()` with an arrow function that gives a list (flattened one level) or a value (kept as `.map()` keeps it); chained and nested; a computed list read by `v-for`, `.join()`, `.length`, `.includes()`, kept by `computed`, or handed to a child |
239
241
  | Dictionaries | `v-for="(value, key, index) in r"` over a `Record<string, T>`, and `([key, value], index) in Object.entries(r)`, in JavaScript's order: keys that are array indices first, in numeric order, then the others as the props give them; `Object.keys(r)`, `Object.values(r)` (of a record of lists too, which `.flat()` joins), `Object.hasOwn(r, key)`, and `Object.entries(r)` read by the list methods, whose arrow functions destructure each pair (`([key, value]) => …`, `([, value]) => …`, `([key, { id }]) => …`) or read its parts (`pair[0]`, `pair[1]`), and walked by `v-for="[key, value] in Object.entries(r).filter(…)"`, kept by `computed` and sorted by a comparator of either part; handed to a child |
240
242
  | Attributes | static and bound attributes, boolean attributes, `:hidden`, `data-*` and `aria-*`, `v-bind` objects |
@@ -244,17 +246,17 @@ ferrovue compiles `<script setup lang="ts">` components, and components with no
244
246
  | `v-bind()` in `<style>` | variables bound to props, setup values, `computed`s and constants, quoted expressions included (`v-bind('size + "px"')`), in a scoped or global `<style>`: written into the root's `style` after its own and what a parent passes, on each root of a fragment, and handed to a root that is a component, as Vue's server writes them; `undefined` and `null` as `initial`, an empty string as a space; named as plugin-vue names them for production or development (`isProduction`) |
245
247
  | CSS modules | `<style module>` and `<style module="name">`, read as `$style.x`, `name.x`, `$style['x-y']` and through `useCssModule()`, each class a constant in the generated code, when `cssModules` mirrors Vite's `css.modules.generateScopedName`: computed as postcss-modules computes it in Vite (`[local]`, `[name]`, `[hash:base64:5]`, `[path]` and the rest), composed classes, `@value`s and keyframes included |
246
248
  | Components | imported child components, `v-bind` of a child's own `Props`, listeners in any form (`@x`, `v-on="obj"`, `@[name]`), which the server skips, `v-model` on a child's `defineModel`, recursion; props named in `kebab-case` or `camelCase`; props named like listeners (`on1`, `onSale`), left out when falsy where Vue's `mergeProps` joins a child's props, as Vue leaves them out; `defineAsyncComponent(() => import("./X.vue"))` (or `{ loader: … }`), rendered as the component it loads, which Vue's server renderer waits for |
247
- | `<component :is>` | over a closed set of choices, compiled to a `match`: an imported component (`:is="Card"`), an HTML element's name (`is="h2"`), a `computed` or `?:` choosing among them, a prop typed as a union of string literals (`as: "h1" \| "h2"`, a `match` over its enum's variants), and an object of imported components (or element names) declared in setup or imported from a `.ts` file, read by such a prop (`ICONS[name]`); props, fallthrough attributes, slots and scope ids reach each choice as they reach a static child; also inside `<KeepAlive>` and `<Transition>`. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#component-is) guide |
248
- | Escape hatches | `<ClientOnly>` from `ferrovue/client`, whose default slot the server never renders, so anything may go in it; components listed in `twins`, rendered by Rust functions of yours. See the crate's [`errors_and_limits`](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#client-only-content) guide |
249
+ | `<component :is>` | over a closed set of choices, compiled to a `match`: an imported component (`:is="Card"`), an HTML element's name (`is="h2"`), a `computed` or `?:` choosing among them, a prop or a `defineModel` typed as a union of string literals (`as: "h1" \| "h2"`, a `match` over its enum's variants), and an object of imported components (or element names) declared in setup or imported from a `.ts` file, read by such a prop (`ICONS[name]`); props, fallthrough attributes, slots and scope ids reach each choice as they reach a static child; also inside `<KeepAlive>` and `<Transition>`. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#component-is) guide |
250
+ | Escape hatches | `<ClientOnly>` from `ferrovue`, whose default slot the server never renders, so anything may go in it; components listed in `twins`, rendered by Rust functions of yours. See the crate's [`errors_and_limits`](https://docs.rs/ferrovue/latest/ferrovue/guide/errors_and_limits/index.html#client-only-content) guide |
249
251
  | Fallthrough attributes | what a parent passes a child beyond its props (static and bound attributes, `class`, `style`, `data-*`, `aria-*`, booleans, `undefined`): onto its single root, merged with the root's own class and style and replacing its other attributes where they stand, as Vue's `mergeProps` merges them; none for two roots; on through a root that is a component, or a `<RouterLink>`; with `inheritAttrs: false`, onto the elements or components that bind `v-bind="$attrs"` or a `useAttrs()` binding, before or after their own; beside scope ids. Listeners are dropped, as Vue's server drops them. See the crate's [`generated_code`](https://docs.rs/ferrovue/latest/ferrovue/guide/generated_code/index.html#fallthrough-attributes) guide |
250
252
  | Slots | default and named slots, fallbacks, `$slots.name` tests and the same through `useSlots()`, scoped slots (`<slot :item="x">` and `#item="{ item }"` or `v-slot="props"`), whose props a parent can hand to its own children |
251
- | Forms | `v-model` on text inputs, checkboxes, radios, `<select>` and `<textarea>` (renders the initial state); a `<textarea>`, or an `<input>` with `v-model`, sits inside another element when it would be the component's root (FV0424) |
253
+ | Forms | `v-model` on text inputs, checkboxes, radios, `<select>` and `<textarea>` (renders the initial state); a `<textarea>`, or an `<input>` with `v-model` of a literal `type`, at the component's root where no parent passes it `value` or `type` (FV0424 otherwise) |
252
254
  | Built-ins | `<Transition>`, `<TransitionGroup>`, `<KeepAlive>`, `<Suspense>` (synchronous content), `<Teleport>` (to any target, nested, disabled: see below), `v-text`, `v-once`, `v-pre`, `v-memo`, custom directives listed in `clientDirectives`, on an element with no content when its attributes are bound by name and it is not the component's root (FV0425) |
253
255
  | Vue Router | `<RouterLink>` (resolved by name or imported) with a string `to` or `{ name, params, query, hash }` / `{ path, query, hash }`, `active-class`, `exact-active-class`, `aria-current-value`, `replace`; vue-router's own encoding and active-link matching, nested routes included (a parent link is active on its children's pages, exact only on its own); a history base; optional parameters (`:id?`); routes built from a folder of pages, which the Vite plugin also gives the client as `ferrovue/routes`. `useRoute()` and `$route`: `path`, `fullPath`, `hash`, `name`, `params`, and `query` (a value written once, without `=`, or repeated, exactly as vue-router parses it; `typeof route.query.q === "string"` narrows one to a single string). `<RouterView>`, at the top and in nested route components: each takes the page it shows as a slot |
254
256
  | Pinia | option stores with a typed `state`, and setup stores (`defineStore(id, () => { … })`) whose returned refs are typed by `ref<T>()` or their initial literal; read through `useX()` or `storeToRefs`, in the template or in `computed`; getters that are an expression of the state, and a setup store's computeds, which may read each other |
255
257
  | vue-i18n | `$t` and `useI18n()`'s `t` and `locale`: named and list values, plurals by vue-i18n's rule, literals, linked messages with `upper`/`lower`/`capitalize`, nested and flat keys, fallback locales, a missing key shown as itself. Messages are parsed at build time by vue-i18n's own compiler |
256
- | `v-html` | only on a `TrustedHtml` prop (`import type { TrustedHtml } from "ferrovue/types"`), or an `InlineHtml` one (inline tags only, `ferrovue::InlineHtml` on the server, with no `trustedHtml` needed), the only HTML it may write inside a `<p>`; never on a table's structure, a `<select>` or `<optgroup>`, or an SVG or MathML element that holds no HTML. See the crate's [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html#where-v-html-may-go) guide |
257
- | Page head | `useHead` (and `useServerHead`) from `@unhead/vue` 3: `title`, `titleTemplate`, `base`, `meta`, `link`, `script`, `style`, `noscript`, `htmlAttrs` and `bodyAttrs`, with values the server computes (props, setup bindings, getters such as `() => props.title`, `list.map(x => ({ … }))` for a list of tags), `key`, `tagPosition`, `tagPriority` and `tagDuplicateStrategy`; `useSeoMeta` (and `useServerSeoMeta`) of string, number, boolean and `null` values. Collected into a `ferrovue::Head` in the order Vue's server runs them, and written as unhead's `renderSSRHead` writes them: its tag order, deduplication, title template and escaping. See the crate's [`head`](https://docs.rs/ferrovue/latest/ferrovue/guide/head/index.html) guide |
258
+ | `v-html` | only on a `TrustedHtml` prop (`import type { TrustedHtml } from "ferrovue/types"`), or an `InlineHtml` one (inline tags only, `ferrovue::html::InlineHtml` on the server, with no `trustedHtml` needed), the only HTML it may write inside a `<p>`; never on a table's structure, a `<select>` or `<optgroup>`, or an SVG or MathML element that holds no HTML. See the crate's [`escaping`](https://docs.rs/ferrovue/latest/ferrovue/guide/escaping/index.html#where-v-html-may-go) guide |
259
+ | Page head | `useHead` (and `useServerHead`) from `@unhead/vue` 3: `title`, `titleTemplate`, `base`, `meta`, `link`, `script`, `style`, `noscript`, `htmlAttrs` and `bodyAttrs`, with values the server computes (props, setup bindings, getters such as `() => props.title`, `list.map(x => ({ … }))` for a list of tags), `key`, `tagPosition`, `tagPriority` and `tagDuplicateStrategy`; `useSeoMeta` (and `useServerSeoMeta`) of string, number, boolean and `null` values. Collected into a `ferrovue::head::Head` in the order Vue's server runs them, and written as unhead's `renderSSRHead` writes them: its tag order, deduplication, title template and escaping. See the crate's [`head`](https://docs.rs/ferrovue/latest/ferrovue/guide/head/index.html) guide |
258
260
 
259
261
  Anything else is refused at compile time, with an error that names the file, line and column,
260
262
  quotes the line and puts a caret under the construct. The
@@ -305,7 +307,8 @@ The crate's guide, `ferrovue::guide` on docs.rs, covers each topic in depth:
305
307
  from a Node server that renders with Vue, and the
306
308
  [support policy](https://docs.rs/ferrovue/latest/ferrovue/guide/support/index.html): supported
307
309
  versions, fixes, deprecations and where to report a problem
308
- - Upgrading a project [from 0.10 to 0.11](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_11/index.html),
310
+ - Upgrading a project [from 0.11 to 0.12](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_12/index.html),
311
+ [from 0.10 to 0.11](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_11/index.html),
309
312
  [from 0.9 to 0.10](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_10/index.html),
310
313
  [from 0.8 to 0.9](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_9/index.html)
311
314
  and [from 0.7 to 0.8](https://docs.rs/ferrovue/latest/ferrovue/guide/upgrading_to_0_8/index.html)
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GenError } from "./model.ts";
1
+ import { GenError } from "./errors.ts";
2
2
  import type { Deprecation } from "./schema.ts";
3
3
  /** A command-line option: its names, short one first, the value it takes, and its line in `--help`. */
4
4
  export interface Flag {
package/dist/args.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GenError } from "./model.js";
1
+ import { GenError } from "./errors.js";
2
2
  /** The commands `ferrovue` takes, with their lines in `--help`. */
3
3
  export const COMMANDS = {
4
4
  init: "scaffold starter ferrovue.config.json and components",
package/dist/attrs.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type N, type Scope, type Val } from "./model.ts";
2
+ import { type ErrorCode } from "./errors.ts";
2
3
  import { Emitter } from "./emitter.ts";
3
4
  export declare function display(e: Emitter, v: Val): boolean;
4
5
  export declare function interpolate(s: Scope, e: Emitter, v: Val, n: N): void;
@@ -17,4 +18,7 @@ export declare function renderAttrs(s: Scope, e: Emitter, n: N): void;
17
18
  export declare function mergeProps(s: Scope, n: N): N;
18
19
  export declare function attrEntries(s: Scope, obj: N, side: "vnode" | "own"): string[];
19
20
  export declare function attrOf(s: Scope, key: string, n: N, side: "vnode" | "own"): string;
21
+ /** A binding that is a string, `null` or `undefined`, as an attribute, refused with `code` and `what`
22
+ * when it is anything else. */
23
+ export declare function stringAttr(s: Scope, n: N, code: ErrorCode, what: string): string;
20
24
  export declare function valueAttr(s: Scope, v: Val, n: N): string;
package/dist/attrs.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { escapeHtml, hyphenate, isBooleanAttr, isSSRSafeAttrName, isSVGTag, propsToAttrMap } from "@vue/shared";
2
- import { fail, nothing, GenError, rustStr } from "./model.js";
2
+ import { fail, nothing, rustStr } from "./model.js";
3
+ import { GenError } from "./errors.js";
3
4
  import { asWritten } from "./numbers.js";
4
- import { CONFIG_FILE, ctx } from "./context.js";
5
+ import { CONFIG_FILE } from "./config.js";
6
+ import { ctx } from "./context.js";
5
7
  import { describeTy, expr, shown } from "./expr.js";
6
8
  import { known, truthy } from "./narrowing.js";
7
9
  import { asCow, isTemporary, unquote } from "./strings.js";
@@ -20,10 +22,10 @@ export function display(e, v) {
20
22
  else if (v.ty.k === "bool" && v.konst !== undefined)
21
23
  e.lit(String(v.konst));
22
24
  else if (v.ty.k === "str")
23
- e.stmt(`fv::escape_into(out, ${strArg(v.code)});`);
25
+ e.stmt(`fv::core::escape_into(out, ${strArg(v.code)});`);
24
26
  else if (v.ty.k === "int" || v.ty.k === "float") {
25
27
  const w = asWritten(v);
26
- e.stmt(`fv::${w.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(w.code)});`);
28
+ e.stmt(`fv::core::${w.ty.k === "int" ? "push_int" : "push_number"}(out, ${bare(w.code)});`);
27
29
  e.expect(NUMBER_BYTES);
28
30
  }
29
31
  else if (v.ty.k === "bool")
@@ -188,7 +190,7 @@ export function renderAttrs(s, e, n) {
188
190
  else
189
191
  e.close(" else {");
190
192
  if (parts.length === 1 && parts[0] === n) {
191
- e.stmt(`fv::passed_attrs_into(out, ${s.fallthrough}.list(), ${isAttrs(n) && s.attrs !== null ? s.attrs : '""'});`);
193
+ e.stmt(`fv::attrs::passed_attrs_into(out, ${s.fallthrough}.list(), ${isAttrs(n) && s.attrs !== null ? s.attrs : '""'});`);
192
194
  e.close();
193
195
  return;
194
196
  }
@@ -209,7 +211,7 @@ export function renderAttrs(s, e, n) {
209
211
  fail(s.comp, "FV0407", "attributes must be an object literal", p);
210
212
  }
211
213
  const ids = idsAt >= 0 && s.attrs !== null ? s.attrs : '""';
212
- e.stmt(`fv::attrs_into(out, &[${sources.join(", ")}], ${idsAt >= 0 ? idsAt : sources.length}, ${ids});`);
214
+ e.stmt(`fv::attrs::attrs_into(out, &[${sources.join(", ")}], ${idsAt >= 0 ? idsAt : sources.length}, ${ids});`);
213
215
  e.close();
214
216
  }
215
217
  function staticAttrs(s, e, n) {
@@ -324,27 +326,35 @@ export function attrOf(s, key, n, side) {
324
326
  if (key === "style")
325
327
  return styleAttr(s, n);
326
328
  if (n.type === "NullLiteral")
327
- return "fv::Attr::Undefined";
329
+ return "fv::attrs::Attr::Undefined";
328
330
  return valueAttr(s, attrShown(s, key, n), n);
329
331
  }
332
+ /** A binding that is a string, `null` or `undefined`, as an attribute, refused with `code` and `what`
333
+ * when it is anything else. */
334
+ export function stringAttr(s, n, code, what) {
335
+ const v = expr(s, n);
336
+ if (v.ty.k === "str" || nothing(v.ty) || (v.ty.k === "opt" && v.ty.of.k === "str"))
337
+ return valueAttr(s, v, n);
338
+ return fail(s.comp, code, what, n);
339
+ }
330
340
  export function valueAttr(s, v, n) {
331
341
  switch (v.ty.k) {
332
342
  case "str":
333
- return isTemporary(v) ? `fv::Attr::Str(${asCow(v)})` : `fv::Attr::str(${strArg(v.code)})`;
343
+ return isTemporary(v) ? `fv::attrs::Attr::Str(${asCow(v)})` : `fv::attrs::Attr::str(${strArg(v.code)})`;
334
344
  case "int":
335
- return `fv::Attr::Int(${bare(v.code)})`;
345
+ return `fv::attrs::Attr::Int(${bare(v.code)})`;
336
346
  case "float":
337
- return `fv::Attr::Float(${bare(v.code)})`;
347
+ return `fv::attrs::Attr::Float(${bare(v.code)})`;
338
348
  case "bool":
339
- return `fv::Attr::Bool(${bare(v.code)})`;
349
+ return `fv::attrs::Attr::Bool(${bare(v.code)})`;
340
350
  case "undef":
341
351
  case "null":
342
- return "fv::Attr::Undefined";
352
+ return "fv::attrs::Attr::Undefined";
343
353
  case "opt": {
344
354
  const of = v.ty.of.k;
345
- const make = of === "str" ? "fv::Attr::str" : of === "int" ? "fv::Attr::Int" : of === "float" ? "fv::Attr::Float" : of === "bool" ? "fv::Attr::Bool" : null;
355
+ const make = of === "str" ? "fv::attrs::Attr::str" : of === "int" ? "fv::attrs::Attr::Int" : of === "float" ? "fv::attrs::Attr::Float" : of === "bool" ? "fv::attrs::Attr::Bool" : null;
346
356
  if (make !== null)
347
- return `${atom(v.code)}.map_or(fv::Attr::Undefined, ${make})`;
357
+ return `${atom(v.code)}.map_or(fv::attrs::Attr::Undefined, ${make})`;
348
358
  break;
349
359
  }
350
360
  default:
package/dist/blocks.js CHANGED
@@ -1,4 +1,6 @@
1
- import { fail, GenError, nothing } from "./model.js";
1
+ import { fail, nothing, patternNames, UNION_STR, unionStr } from "./model.js";
2
+ import { GenError } from "./errors.js";
3
+ import { enumListVal } from "./enums.js";
2
4
  import { ctx } from "./context.js";
3
5
  import { fresh, jsHint } from "./names.js";
4
6
  import { expr, typedBy } from "./expr.js";
@@ -27,7 +29,11 @@ export function bindingOf(v) {
27
29
  const lone = v.lone ? { lone: true } : {};
28
30
  if (v.variant !== undefined) {
29
31
  const optional = v.ty.k === "opt";
30
- return { init: bare(v.variant), read: (at) => ({ code: optional ? `${at}.map(|v| v.as_str())` : `${at}.as_str()`, ty: v.ty, variant: at, ...lone }), owned: false };
32
+ return { init: bare(v.variant), read: (at) => ({ code: optional ? `${at}.map(${UNION_STR})` : unionStr(at), ty: v.ty, variant: at, ...lone }), owned: false };
33
+ }
34
+ if (v.variants?.of === v.code && v.ty.k === "list") {
35
+ const { def } = v.variants;
36
+ return { init: `${v.variants.iter}.collect::<Vec<_>>()`, read: (at) => ({ ...enumListVal(at, v.ty, def), ...lone }), owned: true };
31
37
  }
32
38
  if (v.owned)
33
39
  return { init: bare(v.code), read: (at) => ({ code: v.ty.k === "opt" ? `${at}.as_ref()` : at, ty: v.ty, ...lone }), owned: true };
@@ -63,22 +69,6 @@ function declared(st) {
63
69
  return st.declarations.flatMap((d) => patternNames(d.id));
64
70
  return [];
65
71
  }
66
- function patternNames(p) {
67
- switch (p?.type) {
68
- case "Identifier":
69
- return [p.name];
70
- case "ObjectPattern":
71
- return p.properties.flatMap((q) => patternNames(q.type === "RestElement" ? q.argument : q.value));
72
- case "ArrayPattern":
73
- return p.elements.flatMap((q) => (q ? patternNames(q) : []));
74
- case "AssignmentPattern":
75
- return patternNames(p.left);
76
- case "RestElement":
77
- return patternNames(p.argument);
78
- default:
79
- return [];
80
- }
81
- }
82
72
  class Renamer {
83
73
  comp;
84
74
  constructor(comp) {