rhonojs 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhonojs",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Reactive Web app server focusing on MPAs with a simple server route & browser build api...uses Hono, ESBuild, rmemo, & ctx-core",
5
5
  "keywords": [
6
6
  "reactive",
@@ -54,6 +54,13 @@
54
54
  },
55
55
  "./package.json": "./package.json"
56
56
  },
57
+ "scripts": {
58
+ "build": ":",
59
+ "clean": ":",
60
+ "exec": "$@",
61
+ "test": "bun run test:unit",
62
+ "test:unit": "bun -c node_modules/uvu/bin.js . '\\.test\\.(ts|js)$'"
63
+ },
57
64
  "dependencies": {
58
65
  "ctx-core": "*",
59
66
  "hono": "^4.0.0",
@@ -62,5 +69,10 @@
62
69
  "peerDependencies": {
63
70
  "esbuild": "^0.27.3"
64
71
  },
72
+ "devDependencies": {
73
+ "bun-types": "^1.3.10",
74
+ "typescript": "next",
75
+ "uvu": "^0.5.6"
76
+ },
65
77
  "sideEffects": false
66
78
  }
@@ -127,7 +127,7 @@ export async function app__attach(app) {
127
127
  }
128
128
  }
129
129
  for (const middleware of middleware_a1) {
130
- if (middleware instanceof Hono) {
130
+ if (middleware instanceof Hono || (middleware && typeof middleware.fetch === 'function' && typeof middleware.route === 'function')) {
131
131
  app.route('/', middleware)
132
132
  } else {
133
133
  app.use(middleware)
@@ -0,0 +1,88 @@
1
+ import { Hono } from 'hono'
2
+ import { app_ctx, app_path__set, ctx_, cwd__set, server__metafile__set } from 'rebuildjs/server'
3
+ import { test } from 'uvu'
4
+ import { equal, throws } from 'uvu/assert'
5
+ import { server__metafile0 } from '../../_fixtures/metafile.js'
6
+ import {
7
+ app$_,
8
+ app_,
9
+ app__set,
10
+ server_entry__output__link__path$_,
11
+ server_entry__output__link__path_,
12
+ server_entry__output__path$_,
13
+ server_entry__output__path_,
14
+ server_entry__output__relative_path$_,
15
+ server_entry__output__relative_path_,
16
+ server_entry__relative_path$_,
17
+ server_entry__relative_path_
18
+ } from './index.js'
19
+ test.after.each(()=>{
20
+ app_ctx.s.app.clear()
21
+ })
22
+ test('app', ()=>{
23
+ equal(app$_(app_ctx)(), undefined)
24
+ equal(app_(app_ctx), undefined)
25
+ const app = new Hono()
26
+ app__set(app_ctx, app)
27
+ equal(app$_(app_ctx)(), app)
28
+ equal(app_(app_ctx), app)
29
+ // @ts-expect-error TS2345
30
+ throws(()=>app$_(ctx_())())
31
+ // @ts-expect-error TS2345
32
+ throws(()=>app_(ctx_()))
33
+ // @ts-expect-error TS2345
34
+ throws(()=>app__set(ctx_(), app))
35
+ })
36
+ test('server_entry__relative_path', ()=>{
37
+ equal(server_entry__relative_path$_(app_ctx)(), 'src/app/index.ts')
38
+ equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
39
+ cwd__set(app_ctx, '/cwd')
40
+ app_path__set(app_ctx, '/cwd/src/app2')
41
+ equal(server_entry__relative_path$_(app_ctx)(), 'src/app2/index.ts')
42
+ equal(server_entry__relative_path_(app_ctx), 'src/app2/index.ts')
43
+ // @ts-expect-error TS2345
44
+ throws(()=>server_entry__relative_path$_(ctx_()))
45
+ // @ts-expect-error TS2345
46
+ throws(()=>server_entry__relative_path_(ctx_()))
47
+ })
48
+ test('server_entry__output__relative_path', ()=>{
49
+ equal(server_entry__output__relative_path$_(app_ctx)(), undefined)
50
+ equal(server_entry__output__relative_path_(app_ctx), undefined)
51
+ equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
52
+ server__metafile__set(app_ctx, server__metafile0)
53
+ equal(server__metafile0.outputs['dist/dev-server/index-OUBLL3JD.js'].entryPoint, 'src/app/index.ts')
54
+ equal(server_entry__output__relative_path$_(app_ctx)(), 'dist/dev-server/index-OUBLL3JD.js')
55
+ equal(server_entry__output__relative_path_(app_ctx), 'dist/dev-server/index-OUBLL3JD.js')
56
+ // @ts-expect-error TS2345
57
+ throws(()=>server_entry__output__relative_path$_(ctx_()))
58
+ // @ts-expect-error TS2345
59
+ throws(()=>server_entry__output__relative_path_(ctx_()))
60
+ })
61
+ test('server_entry__output__path', ()=>{
62
+ equal(server_entry__output__path$_(app_ctx)(), undefined)
63
+ equal(server_entry__output__path_(app_ctx), undefined)
64
+ cwd__set(app_ctx, '/cwd')
65
+ server__metafile__set(app_ctx, server__metafile0)
66
+ equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
67
+ equal(server__metafile0.outputs['dist/dev-server/index-OUBLL3JD.js'].entryPoint, 'src/app/index.ts')
68
+ equal(server_entry__output__path$_(app_ctx)(), '/cwd/dist/dev-server/index-OUBLL3JD.js')
69
+ equal(server_entry__output__path_(app_ctx), '/cwd/dist/dev-server/index-OUBLL3JD.js')
70
+ // @ts-expect-error TS2345
71
+ throws(()=>server_entry__output__path$_(ctx_()))
72
+ // @ts-expect-error TS2345
73
+ throws(()=>server_entry__output__path_(ctx_()))
74
+ })
75
+ test('server_entry__output__link__path', ()=>{
76
+ equal(server_entry__output__link__path$_(app_ctx)(), undefined)
77
+ equal(server_entry__output__link__path_(app_ctx), undefined)
78
+ cwd__set(app_ctx, '/cwd')
79
+ server__metafile__set(app_ctx, server__metafile0)
80
+ equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
81
+ equal(server_entry__output__link__path$_(app_ctx)(), '/cwd/dist/dev-server/index.js')
82
+ equal(server_entry__output__link__path_(app_ctx), '/cwd/dist/dev-server/index.js')
83
+ // @ts-expect-error TS2345
84
+ throws(()=>server_entry__output__link__path$_(ctx_()))
85
+ // @ts-expect-error TS2345
86
+ throws(()=>server_entry__output__link__path_(ctx_()))
87
+ })
88
+ test.run()
@@ -0,0 +1,84 @@
1
+ import { sleep } from 'ctx-core/function'
2
+ import { ctx_, ns_be_sig_triple_, rmemo__wait } from 'ctx-core/rmemo'
3
+ import {
4
+ app_ctx,
5
+ build_id_,
6
+ build_id__refresh,
7
+ build_id__set,
8
+ browser__metafile__set,
9
+ metafile__build_id_,
10
+ rebuildjs__build_id__set,
11
+ rebuildjs__esbuild__build_id__set,
12
+ rebuildjs__ready__add,
13
+ server__metafile__set,
14
+ } from 'rebuildjs/server'
15
+ import { test } from 'uvu'
16
+ import { equal, throws } from 'uvu/assert'
17
+ import { browser__metafile0, server__metafile0 } from '../../_fixtures/metafile.js'
18
+ import {
19
+ rhonojs__build_id$_,
20
+ rhonojs__build_id_,
21
+ rhonojs__build_id__set,
22
+ rhonojs__ready__wait,
23
+ } from './index.js'
24
+ test.after.each(()=>{
25
+ app_ctx.s.app.clear()
26
+ })
27
+ test('rhonojs__build_id', ()=>{
28
+ equal(rhonojs__build_id$_(app_ctx)(), undefined)
29
+ equal(rhonojs__build_id_(app_ctx), undefined)
30
+ build_id__refresh()
31
+ equal(typeof build_id_(app_ctx), 'string')
32
+ rhonojs__build_id__set(app_ctx, build_id_(app_ctx)!)
33
+ equal(rhonojs__build_id$_(app_ctx)(), build_id_(app_ctx)!)
34
+ equal(rhonojs__build_id_(app_ctx), build_id_(app_ctx)!)
35
+ // @ts-expect-error TS2345
36
+ throws(()=>rhonojs__build_id$_(ctx_()))
37
+ // @ts-expect-error TS2345
38
+ throws(()=>rhonojs__build_id_(ctx_()))
39
+ })
40
+ test('rhonojs__ready__wait', async ()=>{
41
+ let done = false
42
+ const [
43
+ plugin__ready$_,
44
+ ,
45
+ plugin__ready__set
46
+ ] = ns_be_sig_triple_(
47
+ 'app',
48
+ ()=>false)
49
+ rebuildjs__ready__add(plugin__ready$_)
50
+ rhonojs__ready__wait().then(()=>done = true)
51
+ equal(done, false)
52
+ const build_id = server__metafile0.build_id!
53
+ build_id__set(app_ctx, build_id)
54
+ await sleep(0)
55
+ equal(done, false)
56
+ server__metafile__set(app_ctx, server__metafile0)
57
+ await sleep(0)
58
+ equal(done, false)
59
+ browser__metafile__set(app_ctx, browser__metafile0)
60
+ await sleep(0)
61
+ equal(done, false)
62
+ rebuildjs__esbuild__build_id__set(app_ctx, build_id)
63
+ await sleep(0)
64
+ equal(done, false)
65
+ rhonojs__build_id__set(app_ctx, build_id)
66
+ await sleep(0)
67
+ equal(done, false)
68
+ plugin__ready__set(app_ctx, true)
69
+ await sleep(0)
70
+ equal(done, false)
71
+ rebuildjs__build_id__set(app_ctx, build_id)
72
+ await sleep(0)
73
+ equal(done, true)
74
+ })
75
+ test('rhonojs__ready__wait|timeout', async ()=>{
76
+ let err:Error|undefined = undefined
77
+ try {
78
+ await rhonojs__ready__wait(0)
79
+ } catch (_err) {
80
+ err = _err as Error
81
+ }
82
+ equal(err!.message, 'Timeout 0ms')
83
+ })
84
+ test.run()
@@ -0,0 +1,19 @@
1
+ import { test } from 'uvu'
2
+ import { equal } from 'uvu/assert'
3
+ import { compression_middleware_ } from './index.js'
4
+ test('compression_middleware_|loads', ()=>{
5
+ equal(typeof compression_middleware_, 'function')
6
+ })
7
+ test('compression_middleware_|returns middleware', ()=>{
8
+ const middleware = compression_middleware_()
9
+ equal(typeof middleware, 'function')
10
+ })
11
+ test('compression_middleware_|accepts gzip type', ()=>{
12
+ const middleware = compression_middleware_({ type: 'gzip' })
13
+ equal(typeof middleware, 'function')
14
+ })
15
+ test('compression_middleware_|accepts deflate type', ()=>{
16
+ const middleware = compression_middleware_({ type: 'deflate' })
17
+ equal(typeof middleware, 'function')
18
+ })
19
+ test.run()
@@ -1,103 +1,17 @@
1
1
  /// <reference types="bun-types" />
2
2
  /// <reference types="./index.d.ts" />
3
- import { build } from 'esbuild'
4
- import { mkdir, writeFile } from 'node:fs/promises'
5
- import { dirname, join, resolve } from 'node:path'
6
- import { static_export_ } from '../index.js'
3
+ import {
4
+ cloudflare_export_ as _cloudflare_export_,
5
+ worker_entry__generate_,
6
+ wrangler_toml__generate_,
7
+ } from 'rebuildjs/server/export/cloudflare'
8
+ import { static_export_ as _static_export_ } from 'rebuildjs/server/export'
9
+ import { app__start } from '../../app/index.js'
10
+ export { worker_entry__generate_, wrangler_toml__generate_ }
7
11
  /**
8
12
  * @param {cloudflare_export_config_T} config
9
13
  * @returns {Promise<cloudflare_export_result_T>}
10
14
  */
11
- export async function cloudflare_export_(config) {
12
- const {
13
- dynamic_routes = [],
14
- worker_out = 'dist/worker',
15
- wrangler = {},
16
- ...static_config
17
- } = config
18
- const static_result = await static_export_(static_config)
19
- let worker_entry_path
20
- if (dynamic_routes.length > 0) {
21
- const entry_source = worker_entry__generate_(dynamic_routes)
22
- const generated_path = join(worker_out, '_worker.src.js')
23
- await mkdir(worker_out, { recursive: true })
24
- await writeFile(generated_path, entry_source)
25
- worker_entry_path = join(worker_out, '_worker.js')
26
- await build({
27
- entryPoints: [generated_path],
28
- bundle: true,
29
- format: 'esm',
30
- target: 'es2022',
31
- platform: 'browser',
32
- outfile: worker_entry_path,
33
- minify: true,
34
- })
35
- console.info(`[cloudflare_export] worker bundled: ${worker_entry_path}`)
36
- }
37
- const wrangler_config = wrangler_toml__generate_(wrangler, static_config.out_dir)
38
- const wrangler_path = 'wrangler.toml'
39
- await writeFile(wrangler_path, wrangler_config)
40
- console.info(`[cloudflare_export] wrote ${wrangler_path}`)
41
- return {
42
- ...static_result,
43
- worker_entry_path,
44
- wrangler_path,
45
- }
46
- }
47
- /**
48
- * @param {route_handler_T[]} dynamic_routes
49
- * @returns {string}
50
- */
51
- export function worker_entry__generate_(dynamic_routes) {
52
- const imports = []
53
- const route_checks = []
54
- for (let i = 0; i < dynamic_routes.length; i++) {
55
- const { pattern, handler } = dynamic_routes[i]
56
- const handler_name = `handler_${i}`
57
- imports.push(`import ${handler_name} from '${resolve(handler)}'`)
58
- if (pattern.endsWith('/*')) {
59
- const prefix = pattern.slice(0, -2)
60
- route_checks.push(
61
- `\tif (url.pathname.startsWith('${prefix}')) return ${handler_name}(request, env, ctx)`)
62
- } else {
63
- route_checks.push(
64
- `\tif (url.pathname === '${pattern}') return ${handler_name}(request, env, ctx)`)
65
- }
66
- }
67
- return `${imports.join('\n')}
68
-
69
- export default {
70
- async fetch(request, env, ctx) {
71
- const url = new URL(request.url)
72
- ${route_checks.join('\n')}
73
- return env.ASSETS.fetch(request)
74
- }
75
- }
76
- `
77
- }
78
- /**
79
- * @param {Partial<wrangler_config_T>} overrides
80
- * @param {string} [out_dir]
81
- * @returns {string}
82
- */
83
- export function wrangler_toml__generate_(overrides, out_dir = 'dist/browser') {
84
- const name = overrides.name || 'app'
85
- const compatibility_date = overrides.compatibility_date || new Date().toISOString().slice(0, 10)
86
- let toml = `name = "${name}"
87
- compatibility_date = "${compatibility_date}"
88
- pages_build_output_dir = "${out_dir}"
89
- `
90
- if (overrides.vars) {
91
- toml += '\n[vars]\n'
92
- for (const [key, val] of Object.entries(overrides.vars)) {
93
- toml += `${key} = "${val}"\n`
94
- }
95
- }
96
- if (overrides.routes) {
97
- for (const route of overrides.routes) {
98
- toml += `\n[[routes]]\npattern = "${route.pattern}"\n`
99
- if (route.zone_name) toml += `zone_name = "${route.zone_name}"\n`
100
- }
101
- }
102
- return toml
15
+ export function cloudflare_export_(config) {
16
+ return _cloudflare_export_(config, _static_export_, app__start)
103
17
  }
@@ -0,0 +1,55 @@
1
+ import { test } from 'uvu'
2
+ import { equal, ok } from 'uvu/assert'
3
+ import { worker_entry__generate_, wrangler_toml__generate_ } from './index.js'
4
+ test('worker_entry__generate_|single route', ()=>{
5
+ const source = worker_entry__generate_([
6
+ { pattern: '/api/*', handler: './handlers/api.js' }
7
+ ])
8
+ ok(source.includes("import handler_0 from '"))
9
+ ok(source.includes("url.pathname.startsWith('/api')"))
10
+ ok(source.includes('env.ASSETS.fetch(request)'))
11
+ })
12
+ test('worker_entry__generate_|exact route', ()=>{
13
+ const source = worker_entry__generate_([
14
+ { pattern: '/health', handler: './handlers/health.js' }
15
+ ])
16
+ ok(source.includes("url.pathname === '/health'"))
17
+ })
18
+ test('worker_entry__generate_|multiple routes', ()=>{
19
+ const source = worker_entry__generate_([
20
+ { pattern: '/api/*', handler: './handlers/api.js' },
21
+ { pattern: '/health', handler: './handlers/health.js' }
22
+ ])
23
+ ok(source.includes('handler_0'))
24
+ ok(source.includes('handler_1'))
25
+ })
26
+ test('wrangler_toml__generate_|defaults', ()=>{
27
+ const toml = wrangler_toml__generate_({})
28
+ ok(toml.includes('name = "app"'))
29
+ ok(toml.includes('compatibility_date'))
30
+ ok(toml.includes('pages_build_output_dir = "dist/browser"'))
31
+ })
32
+ test('wrangler_toml__generate_|custom name', ()=>{
33
+ const toml = wrangler_toml__generate_({ name: 'my-app' })
34
+ ok(toml.includes('name = "my-app"'))
35
+ })
36
+ test('wrangler_toml__generate_|custom out_dir', ()=>{
37
+ const toml = wrangler_toml__generate_({}, 'build/output')
38
+ ok(toml.includes('pages_build_output_dir = "build/output"'))
39
+ })
40
+ test('wrangler_toml__generate_|vars', ()=>{
41
+ const toml = wrangler_toml__generate_({
42
+ vars: { API_KEY: 'test123' }
43
+ })
44
+ ok(toml.includes('[vars]'))
45
+ ok(toml.includes('API_KEY = "test123"'))
46
+ })
47
+ test('wrangler_toml__generate_|routes', ()=>{
48
+ const toml = wrangler_toml__generate_({
49
+ routes: [{ pattern: 'example.com/*', zone_name: 'example.com' }]
50
+ })
51
+ ok(toml.includes('[[routes]]'))
52
+ ok(toml.includes('pattern = "example.com/*"'))
53
+ ok(toml.includes('zone_name = "example.com"'))
54
+ })
55
+ test.run()
@@ -1,154 +1,15 @@
1
1
  /// <reference types="bun-types" />
2
2
  /// <reference types="./index.d.ts" />
3
- import { mkdir, readFile, rm, unlink, writeFile } from 'node:fs/promises'
4
- import { dirname, extname, join } from 'node:path'
5
- import { app_ctx, port_ } from 'rebuildjs/server'
3
+ import {
4
+ static_export_ as _static_export_,
5
+ static_export__file_path_,
6
+ } from 'rebuildjs/server/export'
6
7
  import { app__start } from '../app/index.js'
8
+ export { static_export__file_path_ }
7
9
  /**
8
10
  * @param {static_export_config_T} config
9
11
  * @returns {Promise<static_export_result_T>}
10
12
  */
11
- export async function static_export_(config) {
12
- const {
13
- site_url,
14
- out_dir = 'dist/browser',
15
- server_import = './dist/server/index.js',
16
- sitemap = true,
17
- url_rewrite = true,
18
- incremental = false,
19
- manifest: use_manifest = incremental,
20
- clean = false,
21
- on_export,
22
- } = config
23
- let { routes = [], extra_routes = [] } = config
24
- const site_origin = site_url.replace(/\/$/, '')
25
- const exported = []
26
- const errors = []
27
- let app = config.app
28
- let we_started = false
29
- let base_url = config.base_url
30
- let server
31
- try {
32
- if (base_url) {
33
- base_url = base_url.replace(/\/$/, '')
34
- console.info(`[static_export] using external server: ${base_url}`)
35
- } else if (!app) {
36
- const mod = await import(server_import)
37
- app = typeof mod.default === 'function' ? await mod.default() : mod.default
38
- await app__start(app)
39
- we_started = true
40
- base_url = `http://localhost:${port_(app_ctx)}`
41
- console.info(`[static_export] server running on ${base_url}`)
42
- } else {
43
- base_url = `http://localhost:${port_(app_ctx)}`
44
- console.info(`[static_export] using provided app on ${base_url}`)
45
- }
46
- console.info(`[static_export] site_url: ${site_origin}`)
47
- if (clean) {
48
- await rm(out_dir, { recursive: true, force: true })
49
- }
50
- if (sitemap) {
51
- try {
52
- const res = await fetch(`${base_url}/sitemap.xml`)
53
- if (res.ok) {
54
- const xml = await res.text()
55
- for (const m of xml.matchAll(/<loc>https?:\/\/[^<]+<\/loc>/g)) {
56
- const url = m[0].replace(/<\/?loc>/g, '')
57
- const path = new URL(url).pathname
58
- const route = path === '' ? '/' : path
59
- if (!routes.includes(route)) {
60
- routes.push(route)
61
- }
62
- }
63
- console.info(`[static_export] discovered ${routes.length} routes from sitemap`)
64
- }
65
- } catch {
66
- console.warn('[static_export] sitemap fetch failed, using explicit routes only')
67
- }
68
- }
69
- const manifest_path = join(out_dir, '.export-manifest.json')
70
- let prev_manifest = []
71
- if (use_manifest) {
72
- try {
73
- prev_manifest = JSON.parse(await readFile(manifest_path, 'utf-8'))
74
- } catch {
75
- // No previous manifest
76
- }
77
- }
78
- const all_routes = [...routes, ...extra_routes]
79
- for (const route of all_routes) {
80
- const url = base_url + route
81
- try {
82
- const res = await fetch(url)
83
- if (!res.ok) {
84
- console.error(`[static_export] ${route} -> ${res.status} ${res.statusText}`)
85
- errors.push(route)
86
- continue
87
- }
88
- const content_type = res.headers.get('content-type') ?? ''
89
- let body = await res.text()
90
- if (url_rewrite) {
91
- body = body.replaceAll(base_url, site_origin)
92
- }
93
- const file_path = static_export__file_path_(route, out_dir, content_type)
94
- if (incremental) {
95
- try {
96
- const existing = await readFile(file_path, 'utf-8')
97
- if (existing === body) {
98
- exported.push(file_path)
99
- continue
100
- }
101
- } catch {
102
- // File doesn't exist yet
103
- }
104
- }
105
- await mkdir(dirname(file_path), { recursive: true })
106
- await writeFile(file_path, body)
107
- exported.push(file_path)
108
- on_export?.(route, file_path)
109
- console.info(`[static_export] ${route} -> ${file_path} (${body.length} bytes)`)
110
- } catch (err) {
111
- console.error(`[static_export] ${route} -> ${err.message}`)
112
- errors.push(route)
113
- }
114
- }
115
- if (use_manifest) {
116
- await mkdir(dirname(manifest_path), { recursive: true })
117
- await writeFile(manifest_path, JSON.stringify(exported, null, '\t'))
118
- const stale = prev_manifest.filter(f=>!exported.includes(f))
119
- for (const file of stale) {
120
- try {
121
- await unlink(file)
122
- console.info(`[static_export] deleted stale: ${file}`)
123
- } catch {
124
- // Already gone
125
- }
126
- }
127
- }
128
- if (errors.length > 0) {
129
- console.warn(`[static_export] completed with ${errors.length} error(s)`)
130
- } else {
131
- console.info(`[static_export] exported ${exported.length} files`)
132
- }
133
- } finally {
134
- // Bun.serve cleanup is handled by process exit
135
- }
136
- return { exported, errors }
137
- }
138
- /**
139
- * @param {string} route
140
- * @param {string} out_dir
141
- * @param {string} [content_type]
142
- * @returns {string}
143
- */
144
- export function static_export__file_path_(route, out_dir, content_type) {
145
- const ext = extname(route)
146
- const is_html = content_type ? content_type.includes('text/html') : !ext || ext === '.html'
147
- if (ext && !is_html) {
148
- return join(out_dir, route)
149
- }
150
- if (route === '/') {
151
- return join(out_dir, 'index.html')
152
- }
153
- return join(out_dir, route, 'index.html')
13
+ export function static_export_(config) {
14
+ return _static_export_(config, app__start)
154
15
  }
@@ -0,0 +1,41 @@
1
+ import { test } from 'uvu'
2
+ import { equal } from 'uvu/assert'
3
+ import { static_export__file_path_ } from './index.js'
4
+ test('static_export__file_path_|/ returns index.html', ()=>{
5
+ equal(static_export__file_path_('/', 'dist'), 'dist/index.html')
6
+ })
7
+ test('static_export__file_path_|/about returns about/index.html', ()=>{
8
+ equal(static_export__file_path_('/about', 'dist'), 'dist/about/index.html')
9
+ })
10
+ test('static_export__file_path_|/nested/path returns nested/path/index.html', ()=>{
11
+ equal(static_export__file_path_('/nested/path', 'dist'), 'dist/nested/path/index.html')
12
+ })
13
+ test('static_export__file_path_|/robots.txt with text/plain content-type returns bare file', ()=>{
14
+ equal(static_export__file_path_('/robots.txt', 'dist', 'text/plain'), 'dist/robots.txt')
15
+ })
16
+ test('static_export__file_path_|/sitemap.xml with application/xml content-type returns bare file', ()=>{
17
+ equal(static_export__file_path_('/sitemap.xml', 'dist', 'application/xml'), 'dist/sitemap.xml')
18
+ })
19
+ test('static_export__file_path_|/protocol.love with text/html content-type returns index.html', ()=>{
20
+ equal(
21
+ static_export__file_path_('/protocol.love', 'dist', 'text/html;charset=UTF-8'),
22
+ 'dist/protocol.love/index.html')
23
+ })
24
+ test('static_export__file_path_|/protocol.love without content-type falls back to bare file', ()=>{
25
+ equal(static_export__file_path_('/protocol.love', 'dist'), 'dist/protocol.love')
26
+ })
27
+ test('static_export__file_path_|/rss.xml with application/xml returns bare file', ()=>{
28
+ equal(static_export__file_path_('/rss.xml', 'dist', 'application/xml'), 'dist/rss.xml')
29
+ })
30
+ test('static_export__file_path_|/feed.json with application/json returns bare file', ()=>{
31
+ equal(static_export__file_path_('/feed.json', 'dist', 'application/json'), 'dist/feed.json')
32
+ })
33
+ test('static_export__file_path_|/page.html with text/html returns page.html/index.html', ()=>{
34
+ equal(
35
+ static_export__file_path_('/page.html', 'dist', 'text/html'),
36
+ 'dist/page.html/index.html')
37
+ })
38
+ test('static_export__file_path_|/page with no extension and no content-type returns index.html', ()=>{
39
+ equal(static_export__file_path_('/page', 'dist'), 'dist/page/index.html')
40
+ })
41
+ test.run()
@@ -0,0 +1,80 @@
1
+ import { Hono } from 'hono'
2
+ import { app_ctx, ctx_, middleware_ctx__new, request_ctx__new } from 'rebuildjs/server'
3
+ import { test } from 'uvu'
4
+ import { equal, throws } from 'uvu/assert'
5
+ import {
6
+ hono_context$_,
7
+ hono_context_,
8
+ hono_context__set,
9
+ request$_,
10
+ request_,
11
+ request_url$_,
12
+ request_url_,
13
+ } from './index.js'
14
+ test.after.each(()=>{
15
+ app_ctx.s.app.clear()
16
+ })
17
+ test('hono_context', async ()=>{
18
+ const request_ctx = request_ctx__new(middleware_ctx__new())
19
+ equal(hono_context$_(request_ctx)(), undefined)
20
+ equal(hono_context_(request_ctx), undefined)
21
+ // Use a real Hono context by running through a handler
22
+ const app = new Hono()
23
+ let captured_c:any
24
+ app.get('/', c=>{
25
+ captured_c = c
26
+ return c.text('ok')
27
+ })
28
+ await app.request('http://localhost:3000/')
29
+ hono_context__set(request_ctx, captured_c)
30
+ equal(hono_context$_(request_ctx)(), captured_c)
31
+ equal(hono_context_(request_ctx), captured_c)
32
+ // @ts-expect-error TS2345
33
+ throws(()=>hono_context$_(ctx_())())
34
+ // @ts-expect-error TS2345
35
+ throws(()=>hono_context_(ctx_()))
36
+ // @ts-expect-error TS2345
37
+ throws(()=>hono_context__set(ctx_(), captured_c))
38
+ })
39
+ test('request', async ()=>{
40
+ const request_ctx = request_ctx__new(middleware_ctx__new())
41
+ equal(request$_(request_ctx)(), undefined)
42
+ equal(request_(request_ctx), undefined)
43
+ const app = new Hono()
44
+ let captured_c:any
45
+ app.get('/', c=>{
46
+ captured_c = c
47
+ return c.text('ok')
48
+ })
49
+ await app.request('http://localhost:3000/')
50
+ hono_context__set(request_ctx, captured_c)
51
+ const req = request_(request_ctx)
52
+ equal(req instanceof Request, true)
53
+ equal(request$_(request_ctx)(), req)
54
+ // @ts-expect-error TS2345
55
+ throws(()=>request$_(ctx_())())
56
+ // @ts-expect-error TS2345
57
+ throws(()=>request_(ctx_()))
58
+ })
59
+ test('request_url', async ()=>{
60
+ const request_ctx = request_ctx__new(middleware_ctx__new())
61
+ equal(request_url$_(request_ctx)(), undefined)
62
+ equal(request_url_(request_ctx), undefined)
63
+ const app = new Hono()
64
+ let captured_c:any
65
+ app.get('/foo/bar', c=>{
66
+ captured_c = c
67
+ return c.text('ok')
68
+ })
69
+ await app.request('http://localhost:3000/foo/bar')
70
+ hono_context__set(request_ctx, captured_c)
71
+ const url = request_url_(request_ctx)
72
+ equal(url instanceof URL, true)
73
+ equal(url!.pathname, '/foo/bar')
74
+ equal(request_url$_(request_ctx)(), url)
75
+ // @ts-expect-error TS2345
76
+ throws(()=>request_url$_(ctx_())())
77
+ // @ts-expect-error TS2345
78
+ throws(()=>request_url_(ctx_()))
79
+ })
80
+ test.run()
@@ -0,0 +1,64 @@
1
+ import { test } from 'uvu'
2
+ import { equal } from 'uvu/assert'
3
+ import * as server from './index.js'
4
+ // Adapter interface conformance: verify rhonojs/server exports the expected API surface
5
+ // These are the functions/signals that both adapters must provide
6
+ const required_exports = [
7
+ // From rebuildjs/server (re-exported)
8
+ 'app_ctx',
9
+ 'middleware_ctx__new',
10
+ 'request_ctx__new',
11
+ 'html_response__new',
12
+ // html_route_ is intentionally excluded from barrel — ambiguous between rebuildjs and adapter versions.
13
+ // Import from 'rhonojs/server/route' or 'rebuildjs/server/route' instead.
14
+ 'static_middleware__routes_',
15
+ 'rebuildjs_browser__build',
16
+ 'rebuildjs_server__build',
17
+ 'build_id_',
18
+ 'cwd_',
19
+ 'port_',
20
+ // From rhonojs adapter modules
21
+ 'app$_',
22
+ 'app_',
23
+ 'app__set',
24
+ 'app__attach',
25
+ 'app__start',
26
+ 'server_entry__relative_path$_',
27
+ 'server_entry__relative_path_',
28
+ 'server_entry__output__relative_path$_',
29
+ 'server_entry__output__relative_path_',
30
+ 'server_entry__output__path$_',
31
+ 'server_entry__output__path_',
32
+ 'server_entry__output__link__path$_',
33
+ 'server_entry__output__link__path_',
34
+ // Build
35
+ 'rhonojs__build_id$_',
36
+ 'rhonojs__build_id_',
37
+ 'rhonojs__build_id__set',
38
+ 'rhonojs__ready$_',
39
+ 'rhonojs__ready_',
40
+ 'rhonojs__ready__wait',
41
+ 'rhonojs_browser__build',
42
+ 'rhonojs_server__build',
43
+ 'rhonojs_plugin_',
44
+ // Compression
45
+ 'compression_middleware_',
46
+ // Hono context
47
+ 'hono_context$_',
48
+ 'hono_context_',
49
+ 'hono_context__set',
50
+ 'request$_',
51
+ 'request_',
52
+ 'request_url$_',
53
+ 'request_url_',
54
+ // Route
55
+ 'request_ctx__ensure',
56
+ // Static
57
+ 'static_middleware_',
58
+ ]
59
+ for (const name of required_exports) {
60
+ test(`exports|${name}`, ()=>{
61
+ equal(name in server, true, `rhonojs/server should export '${name}'`)
62
+ })
63
+ }
64
+ test.run()
@@ -1,6 +1,7 @@
1
1
  /// <reference types="./index.d.ts" />
2
- import { request_ctx__new } from 'rebuildjs/server'
2
+ import { html_route_ as _html_route_, request_ctx__new } from 'rebuildjs/server'
3
3
  import { hono_context__set } from '../hono/index.js'
4
+ export { html_response__new } from 'rebuildjs/server'
4
5
  /**
5
6
  * @param {middleware_ctx_T}middleware_ctx
6
7
  * @param {($p:{ ctx:request_ctx_T })=>(string|ReadableStream<string|Uint8Array>)}page_
@@ -12,38 +13,7 @@ export function html_route_(
12
13
  page_,
13
14
  response_init
14
15
  ) {
15
- return c=>{
16
- const request_ctx = request_ctx__ensure(middleware_ctx, c)
17
- return html_response__new(
18
- page_({ ctx: request_ctx }),
19
- response_init)
20
- }
21
- }
22
- /**
23
- * @param {string|ReadableStream}html_OR_stream
24
- * @param {ResponseInit}[response_init]
25
- * @returns {Response}
26
- */
27
- export function html_response__new(
28
- html_OR_stream,
29
- response_init
30
- ) {
31
- const headers = new Headers(response_init?.headers)
32
- headers.set('Content-Type', 'text/html;charset=UTF-8')
33
- return new Response(
34
- html_OR_stream.pipeTo
35
- ? html_OR_stream.pipeThrough(new TextEncoderStream())
36
- : new ReadableStream({
37
- start(controller) {
38
- controller.enqueue('' + html_OR_stream)
39
- controller.close()
40
- }
41
- }),
42
- {
43
- ...(response_init ?? {}),
44
- headers
45
- }
46
- )
16
+ return _html_route_(middleware_ctx, page_, request_ctx__ensure, response_init)
47
17
  }
48
18
  /**
49
19
  * @param {middleware_ctx_T}middleware_ctx
@@ -0,0 +1,98 @@
1
+ import { Hono } from 'hono'
2
+ import { app_ctx, middleware_ctx__new } from 'rebuildjs/server'
3
+ import { test } from 'uvu'
4
+ import { equal } from 'uvu/assert'
5
+ import { hono_context_ } from '../hono/index.js'
6
+ import { html_route_, request_ctx__ensure } from './index.js'
7
+ test.after.each(()=>{
8
+ app_ctx.s.app.clear()
9
+ })
10
+ test('html_route_|string', async ()=>{
11
+ const middleware_ctx = middleware_ctx__new()
12
+ const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
13
+ const html_route = html_route_(middleware_ctx, ()=>
14
+ html)
15
+ const app = new Hono()
16
+ app.get('/', html_route)
17
+ const response = await app.request('/')
18
+ equal(html, await response.text())
19
+ })
20
+ test('html_route_|toString', async ()=>{
21
+ const middleware_ctx = middleware_ctx__new()
22
+ const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
23
+ const html_route = html_route_(middleware_ctx, ()=>(
24
+ { toString: ()=>html }))
25
+ const app = new Hono()
26
+ app.get('/', html_route)
27
+ const response = await app.request('/')
28
+ equal(await response.text(), html)
29
+ })
30
+ test('html_route_|ReadableStream|string', async ()=>{
31
+ const middleware_ctx = middleware_ctx__new()
32
+ const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
33
+ const html_route = html_route_(middleware_ctx, ()=>
34
+ new ReadableStream({
35
+ start(controller) {
36
+ let first = true
37
+ for (const chunk of html.split('>')) {
38
+ if (!first) controller.enqueue('>')
39
+ first = false
40
+ if (chunk != null) controller.enqueue(chunk)
41
+ }
42
+ controller.close()
43
+ }
44
+ }))
45
+ const app = new Hono()
46
+ app.get('/', html_route)
47
+ const response = await app.request('/')
48
+ equal(await response.text(), html)
49
+ })
50
+ test('html_route_|response_init|all', async ()=>{
51
+ const middleware_ctx = middleware_ctx__new()
52
+ const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
53
+ const html_route = html_route_(middleware_ctx, ()=>html, {
54
+ status: 403,
55
+ statusText: 'Forbidden',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ 'FOO': 'BAR',
59
+ }
60
+ })
61
+ const app = new Hono()
62
+ app.get('/', html_route)
63
+ const response = await app.request('/')
64
+ equal(response.status, 403)
65
+ equal(response.statusText, 'Forbidden')
66
+ equal(response.headers.get('Content-Type'), 'text/html;charset=UTF-8')
67
+ equal(response.headers.get('FOO'), 'BAR')
68
+ })
69
+ test('html_route_|response_init|additional headers', async ()=>{
70
+ const middleware_ctx = middleware_ctx__new()
71
+ const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
72
+ const html_route = html_route_(middleware_ctx, ()=>html, {
73
+ headers: {
74
+ 'FOO': 'BAR'
75
+ }
76
+ })
77
+ const app = new Hono()
78
+ app.get('/', html_route)
79
+ const response = await app.request('/')
80
+ equal(response.status, 200)
81
+ equal(response.headers.get('Content-Type'), 'text/html;charset=UTF-8')
82
+ equal(response.headers.get('FOO'), 'BAR')
83
+ })
84
+ test('request_ctx__ensure', async ()=>{
85
+ const middleware_ctx = middleware_ctx__new()
86
+ let captured_request_ctx:any
87
+ const app = new Hono()
88
+ app.get('/', c=>{
89
+ const request_ctx = request_ctx__ensure(middleware_ctx, c)
90
+ captured_request_ctx = request_ctx
91
+ equal(c.get('request_ctx'), request_ctx)
92
+ equal(hono_context_(request_ctx), c)
93
+ return c.text('ok')
94
+ })
95
+ await app.request('/')
96
+ equal(captured_request_ctx != null, true)
97
+ })
98
+ test.run()
@@ -1,34 +1,16 @@
1
1
  /// <reference types="bun-types" />
2
2
  /// <reference types="./index.d.ts" />
3
- import { file, Glob } from 'bun'
4
- import { ext_R_mime } from 'ctx-core/http'
5
3
  import { Hono } from 'hono'
6
- import { extname, join } from 'node:path'
7
- import { app_ctx, browser_path_ } from 'rebuildjs/server'
4
+ import { static_middleware__routes_ } from 'rebuildjs/server'
8
5
  /**
9
6
  * @param {static_middleware__config_T}[config]
10
7
  * @returns {Promise<Hono>}
11
8
  */
12
9
  export async function static_middleware_(config) {
13
10
  const app = new Hono()
14
- const glob = new Glob('**')
15
- for await (const relative_path of glob.scan(browser_path_(app_ctx))) {
16
- const url_path = join('/', relative_path)
17
- const content_type = ext_R_mime[extname(relative_path)] ?? 'text/plain'
18
- const path = join(browser_path_(app_ctx), relative_path)
19
- const headers = (config?.headers_ ?? (()=>({})))(
20
- url_path,
21
- content_type,
22
- path)
23
- app.get(url_path, c=>{
24
- const file_ref = file(path)
25
- return new Response(file_ref.size ? file_ref.stream() : '', {
26
- headers: {
27
- 'Content-Type': content_type,
28
- ...headers,
29
- }
30
- })
31
- })
11
+ const routes = await static_middleware__routes_(config)
12
+ for (const { url_path, handler } of routes) {
13
+ app.get(url_path, c=>handler())
32
14
  }
33
15
  return app
34
16
  }
@@ -0,0 +1,11 @@
1
+ import { app_ctx } from 'rebuildjs/server'
2
+ import { test } from 'uvu'
3
+ import { equal } from 'uvu/assert'
4
+ import './index.js'
5
+ test.after.each(()=>{
6
+ app_ctx.s.app.clear()
7
+ })
8
+ test('static_middleware_|loads', ()=>{
9
+ equal(1, 1)
10
+ })
11
+ test.run()