@uniflowed/router 0.0.0-alpha.32 → 0.0.0-alpha.34
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/index.js +7 -0
- package/internal/boundaries.js +11 -110
- package/internal/boundary-data.js +88 -0
- package/internal/inspector.js +12 -1
- package/internal/resolved-summary.js +199 -0
- package/internal/routing.js +56 -8
- package/internal/runtime.js +529 -46
- package/native.js +408 -0
- package/package.json +5 -3
- package/routing.js +9 -0
- package/server.js +7 -7
package/native.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// `@uniflowed/router/native`: the React Native navigator contract.
|
|
4
|
+
//
|
|
5
|
+
// Native routes use the same generated table as the web router, but not the
|
|
6
|
+
// browser's URL bar or History API. This adapter keeps the shared part small:
|
|
7
|
+
// it resolves a uf route path against the table, refuses destinations the
|
|
8
|
+
// native bundle cannot render, and hands a navigator-shaped event to the app's
|
|
9
|
+
// own navigation runtime. Rendering the tree is still the renderer/host-config
|
|
10
|
+
// half of the React Native target.
|
|
11
|
+
//
|
|
12
|
+
// # No interception here, deliberately
|
|
13
|
+
//
|
|
14
|
+
// An intercepting route — a slot's `(.)photo` — is not one of this adapter's
|
|
15
|
+
// features, and that is a decision rather than a gap. What interception does
|
|
16
|
+
// is a browser's: the address bar says `/feed/photo/1` while the page
|
|
17
|
+
// underneath stays on screen with the photo in a slot over it, and a reload
|
|
18
|
+
// renders the page the URL names instead. A native navigator has no address bar
|
|
19
|
+
// for the screen to disagree with, and it already has the thing interception
|
|
20
|
+
// imitates — a screen presented modally over the one below it, with its own
|
|
21
|
+
// back gesture. Doing both would give a native app two answers to "open this
|
|
22
|
+
// over the feed", one of them the router's and one the navigator's.
|
|
23
|
+
//
|
|
24
|
+
// So `resolveNativeNavigation` matches `table.routes` and nothing else:
|
|
25
|
+
// `/feed/photo/1` resolves to the ordinary `app/feed/photo/[id]` route every
|
|
26
|
+
// time, a slot's `intercepts` are never read, and presenting that screen as a
|
|
27
|
+
// modal is the navigator's call, made where the rest of its presentation is.
|
|
28
|
+
|
|
29
|
+
import type { RouteParams, RouteRecord, RouteTable } from "./internal/routing.js";
|
|
30
|
+
import { hasClientPage, matchRoute, splitUrl } from "./internal/routing.js";
|
|
31
|
+
|
|
32
|
+
export type NativeNavigationKind = "push" | "replace" | "prefetch";
|
|
33
|
+
|
|
34
|
+
export type NativeNavigationEvent = {|
|
|
35
|
+
readonly kind: NativeNavigationKind,
|
|
36
|
+
readonly href: string,
|
|
37
|
+
readonly pathname: string,
|
|
38
|
+
readonly search: string,
|
|
39
|
+
readonly route: string,
|
|
40
|
+
readonly params: RouteParams,
|
|
41
|
+
|};
|
|
42
|
+
|
|
43
|
+
export type NativeScreenMap = {
|
|
44
|
+
readonly [route: string]: string,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type NativeScreenEntry = {|
|
|
48
|
+
readonly screen: string,
|
|
49
|
+
readonly route: string,
|
|
50
|
+
readonly file: string,
|
|
51
|
+
|};
|
|
52
|
+
|
|
53
|
+
export type NativeScreenManifest = {|
|
|
54
|
+
readonly screens: NativeScreenMap,
|
|
55
|
+
readonly entries: $ReadOnlyArray<NativeScreenEntry>,
|
|
56
|
+
|};
|
|
57
|
+
|
|
58
|
+
export type NativeScreenNameRoute = {|
|
|
59
|
+
readonly path: string,
|
|
60
|
+
readonly file: string,
|
|
61
|
+
|};
|
|
62
|
+
|
|
63
|
+
export type NativeScreenManifestOptions = {|
|
|
64
|
+
readonly name?: (route: NativeScreenNameRoute) => string,
|
|
65
|
+
|};
|
|
66
|
+
|
|
67
|
+
export type NativeScreenPayload = {|
|
|
68
|
+
readonly screen: string,
|
|
69
|
+
readonly href: string,
|
|
70
|
+
readonly pathname: string,
|
|
71
|
+
readonly search: string,
|
|
72
|
+
readonly route: string,
|
|
73
|
+
readonly params: RouteParams,
|
|
74
|
+
|};
|
|
75
|
+
|
|
76
|
+
export type NativeScreenNavigationState = {|
|
|
77
|
+
readonly params: RouteParams,
|
|
78
|
+
readonly href: string,
|
|
79
|
+
readonly pathname: string,
|
|
80
|
+
readonly search: string,
|
|
81
|
+
readonly route: string,
|
|
82
|
+
|};
|
|
83
|
+
|
|
84
|
+
export type NativeNavigator = {|
|
|
85
|
+
readonly push?: (event: NativeNavigationEvent) => mixed | Promise<mixed>,
|
|
86
|
+
readonly replace?: (event: NativeNavigationEvent) => mixed | Promise<mixed>,
|
|
87
|
+
readonly prefetch?: (event: NativeNavigationEvent) => mixed | Promise<mixed>,
|
|
88
|
+
|};
|
|
89
|
+
|
|
90
|
+
export type NativeScreenNavigator = {|
|
|
91
|
+
readonly push?: (screen: string, state: NativeScreenNavigationState) => mixed | Promise<mixed>,
|
|
92
|
+
readonly replace?: (screen: string, state: NativeScreenNavigationState) => mixed | Promise<mixed>,
|
|
93
|
+
readonly prefetch?: (
|
|
94
|
+
screen: string,
|
|
95
|
+
state: NativeScreenNavigationState,
|
|
96
|
+
) => mixed | Promise<mixed>,
|
|
97
|
+
|};
|
|
98
|
+
|
|
99
|
+
export type NativeRouter = {|
|
|
100
|
+
readonly push: (to: string) => Promise<void>,
|
|
101
|
+
readonly replace: (to: string) => Promise<void>,
|
|
102
|
+
readonly prefetch: (to: string) => Promise<void>,
|
|
103
|
+
readonly resolve: (to: string, kind?: NativeNavigationKind) => NativeNavigationEvent,
|
|
104
|
+
|};
|
|
105
|
+
|
|
106
|
+
export type NativeNavigationErrorCode =
|
|
107
|
+
| "duplicate-screen"
|
|
108
|
+
| "external-url"
|
|
109
|
+
| "fragment"
|
|
110
|
+
| "relative-url"
|
|
111
|
+
| "missing-route"
|
|
112
|
+
| "missing-screen"
|
|
113
|
+
| "server-only-route"
|
|
114
|
+
| "missing-navigator-method";
|
|
115
|
+
|
|
116
|
+
export class NativeNavigationError extends Error {
|
|
117
|
+
code: NativeNavigationErrorCode;
|
|
118
|
+
href: string;
|
|
119
|
+
route: ?string;
|
|
120
|
+
|
|
121
|
+
constructor(code: NativeNavigationErrorCode, message: string, href: string, route?: ?string) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.name = "NativeNavigationError";
|
|
124
|
+
this.code = code;
|
|
125
|
+
this.href = href;
|
|
126
|
+
this.route = route ?? null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function createNativeRouter(
|
|
131
|
+
table: RouteTable<mixed, mixed, mixed, mixed, mixed>,
|
|
132
|
+
navigator: NativeNavigator,
|
|
133
|
+
): NativeRouter {
|
|
134
|
+
const resolve = (to: string, kind?: NativeNavigationKind = "push"): NativeNavigationEvent =>
|
|
135
|
+
resolveNativeNavigation(table, to, kind);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
resolve,
|
|
139
|
+
push: async (to) => {
|
|
140
|
+
const event = resolve(to, "push");
|
|
141
|
+
await invokeNavigator(navigator, event);
|
|
142
|
+
},
|
|
143
|
+
replace: async (to) => {
|
|
144
|
+
const event = resolve(to, "replace");
|
|
145
|
+
await invokeNavigator(navigator, event);
|
|
146
|
+
},
|
|
147
|
+
prefetch: async (to) => {
|
|
148
|
+
const event = resolve(to, "prefetch");
|
|
149
|
+
await loadNativeRoute(table, event.pathname);
|
|
150
|
+
if (typeof navigator.prefetch === "function") {
|
|
151
|
+
await navigator.prefetch(event);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createNativeScreenRouter(
|
|
158
|
+
table: RouteTable<mixed, mixed, mixed, mixed, mixed>,
|
|
159
|
+
screens: NativeScreenMap,
|
|
160
|
+
navigator: NativeScreenNavigator,
|
|
161
|
+
): NativeRouter {
|
|
162
|
+
return createNativeRouter(table, {
|
|
163
|
+
push: (event) => invokeScreenNavigator(navigator, screens, event),
|
|
164
|
+
replace: (event) => invokeScreenNavigator(navigator, screens, event),
|
|
165
|
+
prefetch: (event) => {
|
|
166
|
+
if (typeof navigator.prefetch !== "function") return;
|
|
167
|
+
return invokeScreenNavigator(navigator, screens, event);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function createNativeScreenManifest(
|
|
173
|
+
table: RouteTable<mixed, mixed, mixed, mixed, mixed>,
|
|
174
|
+
options?: NativeScreenManifestOptions,
|
|
175
|
+
): NativeScreenManifest {
|
|
176
|
+
const screens: { [string]: string } = {};
|
|
177
|
+
const entries: Array<NativeScreenEntry> = [];
|
|
178
|
+
const seen = new Map<string, string>();
|
|
179
|
+
for (const route of table.routes) {
|
|
180
|
+
if (!hasClientPage(route)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const screen = screenNameFor(route, options);
|
|
184
|
+
const already = seen.get(screen);
|
|
185
|
+
if (already != null) {
|
|
186
|
+
throw new NativeNavigationError(
|
|
187
|
+
"duplicate-screen",
|
|
188
|
+
`@uniflowed/router/native: ${route.path} and ${already} both map to native screen ${screen}`,
|
|
189
|
+
route.path,
|
|
190
|
+
route.path,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
seen.set(screen, route.path);
|
|
194
|
+
screens[route.path] = screen;
|
|
195
|
+
entries.push({ screen, route: route.path, file: route.file });
|
|
196
|
+
}
|
|
197
|
+
return { screens, entries };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function nativeScreenName(routePath: string): string {
|
|
201
|
+
const segments = routePath.split("/").filter((segment) => segment !== "");
|
|
202
|
+
if (segments.length === 0) {
|
|
203
|
+
return "Home";
|
|
204
|
+
}
|
|
205
|
+
const name = segments
|
|
206
|
+
.map((segment) => {
|
|
207
|
+
if (segment.startsWith(":") && segment.endsWith("*")) {
|
|
208
|
+
return `All${titlePart(segment.slice(1, -1))}`;
|
|
209
|
+
}
|
|
210
|
+
if (segment.startsWith(":")) {
|
|
211
|
+
return `By${titlePart(segment.slice(1))}`;
|
|
212
|
+
}
|
|
213
|
+
return titlePart(segment);
|
|
214
|
+
})
|
|
215
|
+
.join("");
|
|
216
|
+
return name === "" ? "Screen" : name;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function resolveNativeNavigation(
|
|
220
|
+
table: RouteTable<mixed, mixed, mixed, mixed, mixed>,
|
|
221
|
+
to: string,
|
|
222
|
+
kind?: NativeNavigationKind = "push",
|
|
223
|
+
): NativeNavigationEvent {
|
|
224
|
+
const href = normalizeNativeHref(to);
|
|
225
|
+
const { pathname, search } = splitUrl(href);
|
|
226
|
+
const matched = matchRoute(table.routes, pathname);
|
|
227
|
+
if (matched == null) {
|
|
228
|
+
throw new NativeNavigationError(
|
|
229
|
+
"missing-route",
|
|
230
|
+
`@uniflowed/router/native: ${href} does not match a native route in this table`,
|
|
231
|
+
href,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (!hasClientPage(matched.route)) {
|
|
235
|
+
throw new NativeNavigationError(
|
|
236
|
+
"server-only-route",
|
|
237
|
+
`@uniflowed/router/native: ${matched.route.path} is not in the native route table; ` +
|
|
238
|
+
"it has no page module for this bundle",
|
|
239
|
+
href,
|
|
240
|
+
matched.route.path,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
kind,
|
|
245
|
+
href,
|
|
246
|
+
pathname,
|
|
247
|
+
search,
|
|
248
|
+
route: matched.route.path,
|
|
249
|
+
params: matched.params,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function nativeScreenPayload(
|
|
254
|
+
event: NativeNavigationEvent,
|
|
255
|
+
screens: NativeScreenMap,
|
|
256
|
+
): NativeScreenPayload {
|
|
257
|
+
const screen = screens[event.route];
|
|
258
|
+
if (typeof screen !== "string" || screen === "") {
|
|
259
|
+
throw new NativeNavigationError(
|
|
260
|
+
"missing-screen",
|
|
261
|
+
`@uniflowed/router/native: ${event.route} has no native screen mapping`,
|
|
262
|
+
event.href,
|
|
263
|
+
event.route,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
screen,
|
|
268
|
+
href: event.href,
|
|
269
|
+
pathname: event.pathname,
|
|
270
|
+
search: event.search,
|
|
271
|
+
route: event.route,
|
|
272
|
+
params: event.params,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function nativeScreenNavigationState(
|
|
277
|
+
payload: NativeScreenPayload,
|
|
278
|
+
): NativeScreenNavigationState {
|
|
279
|
+
return {
|
|
280
|
+
params: payload.params,
|
|
281
|
+
href: payload.href,
|
|
282
|
+
pathname: payload.pathname,
|
|
283
|
+
search: payload.search,
|
|
284
|
+
route: payload.route,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function normalizeNativeHref(to: string): string {
|
|
289
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(to) || to.startsWith("//")) {
|
|
290
|
+
throw new NativeNavigationError(
|
|
291
|
+
"external-url",
|
|
292
|
+
`@uniflowed/router/native: ${to} is an external URL, and a native navigator needs an app route`,
|
|
293
|
+
to,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (!to.startsWith("/")) {
|
|
297
|
+
throw new NativeNavigationError(
|
|
298
|
+
"relative-url",
|
|
299
|
+
`@uniflowed/router/native: ${to} is relative, and native navigation has no document URL to resolve it against`,
|
|
300
|
+
to,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (to.includes("#")) {
|
|
304
|
+
throw new NativeNavigationError(
|
|
305
|
+
"fragment",
|
|
306
|
+
`@uniflowed/router/native: ${to} contains a fragment, and native routes do not have document anchors`,
|
|
307
|
+
to,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return splitUrl(to).pathname + splitUrl(to).search;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function screenNameFor(
|
|
314
|
+
route: RouteRecord<mixed, mixed, mixed, mixed, mixed>,
|
|
315
|
+
options?: NativeScreenManifestOptions,
|
|
316
|
+
): string {
|
|
317
|
+
const screen =
|
|
318
|
+
options?.name?.({ path: route.path, file: route.file }) ?? nativeScreenName(route.path);
|
|
319
|
+
if (screen === "") {
|
|
320
|
+
throw new NativeNavigationError(
|
|
321
|
+
"missing-screen",
|
|
322
|
+
`@uniflowed/router/native: ${route.path} mapped to an empty native screen name`,
|
|
323
|
+
route.path,
|
|
324
|
+
route.path,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return screen;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function titlePart(segment: string): string {
|
|
331
|
+
const cleaned = segment
|
|
332
|
+
.replace(/^\[+|\]+$/g, "")
|
|
333
|
+
.replace(/[^A-Za-z0-9]+/g, " ")
|
|
334
|
+
.trim();
|
|
335
|
+
if (cleaned === "") {
|
|
336
|
+
return "Segment";
|
|
337
|
+
}
|
|
338
|
+
return cleaned
|
|
339
|
+
.split(/\s+/)
|
|
340
|
+
.map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
|
|
341
|
+
.join("");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function invokeNavigator(
|
|
345
|
+
navigator: NativeNavigator,
|
|
346
|
+
event: NativeNavigationEvent,
|
|
347
|
+
): Promise<void> {
|
|
348
|
+
if (event.kind === "push") {
|
|
349
|
+
if (typeof navigator.push !== "function") {
|
|
350
|
+
throw missingMethod(event);
|
|
351
|
+
}
|
|
352
|
+
await navigator.push(event);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (event.kind === "replace") {
|
|
356
|
+
if (typeof navigator.replace !== "function") {
|
|
357
|
+
throw missingMethod(event);
|
|
358
|
+
}
|
|
359
|
+
await navigator.replace(event);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function missingMethod(event: NativeNavigationEvent): NativeNavigationError {
|
|
364
|
+
return new NativeNavigationError(
|
|
365
|
+
"missing-navigator-method",
|
|
366
|
+
`@uniflowed/router/native: the native navigator does not implement ${event.kind}()`,
|
|
367
|
+
event.href,
|
|
368
|
+
event.route,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function invokeScreenNavigator(
|
|
373
|
+
navigator: NativeScreenNavigator,
|
|
374
|
+
screens: NativeScreenMap,
|
|
375
|
+
event: NativeNavigationEvent,
|
|
376
|
+
): Promise<void> {
|
|
377
|
+
const payload = nativeScreenPayload(event, screens);
|
|
378
|
+
const state = nativeScreenNavigationState(payload);
|
|
379
|
+
if (event.kind === "push") {
|
|
380
|
+
if (typeof navigator.push !== "function") {
|
|
381
|
+
throw missingMethod(event);
|
|
382
|
+
}
|
|
383
|
+
await navigator.push(payload.screen, state);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (event.kind === "replace") {
|
|
387
|
+
if (typeof navigator.replace !== "function") {
|
|
388
|
+
throw missingMethod(event);
|
|
389
|
+
}
|
|
390
|
+
await navigator.replace(payload.screen, state);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (typeof navigator.prefetch !== "function") {
|
|
394
|
+
throw missingMethod(event);
|
|
395
|
+
}
|
|
396
|
+
await navigator.prefetch(payload.screen, state);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function loadNativeRoute(
|
|
400
|
+
table: RouteTable<mixed, mixed, mixed, mixed, mixed>,
|
|
401
|
+
pathname: string,
|
|
402
|
+
): Promise<void> {
|
|
403
|
+
const matched = matchRoute(table.routes, pathname);
|
|
404
|
+
if (matched == null || !hasClientPage(matched.route)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
await Promise.all([matched.route.page?.(), ...matched.route.layouts.map((layout) => layout())]);
|
|
408
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/router",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.34",
|
|
4
4
|
"description": "The file-system router for Flow React applications: matching, layouts, loaders, navigation, server rendering and hydration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
".": "./index.js",
|
|
15
15
|
"./action": "./action.js",
|
|
16
16
|
"./client": "./client.js",
|
|
17
|
+
"./native": "./native.js",
|
|
17
18
|
"./server": "./server.js",
|
|
18
19
|
"./routing": "./routing.js",
|
|
19
20
|
"./package.json": "./package.json",
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
"index.js",
|
|
28
29
|
"internal",
|
|
29
30
|
"middleware.js",
|
|
31
|
+
"native.js",
|
|
30
32
|
"routing.js",
|
|
31
33
|
"server.js",
|
|
32
34
|
"!*.test.js"
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
"react-dom": ">=19"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@uniflowed/hooks": "0.0.0-alpha.
|
|
40
|
-
"@uniflowed/server": "0.0.0-alpha.
|
|
41
|
+
"@uniflowed/hooks": "0.0.0-alpha.34",
|
|
42
|
+
"@uniflowed/server": "0.0.0-alpha.34"
|
|
41
43
|
}
|
|
42
44
|
}
|
package/routing.js
CHANGED
|
@@ -22,6 +22,14 @@ export type {
|
|
|
22
22
|
SlotRouteRecord,
|
|
23
23
|
TemplateRecord,
|
|
24
24
|
} from "./internal/routing.js";
|
|
25
|
+
export type {
|
|
26
|
+
ResolvedBoundarySummary,
|
|
27
|
+
ResolvedErrorBoundarySummary,
|
|
28
|
+
ResolvedRouteErrorSummary,
|
|
29
|
+
ResolvedRouteSummary,
|
|
30
|
+
ResolvedSlotSummary,
|
|
31
|
+
ResolvedTemplateSummary,
|
|
32
|
+
} from "./internal/resolved-summary.js";
|
|
25
33
|
|
|
26
34
|
export {
|
|
27
35
|
ForbiddenError,
|
|
@@ -40,3 +48,4 @@ export {
|
|
|
40
48
|
splitUrl,
|
|
41
49
|
unauthorized,
|
|
42
50
|
} from "./internal/routing.js";
|
|
51
|
+
export { summarizeResolvedRoute } from "./internal/resolved-summary.js";
|
package/server.js
CHANGED
|
@@ -181,13 +181,13 @@ export { DATA_ID, ROOT_ID } from "./internal/document.js";
|
|
|
181
181
|
*
|
|
182
182
|
* Re-exported rather than left to the host to import, and the reason is the
|
|
183
183
|
* one thing about `@uniflowed/server` that is easy to get wrong: the request
|
|
184
|
-
*
|
|
185
|
-
* that resolved `@uniflowed/server/host` for itself — from its own
|
|
186
|
-
* `node_modules`, or from outside the bundle a build produced —
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* it is the same import.
|
|
184
|
+
* store is shared by every copy of one *release* of that package, and no more.
|
|
185
|
+
* A host that resolved `@uniflowed/server/host` for itself — from its own
|
|
186
|
+
* `node_modules`, or from outside the bundle a build produced — may hold a
|
|
187
|
+
* different release, and would begin a request in a store the application
|
|
188
|
+
* never reads, so every `cookies()` in it would still be outside one, silently.
|
|
189
|
+
* Handing it out from here makes the copy the host begins with the copy this
|
|
190
|
+
* module dispatches and renders with, because it is the same import.
|
|
191
191
|
*
|
|
192
192
|
* `run` wraps everything that decides the response; `settle` is called once
|
|
193
193
|
* the response has been *written*, which is a different line in every host.
|