gorsee 0.2.1 → 0.2.2
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 +132 -4
- package/package.json +4 -2
- package/src/auth/index.ts +48 -17
- package/src/auth/redis-session-store.ts +46 -0
- package/src/auth/sqlite-session-store.ts +98 -0
- package/src/auth/store-utils.ts +21 -0
- package/src/build/client.ts +25 -7
- package/src/build/manifest.ts +34 -0
- package/src/build/route-metadata.ts +12 -0
- package/src/build/ssg.ts +19 -49
- package/src/cli/bun-plugin.ts +23 -2
- package/src/cli/cmd-build.ts +42 -71
- package/src/cli/cmd-check.ts +40 -26
- package/src/cli/cmd-create.ts +20 -5
- package/src/cli/cmd-deploy.ts +10 -2
- package/src/cli/cmd-dev.ts +9 -9
- package/src/cli/cmd-docs.ts +11 -4
- package/src/cli/cmd-generate.ts +15 -7
- package/src/cli/cmd-migrate.ts +15 -7
- package/src/cli/cmd-routes.ts +12 -5
- package/src/cli/cmd-start.ts +14 -5
- package/src/cli/cmd-test.ts +11 -3
- package/src/cli/cmd-typegen.ts +13 -3
- package/src/cli/cmd-upgrade.ts +10 -2
- package/src/cli/context.ts +12 -0
- package/src/cli/framework-md.ts +43 -16
- package/src/client.ts +26 -0
- package/src/dev/partial-handler.ts +17 -74
- package/src/dev/request-handler.ts +36 -67
- package/src/dev.ts +92 -157
- package/src/index-client.ts +4 -0
- package/src/index.ts +17 -2
- package/src/prod.ts +195 -253
- package/src/runtime/project.ts +73 -0
- package/src/server/cache-utils.ts +23 -0
- package/src/server/cache.ts +37 -14
- package/src/server/html-shell.ts +69 -0
- package/src/server/index.ts +40 -2
- package/src/server/manifest.ts +36 -0
- package/src/server/middleware.ts +18 -2
- package/src/server/not-found.ts +35 -0
- package/src/server/page-render.ts +123 -0
- package/src/server/redis-cache-store.ts +87 -0
- package/src/server/redis-client.ts +71 -0
- package/src/server/request-preflight.ts +45 -0
- package/src/server/route-request.ts +72 -0
- package/src/server/rpc-utils.ts +27 -0
- package/src/server/rpc.ts +70 -18
- package/src/server/sqlite-cache-store.ts +109 -0
- package/src/server/static-file.ts +63 -0
- package/src/server-entry.ts +36 -0
package/src/cli/cmd-generate.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises"
|
|
6
6
|
import { createMigration } from "../db/migrate.ts"
|
|
7
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
7
8
|
|
|
8
9
|
function capitalize(s: string): string {
|
|
9
10
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
@@ -18,7 +19,7 @@ function singularize(s: string): string {
|
|
|
18
19
|
|
|
19
20
|
function listRoute(entity: string, singular: string): string {
|
|
20
21
|
const cap = capitalize(singular)
|
|
21
|
-
return `import { Head, Link } from "gorsee"
|
|
22
|
+
return `import { Head, Link } from "gorsee/client"
|
|
22
23
|
import { SafeSQL } from "gorsee/types"
|
|
23
24
|
import type { Context } from "gorsee/server"
|
|
24
25
|
|
|
@@ -49,7 +50,7 @@ export default function ${capitalize(entity)}ListPage(props: { data: { ${entity}
|
|
|
49
50
|
|
|
50
51
|
function detailRoute(entity: string, singular: string): string {
|
|
51
52
|
const cap = capitalize(singular)
|
|
52
|
-
return `import { Head, Link } from "gorsee"
|
|
53
|
+
return `import { Head, Link } from "gorsee/client"
|
|
53
54
|
import type { Context } from "gorsee/server"
|
|
54
55
|
|
|
55
56
|
export async function loader(ctx: Context) {
|
|
@@ -73,7 +74,7 @@ export default function ${cap}DetailPage(props: { data: { ${singular}: { id: num
|
|
|
73
74
|
|
|
74
75
|
function newRoute(entity: string, singular: string): string {
|
|
75
76
|
const cap = capitalize(singular)
|
|
76
|
-
return `import { Head, Link } from "gorsee"
|
|
77
|
+
return `import { Head, Link } from "gorsee/client"
|
|
77
78
|
import { defineAction, parseFormData } from "gorsee/server"
|
|
78
79
|
|
|
79
80
|
export const action = defineAction(async (ctx) => {
|
|
@@ -109,7 +110,9 @@ CREATE TABLE IF NOT EXISTS ${entity} (
|
|
|
109
110
|
`
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
export
|
|
113
|
+
export interface GenerateCommandOptions extends RuntimeOptions {}
|
|
114
|
+
|
|
115
|
+
export async function generateCrudScaffold(args: string[], options: GenerateCommandOptions = {}) {
|
|
113
116
|
const entity = args[0]
|
|
114
117
|
if (!entity) {
|
|
115
118
|
console.error("Usage: gorsee generate <entity-name>")
|
|
@@ -117,9 +120,9 @@ export async function runGenerate(args: string[]) {
|
|
|
117
120
|
process.exit(1)
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
const cwd =
|
|
123
|
+
const { cwd, paths } = createProjectContext(options)
|
|
121
124
|
const singular = singularize(entity)
|
|
122
|
-
const routeDir = join(
|
|
125
|
+
const routeDir = join(paths.routesDir, entity)
|
|
123
126
|
|
|
124
127
|
console.log(`\n Generating CRUD for: ${entity}\n`)
|
|
125
128
|
|
|
@@ -130,7 +133,7 @@ export async function runGenerate(args: string[]) {
|
|
|
130
133
|
await writeFile(join(routeDir, "new.tsx"), newRoute(entity, singular))
|
|
131
134
|
|
|
132
135
|
// Create migration
|
|
133
|
-
const migrationDir =
|
|
136
|
+
const migrationDir = paths.migrationsDir
|
|
134
137
|
await mkdir(migrationDir, { recursive: true })
|
|
135
138
|
const migrationFile = await createMigration(migrationDir, `create_${entity}`)
|
|
136
139
|
const migrationPath = join(migrationDir, migrationFile)
|
|
@@ -145,3 +148,8 @@ export async function runGenerate(args: string[]) {
|
|
|
145
148
|
console.log(" Next: run `gorsee migrate` to apply the migration")
|
|
146
149
|
console.log()
|
|
147
150
|
}
|
|
151
|
+
|
|
152
|
+
/** @deprecated Use generateCrudScaffold() for programmatic access. */
|
|
153
|
+
export async function runGenerate(args: string[], options: GenerateCommandOptions = {}) {
|
|
154
|
+
return generateCrudScaffold(args, options)
|
|
155
|
+
}
|
package/src/cli/cmd-migrate.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// gorsee migrate -- run database migrations
|
|
2
2
|
// gorsee migrate create <name> -- create new migration file
|
|
3
3
|
|
|
4
|
-
import { join } from "node:path"
|
|
5
4
|
import { runMigrations, createMigration } from "../db/migrate.ts"
|
|
5
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export interface MigrateCommandOptions extends RuntimeOptions {
|
|
8
|
+
dbPath?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runProjectMigrations(args: string[], options: MigrateCommandOptions = {}) {
|
|
12
|
+
const { env, paths } = createProjectContext(options)
|
|
10
13
|
const subcommand = args[0]
|
|
11
14
|
|
|
12
15
|
if (subcommand === "create") {
|
|
@@ -15,16 +18,16 @@ export async function runMigrate(args: string[]) {
|
|
|
15
18
|
console.error("Usage: gorsee migrate create <migration-name>")
|
|
16
19
|
process.exit(1)
|
|
17
20
|
}
|
|
18
|
-
const filename = await createMigration(migrationsDir, name)
|
|
21
|
+
const filename = await createMigration(paths.migrationsDir, name)
|
|
19
22
|
console.log(`\n Created: migrations/${filename}\n`)
|
|
20
23
|
return
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
// Default: run pending migrations
|
|
24
|
-
const dbPath =
|
|
27
|
+
const dbPath = options.dbPath ?? env.DATABASE_URL ?? paths.dataFile
|
|
25
28
|
console.log("\n Running migrations...\n")
|
|
26
29
|
|
|
27
|
-
const result = await runMigrations(dbPath, migrationsDir)
|
|
30
|
+
const result = await runMigrations(dbPath, paths.migrationsDir)
|
|
28
31
|
|
|
29
32
|
if (result.applied.length > 0) {
|
|
30
33
|
console.log(" Applied:")
|
|
@@ -43,3 +46,8 @@ export async function runMigrate(args: string[]) {
|
|
|
43
46
|
|
|
44
47
|
console.log(`\n Done: ${result.applied.length} migration(s) applied\n`)
|
|
45
48
|
}
|
|
49
|
+
|
|
50
|
+
/** @deprecated Use runProjectMigrations() for programmatic access. */
|
|
51
|
+
export async function runMigrate(args: string[], options: MigrateCommandOptions = {}) {
|
|
52
|
+
return runProjectMigrations(args, options)
|
|
53
|
+
}
|
package/src/cli/cmd-routes.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// gorsee routes -- list all routes
|
|
2
2
|
|
|
3
|
-
import { join } from "node:path"
|
|
4
3
|
import { createRouter } from "../router/scanner.ts"
|
|
4
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export interface RoutesCommandOptions extends RuntimeOptions {}
|
|
7
|
+
|
|
8
|
+
export async function listRoutes(options: RoutesCommandOptions = {}) {
|
|
9
|
+
const { cwd, paths } = createProjectContext(options)
|
|
10
|
+
const routes = await createRouter(paths.routesDir)
|
|
9
11
|
|
|
10
12
|
if (routes.length === 0) {
|
|
11
13
|
console.log("\n No routes found in routes/\n")
|
|
@@ -21,9 +23,14 @@ export async function runRoutes(_args: string[]) {
|
|
|
21
23
|
console.log(
|
|
22
24
|
" " +
|
|
23
25
|
route.path.padEnd(25) +
|
|
24
|
-
route.filePath.replace(
|
|
26
|
+
route.filePath.replace(cwd + "/", "").padEnd(40) +
|
|
25
27
|
params
|
|
26
28
|
)
|
|
27
29
|
}
|
|
28
30
|
console.log()
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
/** @deprecated Use listRoutes() for programmatic access. */
|
|
34
|
+
export async function runRoutes(_args: string[], options: RoutesCommandOptions = {}) {
|
|
35
|
+
return listRoutes(options)
|
|
36
|
+
}
|
package/src/cli/cmd-start.ts
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { join } from "node:path"
|
|
4
4
|
import { stat } from "node:fs/promises"
|
|
5
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export interface StartCommandOptions extends RuntimeOptions {
|
|
8
|
+
port?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function startBuiltProject(options: StartCommandOptions = {}) {
|
|
12
|
+
const { paths } = createProjectContext(options)
|
|
9
13
|
|
|
10
14
|
// Verify build exists
|
|
11
15
|
try {
|
|
12
|
-
await stat(join(distDir, "manifest.json"))
|
|
16
|
+
await stat(join(paths.distDir, "manifest.json"))
|
|
13
17
|
} catch {
|
|
14
18
|
console.error("\n Error: No production build found.")
|
|
15
19
|
console.error(" Run `gorsee build` first.\n")
|
|
@@ -17,5 +21,10 @@ export async function runStart(_args: string[]) {
|
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const { startProductionServer } = await import("../prod.ts")
|
|
20
|
-
await startProductionServer()
|
|
24
|
+
await startProductionServer({ cwd: paths.cwd, port: options.port })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @deprecated Use startBuiltProject() for programmatic access. */
|
|
28
|
+
export async function runStart(_args: string[], options: StartCommandOptions = {}) {
|
|
29
|
+
return startBuiltProject(options)
|
|
21
30
|
}
|
package/src/cli/cmd-test.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { readdir, stat } from "node:fs/promises"
|
|
4
4
|
import { join } from "node:path"
|
|
5
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
5
6
|
|
|
6
7
|
interface TestFlags {
|
|
7
8
|
watch: boolean
|
|
@@ -78,8 +79,10 @@ export function buildTestArgs(flags: TestFlags, files: string[]): string[] {
|
|
|
78
79
|
return bunArgs
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
export
|
|
82
|
-
|
|
82
|
+
export interface TestCommandOptions extends RuntimeOptions {}
|
|
83
|
+
|
|
84
|
+
export async function runTests(args: string[], options: TestCommandOptions = {}) {
|
|
85
|
+
const { cwd, env } = createProjectContext(options)
|
|
83
86
|
const flags = parseFlags(args)
|
|
84
87
|
const pattern = getTestPattern(flags)
|
|
85
88
|
|
|
@@ -100,7 +103,7 @@ export async function runTest(args: string[]) {
|
|
|
100
103
|
stdout: "inherit",
|
|
101
104
|
stderr: "inherit",
|
|
102
105
|
env: {
|
|
103
|
-
...
|
|
106
|
+
...env,
|
|
104
107
|
NODE_ENV: "test",
|
|
105
108
|
GORSEE_TEST: "1",
|
|
106
109
|
},
|
|
@@ -117,5 +120,10 @@ export async function runTest(args: string[]) {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
/** @deprecated Use runTests() for programmatic access. */
|
|
124
|
+
export async function runTest(args: string[], options: TestCommandOptions = {}) {
|
|
125
|
+
return runTests(args, options)
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
// Re-export for testing
|
|
121
129
|
export { parseFlags, findTestFiles, getTestPattern }
|
package/src/cli/cmd-typegen.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises"
|
|
6
6
|
import { createRouter, type Route } from "../router/index.ts"
|
|
7
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
7
8
|
|
|
8
9
|
const OUTPUT_DIR = ".gorsee"
|
|
9
10
|
const TYPES_FILE = "routes.d.ts"
|
|
@@ -54,9 +55,13 @@ function generateDeclaration(routes: Route[]): string {
|
|
|
54
55
|
return lines.join("\n")
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
export
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
export interface TypegenCommandOptions extends RuntimeOptions {
|
|
59
|
+
routesDir?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function generateRouteTypes(args: string[], options: TypegenCommandOptions = {}): Promise<void> {
|
|
63
|
+
const { cwd, paths } = createProjectContext(options)
|
|
64
|
+
const routesDir = options.routesDir ?? args[0] ?? paths.routesDir
|
|
60
65
|
|
|
61
66
|
console.log(`Scanning routes in: ${routesDir}`)
|
|
62
67
|
|
|
@@ -81,3 +86,8 @@ export async function runTypegen(args: string[]): Promise<void> {
|
|
|
81
86
|
console.log(` ${route.path}${params}`)
|
|
82
87
|
}
|
|
83
88
|
}
|
|
89
|
+
|
|
90
|
+
/** @deprecated Use generateRouteTypes() for programmatic access. */
|
|
91
|
+
export async function runTypegen(args: string[], options: TypegenCommandOptions = {}): Promise<void> {
|
|
92
|
+
return generateRouteTypes(args, options)
|
|
93
|
+
}
|
package/src/cli/cmd-upgrade.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { readFile } from "node:fs/promises"
|
|
4
4
|
import { join } from "node:path"
|
|
5
|
+
import { createProjectContext, type RuntimeOptions } from "../runtime/project.ts"
|
|
5
6
|
|
|
6
7
|
interface UpgradeFlags {
|
|
7
8
|
check: boolean
|
|
@@ -75,8 +76,10 @@ async function checkMigrationHints(cwd: string): Promise<string[]> {
|
|
|
75
76
|
return hints
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
export
|
|
79
|
-
|
|
79
|
+
export interface UpgradeCommandOptions extends RuntimeOptions {}
|
|
80
|
+
|
|
81
|
+
export async function upgradeFramework(args: string[], options: UpgradeCommandOptions = {}) {
|
|
82
|
+
const { cwd } = createProjectContext(options)
|
|
80
83
|
const flags = parseUpgradeFlags(args)
|
|
81
84
|
|
|
82
85
|
const current = await getCurrentVersion(cwd)
|
|
@@ -133,3 +136,8 @@ export async function runUpgrade(args: string[]) {
|
|
|
133
136
|
|
|
134
137
|
console.log(`\n Upgraded successfully to v${latest}\n`)
|
|
135
138
|
}
|
|
139
|
+
|
|
140
|
+
/** @deprecated Use upgradeFramework() for programmatic access. */
|
|
141
|
+
export async function runUpgrade(args: string[], options: UpgradeCommandOptions = {}) {
|
|
142
|
+
return upgradeFramework(args, options)
|
|
143
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @deprecated Import shared project context from "../runtime/project" in new code. */
|
|
2
|
+
export {
|
|
3
|
+
createProjectContext as createCommandContext,
|
|
4
|
+
resolveProjectPaths,
|
|
5
|
+
} from "../runtime/project.ts"
|
|
6
|
+
|
|
7
|
+
/** @deprecated Import shared runtime types from "../runtime/project" in new code. */
|
|
8
|
+
export type {
|
|
9
|
+
RuntimeOptions as CommandRuntimeOptions,
|
|
10
|
+
ProjectContext as CommandContext,
|
|
11
|
+
ProjectPaths,
|
|
12
|
+
} from "../runtime/project.ts"
|
package/src/cli/framework-md.ts
CHANGED
|
@@ -35,29 +35,56 @@ app.config.ts Configuration
|
|
|
35
35
|
## Imports
|
|
36
36
|
|
|
37
37
|
\`\`\`typescript
|
|
38
|
-
//
|
|
39
|
-
import { createSignal, createComputed, createEffect, createResource, createStore } from "gorsee/
|
|
38
|
+
// Browser-safe route code
|
|
39
|
+
import { createSignal, createComputed, createEffect, createResource, createStore } from "gorsee/client"
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
import { SafeURL, validateURL } from "gorsee/types"
|
|
45
|
-
import { validate, type UserInput } from "gorsee/types"
|
|
41
|
+
// Server-only code
|
|
42
|
+
import { server, middleware, type Context, createDB, createAuth, cors, log } from "gorsee/server"
|
|
43
|
+
\`\`\`
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
import { createDB } from "gorsee/db"
|
|
45
|
+
## Import Boundaries
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
- \`gorsee/client\` for route components, islands, navigation, forms, and reactive primitives
|
|
48
|
+
- \`gorsee/server\` for middleware, loaders, RPC, auth, db, security, env, and logging
|
|
49
|
+
- Root \`gorsee\` is compatibility-only and should not be used in new code
|
|
50
|
+
- \`gorsee/compat\` is available as an explicit legacy migration entrypoint
|
|
51
|
+
|
|
52
|
+
## Adapter Recipes
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
import {
|
|
54
|
+
\`\`\`typescript
|
|
55
|
+
import { createClient } from "redis"
|
|
56
|
+
import {
|
|
57
|
+
createAuth,
|
|
58
|
+
createRedisSessionStore,
|
|
59
|
+
createRedisCacheStore,
|
|
60
|
+
createNodeRedisLikeClient,
|
|
61
|
+
routeCache,
|
|
62
|
+
} from "gorsee/server"
|
|
63
|
+
|
|
64
|
+
const redis = createClient({ url: process.env.REDIS_URL })
|
|
65
|
+
await redis.connect()
|
|
66
|
+
const redisClient = createNodeRedisLikeClient(redis)
|
|
67
|
+
|
|
68
|
+
const auth = createAuth({
|
|
69
|
+
secret: process.env.SESSION_SECRET!,
|
|
70
|
+
store: createRedisSessionStore(redisClient, { prefix: "app:sessions" }),
|
|
71
|
+
})
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
export const cache = routeCache({
|
|
74
|
+
maxAge: 60,
|
|
75
|
+
staleWhileRevalidate: 300,
|
|
76
|
+
store: createRedisCacheStore(redisClient, {
|
|
77
|
+
prefix: "app:cache",
|
|
78
|
+
maxEntryAgeMs: 360_000,
|
|
79
|
+
}),
|
|
80
|
+
})
|
|
59
81
|
\`\`\`
|
|
60
82
|
|
|
83
|
+
- SQLite adapters are the default persistent single-node path
|
|
84
|
+
- Redis adapters are the default multi-instance path
|
|
85
|
+
- \`createNodeRedisLikeClient()\` and \`createIORedisLikeClient()\` normalize real Redis SDK clients to the framework adapter contract
|
|
86
|
+
- RPC handlers remain process-local by design; do not try to distribute closures through Redis
|
|
87
|
+
|
|
61
88
|
## Patterns
|
|
62
89
|
|
|
63
90
|
### Page Route
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Explicit browser-safe public entrypoint.
|
|
2
|
+
// Use this in route components and client-facing modules.
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
createSignal,
|
|
6
|
+
createComputed,
|
|
7
|
+
createEffect,
|
|
8
|
+
createResource,
|
|
9
|
+
createStore,
|
|
10
|
+
createLive,
|
|
11
|
+
invalidateResource,
|
|
12
|
+
invalidateAll,
|
|
13
|
+
createMutation,
|
|
14
|
+
} from "./reactive/index.ts"
|
|
15
|
+
|
|
16
|
+
export { Suspense } from "./runtime/suspense.ts"
|
|
17
|
+
export { Link } from "./runtime/link.ts"
|
|
18
|
+
export { Head } from "./runtime/head.ts"
|
|
19
|
+
export { navigate, onNavigate, beforeNavigate, getCurrentPath } from "./runtime/router.ts"
|
|
20
|
+
export { useFormAction } from "./runtime/form.ts"
|
|
21
|
+
export { Image } from "./runtime/image.ts"
|
|
22
|
+
export { ErrorBoundary } from "./runtime/error-boundary.ts"
|
|
23
|
+
export { island } from "./runtime/island.ts"
|
|
24
|
+
export { createEventSource } from "./server/sse.ts"
|
|
25
|
+
export { typedLink, typedNavigate } from "./runtime/typed-routes.ts"
|
|
26
|
+
export { defineForm, validateForm, fieldAttrs } from "./runtime/validated-form.ts"
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
// Returns JSON with rendered HTML + metadata instead of full page
|
|
3
3
|
|
|
4
4
|
import { createContext } from "../server/middleware.ts"
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
buildPartialResponsePayload,
|
|
7
|
+
createClientScriptPath,
|
|
8
|
+
renderPageDocument,
|
|
9
|
+
resolvePageRoute,
|
|
10
|
+
} from "../server/page-render.ts"
|
|
7
11
|
import type { MatchResult } from "../router/matcher.ts"
|
|
8
12
|
import type { BuildResult } from "../build/client.ts"
|
|
9
13
|
|
|
@@ -13,15 +17,6 @@ interface PartialRenderOptions {
|
|
|
13
17
|
clientBuild: BuildResult
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
interface PartialResponse {
|
|
17
|
-
html: string
|
|
18
|
-
data?: unknown
|
|
19
|
-
params?: Record<string, string>
|
|
20
|
-
title?: string
|
|
21
|
-
css?: string[]
|
|
22
|
-
script?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
export async function handlePartialNavigation(opts: PartialRenderOptions): Promise<Response> {
|
|
26
21
|
const { match, request, clientBuild } = opts
|
|
27
22
|
const mod = await import(match.route.filePath)
|
|
@@ -32,76 +27,24 @@ export async function handlePartialNavigation(opts: PartialRenderOptions): Promi
|
|
|
32
27
|
return new Response("Not a page route", { status: 400 })
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
30
|
+
const resolved = await resolvePageRoute(mod, match, ctx)
|
|
31
|
+
if (!resolved) {
|
|
37
32
|
return new Response(JSON.stringify({ error: "Route has no default export" }), {
|
|
38
33
|
status: 500,
|
|
39
34
|
headers: { "Content-Type": "application/json" },
|
|
40
35
|
})
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Run layout loaders in parallel
|
|
54
|
-
const layoutLoaderPromises = layoutMods.map((lm) =>
|
|
55
|
-
typeof lm.loader === "function" ? lm.loader(ctx) : undefined,
|
|
38
|
+
const { pageComponent, loaderData, cssFiles } = resolved
|
|
39
|
+
const rendered = renderPageDocument(pageComponent, ctx, match.params, loaderData)
|
|
40
|
+
const clientScript = createClientScriptPath(clientBuild.entryMap.get(match.route.path))
|
|
41
|
+
const result = buildPartialResponsePayload(
|
|
42
|
+
rendered,
|
|
43
|
+
loaderData,
|
|
44
|
+
match.params,
|
|
45
|
+
cssFiles,
|
|
46
|
+
clientScript,
|
|
56
47
|
)
|
|
57
|
-
const layoutLoaderResults = await Promise.all(layoutLoaderPromises)
|
|
58
|
-
|
|
59
|
-
// CSS
|
|
60
|
-
const cssFiles: string[] = []
|
|
61
|
-
if (typeof mod.css === "string") cssFiles.push(mod.css)
|
|
62
|
-
if (Array.isArray(mod.css)) cssFiles.push(...(mod.css as string[]))
|
|
63
|
-
|
|
64
|
-
// Nested layout wrapping: outermost first, innermost wraps page
|
|
65
|
-
let pageComponent: Function = component
|
|
66
|
-
for (let i = layoutMods.length - 1; i >= 0; i--) {
|
|
67
|
-
const Layout = layoutMods[i]!.default
|
|
68
|
-
if (typeof Layout === "function") {
|
|
69
|
-
const inner = pageComponent
|
|
70
|
-
const layoutData = layoutLoaderResults[i]
|
|
71
|
-
pageComponent = (props: Record<string, unknown>) =>
|
|
72
|
-
Layout({ ...props, data: layoutData, children: inner(props) })
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Render
|
|
77
|
-
resetServerHead()
|
|
78
|
-
const pageProps = { params: match.params, ctx, data: loaderData }
|
|
79
|
-
const vnode = ssrJsx(pageComponent as any, pageProps)
|
|
80
|
-
const html = renderToString(vnode)
|
|
81
|
-
|
|
82
|
-
// Extract title from Head component
|
|
83
|
-
const headElements = getServerHead()
|
|
84
|
-
let title: string | undefined
|
|
85
|
-
for (const el of headElements) {
|
|
86
|
-
const titleMatch = el.match(/<title>(.+?)<\/title>/)
|
|
87
|
-
if (titleMatch) {
|
|
88
|
-
title = titleMatch[1]
|
|
89
|
-
break
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Client JS path
|
|
94
|
-
const clientJsFile = clientBuild.entryMap.get(match.route.path)
|
|
95
|
-
const clientScript = clientJsFile ? `/_gorsee/${clientJsFile}` : undefined
|
|
96
|
-
|
|
97
|
-
const result: PartialResponse = {
|
|
98
|
-
html,
|
|
99
|
-
data: loaderData,
|
|
100
|
-
params: Object.keys(match.params).length > 0 ? match.params : undefined,
|
|
101
|
-
title,
|
|
102
|
-
css: cssFiles.length > 0 ? cssFiles : undefined,
|
|
103
|
-
script: clientScript,
|
|
104
|
-
}
|
|
105
48
|
|
|
106
49
|
return new Response(JSON.stringify(result), {
|
|
107
50
|
headers: { "Content-Type": "application/json" },
|