hono-svelte 0.6.2 → 0.7.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/CHANGELOG.md +22 -0
- package/README.md +27 -7
- package/dist/index.d.ts +10 -0
- package/dist/page-data.d.ts +20 -1
- package/dist/page-data.js +119 -19
- package/dist/vite.js +31 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- **feat(vite): per-page `Data` can use imported types.** The generated dts
|
|
6
|
+
rewrites references to types imported in the page's `<script module>` as
|
|
7
|
+
`import("…")` type queries (named, aliased, default and namespace
|
|
8
|
+
imports; alias/package specifiers kept, relative ones re-resolved from the
|
|
9
|
+
dts). Pages whose data comes from the app's own types — every real app —
|
|
10
|
+
can now use per-page typing instead of being skipped with a warning.
|
|
11
|
+
- **fix(vite): typed `c.render` overloads are actually enforced.** The
|
|
12
|
+
catch-all overload accepted every entry, so a wrong `data` for a typed
|
|
13
|
+
page failed its overload and silently matched the catch-all
|
|
14
|
+
(`Record<string, unknown>`). The catch-all now excludes typed entries.
|
|
15
|
+
Behavior change: rendering through a value typed as the whole `Entry`
|
|
16
|
+
union no longer compiles once a page declares `Data`. New `UntypedEntry`
|
|
17
|
+
(pages without `Data`) and `TypedEntry` types: generic helpers take
|
|
18
|
+
`UntypedEntry`.
|
|
19
|
+
- Imported types inside template literal types (`\`row-${RowId}\``) are
|
|
20
|
+
qualified too; a `//` inside an import specifier (URL) no longer cuts it.
|
|
21
|
+
- New `test/types.test.ts` case runs `tsc` on a page whose `Data` imports
|
|
22
|
+
an app type: correct data compiles, a wrong field type and a missing field
|
|
23
|
+
fail.
|
|
24
|
+
|
|
3
25
|
## 0.6.2
|
|
4
26
|
|
|
5
27
|
- **fix(shell): unknown-entry errors never list the app's pages in
|
package/README.md
CHANGED
|
@@ -360,12 +360,16 @@ declare module "hono" {
|
|
|
360
360
|
}
|
|
361
361
|
```
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
The package exports the entry unions — the generated ones once the `.d.ts` is in the program, `string` without it. No import of the generated file needed:
|
|
364
|
+
|
|
365
|
+
- `Entry`: every page.
|
|
366
|
+
- `TypedEntry`: pages that declare a `Data` type (see per-page data typing below).
|
|
367
|
+
- `UntypedEntry`: the others — use it in helpers that forward an entry to `c.render`; typed pages need their own `data` and are rendered directly.
|
|
364
368
|
|
|
365
369
|
```ts
|
|
366
|
-
import type {
|
|
370
|
+
import type { UntypedEntry } from "hono-svelte";
|
|
367
371
|
|
|
368
|
-
export const page = (entry:
|
|
372
|
+
export const page = (entry: UntypedEntry, title: string) => (c: Context) => c.render(entry, { title });
|
|
369
373
|
```
|
|
370
374
|
|
|
371
375
|
`dts: "path/to/entries.d.ts"` sets a custom path (relative to the Vite root). The file regenerates on `buildStart` and via the dev watcher when entries change. Without `dts` (default `false`), declare the renderer manually with a plain `string`:
|
|
@@ -416,14 +420,30 @@ declare module "hono" {
|
|
|
416
420
|
}
|
|
417
421
|
```
|
|
418
422
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
+
`Data` can use types imported in the same `<script module>` — the usual
|
|
424
|
+
case when the data comes from your database layer:
|
|
425
|
+
|
|
426
|
+
```svelte
|
|
427
|
+
<script module lang="ts">
|
|
428
|
+
import type { Row } from "$lib/db";
|
|
429
|
+
import type { LayoutData } from "../dashboard/layout-data";
|
|
430
|
+
export type Data = LayoutData & { rows: Row[] };
|
|
431
|
+
</script>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The generated dts rewrites each imported reference as an `import("…")` type
|
|
435
|
+
query (`import("$lib/db").Row`). Alias and package specifiers are kept as
|
|
436
|
+
written (your `tsconfig` resolves them anywhere); relative ones are
|
|
437
|
+
re-resolved from the dts location. Types declared locally in the module
|
|
438
|
+
script (not exported from another file) can't be referenced. Both
|
|
423
439
|
`export type Data = ...` and `export interface Data { ... }` work (including
|
|
424
440
|
legacy `<script context="module">`). `pages().dataTypes()` exposes the
|
|
425
441
|
extracted declarations.
|
|
426
442
|
|
|
443
|
+
Entries with a `Data` type only accept their own overload: `c.render(entry, …)`
|
|
444
|
+
with an entry typed as the whole `Entry` union no longer compiles — type
|
|
445
|
+
generic helpers with `UntypedEntry`, and render typed pages directly.
|
|
446
|
+
|
|
427
447
|
## Production tips
|
|
428
448
|
|
|
429
449
|
- Pass the Vite manifest to `shell({ assets: manifest })` for hashed files — including the global stylesheet: with `styles: resolve("src/styles.css")` in the client input (or `stylesEntry` pointing at another file), its hashed name comes from the manifest, no hand-written `stylesHref`. The shell walks the entry's static import graph: the CSS of shared chunks (layouts, components used by several pages) is linked together with the entry's own CSS, and every reachable chunk gets a `modulepreload`.
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,16 @@ export interface Register {
|
|
|
35
35
|
export type Entry = Register extends {
|
|
36
36
|
entries: infer E extends string;
|
|
37
37
|
} ? E : string;
|
|
38
|
+
/** Entries whose page declares a `Data` type (they only accept their own `data`). */
|
|
39
|
+
export type TypedEntry = Register extends {
|
|
40
|
+
typedEntries: infer E extends string;
|
|
41
|
+
} ? E : never;
|
|
42
|
+
/**
|
|
43
|
+
* Entries without a `Data` type — what a generic helper forwarding an entry
|
|
44
|
+
* to `c.render` should accept: typed entries need their own `data` and have
|
|
45
|
+
* no overload taking an arbitrary entry. `string` without the generated `.d.ts`.
|
|
46
|
+
*/
|
|
47
|
+
export type UntypedEntry = Exclude<Entry, TypedEntry>;
|
|
38
48
|
export type RenderProps<TData = Record<string, unknown>> = {
|
|
39
49
|
title?: string;
|
|
40
50
|
data?: TData;
|
package/dist/page-data.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
/** An import of the module script that the `Data` declaration references. */
|
|
2
|
+
export type DataImport = {
|
|
3
|
+
/** Local name used in the declaration. */
|
|
4
|
+
local: string;
|
|
5
|
+
/** Exported name: an identifier, `"default"` or `"*"` (namespace import). */
|
|
6
|
+
imported: string;
|
|
7
|
+
/** Module specifier as written in the page. */
|
|
8
|
+
specifier: string;
|
|
9
|
+
};
|
|
1
10
|
export type PageDataExtraction = {
|
|
2
11
|
/** Declaration text (`type Data = ...` / `interface Data { ... }`, `export` stripped). Null when the page declares no `Data`. */
|
|
3
12
|
declaration: string | null;
|
|
4
|
-
/** Imported identifiers referenced by the declaration
|
|
13
|
+
/** Imported identifiers referenced by the declaration. */
|
|
5
14
|
importedRefs: string[];
|
|
15
|
+
/** The imports behind `importedRefs`, used to qualify the declaration for the dts. */
|
|
16
|
+
imports: DataImport[];
|
|
6
17
|
};
|
|
7
18
|
export declare function extractPageDataType(source: string): PageDataExtraction;
|
|
19
|
+
/**
|
|
20
|
+
* Rewrites every reference to an imported identifier as an `import("…")`
|
|
21
|
+
* type query, so the declaration resolves from any file (the generated dts):
|
|
22
|
+
* `Row` → `import("$lib/db").Row`, a default import → `.default`, a
|
|
23
|
+
* namespace `ns.Row` → `import("…").Row`. `specifierFor` maps the page's
|
|
24
|
+
* specifier to one valid from the dts location (relative paths move).
|
|
25
|
+
*/
|
|
26
|
+
export declare function qualifyImports(declaration: string, imports: DataImport[], specifierFor: (specifier: string) => string): string;
|
package/dist/page-data.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// `export type Data = ...` / `export interface Data { ... }` inside a page's
|
|
3
3
|
// `<script module lang="ts">` (legacy `<script context="module">` works too).
|
|
4
4
|
//
|
|
5
|
-
// The declaration is
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// The declaration is copied into the dts. Types it imports in the module
|
|
6
|
+
// script (`import type { Row } from "$lib/db"`) are carried along: each
|
|
7
|
+
// reference becomes an `import("…")` type query (`qualifyImports`), with
|
|
8
|
+
// relative specifiers re-resolved from the dts location by the caller.
|
|
9
9
|
const MODULE_SCRIPT_RE = /<script\b[^>]*\bmodule\b[^>]*>([\s\S]*?)<\/script>/;
|
|
10
10
|
const DATA_DECL_RE = /(?:export\s+)?(type|interface)\s+Data\b/;
|
|
11
11
|
export function extractPageDataType(source) {
|
|
12
|
-
const none = { declaration: null, importedRefs: [] };
|
|
12
|
+
const none = { declaration: null, importedRefs: [], imports: [] };
|
|
13
13
|
const script = MODULE_SCRIPT_RE.exec(source)?.[1];
|
|
14
14
|
if (!script)
|
|
15
15
|
return none;
|
|
@@ -26,8 +26,100 @@ export function extractPageDataType(source) {
|
|
|
26
26
|
.replace(/\s+$/, "");
|
|
27
27
|
if (!declaration)
|
|
28
28
|
return none;
|
|
29
|
-
const
|
|
30
|
-
return { declaration, importedRefs };
|
|
29
|
+
const imports = scriptImports(script).filter((imp) => findReferences(declaration, imp.local).length > 0);
|
|
30
|
+
return { declaration, importedRefs: imports.map((imp) => imp.local), imports };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rewrites every reference to an imported identifier as an `import("…")`
|
|
34
|
+
* type query, so the declaration resolves from any file (the generated dts):
|
|
35
|
+
* `Row` → `import("$lib/db").Row`, a default import → `.default`, a
|
|
36
|
+
* namespace `ns.Row` → `import("…").Row`. `specifierFor` maps the page's
|
|
37
|
+
* specifier to one valid from the dts location (relative paths move).
|
|
38
|
+
*/
|
|
39
|
+
export function qualifyImports(declaration, imports, specifierFor) {
|
|
40
|
+
const edits = [];
|
|
41
|
+
for (const imp of imports) {
|
|
42
|
+
const target = `import(${JSON.stringify(specifierFor(imp.specifier))})`;
|
|
43
|
+
const text = imp.imported === "*" ? target : imp.imported === "default" ? `${target}.default` : `${target}.${imp.imported}`;
|
|
44
|
+
for (const at of findReferences(declaration, imp.local))
|
|
45
|
+
edits.push({ at, length: imp.local.length, text });
|
|
46
|
+
}
|
|
47
|
+
let out = declaration;
|
|
48
|
+
for (const edit of edits.sort((a, b) => b.at - a.at)) {
|
|
49
|
+
out = out.slice(0, edit.at) + edit.text + out.slice(edit.at + edit.length);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Positions where `name` is used as a type reference: a whole identifier,
|
|
55
|
+
* not a property access (`.name`), not an object/interface key
|
|
56
|
+
* (`{ name: …`), outside strings and comments — but inside the `${…}`
|
|
57
|
+
* holes of template literal types (`\`row-${RowId}\``), which are types.
|
|
58
|
+
*/
|
|
59
|
+
function findReferences(text, name) {
|
|
60
|
+
const found = [];
|
|
61
|
+
const isId = (ch) => ch !== undefined && /[\w$]/.test(ch);
|
|
62
|
+
for (let i = 0; i < text.length; i++) {
|
|
63
|
+
const ch = text[i];
|
|
64
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
65
|
+
while (i < text.length && text[i] !== "\n")
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
70
|
+
i += 2;
|
|
71
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
72
|
+
i++;
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (ch === "`") {
|
|
77
|
+
i++;
|
|
78
|
+
while (i < text.length && text[i] !== "`") {
|
|
79
|
+
if (text[i] === "\\") {
|
|
80
|
+
i += 2;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (text[i] === "$" && text[i + 1] === "{") {
|
|
84
|
+
const start = i + 2;
|
|
85
|
+
let depth = 1;
|
|
86
|
+
let j = start;
|
|
87
|
+
while (j < text.length && depth > 0) {
|
|
88
|
+
if (text[j] === "{")
|
|
89
|
+
depth++;
|
|
90
|
+
else if (text[j] === "}")
|
|
91
|
+
depth--;
|
|
92
|
+
j++;
|
|
93
|
+
}
|
|
94
|
+
for (const at of findReferences(text.slice(start, j - 1), name))
|
|
95
|
+
found.push(start + at);
|
|
96
|
+
i = j;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === '"' || ch === "'") {
|
|
104
|
+
i++;
|
|
105
|
+
while (i < text.length && text[i] !== ch) {
|
|
106
|
+
if (text[i] === "\\")
|
|
107
|
+
i++;
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!text.startsWith(name, i) || isId(text[i - 1]) || isId(text[i + name.length]))
|
|
113
|
+
continue;
|
|
114
|
+
const before = text.slice(0, i).replace(/\s+$/, "");
|
|
115
|
+
const after = text.slice(i + name.length).replace(/^\s+/, "");
|
|
116
|
+
const isPropertyAccess = before.endsWith(".");
|
|
117
|
+
const isKey = (before === "" || /[{;,(]$/.test(before)) && /^\??:/.test(after);
|
|
118
|
+
if (!isPropertyAccess && !isKey)
|
|
119
|
+
found.push(i);
|
|
120
|
+
i += name.length - 1;
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
31
123
|
}
|
|
32
124
|
/**
|
|
33
125
|
* Scan to the end of the declaration starting right after `Data`.
|
|
@@ -95,25 +187,33 @@ function scanDeclarationEnd(text, from, isInterface) {
|
|
|
95
187
|
// EOF: a `type` alias that never hit `;` but balanced its brackets.
|
|
96
188
|
return !isInterface && seenBrackets ? text.length : -1;
|
|
97
189
|
}
|
|
98
|
-
/**
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
|
|
190
|
+
/** Bindings created by the `import … from "…"` clauses of the script, in order. */
|
|
191
|
+
function scriptImports(script) {
|
|
192
|
+
const imports = [];
|
|
193
|
+
const add = (imp) => {
|
|
194
|
+
if (!imports.some((other) => other.local === imp.local))
|
|
195
|
+
imports.push(imp);
|
|
196
|
+
};
|
|
197
|
+
const code = script.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/gm, "$1");
|
|
198
|
+
for (const m of code.matchAll(/import\s+(?:type\s+)?([\s\S]*?)\s*from\s*(["'])([^"']+)\2/g)) {
|
|
102
199
|
const clause = m[1];
|
|
200
|
+
const specifier = m[3];
|
|
103
201
|
const def = /^\s*([\w$]+)/.exec(clause);
|
|
104
202
|
if (def)
|
|
105
|
-
|
|
203
|
+
add({ local: def[1], imported: "default", specifier });
|
|
106
204
|
const ns = /\*\s+as\s+([\w$]+)/.exec(clause);
|
|
107
205
|
if (ns)
|
|
108
|
-
|
|
206
|
+
add({ local: ns[1], imported: "*", specifier });
|
|
109
207
|
for (const named of clause.matchAll(/\{([^}]*)\}/g)) {
|
|
110
|
-
for (const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
208
|
+
for (const raw of named[1].split(",")) {
|
|
209
|
+
const part = raw.trim().replace(/^type\s+/, "");
|
|
210
|
+
if (!part)
|
|
211
|
+
continue;
|
|
212
|
+
const [imported, local = imported] = part.split(/\s+as\s+/).map((x) => x.trim());
|
|
213
|
+
if (/^[\w$]+$/.test(imported) && /^[\w$]+$/.test(local))
|
|
214
|
+
add({ local, imported, specifier });
|
|
115
215
|
}
|
|
116
216
|
}
|
|
117
217
|
}
|
|
118
|
-
return
|
|
218
|
+
return imports;
|
|
119
219
|
}
|
package/dist/vite.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from "no
|
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { getIds } from "./ids.js";
|
|
4
4
|
import { isLayoutEntry, layoutChain } from "./layouts.js";
|
|
5
|
-
import { extractPageDataType } from "./page-data.js";
|
|
5
|
+
import { extractPageDataType, qualifyImports } from "./page-data.js";
|
|
6
6
|
import { ENTRY_PREFIX, ENTRY_RESOLVED_PREFIX, MANIFEST_ID, MANIFEST_RESOLVED, } from "./virtual.js";
|
|
7
7
|
import { createFilter } from "vite";
|
|
8
8
|
const DEFAULT_IGNORE = ["**/_*.svelte"];
|
|
@@ -47,7 +47,6 @@ export function pages(options = {}) {
|
|
|
47
47
|
let vitePlugins = [];
|
|
48
48
|
let viteMode = "";
|
|
49
49
|
let lastWrittenDts = null;
|
|
50
|
-
const warnedDataImports = new Set();
|
|
51
50
|
function computeLayouts(allFiles) {
|
|
52
51
|
if (!layoutsEnabled)
|
|
53
52
|
return [];
|
|
@@ -115,26 +114,11 @@ export function pages(options = {}) {
|
|
|
115
114
|
// the page needs JS. Layout-wrapped static pages ship zero JS.
|
|
116
115
|
const isStatic = !hasScript && !alwaysClient.has(entryName);
|
|
117
116
|
// Per-page typed data: `export type Data` / `interface Data` in a
|
|
118
|
-
// <script module> feeds a typed overload in the generated dts.
|
|
119
|
-
//
|
|
120
|
-
// type referencing imports can't be resolved from the dts location.
|
|
117
|
+
// <script module> feeds a typed overload in the generated dts. Types it
|
|
118
|
+
// imports are carried along as `import("…")` queries (see dataDeclaration).
|
|
121
119
|
const extracted = extractPageDataType(source);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (extracted.importedRefs.length > 0) {
|
|
125
|
-
if (!warnedDataImports.has(entryName)) {
|
|
126
|
-
warnedDataImports.add(entryName);
|
|
127
|
-
console.warn(`[hono-svelte] page "${entryName}": the Data type references imported ` +
|
|
128
|
-
`identifier(s) [${extracted.importedRefs.join(", ")}]. Per-page data typing ` +
|
|
129
|
-
`needs a self-contained type (inline shapes or built-ins only) — this page ` +
|
|
130
|
-
`keeps Record<string, unknown> data.`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
dataType = extracted.declaration;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
result.push({ entryName, file, absFile, isStatic, layouts, dataType });
|
|
120
|
+
const dataType = extracted.declaration ?? undefined;
|
|
121
|
+
result.push({ entryName, file, absFile, isStatic, layouts, dataType, dataImports: extracted.imports });
|
|
138
122
|
}
|
|
139
123
|
return result;
|
|
140
124
|
}
|
|
@@ -160,7 +144,7 @@ export function pages(options = {}) {
|
|
|
160
144
|
}
|
|
161
145
|
function stateKey() {
|
|
162
146
|
return listPages()
|
|
163
|
-
.map((p) => (p.isStatic ? "S:" : "C:") + p.entryName + (p.dataType ? "@" + p.dataType : ""))
|
|
147
|
+
.map((p) => (p.isStatic ? "S:" : "C:") + p.entryName + (p.dataType ? "@" + p.dataType + JSON.stringify(p.dataImports) : ""))
|
|
164
148
|
.join("|");
|
|
165
149
|
}
|
|
166
150
|
function resolveDtsPath() {
|
|
@@ -170,6 +154,24 @@ export function pages(options = {}) {
|
|
|
170
154
|
const rel = typeof options.dts === "string" ? options.dts : "src/hono-svelte-entries.d.ts";
|
|
171
155
|
return isAbsolute(rel) ? rel : resolve(root, rel);
|
|
172
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The page's `Data` declaration, valid from the dts location: imported
|
|
159
|
+
* types become `import("…")` queries. Bare/alias specifiers (`$lib/db`,
|
|
160
|
+
* `some-package`) stay as written; relative ones are re-resolved from the
|
|
161
|
+
* page file to the dts file (or the Vite root when `dts` is off).
|
|
162
|
+
*/
|
|
163
|
+
function dataDeclaration(page) {
|
|
164
|
+
const declaration = page.dataType;
|
|
165
|
+
if (page.dataImports.length === 0)
|
|
166
|
+
return declaration;
|
|
167
|
+
const fromDir = dirname(resolveDtsPath() ?? resolve(viteRoot ?? process.cwd(), "index.d.ts"));
|
|
168
|
+
return qualifyImports(declaration, page.dataImports, (specifier) => {
|
|
169
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../"))
|
|
170
|
+
return specifier;
|
|
171
|
+
const target = normalizeSlashes(relative(fromDir, resolve(dirname(page.absFile), specifier)));
|
|
172
|
+
return target.startsWith(".") ? target : "./" + target;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
173
175
|
function dataTypeNames() {
|
|
174
176
|
const names = new Map();
|
|
175
177
|
const used = new Set();
|
|
@@ -193,7 +195,7 @@ export function pages(options = {}) {
|
|
|
193
195
|
const dataNames = dataTypeNames();
|
|
194
196
|
const typed = listPages().filter((p) => p.dataType && dataNames.has(p.entryName));
|
|
195
197
|
const declarationFor = (p) => {
|
|
196
|
-
const decl = p.
|
|
198
|
+
const decl = dataDeclaration(p).replace("Data", dataNames.get(p.entryName));
|
|
197
199
|
return decl.endsWith(";") ? decl : decl + ";";
|
|
198
200
|
};
|
|
199
201
|
return [
|
|
@@ -211,15 +213,18 @@ export function pages(options = {}) {
|
|
|
211
213
|
`declare module "hono-svelte" {`,
|
|
212
214
|
` interface Register {`,
|
|
213
215
|
` entries: ${typeName};`,
|
|
216
|
+
` typedEntries: ${typed.length > 0 ? typed.map((p) => JSON.stringify(p.entryName)).join(" | ") : "never"};`,
|
|
214
217
|
` }`,
|
|
215
218
|
`}`,
|
|
216
219
|
"",
|
|
217
220
|
`declare module "hono" {`,
|
|
218
221
|
` interface ContextRenderer {`,
|
|
219
|
-
//
|
|
220
|
-
//
|
|
222
|
+
// One typed overload per page with `Data`, then a catch-all for the
|
|
223
|
+
// other entries. The catch-all EXCLUDES the typed entries: otherwise a
|
|
224
|
+
// wrong `data` fails the typed overload and silently matches the
|
|
225
|
+
// catch-all (Record<string, unknown>), and nothing is checked.
|
|
221
226
|
...typed.map((p) => ` (entryName: ${JSON.stringify(p.entryName)}, props?: import("hono-svelte").RenderProps<${dataNames.get(p.entryName)}>): Response | Promise<Response>;`),
|
|
222
|
-
` (entryName: ${typeName}, props?: import("hono-svelte").RenderProps): Response | Promise<Response>;`,
|
|
227
|
+
` (entryName: ${typed.length > 0 ? `Exclude<${typeName}, ${typed.map((p) => JSON.stringify(p.entryName)).join(" | ")}>` : typeName}, props?: import("hono-svelte").RenderProps): Response | Promise<Response>;`,
|
|
223
228
|
` }`,
|
|
224
229
|
`}`,
|
|
225
230
|
"",
|