brustjs 0.1.54-alpha → 0.1.56-alpha
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 +7 -7
- package/runtime/index.js +52 -52
- package/runtime/index.ts +11 -0
- package/runtime/navigation/server-context.ts +22 -0
- package/runtime/navigation/store.ts +39 -0
- package/runtime/render/fragment.ts +15 -2
- package/runtime/routes.ts +203 -141
- package/types/navigation/server-context.d.ts +5 -0
- package/types/navigation/store.d.ts +6 -0
- package/types/render/fragment.d.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brustjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56-alpha",
|
|
4
4
|
"description": "Bun + Rust SSR framework — React on the server, Rust everywhere else (napi cdylib + per-worker SharedArrayBuffer).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"typescript": "^6.0.3"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"brustjs-darwin-x64": "0.1.
|
|
45
|
-
"brustjs-darwin-arm64": "0.1.
|
|
46
|
-
"brustjs-linux-x64-gnu": "0.1.
|
|
47
|
-
"brustjs-linux-arm64-gnu": "0.1.
|
|
48
|
-
"brustjs-linux-x64-musl": "0.1.
|
|
49
|
-
"brustjs-linux-arm64-musl": "0.1.
|
|
44
|
+
"brustjs-darwin-x64": "0.1.56-alpha",
|
|
45
|
+
"brustjs-darwin-arm64": "0.1.56-alpha",
|
|
46
|
+
"brustjs-linux-x64-gnu": "0.1.56-alpha",
|
|
47
|
+
"brustjs-linux-arm64-gnu": "0.1.56-alpha",
|
|
48
|
+
"brustjs-linux-x64-musl": "0.1.56-alpha",
|
|
49
|
+
"brustjs-linux-arm64-musl": "0.1.56-alpha"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^19.2.6",
|
package/runtime/index.js
CHANGED
|
@@ -77,8 +77,8 @@ function requireNative() {
|
|
|
77
77
|
try {
|
|
78
78
|
const binding = require('brustjs-android-arm64')
|
|
79
79
|
const bindingPackageVersion = require('brustjs-android-arm64/package.json').version
|
|
80
|
-
if (bindingPackageVersion !== '0.1.
|
|
81
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
80
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
81
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
82
82
|
}
|
|
83
83
|
return binding
|
|
84
84
|
} catch (e) {
|
|
@@ -93,8 +93,8 @@ function requireNative() {
|
|
|
93
93
|
try {
|
|
94
94
|
const binding = require('brustjs-android-arm-eabi')
|
|
95
95
|
const bindingPackageVersion = require('brustjs-android-arm-eabi/package.json').version
|
|
96
|
-
if (bindingPackageVersion !== '0.1.
|
|
97
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
96
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
97
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
98
98
|
}
|
|
99
99
|
return binding
|
|
100
100
|
} catch (e) {
|
|
@@ -114,8 +114,8 @@ function requireNative() {
|
|
|
114
114
|
try {
|
|
115
115
|
const binding = require('brustjs-win32-x64-gnu')
|
|
116
116
|
const bindingPackageVersion = require('brustjs-win32-x64-gnu/package.json').version
|
|
117
|
-
if (bindingPackageVersion !== '0.1.
|
|
118
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
117
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
118
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
119
119
|
}
|
|
120
120
|
return binding
|
|
121
121
|
} catch (e) {
|
|
@@ -130,8 +130,8 @@ function requireNative() {
|
|
|
130
130
|
try {
|
|
131
131
|
const binding = require('brustjs-win32-x64-msvc')
|
|
132
132
|
const bindingPackageVersion = require('brustjs-win32-x64-msvc/package.json').version
|
|
133
|
-
if (bindingPackageVersion !== '0.1.
|
|
134
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
133
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
134
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
135
135
|
}
|
|
136
136
|
return binding
|
|
137
137
|
} catch (e) {
|
|
@@ -147,8 +147,8 @@ function requireNative() {
|
|
|
147
147
|
try {
|
|
148
148
|
const binding = require('brustjs-win32-ia32-msvc')
|
|
149
149
|
const bindingPackageVersion = require('brustjs-win32-ia32-msvc/package.json').version
|
|
150
|
-
if (bindingPackageVersion !== '0.1.
|
|
151
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
150
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
151
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
152
152
|
}
|
|
153
153
|
return binding
|
|
154
154
|
} catch (e) {
|
|
@@ -163,8 +163,8 @@ function requireNative() {
|
|
|
163
163
|
try {
|
|
164
164
|
const binding = require('brustjs-win32-arm64-msvc')
|
|
165
165
|
const bindingPackageVersion = require('brustjs-win32-arm64-msvc/package.json').version
|
|
166
|
-
if (bindingPackageVersion !== '0.1.
|
|
167
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
166
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
167
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
168
168
|
}
|
|
169
169
|
return binding
|
|
170
170
|
} catch (e) {
|
|
@@ -182,8 +182,8 @@ function requireNative() {
|
|
|
182
182
|
try {
|
|
183
183
|
const binding = require('brustjs-darwin-universal')
|
|
184
184
|
const bindingPackageVersion = require('brustjs-darwin-universal/package.json').version
|
|
185
|
-
if (bindingPackageVersion !== '0.1.
|
|
186
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
185
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
186
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
187
187
|
}
|
|
188
188
|
return binding
|
|
189
189
|
} catch (e) {
|
|
@@ -198,8 +198,8 @@ function requireNative() {
|
|
|
198
198
|
try {
|
|
199
199
|
const binding = require('brustjs-darwin-x64')
|
|
200
200
|
const bindingPackageVersion = require('brustjs-darwin-x64/package.json').version
|
|
201
|
-
if (bindingPackageVersion !== '0.1.
|
|
202
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
201
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
202
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
203
203
|
}
|
|
204
204
|
return binding
|
|
205
205
|
} catch (e) {
|
|
@@ -214,8 +214,8 @@ function requireNative() {
|
|
|
214
214
|
try {
|
|
215
215
|
const binding = require('brustjs-darwin-arm64')
|
|
216
216
|
const bindingPackageVersion = require('brustjs-darwin-arm64/package.json').version
|
|
217
|
-
if (bindingPackageVersion !== '0.1.
|
|
218
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
217
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
218
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
219
219
|
}
|
|
220
220
|
return binding
|
|
221
221
|
} catch (e) {
|
|
@@ -234,8 +234,8 @@ function requireNative() {
|
|
|
234
234
|
try {
|
|
235
235
|
const binding = require('brustjs-freebsd-x64')
|
|
236
236
|
const bindingPackageVersion = require('brustjs-freebsd-x64/package.json').version
|
|
237
|
-
if (bindingPackageVersion !== '0.1.
|
|
238
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
237
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
238
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
239
239
|
}
|
|
240
240
|
return binding
|
|
241
241
|
} catch (e) {
|
|
@@ -250,8 +250,8 @@ function requireNative() {
|
|
|
250
250
|
try {
|
|
251
251
|
const binding = require('brustjs-freebsd-arm64')
|
|
252
252
|
const bindingPackageVersion = require('brustjs-freebsd-arm64/package.json').version
|
|
253
|
-
if (bindingPackageVersion !== '0.1.
|
|
254
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
253
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
254
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
255
255
|
}
|
|
256
256
|
return binding
|
|
257
257
|
} catch (e) {
|
|
@@ -271,8 +271,8 @@ function requireNative() {
|
|
|
271
271
|
try {
|
|
272
272
|
const binding = require('brustjs-linux-x64-musl')
|
|
273
273
|
const bindingPackageVersion = require('brustjs-linux-x64-musl/package.json').version
|
|
274
|
-
if (bindingPackageVersion !== '0.1.
|
|
275
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
274
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
275
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
276
276
|
}
|
|
277
277
|
return binding
|
|
278
278
|
} catch (e) {
|
|
@@ -287,8 +287,8 @@ function requireNative() {
|
|
|
287
287
|
try {
|
|
288
288
|
const binding = require('brustjs-linux-x64-gnu')
|
|
289
289
|
const bindingPackageVersion = require('brustjs-linux-x64-gnu/package.json').version
|
|
290
|
-
if (bindingPackageVersion !== '0.1.
|
|
291
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
290
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
291
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
292
292
|
}
|
|
293
293
|
return binding
|
|
294
294
|
} catch (e) {
|
|
@@ -305,8 +305,8 @@ function requireNative() {
|
|
|
305
305
|
try {
|
|
306
306
|
const binding = require('brustjs-linux-arm64-musl')
|
|
307
307
|
const bindingPackageVersion = require('brustjs-linux-arm64-musl/package.json').version
|
|
308
|
-
if (bindingPackageVersion !== '0.1.
|
|
309
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
308
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
309
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
310
310
|
}
|
|
311
311
|
return binding
|
|
312
312
|
} catch (e) {
|
|
@@ -321,8 +321,8 @@ function requireNative() {
|
|
|
321
321
|
try {
|
|
322
322
|
const binding = require('brustjs-linux-arm64-gnu')
|
|
323
323
|
const bindingPackageVersion = require('brustjs-linux-arm64-gnu/package.json').version
|
|
324
|
-
if (bindingPackageVersion !== '0.1.
|
|
325
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
324
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
325
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
326
326
|
}
|
|
327
327
|
return binding
|
|
328
328
|
} catch (e) {
|
|
@@ -339,8 +339,8 @@ function requireNative() {
|
|
|
339
339
|
try {
|
|
340
340
|
const binding = require('brustjs-linux-arm-musleabihf')
|
|
341
341
|
const bindingPackageVersion = require('brustjs-linux-arm-musleabihf/package.json').version
|
|
342
|
-
if (bindingPackageVersion !== '0.1.
|
|
343
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
342
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
343
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
344
344
|
}
|
|
345
345
|
return binding
|
|
346
346
|
} catch (e) {
|
|
@@ -355,8 +355,8 @@ function requireNative() {
|
|
|
355
355
|
try {
|
|
356
356
|
const binding = require('brustjs-linux-arm-gnueabihf')
|
|
357
357
|
const bindingPackageVersion = require('brustjs-linux-arm-gnueabihf/package.json').version
|
|
358
|
-
if (bindingPackageVersion !== '0.1.
|
|
359
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
358
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
359
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
360
360
|
}
|
|
361
361
|
return binding
|
|
362
362
|
} catch (e) {
|
|
@@ -373,8 +373,8 @@ function requireNative() {
|
|
|
373
373
|
try {
|
|
374
374
|
const binding = require('brustjs-linux-loong64-musl')
|
|
375
375
|
const bindingPackageVersion = require('brustjs-linux-loong64-musl/package.json').version
|
|
376
|
-
if (bindingPackageVersion !== '0.1.
|
|
377
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
376
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
377
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
378
378
|
}
|
|
379
379
|
return binding
|
|
380
380
|
} catch (e) {
|
|
@@ -389,8 +389,8 @@ function requireNative() {
|
|
|
389
389
|
try {
|
|
390
390
|
const binding = require('brustjs-linux-loong64-gnu')
|
|
391
391
|
const bindingPackageVersion = require('brustjs-linux-loong64-gnu/package.json').version
|
|
392
|
-
if (bindingPackageVersion !== '0.1.
|
|
393
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
392
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
393
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
394
394
|
}
|
|
395
395
|
return binding
|
|
396
396
|
} catch (e) {
|
|
@@ -407,8 +407,8 @@ function requireNative() {
|
|
|
407
407
|
try {
|
|
408
408
|
const binding = require('brustjs-linux-riscv64-musl')
|
|
409
409
|
const bindingPackageVersion = require('brustjs-linux-riscv64-musl/package.json').version
|
|
410
|
-
if (bindingPackageVersion !== '0.1.
|
|
411
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
410
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
411
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
412
412
|
}
|
|
413
413
|
return binding
|
|
414
414
|
} catch (e) {
|
|
@@ -423,8 +423,8 @@ function requireNative() {
|
|
|
423
423
|
try {
|
|
424
424
|
const binding = require('brustjs-linux-riscv64-gnu')
|
|
425
425
|
const bindingPackageVersion = require('brustjs-linux-riscv64-gnu/package.json').version
|
|
426
|
-
if (bindingPackageVersion !== '0.1.
|
|
427
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
426
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
427
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
428
428
|
}
|
|
429
429
|
return binding
|
|
430
430
|
} catch (e) {
|
|
@@ -440,8 +440,8 @@ function requireNative() {
|
|
|
440
440
|
try {
|
|
441
441
|
const binding = require('brustjs-linux-ppc64-gnu')
|
|
442
442
|
const bindingPackageVersion = require('brustjs-linux-ppc64-gnu/package.json').version
|
|
443
|
-
if (bindingPackageVersion !== '0.1.
|
|
444
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
443
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
444
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
445
445
|
}
|
|
446
446
|
return binding
|
|
447
447
|
} catch (e) {
|
|
@@ -456,8 +456,8 @@ function requireNative() {
|
|
|
456
456
|
try {
|
|
457
457
|
const binding = require('brustjs-linux-s390x-gnu')
|
|
458
458
|
const bindingPackageVersion = require('brustjs-linux-s390x-gnu/package.json').version
|
|
459
|
-
if (bindingPackageVersion !== '0.1.
|
|
460
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
459
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
460
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
461
461
|
}
|
|
462
462
|
return binding
|
|
463
463
|
} catch (e) {
|
|
@@ -476,8 +476,8 @@ function requireNative() {
|
|
|
476
476
|
try {
|
|
477
477
|
const binding = require('brustjs-openharmony-arm64')
|
|
478
478
|
const bindingPackageVersion = require('brustjs-openharmony-arm64/package.json').version
|
|
479
|
-
if (bindingPackageVersion !== '0.1.
|
|
480
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
479
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
480
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
481
481
|
}
|
|
482
482
|
return binding
|
|
483
483
|
} catch (e) {
|
|
@@ -492,8 +492,8 @@ function requireNative() {
|
|
|
492
492
|
try {
|
|
493
493
|
const binding = require('brustjs-openharmony-x64')
|
|
494
494
|
const bindingPackageVersion = require('brustjs-openharmony-x64/package.json').version
|
|
495
|
-
if (bindingPackageVersion !== '0.1.
|
|
496
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
495
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
496
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
497
497
|
}
|
|
498
498
|
return binding
|
|
499
499
|
} catch (e) {
|
|
@@ -508,8 +508,8 @@ function requireNative() {
|
|
|
508
508
|
try {
|
|
509
509
|
const binding = require('brustjs-openharmony-arm')
|
|
510
510
|
const bindingPackageVersion = require('brustjs-openharmony-arm/package.json').version
|
|
511
|
-
if (bindingPackageVersion !== '0.1.
|
|
512
|
-
throw new Error(`Native binding package version mismatch, expected 0.1.
|
|
511
|
+
if (bindingPackageVersion !== '0.1.56-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
|
512
|
+
throw new Error(`Native binding package version mismatch, expected 0.1.56-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
|
513
513
|
}
|
|
514
514
|
return binding
|
|
515
515
|
} catch (e) {
|
package/runtime/index.ts
CHANGED
|
@@ -461,6 +461,17 @@ export const brust = {
|
|
|
461
461
|
const scanRoot = opts.scanRoot ?? path.dirname(fileURLToPath(opts.entry))
|
|
462
462
|
|
|
463
463
|
const dev = opts.dev === true || process.env.BRUST_DEV === '1'
|
|
464
|
+
// Unify the `dev: true` option with the BRUST_DEV env signal. The dev
|
|
465
|
+
// coordinator/watcher (which calls worker-registry spawnAll() on hot reload)
|
|
466
|
+
// keys off this `dev` variable, but serve()'s registerInitialPool — plus
|
|
467
|
+
// every other `process.env.BRUST_DEV === '1'` gate (dev-client injection,
|
|
468
|
+
// store/css `no-store`, cookie dev warnings) — keys off the env var ALONE.
|
|
469
|
+
// The `brust dev` CLI sets BRUST_DEV=1; the programmatic `run({ dev: true })`
|
|
470
|
+
// path must too, or hot reload calls spawnAll() against a pool that
|
|
471
|
+
// registerInitialPool() never recorded ("spawnAll called before
|
|
472
|
+
// registerInitialPool"). Set it here, before serve() spawns workers, so they
|
|
473
|
+
// inherit it via baseEnv.
|
|
474
|
+
if (dev) process.env.BRUST_DEV = '1'
|
|
464
475
|
let routes = opts.routes
|
|
465
476
|
if (dev) {
|
|
466
477
|
const { createDevWsRoute } = await import('./dev/ws-channel.ts')
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// runtime/navigation/server-context.ts
|
|
2
|
+
// Server-only: imports a Node builtin (node:async_hooks), so it must NOT be
|
|
3
|
+
// reachable from browser/island bundles (brustjs/client) or the React-free,
|
|
4
|
+
// DOM-free navigation entry (brustjs/navigation = ./index.ts). It mirrors
|
|
5
|
+
// store/server-context.ts: the server pulls this module in via routes.ts, where
|
|
6
|
+
// each render is wrapped in a per-request nav scope; navigation/store.ts reaches
|
|
7
|
+
// the active scope through the resolver registered below (__setServerNavResolver),
|
|
8
|
+
// which stays out of the client bundle (store.ts never imports this file).
|
|
9
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
10
|
+
import { __setServerNavResolver, type ServerNavScope } from './store.ts'
|
|
11
|
+
|
|
12
|
+
const navContext = new AsyncLocalStorage<ServerNavScope>()
|
|
13
|
+
|
|
14
|
+
/** Open a per-request nav scope so SSR — React islands AND native island SSR —
|
|
15
|
+
* sees the current request path via useNav()/getNavState() instead of the idle
|
|
16
|
+
* singleton default. `search` is the raw query string INCLUDING the leading '?'
|
|
17
|
+
* (so it matches the client's `location.search`), or '' when there is none. */
|
|
18
|
+
export function runInNavContext<T>(path: string, search: string, fn: () => T): T {
|
|
19
|
+
return navContext.run({ path, search }, fn)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
__setServerNavResolver(() => navContext.getStore())
|
|
@@ -101,7 +101,46 @@ export const nav: NavStore = new Proxy({} as NavStore, {
|
|
|
101
101
|
},
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
+
// ─── Server-side request-scoped path (SSR seeding) ──────────────────────────
|
|
105
|
+
// The `nav` singleton is shared by every concurrent request in a worker, so on
|
|
106
|
+
// the server it CANNOT hold a per-request path. A server-only module
|
|
107
|
+
// (navigation/server-context.ts) registers a resolver that returns the active
|
|
108
|
+
// request's nav scope via AsyncLocalStorage — mirroring store/server-context.ts'
|
|
109
|
+
// __setServerResolver. The resolver stays null in browser/island bundles (which
|
|
110
|
+
// never import the Node module), so client behavior is byte-identical.
|
|
111
|
+
export interface ServerNavScope {
|
|
112
|
+
path: string
|
|
113
|
+
search: string
|
|
114
|
+
// Per-scope memo of the built NavState: getServerSnapshot may be called more
|
|
115
|
+
// than once per render and useSyncExternalStore requires referential stability.
|
|
116
|
+
_snap?: NavState
|
|
117
|
+
}
|
|
118
|
+
let serverNavResolver: (() => ServerNavScope | undefined) | null = null
|
|
119
|
+
export function __setServerNavResolver(fn: () => ServerNavScope | undefined): void {
|
|
120
|
+
serverNavResolver = fn
|
|
121
|
+
}
|
|
122
|
+
|
|
104
123
|
export function getNavState(): NavState {
|
|
124
|
+
// Server SSR: when a request-scoped nav context is active, derive path/search
|
|
125
|
+
// from it (the singleton is not per-request-safe). `phase` is always 'idle'
|
|
126
|
+
// during SSR — no navigation is in flight — and from/to/error stay null. The
|
|
127
|
+
// client then seeds the SAME path via __navInit before hydration, so
|
|
128
|
+
// getServerSnapshot (this getter) and the server HTML agree → no mismatch,
|
|
129
|
+
// active from first paint.
|
|
130
|
+
const scope = serverNavResolver?.()
|
|
131
|
+
if (scope) {
|
|
132
|
+
if (!scope._snap) {
|
|
133
|
+
scope._snap = {
|
|
134
|
+
path: canonicalPath(scope.path),
|
|
135
|
+
search: scope.search,
|
|
136
|
+
phase: 'idle',
|
|
137
|
+
error: null,
|
|
138
|
+
from: null,
|
|
139
|
+
to: null,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return scope._snap
|
|
143
|
+
}
|
|
105
144
|
const s = store()
|
|
106
145
|
// Return the memoized snapshot if no transition has happened since it was
|
|
107
146
|
// built (referential stability for useSyncExternalStore).
|
|
@@ -19,6 +19,7 @@ import { Buffer } from 'node:buffer'
|
|
|
19
19
|
import { runInRequestScope } from '../request-context.ts'
|
|
20
20
|
import { runInRequestCache } from '../loader-cache.ts'
|
|
21
21
|
import { runInStoreContext } from '../store/server-context.ts'
|
|
22
|
+
import { runInNavContext } from '../navigation/server-context.ts'
|
|
22
23
|
|
|
23
24
|
/** Render a React element to a single HTML string, awaiting all Suspense
|
|
24
25
|
* boundaries via onAllReady. Used by navigationBranch — renderToString
|
|
@@ -59,6 +60,13 @@ export function renderToAwaitedString(element: ReactNode): Promise<string> {
|
|
|
59
60
|
export interface RenderFragmentOpts {
|
|
60
61
|
/** Request cookies visible to cookies()/session helpers inside the tree. */
|
|
61
62
|
cookies?: Record<string, string>
|
|
63
|
+
/** Seed `useNav()` / `getNavState()` for islands inside the fragment. Provide
|
|
64
|
+
* when rendering for a known URL (e.g. a server-side preview of a specific
|
|
65
|
+
* route) so active-nav resolves; omit for a context-free fragment, where
|
|
66
|
+
* `useNav()` reports the idle defaults (path ''). `search` is the raw query
|
|
67
|
+
* string including the leading '?'. */
|
|
68
|
+
path?: string
|
|
69
|
+
search?: string
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
/** Render a component to an HTML string with framework contexts scoped:
|
|
@@ -76,11 +84,16 @@ export async function renderFragment<P extends object>(
|
|
|
76
84
|
opts?: RenderFragmentOpts,
|
|
77
85
|
): Promise<string> {
|
|
78
86
|
// Composition mirrors routes.ts runInRequestContext: scope outermost, then
|
|
79
|
-
// the request-scoped loader cache, then the per-call store context
|
|
87
|
+
// the request-scoped loader cache, then the per-call store context, then —
|
|
88
|
+
// only when the caller seeds a URL — the nav scope (innermost), so useNav()
|
|
89
|
+
// inside the fragment resolves active-nav. Without opts.path, no nav scope is
|
|
90
|
+
// opened and useNav() reports idle (the context-free default).
|
|
91
|
+
const render = () =>
|
|
92
|
+
renderToAwaitedString(createElement(Component as ComponentType, props as object))
|
|
80
93
|
return runInRequestScope(opts?.cookies ?? {}, () =>
|
|
81
94
|
runInRequestCache(() =>
|
|
82
95
|
runInStoreContext(() =>
|
|
83
|
-
|
|
96
|
+
opts?.path !== undefined ? runInNavContext(opts.path, opts.search ?? '', render) : render(),
|
|
84
97
|
),
|
|
85
98
|
),
|
|
86
99
|
)
|
package/runtime/routes.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as native from './index.js'
|
|
|
4
4
|
import { renderBranchStreaming } from './render/stream.ts'
|
|
5
5
|
import { renderToAwaitedString } from './render/fragment.ts'
|
|
6
6
|
import { runInStoreContext, collectSnapshot } from './store/server-context.ts'
|
|
7
|
+
import { runInNavContext } from './navigation/server-context.ts'
|
|
7
8
|
import { buildStoreScripts } from './render/inject-store.ts'
|
|
8
9
|
import { getCssHrefsForRoute } from './css.ts'
|
|
9
10
|
import { runInRequestCache } from './loader-cache.ts'
|
|
@@ -45,8 +46,34 @@ export { slugifyHeading } from './md/slug.ts'
|
|
|
45
46
|
// store scope (per-request store instance). `reqCookies` is the parsed incoming
|
|
46
47
|
// Cookie header so a loader's `cookies.get` reads it and `cookies.set` stages
|
|
47
48
|
// onto this scope (flushed onto response headers via flushSetCookie).
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
/** Raw query string (including the leading '?') of a `path+query` URL, or ''
|
|
50
|
+
* when there is none. Mirrors the client's `location.search` so the SSR-seeded
|
|
51
|
+
* nav.search matches what the client re-seeds via __navInit at hydration. */
|
|
52
|
+
function searchStringOf(url: string | undefined): string {
|
|
53
|
+
if (!url) return ''
|
|
54
|
+
const q = url.indexOf('?')
|
|
55
|
+
if (q === -1) return ''
|
|
56
|
+
// Strip any fragment so the seeded nav.search matches location.search (which
|
|
57
|
+
// never includes '#...'). HTTP request lines don't carry fragments today, but
|
|
58
|
+
// this keeps searchStringOf correct-by-construction.
|
|
59
|
+
const h = url.indexOf('#', q)
|
|
60
|
+
return h === -1 ? url.slice(q) : url.slice(q, h)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// `navPath`/`navSearch` (optional, trailing) open a per-request nav scope so
|
|
64
|
+
// island / React SSR sees the current request path via useNav()/getNavState()
|
|
65
|
+
// — active-nav at first paint with no hydration flash. They default to '' (the
|
|
66
|
+
// idle nav state), so loader-only / action scopes are unchanged. Trailing args
|
|
67
|
+
// keep the callback in the conventional 2nd position at the bare call sites.
|
|
68
|
+
function runInRequestContext<T>(
|
|
69
|
+
reqCookies: Record<string, string>,
|
|
70
|
+
fn: () => T,
|
|
71
|
+
navPath = '',
|
|
72
|
+
navSearch = '',
|
|
73
|
+
): T {
|
|
74
|
+
return runInRequestScope(reqCookies, () =>
|
|
75
|
+
runInRequestCache(() => runInStoreContext(() => runInNavContext(navPath, navSearch, fn))),
|
|
76
|
+
)
|
|
50
77
|
}
|
|
51
78
|
|
|
52
79
|
// Sub-project J — island ISR cache, backed by the Rust-side store (shared across
|
|
@@ -1290,20 +1317,33 @@ export function makeRenderer(
|
|
|
1290
1317
|
const compManifest = loadComponentManifest(flat.nativeTemplate)
|
|
1291
1318
|
if ((manifest && manifest.length > 0) || (compManifest && compManifest.length > 0)) {
|
|
1292
1319
|
const rt = JSON.parse(json) as Record<string, unknown>
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1320
|
+
// Capture the (narrowed) template name in this scope: the runInNavContext
|
|
1321
|
+
// closure below would otherwise lose the `flat.nativeTemplate !== undefined`
|
|
1322
|
+
// narrowing (TS doesn't carry control-flow narrowing into a nested fn).
|
|
1323
|
+
const nativeTemplate = flat.nativeTemplate
|
|
1324
|
+
// Seed the request path so island / SSR-component renderToString sees
|
|
1325
|
+
// the active route via useNav() (active-nav at first paint). The native
|
|
1326
|
+
// template itself is Rust-rendered and needs no scope. The loaders ran
|
|
1327
|
+
// in their own request scope above; this island SSR is outside it.
|
|
1328
|
+
const [islandExtra, componentExtra] = await runInNavContext(
|
|
1329
|
+
call.path,
|
|
1330
|
+
searchStringOf(call.req?.url),
|
|
1331
|
+
() =>
|
|
1332
|
+
Promise.all([
|
|
1333
|
+
manifest && manifest.length > 0
|
|
1334
|
+
? resolveIslandContext(manifest, rt, islandCache)
|
|
1335
|
+
: Promise.resolve({} as Record<string, string>),
|
|
1336
|
+
compManifest && compManifest.length > 0
|
|
1337
|
+
? resolveComponentContext(
|
|
1338
|
+
compManifest,
|
|
1339
|
+
rt,
|
|
1340
|
+
nativeTemplate,
|
|
1341
|
+
undefined,
|
|
1342
|
+
islandCache,
|
|
1343
|
+
)
|
|
1344
|
+
: Promise.resolve({} as Record<string, string>),
|
|
1345
|
+
]),
|
|
1346
|
+
)
|
|
1307
1347
|
const ctx = { ...rt, ...islandExtra, ...componentExtra }
|
|
1308
1348
|
const finalBytes = encoder.encode(JSON.stringify(ctx))
|
|
1309
1349
|
// The original size check guarded the pre-island bytes; the merged
|
|
@@ -1373,59 +1413,77 @@ export function makeRenderer(
|
|
|
1373
1413
|
// or render resolves the same per-request instance. Snapshot is collected
|
|
1374
1414
|
// after loaders (buildRenderElement resolved) — that's where Spec A stores
|
|
1375
1415
|
// are seeded — and threaded into the render for <script> injection.
|
|
1376
|
-
return await runInRequestContext(
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1416
|
+
return await runInRequestContext(
|
|
1417
|
+
call.req?.cookies ?? {},
|
|
1418
|
+
async () => {
|
|
1419
|
+
// Computed ONCE and shared by buildRenderElement (leaf swap) and
|
|
1420
|
+
// renderBranchStreaming (forceIslands) so the two can never diverge.
|
|
1421
|
+
let shellMode = wantsSsgFallbackShell(flat, call)
|
|
1422
|
+
let element: ReactNode = null
|
|
1423
|
+
// The route actually rendered + its HTTP status. Normally the matched
|
|
1424
|
+
// `flat` at the verdict status (404 when the matched route IS a
|
|
1425
|
+
// catch-all). A React loader that `throw`s `notFound()` swaps both to
|
|
1426
|
+
// the nearest catch-all at 404 below.
|
|
1427
|
+
let renderFlat = flat
|
|
1428
|
+
let renderStatus = flat.notFound === true ? 404 : verdict.status
|
|
1429
|
+
try {
|
|
1430
|
+
element = await buildRenderElement(call, flat, opts.getWorkerId, shellMode)
|
|
1431
|
+
} catch (err) {
|
|
1432
|
+
if (isHttpErrorTrigger(err)) {
|
|
1433
|
+
// React loader threw httpError() — short-circuit with the trigger's
|
|
1434
|
+
// response. NOT the errorBoundary, NOT the 500 path below: this is
|
|
1435
|
+
// a deliberate response, like a middleware short-circuit.
|
|
1436
|
+
return await emitSingleChunkResponse(
|
|
1437
|
+
slotView,
|
|
1438
|
+
napi,
|
|
1439
|
+
workerId,
|
|
1440
|
+
encoder,
|
|
1441
|
+
{
|
|
1442
|
+
status: err.status,
|
|
1443
|
+
contentType: err.contentType,
|
|
1444
|
+
body: err.body,
|
|
1445
|
+
headers: err.headers,
|
|
1446
|
+
},
|
|
1447
|
+
slot,
|
|
1448
|
+
)
|
|
1449
|
+
}
|
|
1450
|
+
if (isNotFoundTrigger(err)) {
|
|
1451
|
+
// React `notFound()` trigger: abandon the matched route and render
|
|
1452
|
+
// the NEAREST catch-all (same selection as a Rust-unmatched path)
|
|
1453
|
+
// at HTTP 404 — NOT the route's own Component, NOT a 500. Reuse the
|
|
1454
|
+
// existing 404-render machinery by re-running buildRenderElement
|
|
1455
|
+
// against the catch-all's chain.
|
|
1456
|
+
// nfId is the array index, which IS the route_id (catch-alls keep
|
|
1457
|
+
// their natural slot — see FlatRoute.notFound), so byRouteId resolves
|
|
1458
|
+
// the same flat route the Rust tier picks for an unmatched path.
|
|
1459
|
+
const nfId = selectNotFound(routes, call.path)
|
|
1460
|
+
const nfFlat = nfId !== undefined ? byRouteId.get(nfId) : undefined
|
|
1461
|
+
renderStatus = 404
|
|
1462
|
+
if (nfFlat) {
|
|
1463
|
+
renderFlat = nfFlat
|
|
1464
|
+
shellMode = wantsSsgFallbackShell(nfFlat, call)
|
|
1465
|
+
try {
|
|
1466
|
+
element = await buildRenderElement(call, nfFlat, opts.getWorkerId, shellMode)
|
|
1467
|
+
} catch (nfErr) {
|
|
1468
|
+
// The catch-all's OWN loader/render setup failed — don't loop;
|
|
1469
|
+
// fall back to the framework default 404 body at 404.
|
|
1470
|
+
console.error(`[brust] catch-all render setup failed:`, nfErr)
|
|
1471
|
+
return await emitSingleChunkResponse(
|
|
1472
|
+
slotView,
|
|
1473
|
+
napi,
|
|
1474
|
+
workerId,
|
|
1475
|
+
encoder,
|
|
1476
|
+
{
|
|
1477
|
+
status: 404,
|
|
1478
|
+
contentType: 'text/html; charset=utf-8',
|
|
1479
|
+
body: DEFAULT_NOT_FOUND_BODY,
|
|
1480
|
+
},
|
|
1481
|
+
slot,
|
|
1482
|
+
)
|
|
1483
|
+
}
|
|
1484
|
+
} else {
|
|
1485
|
+
// No catch-all registered for this prefix → framework default 404
|
|
1486
|
+
// body at status 404 (don't crash, don't 500).
|
|
1429
1487
|
return await emitSingleChunkResponse(
|
|
1430
1488
|
slotView,
|
|
1431
1489
|
napi,
|
|
@@ -1440,65 +1498,56 @@ export function makeRenderer(
|
|
|
1440
1498
|
)
|
|
1441
1499
|
}
|
|
1442
1500
|
} else {
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1501
|
+
// Setup failure BEFORE renderToPipeableStream — loader throw, params
|
|
1502
|
+
// bind throw. Shape matches the legacy "internal error" path so
|
|
1503
|
+
// existing integration tests stay green.
|
|
1504
|
+
console.error(`[brust] render setup failed:`, err)
|
|
1445
1505
|
return await emitSingleChunkResponse(
|
|
1446
1506
|
slotView,
|
|
1447
1507
|
napi,
|
|
1448
1508
|
workerId,
|
|
1449
1509
|
encoder,
|
|
1450
1510
|
{
|
|
1451
|
-
status:
|
|
1511
|
+
status: 500,
|
|
1452
1512
|
contentType: 'text/html; charset=utf-8',
|
|
1453
|
-
body:
|
|
1513
|
+
body: 'internal error',
|
|
1454
1514
|
},
|
|
1455
1515
|
slot,
|
|
1456
1516
|
)
|
|
1457
1517
|
}
|
|
1458
|
-
} else {
|
|
1459
|
-
// Setup failure BEFORE renderToPipeableStream — loader throw, params
|
|
1460
|
-
// bind throw. Shape matches the legacy "internal error" path so
|
|
1461
|
-
// existing integration tests stay green.
|
|
1462
|
-
console.error(`[brust] render setup failed:`, err)
|
|
1463
|
-
return await emitSingleChunkResponse(
|
|
1464
|
-
slotView,
|
|
1465
|
-
napi,
|
|
1466
|
-
workerId,
|
|
1467
|
-
encoder,
|
|
1468
|
-
{
|
|
1469
|
-
status: 500,
|
|
1470
|
-
contentType: 'text/html; charset=utf-8',
|
|
1471
|
-
body: 'internal error',
|
|
1472
|
-
},
|
|
1473
|
-
slot,
|
|
1474
|
-
)
|
|
1475
1518
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1519
|
+
const errorBoundary: ComponentType<{ error: Error }> =
|
|
1520
|
+
renderFlat.errorBoundary ??
|
|
1521
|
+
(({ error }) => createElement('div', null, `Internal Server Error: ${error.message}`))
|
|
1522
|
+
const storeSnapshot = collectSnapshot()
|
|
1523
|
+
await renderBranchStreaming({
|
|
1524
|
+
element,
|
|
1525
|
+
view: slotView,
|
|
1526
|
+
slot,
|
|
1527
|
+
workerId,
|
|
1528
|
+
napi,
|
|
1529
|
+
errorBoundary,
|
|
1530
|
+
// Catch-all (`path: '*'`) leaf rendered on an unmatched path OR a
|
|
1531
|
+
// React `notFound()` swap: stamp HTTP 404 (mirrors the native path).
|
|
1532
|
+
status: renderStatus,
|
|
1533
|
+
headers: flushSetCookie(verdict.headers),
|
|
1534
|
+
routePath: renderFlat.fullPath,
|
|
1535
|
+
shellId: renderFlat.shellId,
|
|
1536
|
+
storeSnapshot,
|
|
1537
|
+
// SSG fallback shells have zero islands on the page but the
|
|
1538
|
+
// client-loader runtime still needs the importmap + bootstrap.
|
|
1539
|
+
forceIslands: shellMode,
|
|
1540
|
+
})
|
|
1541
|
+
// renderBranchStreaming wrote via the chunk channel.
|
|
1542
|
+
return 0
|
|
1543
|
+
},
|
|
1544
|
+
// Seed nav with the REQUEST path (call.path), not renderFlat.fullPath: on
|
|
1545
|
+
// a React notFound() swap the catch-all renders, but the user is still at
|
|
1546
|
+
// the unmatched URL — and the client __navInit's from location.pathname
|
|
1547
|
+
// (= that URL), so both sides agree. nav = where you are, not what handled it.
|
|
1548
|
+
call.path,
|
|
1549
|
+
searchStringOf(call.req?.url),
|
|
1550
|
+
)
|
|
1502
1551
|
}
|
|
1503
1552
|
if (call.kind === 'navigation') {
|
|
1504
1553
|
await navigationBranch(call, byRouteId, routes, slotView, encoder, opts.getWorkerId, slot)
|
|
@@ -1717,23 +1766,28 @@ async function navigationBranch(
|
|
|
1717
1766
|
// document that already booted the bootstrap (the navigator IS the
|
|
1718
1767
|
// bootstrap), and the takeover runtime imports its chunk itself.
|
|
1719
1768
|
const renderFlatToHtml = (target: FlatRoute): Promise<string> =>
|
|
1720
|
-
runInRequestContext(
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1769
|
+
runInRequestContext(
|
|
1770
|
+
call.req?.cookies ?? {},
|
|
1771
|
+
async () => {
|
|
1772
|
+
const element = await buildRenderElement(
|
|
1773
|
+
call as any,
|
|
1774
|
+
target,
|
|
1775
|
+
getWorkerId,
|
|
1776
|
+
wantsSsgFallbackShell(target, call as any),
|
|
1777
|
+
)
|
|
1778
|
+
if (!element) throw new Error('render setup failed')
|
|
1779
|
+
// Use renderToPipeableStream + onAllReady so pages with <Suspense> emit
|
|
1780
|
+
// their RESOLVED markup, not the fallback. renderToString would only
|
|
1781
|
+
// capture the shell — navigating SPA-style to a Suspense-using route
|
|
1782
|
+
// would otherwise ship "loading…" and never recover.
|
|
1783
|
+
const html = await renderToAwaitedString(element)
|
|
1784
|
+
store = collectSnapshot()
|
|
1785
|
+
navHeaders = flushSetCookie(undefined)
|
|
1786
|
+
return html
|
|
1787
|
+
},
|
|
1788
|
+
call.path,
|
|
1789
|
+
searchStringOf(call.req?.url),
|
|
1790
|
+
)
|
|
1737
1791
|
try {
|
|
1738
1792
|
fullHtml = await renderFlatToHtml(flat)
|
|
1739
1793
|
} catch (err) {
|
|
@@ -1898,14 +1952,22 @@ async function renderNativeRouteToHtml(
|
|
|
1898
1952
|
const manifest = loadIslandManifest(templateName)
|
|
1899
1953
|
const compManifest = loadComponentManifest(templateName)
|
|
1900
1954
|
if ((manifest && manifest.length > 0) || (compManifest && compManifest.length > 0)) {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1955
|
+
// Seed the request path so island SSR sees the active route via useNav()
|
|
1956
|
+
// (same as the full-document native branch — this runs outside the loader
|
|
1957
|
+
// request scope opened above).
|
|
1958
|
+
const [islandExtra, componentExtra] = await runInNavContext(
|
|
1959
|
+
call.path,
|
|
1960
|
+
searchStringOf(call.req?.url),
|
|
1961
|
+
() =>
|
|
1962
|
+
Promise.all([
|
|
1963
|
+
manifest && manifest.length > 0
|
|
1964
|
+
? resolveIslandContext(manifest, ctx, islandCache)
|
|
1965
|
+
: Promise.resolve({} as Record<string, string>),
|
|
1966
|
+
compManifest && compManifest.length > 0
|
|
1967
|
+
? resolveComponentContext(compManifest, ctx, templateName, undefined, islandCache)
|
|
1968
|
+
: Promise.resolve({} as Record<string, string>),
|
|
1969
|
+
]),
|
|
1970
|
+
)
|
|
1909
1971
|
ctx = { ...ctx, ...islandExtra, ...componentExtra }
|
|
1910
1972
|
}
|
|
1911
1973
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Open a per-request nav scope so SSR — React islands AND native island SSR —
|
|
2
|
+
* sees the current request path via useNav()/getNavState() instead of the idle
|
|
3
|
+
* singleton default. `search` is the raw query string INCLUDING the leading '?'
|
|
4
|
+
* (so it matches the client's `location.search`), or '' when there is none. */
|
|
5
|
+
export declare function runInNavContext<T>(path: string, search: string, fn: () => T): T;
|
|
@@ -26,6 +26,12 @@ type ErrorCb = (e: {
|
|
|
26
26
|
error: Error;
|
|
27
27
|
}) => void;
|
|
28
28
|
export declare const nav: NavStore;
|
|
29
|
+
export interface ServerNavScope {
|
|
30
|
+
path: string;
|
|
31
|
+
search: string;
|
|
32
|
+
_snap?: NavState;
|
|
33
|
+
}
|
|
34
|
+
export declare function __setServerNavResolver(fn: () => ServerNavScope | undefined): void;
|
|
29
35
|
export declare function getNavState(): NavState;
|
|
30
36
|
export declare function subscribe(cb: NavCb): () => void;
|
|
31
37
|
export declare function onBeforeNavigate(cb: BeforeCb): () => void;
|
|
@@ -7,6 +7,13 @@ export declare function renderToAwaitedString(element: ReactNode): Promise<strin
|
|
|
7
7
|
export interface RenderFragmentOpts {
|
|
8
8
|
/** Request cookies visible to cookies()/session helpers inside the tree. */
|
|
9
9
|
cookies?: Record<string, string>;
|
|
10
|
+
/** Seed `useNav()` / `getNavState()` for islands inside the fragment. Provide
|
|
11
|
+
* when rendering for a known URL (e.g. a server-side preview of a specific
|
|
12
|
+
* route) so active-nav resolves; omit for a context-free fragment, where
|
|
13
|
+
* `useNav()` reports the idle defaults (path ''). `search` is the raw query
|
|
14
|
+
* string including the leading '?'. */
|
|
15
|
+
path?: string;
|
|
16
|
+
search?: string;
|
|
10
17
|
}
|
|
11
18
|
/** Render a component to an HTML string with framework contexts scoped:
|
|
12
19
|
* request scope (cookies) ∘ request cache (cachedFetch/dedupe) ∘ store
|