@abide/abide 0.103.0 → 0.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +27 -27
- package/docs/RULEBOOK.md +68 -24
- package/docs/abide.md +31 -46
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +46 -89
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -25
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +12 -10
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +183 -478
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/projectShadow.ts +1 -1
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +152 -146
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +59 -73
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/generatedSurfaces.ts +12 -53
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/versionOf.ts +1 -2
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +6 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +69 -124
- package/src/shared/reactive/Reactive.ts +296 -162
- package/src/shared/reactive/callLoad.ts +4 -18
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -110
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/reconcile.ts +4 -110
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -57
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// built per request, since a trigger names args rather than a value. A `global` value is also fed its productions, a guarded one only to a
|
|
11
11
|
// subscription its rungs admitted when it opened (18.8) — they run once, in the connection's request.
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { problemOf } from '#shared/Failed.ts'
|
|
14
14
|
import { GLOBAL_GETS } from '#shared/GLOBAL_GETS.ts'
|
|
15
15
|
import { namedLogger } from '#shared/log.ts'
|
|
16
16
|
import { compose } from '#shared/Middleware.ts'
|
|
@@ -34,19 +34,103 @@ import {
|
|
|
34
34
|
releaseScope,
|
|
35
35
|
scopeFor,
|
|
36
36
|
} from './requestScope.ts'
|
|
37
|
+
import { EVENT_STREAM_OPENER, streamEvent } from './streamEvent.ts'
|
|
37
38
|
import { patchesSince, versionOf } from './versionOf.ts'
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* The far end of one connection to the mux, which its frames are written to.
|
|
41
|
+
* The far end of one connection to the mux, which its frames are written to as text.
|
|
41
42
|
*
|
|
42
43
|
* Bun's socket under `abide start`, and under `abide dev` a stand-in that posts each frame to the
|
|
43
44
|
* listener holding the real one (38.14).
|
|
44
45
|
*/
|
|
45
|
-
export type
|
|
46
|
+
export type Socket = {
|
|
46
47
|
send(text: string): unknown
|
|
47
48
|
close(code: number, reason: string): unknown
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
const ENCODER = new TextEncoder()
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* One `ServerFrame`, for every follower it is sent to.
|
|
55
|
+
*
|
|
56
|
+
* 18.6 — ITS BODY IS SERIALIZED ONCE PER VERSION, and each encoding of it once however many
|
|
57
|
+
* followers it reaches: a socket is sent the JSON with its follower's id spliced in, and an event
|
|
58
|
+
* stream the event, which names no follower and is the same text for every stream.
|
|
59
|
+
*/
|
|
60
|
+
class Frame {
|
|
61
|
+
readonly op: ServerFrame['op']
|
|
62
|
+
readonly base: number | undefined
|
|
63
|
+
readonly version: number | undefined
|
|
64
|
+
// The value, the patch's ops or the refusal, as JSON.
|
|
65
|
+
readonly body: string | undefined
|
|
66
|
+
// What follows the id in the JSON, and the event's bytes, each written on first use.
|
|
67
|
+
private tail: string | undefined = undefined
|
|
68
|
+
private event: Uint8Array | undefined = undefined
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
op: ServerFrame['op'],
|
|
72
|
+
base: number | undefined,
|
|
73
|
+
version: number | undefined,
|
|
74
|
+
body: string | undefined,
|
|
75
|
+
) {
|
|
76
|
+
this.op = op
|
|
77
|
+
this.base = base
|
|
78
|
+
this.version = version
|
|
79
|
+
this.body = body
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** As `ServerFrame` JSON for `id`: `base` and `version` where it has them, then the body under its op's member. */
|
|
83
|
+
json(id: number): string {
|
|
84
|
+
if (this.tail === undefined) {
|
|
85
|
+
let tail = ''
|
|
86
|
+
if (this.base !== undefined) tail += `,"base":${this.base}`
|
|
87
|
+
if (this.version !== undefined) tail += `,"version":${this.version}`
|
|
88
|
+
if (this.body !== undefined)
|
|
89
|
+
tail += `,"${this.op === 'patch' ? 'ops' : this.op}":${this.body}`
|
|
90
|
+
this.tail = `${tail}}`
|
|
91
|
+
}
|
|
92
|
+
return `{"op":"${this.op}","id":${id}${this.tail}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** As the SSE event 18.20 makes it, encoded: the op its name, the version its id. */
|
|
96
|
+
asEvent(): Uint8Array {
|
|
97
|
+
this.event ??= ENCODER.encode(
|
|
98
|
+
streamEvent(
|
|
99
|
+
this.op,
|
|
100
|
+
this.version,
|
|
101
|
+
this.body ?? (this.op === 'value' ? 'null' : ''),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
return this.event
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A trigger's frame (7.10, 7.18), the same for every follower and every trigger. */
|
|
109
|
+
const TRIGGERS = {
|
|
110
|
+
invalidate: new Frame('invalidate', undefined, undefined, undefined),
|
|
111
|
+
refresh: new Frame('refresh', undefined, undefined, undefined),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** What a frame is written to, under the follower's id: a socket, or an event stream. */
|
|
115
|
+
type Peer = {
|
|
116
|
+
send(id: number, frame: Frame): void
|
|
117
|
+
close(code: number, reason: string): unknown
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A socket's peer: each frame as the `ServerFrame` JSON a browser parses. */
|
|
121
|
+
class SocketPeer implements Peer {
|
|
122
|
+
readonly socket: Socket
|
|
123
|
+
constructor(socket: Socket) {
|
|
124
|
+
this.socket = socket
|
|
125
|
+
}
|
|
126
|
+
send(id: number, frame: Frame): void {
|
|
127
|
+
this.socket.send(frame.json(id))
|
|
128
|
+
}
|
|
129
|
+
close(code: number, reason: string): unknown {
|
|
130
|
+
return this.socket.close(code, reason)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
50
134
|
const SOCKET_LOG = namedLogger('abide:socket')
|
|
51
135
|
|
|
52
136
|
// 18.3 — EVERY SUBSCRIPTION TO A HANDLER, by handler, for its triggers to reach (7.10, 7.18).
|
|
@@ -95,7 +179,7 @@ export function broadcast(
|
|
|
95
179
|
// the cell it is fed from (`FOLLOWED`), whichever trigger reached it — where the entry it is
|
|
96
180
|
// fed from is still the one standing.
|
|
97
181
|
if (attach(rpc as Mounted['handler'], follower) !== undefined) continue
|
|
98
|
-
send(follower.
|
|
182
|
+
follower.peer.send(follower.id, TRIGGERS[member])
|
|
99
183
|
}
|
|
100
184
|
}
|
|
101
185
|
|
|
@@ -111,7 +195,7 @@ export function swept(
|
|
|
111
195
|
for (const follower of followed.all) {
|
|
112
196
|
if (attach(rpc as Mounted['handler'], follower) !== undefined)
|
|
113
197
|
continue
|
|
114
|
-
send(follower.
|
|
198
|
+
follower.peer.send(follower.id, TRIGGERS[member])
|
|
115
199
|
}
|
|
116
200
|
}
|
|
117
201
|
}
|
|
@@ -157,12 +241,14 @@ function attach(rpc: Mounted['handler'], follower: Follower): unknown {
|
|
|
157
241
|
return serving
|
|
158
242
|
}
|
|
159
243
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
244
|
+
/** A refusal's frame: the failure `error` is, as Problem Details (15.21). */
|
|
245
|
+
function refusal(error: unknown): Frame {
|
|
246
|
+
return new Frame(
|
|
247
|
+
'error',
|
|
248
|
+
undefined,
|
|
249
|
+
undefined,
|
|
250
|
+
JSON.stringify(problemOf(failureOf(error))),
|
|
251
|
+
)
|
|
166
252
|
}
|
|
167
253
|
|
|
168
254
|
/**
|
|
@@ -180,11 +266,15 @@ export class Mux {
|
|
|
180
266
|
this.app = app
|
|
181
267
|
}
|
|
182
268
|
|
|
183
|
-
/** One connection, upgraded from `request`, whose frames go to `
|
|
184
|
-
connect(request: Request,
|
|
269
|
+
/** One connection, upgraded from `request`, whose frames go to `socket`. */
|
|
270
|
+
connect(request: Request, socket: Socket): MuxConnection {
|
|
185
271
|
if (SOCKET_LOG.enabled())
|
|
186
272
|
SOCKET_LOG.debug('open', new URL(request.url).pathname)
|
|
187
|
-
const connection = new MuxConnection(
|
|
273
|
+
const connection = new MuxConnection(
|
|
274
|
+
this,
|
|
275
|
+
request,
|
|
276
|
+
new SocketPeer(socket),
|
|
277
|
+
)
|
|
188
278
|
this.connections.add(connection)
|
|
189
279
|
return connection
|
|
190
280
|
}
|
|
@@ -267,26 +357,24 @@ export class MuxConnection {
|
|
|
267
357
|
: undefined
|
|
268
358
|
const followed = handler?.ui ? handler : undefined
|
|
269
359
|
if (followed === undefined) {
|
|
270
|
-
|
|
271
|
-
op: 'error',
|
|
360
|
+
this.peer.send(
|
|
272
361
|
id,
|
|
273
|
-
|
|
362
|
+
refusal(
|
|
274
363
|
refuse(
|
|
275
364
|
404,
|
|
276
365
|
`No handler a browser follows answers at ${address}.`,
|
|
277
366
|
),
|
|
278
367
|
),
|
|
279
|
-
|
|
368
|
+
)
|
|
280
369
|
return
|
|
281
370
|
}
|
|
282
371
|
// 18.2 — ANOTHER ORIGIN IS CLOSED OUT unless the handler declared it: the same gate, and the
|
|
283
372
|
// same own origin, as its http answer's (16.13, 16.24, 36.2).
|
|
284
373
|
if (closedTo(followed.handler.crossOrigin, this.crossing)) {
|
|
285
|
-
|
|
286
|
-
op: 'error',
|
|
374
|
+
this.peer.send(
|
|
287
375
|
id,
|
|
288
|
-
|
|
289
|
-
|
|
376
|
+
refusal(crossingRefusal(this.crossing, 'subscription')),
|
|
377
|
+
)
|
|
290
378
|
return
|
|
291
379
|
}
|
|
292
380
|
// A SUBSCRIPTION IS HELD FROM THE FRAME ON, not from when its rungs let it through: an
|
|
@@ -322,7 +410,7 @@ export class MuxConnection {
|
|
|
322
410
|
follow(this, id, followed, args, frame.version, admitted)
|
|
323
411
|
} catch (thrown) {
|
|
324
412
|
if (subscriptions.get(id) === pending) subscriptions.delete(id)
|
|
325
|
-
|
|
413
|
+
this.peer.send(id, refusal(thrown))
|
|
326
414
|
}
|
|
327
415
|
}
|
|
328
416
|
}
|
|
@@ -444,14 +532,12 @@ export function followAsEvents(
|
|
|
444
532
|
args: unknown,
|
|
445
533
|
held: number | undefined,
|
|
446
534
|
): ReadableStream<Uint8Array> {
|
|
447
|
-
const encoder = new TextEncoder()
|
|
448
535
|
let stop = (): void => {}
|
|
449
536
|
let unhold = (): void => {}
|
|
450
537
|
let ended = false
|
|
451
538
|
return new ReadableStream({
|
|
452
539
|
start(controller) {
|
|
453
|
-
|
|
454
|
-
controller.enqueue(encoder.encode(':\n\n'))
|
|
540
|
+
controller.enqueue(ENCODER.encode(EVENT_STREAM_OPENER))
|
|
455
541
|
// Ended by the app's stop as any body it holds open is, or that stop waits on it.
|
|
456
542
|
unhold = heldByApp(() => {
|
|
457
543
|
if (ended) return
|
|
@@ -461,8 +547,10 @@ export function followAsEvents(
|
|
|
461
547
|
})
|
|
462
548
|
stop = followWith(
|
|
463
549
|
{
|
|
464
|
-
|
|
465
|
-
|
|
550
|
+
// 18.6 — the event written and encoded once for every stream, from the body
|
|
551
|
+
// serialized once for every follower: nothing is parsed back.
|
|
552
|
+
send(_id, frame) {
|
|
553
|
+
controller.enqueue(frame.asEvent())
|
|
466
554
|
},
|
|
467
555
|
close() {
|
|
468
556
|
if (ended) return
|
|
@@ -487,37 +575,6 @@ export function followAsEvents(
|
|
|
487
575
|
})
|
|
488
576
|
}
|
|
489
577
|
|
|
490
|
-
/**
|
|
491
|
-
* One mux frame as the SSE event it is: `event`, `id` where it names a version, and `data`.
|
|
492
|
-
*
|
|
493
|
-
* 18.6 — READ OFF THE FRAME'S HEAD, never parsed: the body was serialized once for every follower
|
|
494
|
-
* (`sendAll`), and a parse and a stringify per event stream would pay it again per follower. Every
|
|
495
|
-
* frame this module writes is `{"op":…,"id":…`, numbers named `base` or `version`, and at most one
|
|
496
|
-
* body last — so the body is the rest of the text, as it was written.
|
|
497
|
-
*/
|
|
498
|
-
function eventOf(text: string): string {
|
|
499
|
-
const opEnd = text.indexOf('"', 7)
|
|
500
|
-
const op = text.slice(7, opEnd)
|
|
501
|
-
let id = ''
|
|
502
|
-
let data =
|
|
503
|
-
op === 'value' || op === 'error' ? 'null' : op === 'patch' ? '[]' : ''
|
|
504
|
-
// Past `,"id":` — a number, so the next comma is the next member's.
|
|
505
|
-
let at = text.indexOf(',', opEnd + 6)
|
|
506
|
-
while (at !== -1) {
|
|
507
|
-
const nameEnd = text.indexOf('"', at + 2)
|
|
508
|
-
const name = text.slice(at + 2, nameEnd)
|
|
509
|
-
const from = nameEnd + 2
|
|
510
|
-
if (name !== 'base' && name !== 'version') {
|
|
511
|
-
data = text.slice(from, -1)
|
|
512
|
-
break
|
|
513
|
-
}
|
|
514
|
-
at = text.indexOf(',', from)
|
|
515
|
-
if (name === 'version')
|
|
516
|
-
id = `id: ${text.slice(from, at === -1 ? -1 : at)}\n`
|
|
517
|
-
}
|
|
518
|
-
return `event: ${op}\n${id}data: ${data}\n\n`
|
|
519
|
-
}
|
|
520
|
-
|
|
521
578
|
// 18.6 — ONE WATCH PER VALUE SERVED, however many follow it, and each frame's body serialized once
|
|
522
579
|
// per version: every follower standing where the others stand is sent the same bytes, with its own
|
|
523
580
|
// id spliced in front.
|
|
@@ -544,7 +601,7 @@ function startFeed(serving: unknown): Feed {
|
|
|
544
601
|
const fed = new Set<Follower>()
|
|
545
602
|
// 7.10 — an invalidate of the value, however it was spelled, reaches every copy it feeds.
|
|
546
603
|
const invalidated = (): void => {
|
|
547
|
-
for (const one of fed) send(one.
|
|
604
|
+
for (const one of fed) one.peer.send(one.id, TRIGGERS.invalidate)
|
|
548
605
|
}
|
|
549
606
|
let followed = FOLLOWED.get(past)
|
|
550
607
|
if (followed === undefined) {
|
|
@@ -577,33 +634,36 @@ function startFeed(serving: unknown): Feed {
|
|
|
577
634
|
|
|
578
635
|
function sendAll(serving: unknown, fed: Iterable<Follower>): void {
|
|
579
636
|
const version = versionOf(serving)
|
|
580
|
-
let
|
|
581
|
-
// ONE
|
|
637
|
+
let valueFrame: Frame | undefined
|
|
638
|
+
// ONE FRAME PER VERSION A FOLLOWER STANDS AT, since followers a burst left at different places
|
|
582
639
|
// along the chain are each sent the writes since their own.
|
|
583
|
-
let
|
|
640
|
+
let patchFrames: Map<number, Frame> | undefined
|
|
584
641
|
for (const one of fed) {
|
|
585
642
|
if (one.sent === version) continue
|
|
586
643
|
// 18.11 — the writes, where the follower stands on the chain they were written along.
|
|
587
644
|
const since = one.sent
|
|
588
|
-
let
|
|
589
|
-
if (
|
|
645
|
+
let frame = since === undefined ? undefined : patchFrames?.get(since)
|
|
646
|
+
if (frame === undefined && since !== undefined) {
|
|
590
647
|
const ops = patchesSince(serving, since)
|
|
591
648
|
if (ops !== undefined) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
649
|
+
frame = new Frame('patch', since, version, JSON.stringify(ops))
|
|
650
|
+
patchFrames ??= new Map()
|
|
651
|
+
patchFrames.set(since, frame)
|
|
595
652
|
}
|
|
596
653
|
}
|
|
597
|
-
if (
|
|
598
|
-
if (
|
|
654
|
+
if (frame === undefined) {
|
|
655
|
+
if (valueFrame === undefined) {
|
|
599
656
|
const held = (serving as { peek(): unknown }).peek()
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
657
|
+
valueFrame = new Frame(
|
|
658
|
+
'value',
|
|
659
|
+
undefined,
|
|
660
|
+
version,
|
|
661
|
+
held === undefined ? undefined : JSON.stringify(held),
|
|
662
|
+
)
|
|
604
663
|
}
|
|
605
|
-
|
|
606
|
-
}
|
|
664
|
+
frame = valueFrame
|
|
665
|
+
}
|
|
666
|
+
one.peer.send(one.id, frame)
|
|
607
667
|
one.sent = version
|
|
608
668
|
}
|
|
609
669
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// `Values` a handler returned — framed one chunk per pull (16.32, 16.44–16.47).
|
|
5
5
|
|
|
6
6
|
import { isFailed, problemOf } from '#shared/Failed.ts'
|
|
7
|
-
import { refusalFrame } from '#shared/framing.ts'
|
|
8
7
|
import { compressAnswer } from './compressAnswer.ts'
|
|
9
8
|
import { prefers } from './prefers.ts'
|
|
10
9
|
import {
|
|
@@ -13,10 +12,13 @@ import {
|
|
|
13
12
|
heldByApp,
|
|
14
13
|
holdScope,
|
|
15
14
|
} from './requestScope.ts'
|
|
15
|
+
import {
|
|
16
|
+
EVENT_STREAM_OPENER,
|
|
17
|
+
refusalFrame,
|
|
18
|
+
streamEvent,
|
|
19
|
+
} from './streamEvent.ts'
|
|
16
20
|
|
|
17
21
|
const ENCODER = new TextEncoder()
|
|
18
|
-
// AN EVENT STREAM OPENS ON A COMMENT — `bodyOf` says why.
|
|
19
|
-
const SSE_OPENER = ':\n\n'
|
|
20
22
|
|
|
21
23
|
function isValues(
|
|
22
24
|
value: unknown,
|
|
@@ -70,6 +72,8 @@ export async function answerValue(
|
|
|
70
72
|
headers.set('content-type', 'text/html; charset=utf-8')
|
|
71
73
|
if (typeof value === 'string' || value instanceof Uint8Array)
|
|
72
74
|
return new Response(value as BodyInit, { status, headers })
|
|
75
|
+
// 16.75.
|
|
76
|
+
headers.set('x-accel-buffering', 'no')
|
|
73
77
|
return new Response(bodyOf(chunksOf(value), 'bytes'), {
|
|
74
78
|
status,
|
|
75
79
|
headers,
|
|
@@ -96,11 +100,11 @@ function isIterable(value: unknown): value is Iterable<unknown> {
|
|
|
96
100
|
|
|
97
101
|
/**
|
|
98
102
|
* The head of an event stream. 16.45 — never stored, whatever the value's own life said (21.8), and
|
|
99
|
-
* never buffered by a proxy in front.
|
|
103
|
+
* 16.75 — never buffered by a proxy in front.
|
|
100
104
|
*/
|
|
101
105
|
export function sseHeaders(headers: Headers): void {
|
|
102
106
|
headers.set('content-type', 'text/event-stream')
|
|
103
|
-
headers.set('cache-control', 'no-
|
|
107
|
+
headers.set('cache-control', 'private, no-store')
|
|
104
108
|
headers.set('x-accel-buffering', 'no')
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -169,10 +173,16 @@ export function framed(
|
|
|
169
173
|
first?: IteratorResult<unknown>,
|
|
170
174
|
lines = 'application/jsonl',
|
|
171
175
|
): Response {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
else
|
|
176
|
+
// 16.75 — whatever the framing: a proxy holding frames back until the end undoes one per pull.
|
|
177
|
+
// An event stream's head says so itself.
|
|
178
|
+
if (framing === 'sse') sseHeaders(headers)
|
|
179
|
+
else {
|
|
180
|
+
headers.set(
|
|
181
|
+
'content-type',
|
|
182
|
+
framing === 'bytes' ? 'application/octet-stream' : lines,
|
|
183
|
+
)
|
|
184
|
+
headers.set('x-accel-buffering', 'no')
|
|
185
|
+
}
|
|
176
186
|
return new Response(bodyOf(chunks, framing, first), {
|
|
177
187
|
status,
|
|
178
188
|
headers,
|
|
@@ -214,7 +224,7 @@ export function bodyOf(
|
|
|
214
224
|
return new ReadableStream<Uint8Array>({
|
|
215
225
|
start(controller) {
|
|
216
226
|
if (framing === 'sse')
|
|
217
|
-
controller.enqueue(ENCODER.encode(
|
|
227
|
+
controller.enqueue(ENCODER.encode(EVENT_STREAM_OPENER))
|
|
218
228
|
stopped = () => {
|
|
219
229
|
if (ended) return
|
|
220
230
|
release()
|
|
@@ -238,10 +248,7 @@ export function bodyOf(
|
|
|
238
248
|
if (framing !== 'bytes' && isFailed(thrown)) {
|
|
239
249
|
controller.enqueue(
|
|
240
250
|
ENCODER.encode(
|
|
241
|
-
refusalFrame(
|
|
242
|
-
problemOf(thrown),
|
|
243
|
-
framing === 'sse' ? 'data: ' : '',
|
|
244
|
-
),
|
|
251
|
+
refusalFrame(problemOf(thrown), framing),
|
|
245
252
|
),
|
|
246
253
|
)
|
|
247
254
|
controller.close()
|
|
@@ -267,7 +274,13 @@ export function bodyOf(
|
|
|
267
274
|
controller.enqueue(ENCODER.encode(`${JSON.stringify(chunk)}\n`))
|
|
268
275
|
else
|
|
269
276
|
controller.enqueue(
|
|
270
|
-
ENCODER.encode(
|
|
277
|
+
ENCODER.encode(
|
|
278
|
+
streamEvent(
|
|
279
|
+
undefined,
|
|
280
|
+
undefined,
|
|
281
|
+
JSON.stringify(chunk),
|
|
282
|
+
),
|
|
283
|
+
),
|
|
271
284
|
)
|
|
272
285
|
},
|
|
273
286
|
cancel() {
|