brustjs 0.1.35-alpha → 0.1.36-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/README.md +18 -13
- package/example/pokedex/lib/pokeapi.ts +45 -15
- package/package.json +7 -7
- package/runtime/index.d.ts +45 -4
- package/runtime/index.js +53 -52
- package/runtime/index.ts +51 -11
- package/runtime/islands/island.tsx +26 -11
- package/runtime/render/stream.ts +36 -18
- package/runtime/routes.ts +167 -82
package/runtime/routes.ts
CHANGED
|
@@ -612,15 +612,19 @@ export interface MakeRendererOptions {
|
|
|
612
612
|
actions?: EndpointDef[]
|
|
613
613
|
/** MCP server instance — built once per worker at module top-level. */
|
|
614
614
|
mcp?: import('./mcp/server.ts').McpServer
|
|
615
|
+
/** Render slots per worker. The `view` SAB is partitioned into this many
|
|
616
|
+
* disjoint sub-views; each render is dispatched with a `slot` index and
|
|
617
|
+
* operates on `view.subarray(slot*sub, slot*sub+sub)`. Default 1 (the whole
|
|
618
|
+
* view → byte-identical to the pre-multi-slot path). */
|
|
619
|
+
slots?: number
|
|
615
620
|
}
|
|
616
621
|
|
|
617
622
|
export function makeRenderer(
|
|
618
623
|
routes: FlatRoute[],
|
|
619
624
|
view: Uint8Array,
|
|
620
625
|
opts: MakeRendererOptions = {},
|
|
621
|
-
): (
|
|
626
|
+
): (envelopeJson: string, slot?: number) => Promise<number> {
|
|
622
627
|
const encoder = new TextEncoder()
|
|
623
|
-
const decoder = new TextDecoder()
|
|
624
628
|
const byRouteId = new Map<number, FlatRoute>()
|
|
625
629
|
routes.forEach((r, i) => {
|
|
626
630
|
byRouteId.set(i, r)
|
|
@@ -630,28 +634,41 @@ export function makeRenderer(
|
|
|
630
634
|
byActionId.set(String(i), e)
|
|
631
635
|
})
|
|
632
636
|
|
|
637
|
+
// Slot count: the SAB is partitioned into `slots` disjoint sub-views. At
|
|
638
|
+
// slots=1 the sub-view is the whole buffer (byte-identical to before).
|
|
639
|
+
const slots = Math.max(1, opts.slots ?? 1)
|
|
640
|
+
const sub = Math.floor(view.length / slots)
|
|
641
|
+
|
|
633
642
|
// napi shim for the chunk channel. The sabBytes arg is ignored by the
|
|
634
|
-
// native fn (Rust reads from the pre-registered BufPtr
|
|
635
|
-
// still pass it so renderBranchStreaming can be unit-tested
|
|
636
|
-
// mock that captures the bytes.
|
|
643
|
+
// native fn (Rust reads from the pre-registered BufPtr, offset by slot) —
|
|
644
|
+
// the call sites still pass it so renderBranchStreaming can be unit-tested
|
|
645
|
+
// against a mock that captures the bytes. The `slot` arg selects the SAB
|
|
646
|
+
// sub-region Rust reads from.
|
|
637
647
|
const napi = {
|
|
638
|
-
renderChunk: async (
|
|
639
|
-
|
|
648
|
+
renderChunk: async (
|
|
649
|
+
workerId: bigint,
|
|
650
|
+
slot: number,
|
|
651
|
+
len: number,
|
|
652
|
+
_sabBytes: Uint8Array,
|
|
653
|
+
): Promise<void> => {
|
|
654
|
+
await (native as any).napiRenderChunk(Number(workerId), slot, len)
|
|
640
655
|
},
|
|
641
656
|
renderChunkFinal: async (
|
|
642
657
|
workerId: bigint,
|
|
658
|
+
slot: number,
|
|
643
659
|
len: number,
|
|
644
660
|
_sabBytes: Uint8Array,
|
|
645
661
|
): Promise<void> => {
|
|
646
|
-
await (native as any).napiRenderChunkFinal(Number(workerId), len)
|
|
662
|
+
await (native as any).napiRenderChunkFinal(Number(workerId), slot, len)
|
|
647
663
|
},
|
|
648
664
|
}
|
|
649
665
|
|
|
650
|
-
return async (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
666
|
+
return async (envelopeJson: string, slot = 0): Promise<number> => {
|
|
667
|
+
// The disjoint SAB sub-view this render owns. At slots=1, slotView === the
|
|
668
|
+
// whole view (offset 0, full length) so the K=1 path is byte-identical.
|
|
669
|
+
// The request always arrives as an INLINE JSON string (SAB-request is closed
|
|
670
|
+
// — see the dispatch module doc); the SAB is used only for the RESPONSE.
|
|
671
|
+
const slotView = slots === 1 ? view : view.subarray(slot * sub, slot * sub + sub)
|
|
655
672
|
const call = JSON.parse(envelopeJson) as RouteCall
|
|
656
673
|
const wid = opts.getWorkerId?.() ?? 0
|
|
657
674
|
const workerId = BigInt(wid)
|
|
@@ -660,11 +677,18 @@ export function makeRenderer(
|
|
|
660
677
|
const flat = byRouteId.get(call.route_id)
|
|
661
678
|
if (!flat) {
|
|
662
679
|
console.error(`[brust] unknown route_id=${call.route_id} for path=${call.path}`)
|
|
663
|
-
await emitSingleChunkResponse(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
680
|
+
await emitSingleChunkResponse(
|
|
681
|
+
slotView,
|
|
682
|
+
napi,
|
|
683
|
+
workerId,
|
|
684
|
+
encoder,
|
|
685
|
+
{
|
|
686
|
+
status: 404,
|
|
687
|
+
contentType: 'text/plain; charset=utf-8',
|
|
688
|
+
body: 'not found',
|
|
689
|
+
},
|
|
690
|
+
slot,
|
|
691
|
+
)
|
|
668
692
|
return 0
|
|
669
693
|
}
|
|
670
694
|
// Inject the permanently-unaborted signal — non-SSE routes don't
|
|
@@ -695,7 +719,7 @@ export function makeRenderer(
|
|
|
695
719
|
// FAST LANE: single-chunk error. Works for both React (big dispatch
|
|
696
720
|
// reads the SAB via its fast-lane arm) and native routes (which take
|
|
697
721
|
// the channel-free dispatch_single_chunk).
|
|
698
|
-
return packSingleChunkResponse(
|
|
722
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
699
723
|
status: 500,
|
|
700
724
|
contentType: 'text/html; charset=utf-8',
|
|
701
725
|
body: 'internal error',
|
|
@@ -705,7 +729,7 @@ export function makeRenderer(
|
|
|
705
729
|
if (verdict._brustStream !== STREAM_MARKER) {
|
|
706
730
|
// Middleware short-circuited with a concrete response. FAST LANE —
|
|
707
731
|
// single-chunk; same dual-dispatch safety as the error path above.
|
|
708
|
-
return packSingleChunkResponse(
|
|
732
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
709
733
|
status: verdict.status,
|
|
710
734
|
contentType: verdict.contentType ?? 'text/html; charset=utf-8',
|
|
711
735
|
body: verdict.body,
|
|
@@ -750,7 +774,7 @@ export function makeRenderer(
|
|
|
750
774
|
console.error(`[brust] loader failed for native route ${flat.fullPath}:`, err)
|
|
751
775
|
// FAST LANE: native routes take dispatch_single_chunk (no chunk
|
|
752
776
|
// channel), so every native fallback MUST pack + return a length.
|
|
753
|
-
return packSingleChunkResponse(
|
|
777
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
754
778
|
status: 500,
|
|
755
779
|
contentType: 'text/html; charset=utf-8',
|
|
756
780
|
body: 'internal error',
|
|
@@ -762,7 +786,7 @@ export function makeRenderer(
|
|
|
762
786
|
const verdict = chainResult.verdict
|
|
763
787
|
if (!verdict.render) {
|
|
764
788
|
// redirect — no template render; fast-lane packed response with Location.
|
|
765
|
-
return packSingleChunkResponse(
|
|
789
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
766
790
|
status: verdict.status,
|
|
767
791
|
contentType: 'text/html; charset=utf-8',
|
|
768
792
|
body: '',
|
|
@@ -828,24 +852,25 @@ export function makeRenderer(
|
|
|
828
852
|
const finalBytes = encoder.encode(JSON.stringify(ctx))
|
|
829
853
|
// The original size check guarded the pre-island bytes; the merged
|
|
830
854
|
// context (with island props + component html) can be larger. Re-check on finalBytes.
|
|
831
|
-
if (finalBytes.length >
|
|
832
|
-
return packSingleChunkResponse(
|
|
855
|
+
if (finalBytes.length > slotView.length) {
|
|
856
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
833
857
|
status: 413,
|
|
834
858
|
contentType: 'text/plain; charset=utf-8',
|
|
835
859
|
body: 'loader data too large for SAB',
|
|
836
860
|
})
|
|
837
861
|
}
|
|
838
|
-
|
|
862
|
+
slotView.set(finalBytes, 0)
|
|
839
863
|
try {
|
|
840
864
|
return (native as any).napiRenderJinja(
|
|
841
865
|
Number(workerId),
|
|
866
|
+
slot,
|
|
842
867
|
finalBytes.length,
|
|
843
868
|
flat.nativeTemplate,
|
|
844
869
|
renderStatus,
|
|
845
870
|
)
|
|
846
871
|
} catch (err) {
|
|
847
872
|
console.error(`[brust] napiRenderJinja failed for "${flat.nativeTemplate}":`, err)
|
|
848
|
-
return packSingleChunkResponse(
|
|
873
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
849
874
|
status: 500,
|
|
850
875
|
contentType: 'text/html; charset=utf-8',
|
|
851
876
|
body: 'internal error',
|
|
@@ -853,14 +878,14 @@ export function makeRenderer(
|
|
|
853
878
|
}
|
|
854
879
|
}
|
|
855
880
|
const dataBytes = encoder.encode(json)
|
|
856
|
-
if (dataBytes.length >
|
|
857
|
-
return packSingleChunkResponse(
|
|
881
|
+
if (dataBytes.length > slotView.length) {
|
|
882
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
858
883
|
status: 413,
|
|
859
884
|
contentType: 'text/plain; charset=utf-8',
|
|
860
885
|
body: 'loader data too large for SAB',
|
|
861
886
|
})
|
|
862
887
|
}
|
|
863
|
-
|
|
888
|
+
slotView.set(dataBytes, 0)
|
|
864
889
|
try {
|
|
865
890
|
// FAST LANE: napiRenderJinja is a SYNC napi call — renders Rust-side,
|
|
866
891
|
// writes the framed response into the SAB, and returns its length
|
|
@@ -868,13 +893,14 @@ export function makeRenderer(
|
|
|
868
893
|
// fast-lane arm reads the SAB directly (no chunk channel).
|
|
869
894
|
return (native as any).napiRenderJinja(
|
|
870
895
|
Number(workerId),
|
|
896
|
+
slot,
|
|
871
897
|
dataBytes.length,
|
|
872
898
|
flat.nativeTemplate,
|
|
873
899
|
renderStatus,
|
|
874
900
|
)
|
|
875
901
|
} catch (err) {
|
|
876
902
|
console.error(`[brust] napiRenderJinja failed for "${flat.nativeTemplate}":`, err)
|
|
877
|
-
return packSingleChunkResponse(
|
|
903
|
+
return packSingleChunkResponse(slotView, encoder, {
|
|
878
904
|
status: 500,
|
|
879
905
|
contentType: 'text/html; charset=utf-8',
|
|
880
906
|
body: 'internal error',
|
|
@@ -900,16 +926,24 @@ export function makeRenderer(
|
|
|
900
926
|
// bind throw. Shape matches the legacy "internal error" path so
|
|
901
927
|
// existing integration tests stay green.
|
|
902
928
|
console.error(`[brust] render setup failed:`, err)
|
|
903
|
-
return await emitSingleChunkResponse(
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
929
|
+
return await emitSingleChunkResponse(
|
|
930
|
+
slotView,
|
|
931
|
+
napi,
|
|
932
|
+
workerId,
|
|
933
|
+
encoder,
|
|
934
|
+
{
|
|
935
|
+
status: 500,
|
|
936
|
+
contentType: 'text/html; charset=utf-8',
|
|
937
|
+
body: 'internal error',
|
|
938
|
+
},
|
|
939
|
+
slot,
|
|
940
|
+
)
|
|
908
941
|
}
|
|
909
942
|
const storeSnapshot = collectSnapshot()
|
|
910
943
|
await renderBranchStreaming({
|
|
911
944
|
element,
|
|
912
|
-
view,
|
|
945
|
+
view: slotView,
|
|
946
|
+
slot,
|
|
913
947
|
workerId,
|
|
914
948
|
napi,
|
|
915
949
|
errorBoundary,
|
|
@@ -923,22 +957,22 @@ export function makeRenderer(
|
|
|
923
957
|
})
|
|
924
958
|
}
|
|
925
959
|
if (call.kind === 'navigation') {
|
|
926
|
-
await navigationBranch(call, byRouteId,
|
|
960
|
+
await navigationBranch(call, byRouteId, slotView, encoder, opts.getWorkerId, slot)
|
|
927
961
|
return 0
|
|
928
962
|
}
|
|
929
963
|
if (call.kind === 'action') {
|
|
930
964
|
// FAST LANE: pack the framed response into the SAB and return its length.
|
|
931
965
|
// Rust reads it directly after the Promise settles — no chunk channel.
|
|
932
966
|
const resp = await dispatchAction(call, byActionId)
|
|
933
|
-
return packSingleChunkResponse(
|
|
967
|
+
return packSingleChunkResponse(slotView, encoder, resp)
|
|
934
968
|
}
|
|
935
969
|
if (call.kind === 'mcp') {
|
|
936
970
|
const resp = await mcpBranchToResponse(call, opts.mcp)
|
|
937
|
-
return await emitSingleChunkResponse(
|
|
971
|
+
return await emitSingleChunkResponse(slotView, napi, workerId, encoder, resp, slot)
|
|
938
972
|
}
|
|
939
973
|
if (call.kind === 'sse') {
|
|
940
974
|
try {
|
|
941
|
-
await sseBranch(call,
|
|
975
|
+
await sseBranch(call, slotView, encoder, routes)
|
|
942
976
|
} catch (err) {
|
|
943
977
|
// Setup-time errors only (BigInt coerce, dynamic import resolve,
|
|
944
978
|
// napi shim build). Once handleSseStream has started streaming,
|
|
@@ -951,7 +985,7 @@ export function makeRenderer(
|
|
|
951
985
|
}
|
|
952
986
|
if (call.kind === 'ws') {
|
|
953
987
|
try {
|
|
954
|
-
await wsBranch(call,
|
|
988
|
+
await wsBranch(call, slotView, encoder, routes)
|
|
955
989
|
} catch (err) {
|
|
956
990
|
// Setup-time errors only — same reasoning as sseBranch above.
|
|
957
991
|
console.error('[brust] wsBranch uncaught:', err)
|
|
@@ -963,11 +997,18 @@ export function makeRenderer(
|
|
|
963
997
|
// Unknown kind — log and 500. Shouldn't happen unless Rust ships
|
|
964
998
|
// something out of band.
|
|
965
999
|
console.error(`[brust] unknown envelope kind in worker:`, (call as { kind?: string }).kind)
|
|
966
|
-
return await emitSingleChunkResponse(
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1000
|
+
return await emitSingleChunkResponse(
|
|
1001
|
+
slotView,
|
|
1002
|
+
napi,
|
|
1003
|
+
workerId,
|
|
1004
|
+
encoder,
|
|
1005
|
+
{
|
|
1006
|
+
status: 500,
|
|
1007
|
+
contentType: 'text/plain; charset=utf-8',
|
|
1008
|
+
body: 'invalid envelope kind',
|
|
1009
|
+
},
|
|
1010
|
+
slot,
|
|
1011
|
+
)
|
|
971
1012
|
}
|
|
972
1013
|
}
|
|
973
1014
|
|
|
@@ -1013,24 +1054,37 @@ async function navigationBranch(
|
|
|
1013
1054
|
view: Uint8Array,
|
|
1014
1055
|
encoder: TextEncoder,
|
|
1015
1056
|
getWorkerId: (() => number | null) | undefined,
|
|
1057
|
+
slot = 0,
|
|
1016
1058
|
): Promise<void> {
|
|
1017
1059
|
const workerId = BigInt(getWorkerId?.() ?? 0)
|
|
1018
1060
|
const napi = {
|
|
1019
|
-
renderChunk: async (wid: bigint, len: number, _view: Uint8Array): Promise<void> => {
|
|
1020
|
-
await (native as any).napiRenderChunk(Number(wid), len)
|
|
1061
|
+
renderChunk: async (wid: bigint, s: number, len: number, _view: Uint8Array): Promise<void> => {
|
|
1062
|
+
await (native as any).napiRenderChunk(Number(wid), s, len)
|
|
1021
1063
|
},
|
|
1022
|
-
renderChunkFinal: async (
|
|
1023
|
-
|
|
1064
|
+
renderChunkFinal: async (
|
|
1065
|
+
wid: bigint,
|
|
1066
|
+
s: number,
|
|
1067
|
+
len: number,
|
|
1068
|
+
_view: Uint8Array,
|
|
1069
|
+
): Promise<void> => {
|
|
1070
|
+
await (native as any).napiRenderChunkFinal(Number(wid), s, len)
|
|
1024
1071
|
},
|
|
1025
1072
|
}
|
|
1026
1073
|
|
|
1027
1074
|
const flat = byRouteId.get(call.route_id)
|
|
1028
1075
|
if (!flat) {
|
|
1029
|
-
await emitSingleChunkResponse(
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1076
|
+
await emitSingleChunkResponse(
|
|
1077
|
+
view,
|
|
1078
|
+
napi,
|
|
1079
|
+
workerId,
|
|
1080
|
+
encoder,
|
|
1081
|
+
{
|
|
1082
|
+
status: 404,
|
|
1083
|
+
contentType: 'application/json; charset=utf-8',
|
|
1084
|
+
body: '{"error":"not found"}',
|
|
1085
|
+
},
|
|
1086
|
+
slot,
|
|
1087
|
+
)
|
|
1034
1088
|
return
|
|
1035
1089
|
}
|
|
1036
1090
|
|
|
@@ -1059,11 +1113,18 @@ async function navigationBranch(
|
|
|
1059
1113
|
navVerdict = (await navChain()) as NavMarkerResponse
|
|
1060
1114
|
} catch (err) {
|
|
1061
1115
|
console.error('[brust] navigation middleware threw:', err)
|
|
1062
|
-
await emitSingleChunkResponse(
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1116
|
+
await emitSingleChunkResponse(
|
|
1117
|
+
view,
|
|
1118
|
+
napi,
|
|
1119
|
+
workerId,
|
|
1120
|
+
encoder,
|
|
1121
|
+
{
|
|
1122
|
+
status: 500,
|
|
1123
|
+
contentType: 'application/json; charset=utf-8',
|
|
1124
|
+
body: '{"error":"middleware threw"}',
|
|
1125
|
+
},
|
|
1126
|
+
slot,
|
|
1127
|
+
)
|
|
1067
1128
|
return
|
|
1068
1129
|
}
|
|
1069
1130
|
|
|
@@ -1071,12 +1132,19 @@ async function navigationBranch(
|
|
|
1071
1132
|
// Middleware short-circuited — emit the verdict. Client's non-2xx check
|
|
1072
1133
|
// triggers the full-reload fallback so the user hits the real route and
|
|
1073
1134
|
// sees the middleware's challenge page.
|
|
1074
|
-
await emitSingleChunkResponse(
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1135
|
+
await emitSingleChunkResponse(
|
|
1136
|
+
view,
|
|
1137
|
+
napi,
|
|
1138
|
+
workerId,
|
|
1139
|
+
encoder,
|
|
1140
|
+
{
|
|
1141
|
+
status: navVerdict.status,
|
|
1142
|
+
contentType: navVerdict.contentType ?? 'application/json; charset=utf-8',
|
|
1143
|
+
body: navVerdict.body,
|
|
1144
|
+
headers: navVerdict.headers,
|
|
1145
|
+
},
|
|
1146
|
+
slot,
|
|
1147
|
+
)
|
|
1080
1148
|
return
|
|
1081
1149
|
}
|
|
1082
1150
|
|
|
@@ -1100,7 +1168,7 @@ async function navigationBranch(
|
|
|
1100
1168
|
// full-document native render path).
|
|
1101
1169
|
let navHeaders: Record<string, string> | undefined
|
|
1102
1170
|
if (flat.nativeTemplate !== undefined) {
|
|
1103
|
-
fullHtml = await renderNativeRouteToHtml(call, flat, view, encoder, workerId)
|
|
1171
|
+
fullHtml = await renderNativeRouteToHtml(call, flat, view, encoder, workerId, slot)
|
|
1104
1172
|
} else {
|
|
1105
1173
|
// Wrap loader run (inside buildRenderElement) + render in one store scope so
|
|
1106
1174
|
// store reads resolve the per-request instance; collect after render.
|
|
@@ -1136,19 +1204,33 @@ async function navigationBranch(
|
|
|
1136
1204
|
const title = titleMatch ? titleMatch[1].replace(/<!--.*?-->/g, '').trim() : ''
|
|
1137
1205
|
|
|
1138
1206
|
const body = JSON.stringify({ html: innerHtml, title, store })
|
|
1139
|
-
await emitSingleChunkResponse(
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1207
|
+
await emitSingleChunkResponse(
|
|
1208
|
+
view,
|
|
1209
|
+
napi,
|
|
1210
|
+
workerId,
|
|
1211
|
+
encoder,
|
|
1212
|
+
{
|
|
1213
|
+
status: 200,
|
|
1214
|
+
contentType: 'application/json; charset=utf-8',
|
|
1215
|
+
body,
|
|
1216
|
+
headers: navHeaders,
|
|
1217
|
+
},
|
|
1218
|
+
slot,
|
|
1219
|
+
)
|
|
1145
1220
|
} catch (err) {
|
|
1146
1221
|
console.error('[brust] navigation render failed:', err)
|
|
1147
|
-
await emitSingleChunkResponse(
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1222
|
+
await emitSingleChunkResponse(
|
|
1223
|
+
view,
|
|
1224
|
+
napi,
|
|
1225
|
+
workerId,
|
|
1226
|
+
encoder,
|
|
1227
|
+
{
|
|
1228
|
+
status: 500,
|
|
1229
|
+
contentType: 'application/json; charset=utf-8',
|
|
1230
|
+
body: '{"error":"render failed"}',
|
|
1231
|
+
},
|
|
1232
|
+
slot,
|
|
1233
|
+
)
|
|
1152
1234
|
}
|
|
1153
1235
|
}
|
|
1154
1236
|
|
|
@@ -1170,6 +1252,7 @@ async function renderNativeRouteToHtml(
|
|
|
1170
1252
|
view: Uint8Array,
|
|
1171
1253
|
encoder: TextEncoder,
|
|
1172
1254
|
workerId: bigint,
|
|
1255
|
+
slot = 0,
|
|
1173
1256
|
): Promise<string> {
|
|
1174
1257
|
const templateName = flat.nativeTemplate as string
|
|
1175
1258
|
|
|
@@ -1211,6 +1294,7 @@ async function renderNativeRouteToHtml(
|
|
|
1211
1294
|
|
|
1212
1295
|
const len = (native as any).napiRenderJinja(
|
|
1213
1296
|
Number(workerId),
|
|
1297
|
+
slot,
|
|
1214
1298
|
bytes.length,
|
|
1215
1299
|
templateName,
|
|
1216
1300
|
) as number
|
|
@@ -1331,8 +1415,8 @@ function packSingleChunkResponse(
|
|
|
1331
1415
|
async function emitSingleChunkResponse(
|
|
1332
1416
|
view: Uint8Array,
|
|
1333
1417
|
napi: {
|
|
1334
|
-
renderChunk: (w: bigint, len: number, view: Uint8Array) => Promise<void>
|
|
1335
|
-
renderChunkFinal: (w: bigint, len: number, view: Uint8Array) => Promise<void>
|
|
1418
|
+
renderChunk: (w: bigint, slot: number, len: number, view: Uint8Array) => Promise<void>
|
|
1419
|
+
renderChunkFinal: (w: bigint, slot: number, len: number, view: Uint8Array) => Promise<void>
|
|
1336
1420
|
},
|
|
1337
1421
|
workerId: bigint,
|
|
1338
1422
|
encoder: TextEncoder,
|
|
@@ -1342,6 +1426,7 @@ async function emitSingleChunkResponse(
|
|
|
1342
1426
|
body: string | Uint8Array
|
|
1343
1427
|
headers?: Record<string, string>
|
|
1344
1428
|
},
|
|
1429
|
+
slot = 0,
|
|
1345
1430
|
): Promise<number> {
|
|
1346
1431
|
const bodyBytes = typeof resp.body === 'string' ? encoder.encode(resp.body) : resp.body
|
|
1347
1432
|
const meta = JSON.stringify({
|
|
@@ -1372,14 +1457,14 @@ async function emitSingleChunkResponse(
|
|
|
1372
1457
|
view[1] = errMetaBytes.length & 0xff
|
|
1373
1458
|
view.set(errMetaBytes, 2)
|
|
1374
1459
|
view.set(errBody, 2 + errMetaBytes.length)
|
|
1375
|
-
await napi.renderChunkFinal(workerId, errTotal, view)
|
|
1460
|
+
await napi.renderChunkFinal(workerId, slot, errTotal, view)
|
|
1376
1461
|
return 0
|
|
1377
1462
|
}
|
|
1378
1463
|
view[0] = (metaBytes.length >> 8) & 0xff
|
|
1379
1464
|
view[1] = metaBytes.length & 0xff
|
|
1380
1465
|
view.set(metaBytes, 2)
|
|
1381
1466
|
view.set(bodyBytes, 2 + metaBytes.length)
|
|
1382
|
-
await napi.renderChunkFinal(workerId, total, view)
|
|
1467
|
+
await napi.renderChunkFinal(workerId, slot, total, view)
|
|
1383
1468
|
return 0
|
|
1384
1469
|
}
|
|
1385
1470
|
|