ferrovue 0.7.2 → 0.9.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.
- package/README.md +154 -691
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +146 -13
- package/dist/children.js +8 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +25 -3
- package/dist/compiler.js +5 -2
- package/dist/component.js +3 -2
- package/dist/context.d.ts +7 -0
- package/dist/context.js +4 -1
- package/dist/errors.d.ts +116 -5
- package/dist/errors.js +81 -6
- package/dist/expr.js +143 -30
- package/dist/fixture.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lists.d.ts +9 -0
- package/dist/lists.js +421 -24
- package/dist/model.d.ts +1 -0
- package/dist/narrowing.d.ts +9 -0
- package/dist/narrowing.js +98 -9
- package/dist/numbers.d.ts +4 -0
- package/dist/numbers.js +61 -2
- package/dist/parens.js +7 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugins/dates.d.ts +25 -0
- package/dist/plugins/dates.js +224 -0
- package/dist/plugins/i18n.js +16 -14
- package/dist/plugins/index.js +2 -1
- package/dist/plugins/router.js +8 -5
- package/dist/rust.d.ts +1 -1
- package/dist/rust.js +22 -9
- package/dist/schema.js +1 -0
- package/dist/script.d.ts +1 -0
- package/dist/script.js +41 -32
- package/dist/strings.d.ts +9 -1
- package/dist/strings.js +113 -23
- package/dist/styles.d.ts +3 -0
- package/dist/styles.js +22 -0
- package/dist/typescript.d.ts +12 -4
- package/dist/typescript.js +50 -12
- package/package.json +20 -20
- package/schema.json +6 -0
package/dist/typescript.js
CHANGED
|
@@ -27,7 +27,10 @@ export function typesImports(comp, body) {
|
|
|
27
27
|
}
|
|
28
28
|
export const ONE_NOTHING = "its Rust type is an `Option`, whose `None` cannot be both, as `=== null` and the props sent to the client would need";
|
|
29
29
|
const NULL_OR_UNDEFINED = `a type that is both \`null\` and \`undefined\`: ${ONE_NOTHING}; keep one of them`;
|
|
30
|
-
export
|
|
30
|
+
export const DATE_PROPS_ONLY = "only a component's own prop may be a `Date`, which the client turns back into one from the prop's declared type: pass an ISO string or a number of milliseconds and build the date with `new Date()` (see the `dates_and_prices` guide)";
|
|
31
|
+
/** A TypeScript type's type, where `dates` says whether it may be a `Date`: a component's own prop,
|
|
32
|
+
* which may be optional or nullable too. */
|
|
33
|
+
export function tyOfTs(comp, t, structs, seen = new Set(), dates = false) {
|
|
31
34
|
switch (t.type) {
|
|
32
35
|
case "TSStringKeyword":
|
|
33
36
|
return STR;
|
|
@@ -38,7 +41,7 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
38
41
|
case "TSArrayType":
|
|
39
42
|
return { k: "list", of: tyOfTs(comp, t.elementType, structs, seen) };
|
|
40
43
|
case "TSParenthesizedType":
|
|
41
|
-
return tyOfTs(comp, t.typeAnnotation, structs, seen);
|
|
44
|
+
return tyOfTs(comp, t.typeAnnotation, structs, seen, dates);
|
|
42
45
|
case "TSTypeLiteral": {
|
|
43
46
|
const [sig] = t.members;
|
|
44
47
|
const key = sig?.parameters?.[0]?.typeAnnotation?.typeAnnotation;
|
|
@@ -49,7 +52,7 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
49
52
|
}
|
|
50
53
|
case "TSTypeOperator":
|
|
51
54
|
if (t.operator === "readonly")
|
|
52
|
-
return tyOfTs(comp, t.typeAnnotation, structs, seen);
|
|
55
|
+
return tyOfTs(comp, t.typeAnnotation, structs, seen, dates);
|
|
53
56
|
break;
|
|
54
57
|
case "TSLiteralType":
|
|
55
58
|
if (t.literal.type === "StringLiteral" || t.literal.type === "TemplateLiteral")
|
|
@@ -69,7 +72,7 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
69
72
|
else if (isNull(u))
|
|
70
73
|
none = joinAbsence(none, "null");
|
|
71
74
|
else {
|
|
72
|
-
const ty = tyOfTs(comp, u, structs, seen);
|
|
75
|
+
const ty = tyOfTs(comp, u, structs, seen, dates);
|
|
73
76
|
none = joinAbsence(none, absence(ty));
|
|
74
77
|
tys.push(ty.k === "opt" ? ty.of : ty);
|
|
75
78
|
}
|
|
@@ -103,6 +106,11 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
103
106
|
}
|
|
104
107
|
if (comp.childProps.has(name))
|
|
105
108
|
return { k: "child", name: comp.childProps.get(name) };
|
|
109
|
+
if (name === "Date" && !comp.aliases.has(name) && !comp.importedTypes.has(name)) {
|
|
110
|
+
if (!dates)
|
|
111
|
+
fail(comp, "FV1806", `a \`Date\` here: ${DATE_PROPS_ONLY}`, t);
|
|
112
|
+
return { k: "date" };
|
|
113
|
+
}
|
|
106
114
|
if (comp.floatName !== null && name === comp.floatName)
|
|
107
115
|
return FLOAT;
|
|
108
116
|
if (comp.inlineName !== null && name === comp.inlineName)
|
|
@@ -123,7 +131,7 @@ export function tyOfTs(comp, t, structs, seen = new Set()) {
|
|
|
123
131
|
if (alias) {
|
|
124
132
|
if (seen.has(name))
|
|
125
133
|
fail(comp, "FV0314", `type \`${name}\` refers to itself through an alias`, t);
|
|
126
|
-
return tyOfTs(comp, alias, structs, new Set(seen).add(name));
|
|
134
|
+
return tyOfTs(comp, alias, structs, new Set(seen).add(name), dates);
|
|
127
135
|
}
|
|
128
136
|
return fail(comp, "FV0315", `unsupported prop type \`${name}\`: declare it as an interface in the component, or import it from a \`.ts\` file`, t);
|
|
129
137
|
}
|
|
@@ -233,15 +241,16 @@ export function reachType(name) {
|
|
|
233
241
|
throw e;
|
|
234
242
|
}
|
|
235
243
|
}
|
|
236
|
-
/** Translate an interface a component declares, the first time something the server renders
|
|
237
|
-
|
|
244
|
+
/** Translate an interface a component declares, the first time something the server renders
|
|
245
|
+
* reaches it; `dates` when it is the component's props. */
|
|
246
|
+
export function reachLocal(comp, name, dates = false) {
|
|
238
247
|
const decl = comp.typeDecls.get(name);
|
|
239
248
|
if (decl === undefined || comp.reached.has(name))
|
|
240
249
|
return;
|
|
241
250
|
comp.reached.add(name);
|
|
242
251
|
try {
|
|
243
252
|
refusePrelude(comp, { name, node: decl.node });
|
|
244
|
-
comp.structs.set(name, structOf(comp, name, decl.members, comp.structs, true));
|
|
253
|
+
comp.structs.set(name, structOf(comp, name, decl.members, comp.structs, true, dates));
|
|
245
254
|
}
|
|
246
255
|
catch (e) {
|
|
247
256
|
comp.reached.delete(name);
|
|
@@ -265,6 +274,35 @@ export function requireWhole(comp, ty, seen = new Set()) {
|
|
|
265
274
|
for (const f of st.fields)
|
|
266
275
|
requireWhole(owner, f.ty, seen);
|
|
267
276
|
}
|
|
277
|
+
/** Whether a value of the type holds a `Date` inside a list, a dictionary or an object. */
|
|
278
|
+
export function holdsDate(comp, ty, seen = new Set()) {
|
|
279
|
+
if (ty.k === "date")
|
|
280
|
+
return true;
|
|
281
|
+
if (ty.k === "opt" || ty.k === "list" || ty.k === "record")
|
|
282
|
+
return holdsDate(comp, ty.of, seen);
|
|
283
|
+
if (ty.k === "child") {
|
|
284
|
+
const child = childOf(ty.name);
|
|
285
|
+
if (seen.has(child))
|
|
286
|
+
return false;
|
|
287
|
+
seen.add(child);
|
|
288
|
+
return child.props.fields.some((f) => holdsDate(child, f.ty, seen));
|
|
289
|
+
}
|
|
290
|
+
if (ty.k !== "struct")
|
|
291
|
+
return false;
|
|
292
|
+
const { st, owner } = lookupStruct(comp, ty);
|
|
293
|
+
if (st === undefined || seen.has(st))
|
|
294
|
+
return false;
|
|
295
|
+
seen.add(st);
|
|
296
|
+
return st.fields.some((f) => holdsDate(owner, f.ty, seen));
|
|
297
|
+
}
|
|
298
|
+
/** Refuse a prop that holds a `Date` anywhere but as itself, optional or nullable. */
|
|
299
|
+
export function refuseNestedDates(comp, fields, node) {
|
|
300
|
+
for (const f of fields) {
|
|
301
|
+
const of = f.ty.k === "opt" ? f.ty.of : f.ty;
|
|
302
|
+
if (of.k !== "date" && holdsDate(comp, of))
|
|
303
|
+
fail(comp, "FV1806", `\`${f.js}\` holds a \`Date\` inside a list or an object: ${DATE_PROPS_ONLY}`, node);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
268
306
|
/** Whether `types.rs` holds the type: one built from a constant list, or a declared one something reached. */
|
|
269
307
|
export function typeWritten(name) {
|
|
270
308
|
return !ctx.typeDecls.has(name) || ctx.typeReached.has(name);
|
|
@@ -294,12 +332,12 @@ export function lookupStruct(comp, ty) {
|
|
|
294
332
|
reachLocal(comp, ty.name);
|
|
295
333
|
return { st: comp.structs.get(ty.name), owner: comp, path: "" };
|
|
296
334
|
}
|
|
297
|
-
export function structOf(comp, name, members, structs, lenient = false) {
|
|
335
|
+
export function structOf(comp, name, members, structs, lenient = false, dates = false) {
|
|
298
336
|
const fields = [];
|
|
299
337
|
const refused = new Map();
|
|
300
338
|
for (const m of members) {
|
|
301
339
|
try {
|
|
302
|
-
fields.push(fieldOf(comp, name, m, structs));
|
|
340
|
+
fields.push(fieldOf(comp, name, m, structs, dates));
|
|
303
341
|
}
|
|
304
342
|
catch (e) {
|
|
305
343
|
if (!lenient || !(e instanceof GenError))
|
|
@@ -318,11 +356,11 @@ function memberKey(m) {
|
|
|
318
356
|
return "";
|
|
319
357
|
return m.key.type === "Identifier" ? m.key.name : m.key.type === "StringLiteral" ? m.key.value : "";
|
|
320
358
|
}
|
|
321
|
-
function fieldOf(comp, name, m, structs) {
|
|
359
|
+
function fieldOf(comp, name, m, structs, dates) {
|
|
322
360
|
if (m.type !== "TSPropertySignature" || m.key.type !== "Identifier") {
|
|
323
361
|
fail(comp, "FV0320", `\`${name}\` may only hold plain named fields`, m);
|
|
324
362
|
}
|
|
325
|
-
const base = tyOfTs(comp, m.typeAnnotation.typeAnnotation, structs);
|
|
363
|
+
const base = tyOfTs(comp, m.typeAnnotation.typeAnnotation, structs, new Set(), dates);
|
|
326
364
|
if (m.optional && absence(base) === "null") {
|
|
327
365
|
fail(comp, "FV0321", `\`${m.key.name}?: T | null\` may be absent, which is \`undefined\`, or \`null\`: ${ONE_NOTHING}; declare it \`${m.key.name}: T | null\` or \`${m.key.name}?: T\``, m);
|
|
328
366
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ferrovue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Vue templates compiled to Rust render functions: server-side rendering with no JavaScript at run time",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@babel/parser": "^7.29.9",
|
|
80
|
-
"@intlify/message-compiler": "11.4.
|
|
81
|
-
"@vue/compiler-sfc": "3.5.
|
|
82
|
-
"@vue/shared": "3.5.
|
|
80
|
+
"@intlify/message-compiler": "11.4.15",
|
|
81
|
+
"@vue/compiler-sfc": "3.5.44",
|
|
82
|
+
"@vue/shared": "3.5.44",
|
|
83
83
|
"generic-names": "^4.0.0",
|
|
84
|
-
"postcss": "^8.5.
|
|
84
|
+
"postcss": "^8.5.30",
|
|
85
85
|
"postcss-modules-extract-imports": "^3.1.0",
|
|
86
86
|
"postcss-modules-local-by-default": "^4.2.0",
|
|
87
87
|
"postcss-modules-scope": "^3.2.1",
|
|
@@ -89,30 +89,30 @@
|
|
|
89
89
|
"source-map-js": "^1.2.2"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
|
-
"@unhead/vue": "^3.4.
|
|
93
|
-
"@vue/language-core": "3.3.
|
|
94
|
-
"@vue/language-service": "3.3.
|
|
95
|
-
"@vue/server-renderer": "3.5.
|
|
96
|
-
"pinia": "^4.0
|
|
97
|
-
"playwright": "1.64.
|
|
92
|
+
"@unhead/vue": "^3.4.3",
|
|
93
|
+
"@vue/language-core": "3.3.13",
|
|
94
|
+
"@vue/language-service": "3.3.13",
|
|
95
|
+
"@vue/server-renderer": "3.5.44",
|
|
96
|
+
"pinia": "^4.1.0",
|
|
97
|
+
"playwright": "1.64.1",
|
|
98
98
|
"typescript": "^7.0.2",
|
|
99
99
|
"typescript-js": "npm:typescript@6.0.3",
|
|
100
100
|
"vite": "^8.3.4",
|
|
101
|
-
"vitest": "^5.0.
|
|
101
|
+
"vitest": "^5.0.5",
|
|
102
102
|
"vscode-uri": "3.2.0",
|
|
103
|
-
"vue": "3.5.
|
|
104
|
-
"vue-i18n": "11.4.
|
|
105
|
-
"vue-router": "^5.
|
|
103
|
+
"vue": "3.5.44",
|
|
104
|
+
"vue-i18n": "11.4.15",
|
|
105
|
+
"vue-router": "^5.4.0"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@unhead/vue": "~3.4.
|
|
108
|
+
"@unhead/vue": "~3.4.3",
|
|
109
109
|
"@vue/language-core": "^3.3.0",
|
|
110
|
-
"pinia": "~4.0
|
|
110
|
+
"pinia": "~4.1.0",
|
|
111
111
|
"vite": ">=6",
|
|
112
112
|
"vitest": ">=5",
|
|
113
|
-
"vue": "~3.5.
|
|
114
|
-
"vue-i18n": "~11.4.
|
|
115
|
-
"vue-router": "~5.
|
|
113
|
+
"vue": "~3.5.44",
|
|
114
|
+
"vue-i18n": "~11.4.15",
|
|
115
|
+
"vue-router": "~5.4.0"
|
|
116
116
|
},
|
|
117
117
|
"peerDependenciesMeta": {
|
|
118
118
|
"@unhead/vue": {
|
package/schema.json
CHANGED
|
@@ -144,6 +144,12 @@
|
|
|
144
144
|
"markdownDescription": "The Rust type a `TrustedHtml` prop is, which must implement `ferrovue::TrustedHtml`, e.g. `crate::sanitize::SafeHtml`. A type that borrows names the props' lifetime, `'a`: `crate::render::Trusted<'a>`. `ferrovue::InlineHtml` makes every `TrustedHtml` prop inline HTML. Without it, only an `InlineHtml` prop may reach `v-html`.",
|
|
145
145
|
"type": "string"
|
|
146
146
|
},
|
|
147
|
+
"dateType": {
|
|
148
|
+
"description": "The Rust type a `Date` prop is, which must implement `ferrovue::JsTime`: `ferrovue::JsDate` by default, or `chrono::DateTime<chrono::Utc>` with the crate's `chrono` feature.",
|
|
149
|
+
"markdownDescription": "The Rust type a `Date` prop is, which must implement `ferrovue::JsTime`: `ferrovue::JsDate` by default, or `chrono::DateTime<chrono::Utc>` with the crate's `chrono` feature.",
|
|
150
|
+
"type": "string",
|
|
151
|
+
"default": "ferrovue::JsDate"
|
|
152
|
+
},
|
|
147
153
|
"routes": {
|
|
148
154
|
"description": "A JSON file listing the app's routes as vue-router paths (`/users/:id`), which `<RouterLink>` resolves against, or `{ pages }`: a folder of pages whose file names give the routes, as vue-router's file-based routing reads them. Without it, a component cannot use `<RouterLink>`.",
|
|
149
155
|
"markdownDescription": "A JSON file listing the app's routes as vue-router paths (`/users/:id`), which `<RouterLink>` resolves against, or `{ pages }`: a folder of pages whose file names give the routes, as vue-router's file-based routing reads them. Without it, a component cannot use `<RouterLink>`.",
|