@rangojs/router 0.22.1 → 0.24.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/dist/bin/rango.js +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
package/src/server/context.ts
CHANGED
|
@@ -117,6 +117,13 @@ export type EntryPropDatas = {
|
|
|
117
117
|
*/
|
|
118
118
|
export type EntryPropRender = {
|
|
119
119
|
loading?: ReactNode | false;
|
|
120
|
+
/**
|
|
121
|
+
* loading(fallback, { prefetch: false }) with a renderable fallback: a
|
|
122
|
+
* prefetch defers what the fallback covers while the entry's segment is
|
|
123
|
+
* new to the client (router/segment-resolution/prefetch-deferral.ts). Set
|
|
124
|
+
* on both DSL evaluations, whatever `ssr` did to `loading`.
|
|
125
|
+
*/
|
|
126
|
+
loadingPrefetch?: false;
|
|
120
127
|
transition?: TransitionConfig;
|
|
121
128
|
};
|
|
122
129
|
|
|
@@ -150,6 +157,12 @@ export type LoaderEntry = {
|
|
|
150
157
|
* navigation evaluation never has it.
|
|
151
158
|
*/
|
|
152
159
|
bake?: true;
|
|
160
|
+
/**
|
|
161
|
+
* loader(Def, { prefetch: false }): a prefetch defers this registration
|
|
162
|
+
* while its segment is new to the client
|
|
163
|
+
* (router/segment-resolution/prefetch-deferral.ts).
|
|
164
|
+
*/
|
|
165
|
+
prefetch?: false;
|
|
153
166
|
};
|
|
154
167
|
|
|
155
168
|
/**
|
|
@@ -867,9 +880,14 @@ export function track(label: string, depth?: number): () => void {
|
|
|
867
880
|
* nested RangoContext.run() calls in Vite's module runner.
|
|
868
881
|
*/
|
|
869
882
|
const LOADER_SCOPE_KEY = Symbol.for("rangojs-router:loader-scope");
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
883
|
+
interface LoaderScope {
|
|
884
|
+
active: true;
|
|
885
|
+
/** $$id of the DSL loader this scope resolves (runInsideLoaderScope). */
|
|
886
|
+
loaderId?: string;
|
|
887
|
+
}
|
|
888
|
+
const loaderScopeALS: AsyncLocalStorage<LoaderScope> = ((globalThis as any)[
|
|
889
|
+
LOADER_SCOPE_KEY
|
|
890
|
+
] ??= new AsyncLocalStorage<LoaderScope>());
|
|
873
891
|
|
|
874
892
|
// Purity-only scope: marks that a loader FUNCTION BODY is executing, regardless
|
|
875
893
|
// of how the loader was invoked (DSL via runInsideLoaderScope, or handler-
|
|
@@ -1429,8 +1447,24 @@ export function assertCachedHeaderWriteAllowed(
|
|
|
1429
1447
|
* are bypassed because loaders are always fresh (never cached) and
|
|
1430
1448
|
* their side effects (setCookie, header, etc.) are safe.
|
|
1431
1449
|
*/
|
|
1432
|
-
export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
1433
|
-
return loaderScopeALS.run({ active: true }, fn);
|
|
1450
|
+
export function runInsideLoaderScope<T>(fn: () => T, loaderId?: string): T {
|
|
1451
|
+
return loaderScopeALS.run({ active: true, loaderId }, fn);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* The loader whose run a handle push here belongs to, for the browser's
|
|
1456
|
+
* merge of a partial response (handles/pushed-by.ts): the DSL loader
|
|
1457
|
+
* this scope resolves, else the outermost loader body around the push (a
|
|
1458
|
+
* loader a handler reads with ctx.use). Undefined for a handler's own push.
|
|
1459
|
+
*/
|
|
1460
|
+
export function getHandlePushOwner(): string | undefined {
|
|
1461
|
+
const dsl = loaderScopeALS.getStore()?.loaderId;
|
|
1462
|
+
if (dsl !== undefined) return dsl;
|
|
1463
|
+
let outermost: string | undefined;
|
|
1464
|
+
for (let s = loaderBodyScopeALS.getStore(); s; s = s.parent) {
|
|
1465
|
+
if (s.loaderId !== undefined) outermost = s.loaderId;
|
|
1466
|
+
}
|
|
1467
|
+
return outermost;
|
|
1434
1468
|
}
|
|
1435
1469
|
|
|
1436
1470
|
/**
|
|
@@ -10,23 +10,47 @@
|
|
|
10
10
|
export function parseCookiesFromHeader(
|
|
11
11
|
cookieHeader: string | null,
|
|
12
12
|
): Record<string, string> {
|
|
13
|
-
if (!cookieHeader) return {};
|
|
14
|
-
|
|
15
13
|
const cookies: Record<string, string> = {};
|
|
16
|
-
const pairs = cookieHeader.split(";");
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
if (cookieHeader) {
|
|
16
|
+
for (const pair of cookieHeader.split(";")) {
|
|
17
|
+
const [name, ...rest] = pair.trim().split("=");
|
|
18
|
+
if (name) {
|
|
19
|
+
const raw = rest.join("=");
|
|
20
|
+
let value: string;
|
|
21
|
+
try {
|
|
22
|
+
value = decodeURIComponent(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
// Malformed percent-encoding: fall back to raw value
|
|
25
|
+
value = raw;
|
|
26
|
+
}
|
|
27
|
+
setCookie(cookies, name, value);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// Null prototype (#1109): cookie names come from the request, so a lookup
|
|
33
|
+
// must never answer `constructor` or `toString` with an inherited member.
|
|
34
|
+
// Set once at the end: Object.create(null) starts in dictionary mode, which
|
|
35
|
+
// makes a spread of the jar several times slower.
|
|
36
|
+
Object.setPrototypeOf(cookies, null);
|
|
31
37
|
return cookies;
|
|
32
38
|
}
|
|
39
|
+
|
|
40
|
+
/** `cookies[name] = value`, a cookie named `__proto__` as an own key too. */
|
|
41
|
+
function setCookie(
|
|
42
|
+
cookies: Record<string, string>,
|
|
43
|
+
name: string,
|
|
44
|
+
value: string,
|
|
45
|
+
): void {
|
|
46
|
+
if (name === "__proto__") {
|
|
47
|
+
Object.defineProperty(cookies, name, {
|
|
48
|
+
value,
|
|
49
|
+
writable: true,
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
cookies[name] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -367,12 +367,16 @@ function createCookieStore(
|
|
|
367
367
|
|
|
368
368
|
getAll(name?: string): Cookie[] {
|
|
369
369
|
recordLoaderIdentityRead("cookies()");
|
|
370
|
-
const all = ctx._readCookies();
|
|
371
370
|
if (name !== undefined) {
|
|
372
|
-
|
|
371
|
+
// The single-name read, not an index into the plain object
|
|
372
|
+
// _readCookies() returns: that would answer an inherited member.
|
|
373
|
+
const value = ctx._readCookie(name);
|
|
373
374
|
return value !== undefined ? [{ name, value }] : [];
|
|
374
375
|
}
|
|
375
|
-
return Object.entries(
|
|
376
|
+
return Object.entries(ctx._readCookies()).map(([n, v]) => ({
|
|
377
|
+
name: n,
|
|
378
|
+
value: v,
|
|
379
|
+
}));
|
|
376
380
|
},
|
|
377
381
|
|
|
378
382
|
has(name: string): boolean {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
findEnclosingLoaderBody,
|
|
3
|
+
getHandlePushOwner,
|
|
4
|
+
isInsideLoaderScope,
|
|
5
|
+
} from "./context.js";
|
|
2
6
|
import { holdsThenable } from "../router/segment-resolution/mask-nested.js";
|
|
7
|
+
import { pushedByKey } from "../handles/pushed-by.js";
|
|
8
|
+
import { defineSlot } from "./slot-accessors.js";
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
11
|
* Handle data structure: handleName -> segmentId -> entries[]
|
|
@@ -75,6 +81,12 @@ interface SlotTag {
|
|
|
75
81
|
restoredOf?: string;
|
|
76
82
|
/** A restored slot a dropped settled push of its loader stood in for. */
|
|
77
83
|
matched?: true;
|
|
84
|
+
/**
|
|
85
|
+
* The loader whose run made the push, for the browser (pushed-by.ts): the
|
|
86
|
+
* DSL loader or loader body it is made in (getHandlePushOwner), else the
|
|
87
|
+
* loader a copy stands for. Unset for a handler's push.
|
|
88
|
+
*/
|
|
89
|
+
by?: string;
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
// Shared by every plain DSL-loader push; tags are never mutated in place.
|
|
@@ -111,6 +123,24 @@ function cloneHandleData(data: HandleData): HandleData {
|
|
|
111
123
|
return clone;
|
|
112
124
|
}
|
|
113
125
|
|
|
126
|
+
// SlotTag.by of each value, index-aligned; undefined when no value has one.
|
|
127
|
+
function pushedByOf(
|
|
128
|
+
values: unknown[],
|
|
129
|
+
tags: (SlotTag | undefined)[] | undefined,
|
|
130
|
+
keep?: (i: number) => boolean,
|
|
131
|
+
): (string | null)[] | undefined {
|
|
132
|
+
if (!tags) return undefined;
|
|
133
|
+
const by: (string | null)[] = [];
|
|
134
|
+
let any = false;
|
|
135
|
+
for (let i = 0; i < values.length; i++) {
|
|
136
|
+
if (keep && !keep(i)) continue;
|
|
137
|
+
const run = tags[i]?.by ?? null;
|
|
138
|
+
if (run !== null) any = true;
|
|
139
|
+
by.push(run);
|
|
140
|
+
}
|
|
141
|
+
return any ? by : undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
/**
|
|
115
145
|
* HandleStore tracks pending handler promises and stores handle data.
|
|
116
146
|
*
|
|
@@ -297,7 +327,8 @@ export interface HandleStore {
|
|
|
297
327
|
* nav/action payloads, whose consumer applies each yield progressively) or
|
|
298
328
|
* "settled" (the document lane, whose consumers drain to completion in
|
|
299
329
|
* blocking positions and must not wait on loader bodies).
|
|
300
|
-
* Each yield contains the full accumulated state (not just the delta)
|
|
330
|
+
* Each yield contains the full accumulated state (not just the delta),
|
|
331
|
+
* with the pushedBy records of its buckets (handles/pushed-by.ts).
|
|
301
332
|
* Safe for concurrent consumers (per-consumer version cursor).
|
|
302
333
|
* The "settled" lane of a frozen store (freezeDocumentSnapshot) yields the
|
|
303
334
|
* frozen state once, at once, and ends. That yield is the store's own
|
|
@@ -331,6 +362,17 @@ export interface HandleStore {
|
|
|
331
362
|
excludeLoaderPushes?: boolean,
|
|
332
363
|
): Record<string, unknown[]>;
|
|
333
364
|
|
|
365
|
+
/**
|
|
366
|
+
* The loader whose run pushed each value in getDataForSegment(segmentId,
|
|
367
|
+
* excludeLoaderPushes), index-aligned per handle (null: the handler's).
|
|
368
|
+
* Handles without such a value are omitted; undefined when none has one.
|
|
369
|
+
* The browser's pushedBy record (handles/pushed-by.ts).
|
|
370
|
+
*/
|
|
371
|
+
getPushedBy(
|
|
372
|
+
segmentId: string,
|
|
373
|
+
excludeLoaderPushes?: boolean,
|
|
374
|
+
): Record<string, (string | null)[]> | undefined;
|
|
375
|
+
|
|
334
376
|
/**
|
|
335
377
|
* The push `owner` of each value in getDataForSegment(segmentId, true),
|
|
336
378
|
* index-aligned per handle (null: no owner). Handles without an owned
|
|
@@ -353,6 +395,34 @@ export interface HandleStore {
|
|
|
353
395
|
): void;
|
|
354
396
|
}
|
|
355
397
|
|
|
398
|
+
/**
|
|
399
|
+
* settled and fullySettled: shared descriptors (slot-accessors.ts) that call
|
|
400
|
+
* the store's closure in SETTLEMENT_SLOT with the lane they wait for.
|
|
401
|
+
*/
|
|
402
|
+
const SETTLEMENT_SLOT: unique symbol = Symbol("rango.handleStore.settlement");
|
|
403
|
+
|
|
404
|
+
type SettlementSlot = { [SETTLEMENT_SLOT]: (full: boolean) => Promise<void> };
|
|
405
|
+
|
|
406
|
+
const SETTLEMENT_ACCESSORS: Record<
|
|
407
|
+
"settled" | "fullySettled",
|
|
408
|
+
PropertyDescriptor
|
|
409
|
+
> = {
|
|
410
|
+
settled: {
|
|
411
|
+
get(this: SettlementSlot): Promise<void> {
|
|
412
|
+
return this[SETTLEMENT_SLOT](false);
|
|
413
|
+
},
|
|
414
|
+
enumerable: true,
|
|
415
|
+
configurable: true,
|
|
416
|
+
},
|
|
417
|
+
fullySettled: {
|
|
418
|
+
get(this: SettlementSlot): Promise<void> {
|
|
419
|
+
return this[SETTLEMENT_SLOT](true);
|
|
420
|
+
},
|
|
421
|
+
enumerable: true,
|
|
422
|
+
configurable: true,
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
|
|
356
426
|
/**
|
|
357
427
|
* Create a new HandleStore instance.
|
|
358
428
|
*
|
|
@@ -379,6 +449,14 @@ export function createHandleStore(): HandleStore {
|
|
|
379
449
|
// (not value identity) so primitive values are covered too.
|
|
380
450
|
// replaySegmentData installs fresh arrays, so restored values are untagged.
|
|
381
451
|
const slotTags = new WeakMap<unknown[], (SlotTag | undefined)[]>();
|
|
452
|
+
// Frozen tags of plain pushes made by a run (SlotTag.by), one per loader.
|
|
453
|
+
const pushedBySlots = new Map<string, SlotTag>();
|
|
454
|
+
const pushedBySlot = (loader: true | undefined, by: string): SlotTag => {
|
|
455
|
+
const key = loader ? `L${by}` : `H${by}`;
|
|
456
|
+
let tag = pushedBySlots.get(key);
|
|
457
|
+
if (!tag) pushedBySlots.set(key, (tag = Object.freeze({ loader, by })));
|
|
458
|
+
return tag;
|
|
459
|
+
};
|
|
382
460
|
// Copies standing in for a loader that has not pushed live yet: loaderId ->
|
|
383
461
|
// the arrays holding them. Kept apart by kind: only a placeholder gives way
|
|
384
462
|
// to a run that ends without a push, or to a cached unit's delivery.
|
|
@@ -602,6 +680,25 @@ export function createHandleStore(): HandleStore {
|
|
|
602
680
|
}
|
|
603
681
|
}
|
|
604
682
|
|
|
683
|
+
// What a stream yields: a copy of the data, with each bucket's pushedBy
|
|
684
|
+
// record (pushed-by.ts) for the browser's merge.
|
|
685
|
+
function snapshot(): HandleData {
|
|
686
|
+
const out: HandleData = {};
|
|
687
|
+
for (const handleName in data) {
|
|
688
|
+
const buckets: Record<string, unknown[]> = {};
|
|
689
|
+
let pushedBy: Record<string, (string | null)[]> | undefined;
|
|
690
|
+
for (const segmentId in data[handleName]) {
|
|
691
|
+
const values = data[handleName][segmentId];
|
|
692
|
+
buckets[segmentId] = [...values];
|
|
693
|
+
const by = pushedByOf(values, slotTags.get(values));
|
|
694
|
+
if (by) (pushedBy ??= {})[segmentId] = by;
|
|
695
|
+
}
|
|
696
|
+
out[handleName] = buckets;
|
|
697
|
+
if (pushedBy) out[pushedByKey(handleName)] = pushedBy;
|
|
698
|
+
}
|
|
699
|
+
return out;
|
|
700
|
+
}
|
|
701
|
+
|
|
605
702
|
// Settlement barriers: `settled` (handler lane) resolves when sealed AND
|
|
606
703
|
// handler inflight === 0. `fullySettled` additionally waits for the
|
|
607
704
|
// auxiliary (loader) lane. seal() signals "no more track() calls"; each
|
|
@@ -706,7 +803,9 @@ export function createHandleStore(): HandleStore {
|
|
|
706
803
|
});
|
|
707
804
|
}
|
|
708
805
|
|
|
709
|
-
|
|
806
|
+
// Data members only: the accessors are SETTLEMENT_ACCESSORS.
|
|
807
|
+
const store: Omit<HandleStore, "settled" | "fullySettled"> &
|
|
808
|
+
ThisType<HandleStore> = {
|
|
710
809
|
track<T>(promise: Promise<T>): Promise<T> {
|
|
711
810
|
inflightCount++;
|
|
712
811
|
return trackSettlement(promise, () => {
|
|
@@ -725,22 +824,6 @@ export function createHandleStore(): HandleStore {
|
|
|
725
824
|
sealInternal();
|
|
726
825
|
},
|
|
727
826
|
|
|
728
|
-
get settled(): Promise<void> {
|
|
729
|
-
if (sealed && inflightCount === 0) return Promise.resolve();
|
|
730
|
-
return new Promise<void>((resolve) => {
|
|
731
|
-
drainWaiters.push(resolve);
|
|
732
|
-
});
|
|
733
|
-
},
|
|
734
|
-
|
|
735
|
-
get fullySettled(): Promise<void> {
|
|
736
|
-
if (sealed && inflightCount === 0 && auxInflightCount === 0) {
|
|
737
|
-
return Promise.resolve();
|
|
738
|
-
}
|
|
739
|
-
return new Promise<void>((resolve) => {
|
|
740
|
-
fullDrainWaiters.push(resolve);
|
|
741
|
-
});
|
|
742
|
-
},
|
|
743
|
-
|
|
744
827
|
push(
|
|
745
828
|
handleName: string,
|
|
746
829
|
segmentId: string,
|
|
@@ -812,6 +895,12 @@ export function createHandleStore(): HandleStore {
|
|
|
812
895
|
at = restoredInsertIndex(values, restoredBy);
|
|
813
896
|
if (replayOf === undefined) liveOf = restoredBy;
|
|
814
897
|
}
|
|
898
|
+
const by =
|
|
899
|
+
getHandlePushOwner() ??
|
|
900
|
+
replayOf ??
|
|
901
|
+
restoredOf ??
|
|
902
|
+
placeholderOf ??
|
|
903
|
+
owner;
|
|
815
904
|
insertSlot(
|
|
816
905
|
values,
|
|
817
906
|
at,
|
|
@@ -829,10 +918,13 @@ export function createHandleStore(): HandleStore {
|
|
|
829
918
|
liveOf,
|
|
830
919
|
under,
|
|
831
920
|
owner,
|
|
921
|
+
by,
|
|
832
922
|
}
|
|
833
|
-
:
|
|
834
|
-
?
|
|
835
|
-
:
|
|
923
|
+
: by !== undefined
|
|
924
|
+
? pushedBySlot(loader, by)
|
|
925
|
+
: loader
|
|
926
|
+
? LOADER_SLOT
|
|
927
|
+
: undefined,
|
|
836
928
|
);
|
|
837
929
|
if (replayOf !== undefined) {
|
|
838
930
|
addPending(pendingReplays, replayOf, values);
|
|
@@ -958,7 +1050,7 @@ export function createHandleStore(): HandleStore {
|
|
|
958
1050
|
if (this.onError) this.onError(error);
|
|
959
1051
|
else console.error(error);
|
|
960
1052
|
}
|
|
961
|
-
documentSnapshot = { version, data:
|
|
1053
|
+
documentSnapshot = { version, data: snapshot() };
|
|
962
1054
|
},
|
|
963
1055
|
|
|
964
1056
|
async *stream(
|
|
@@ -1005,7 +1097,7 @@ export function createHandleStore(): HandleStore {
|
|
|
1005
1097
|
while (true) {
|
|
1006
1098
|
if (version > seen && Object.keys(data).length > 0) {
|
|
1007
1099
|
seen = version;
|
|
1008
|
-
yield
|
|
1100
|
+
yield snapshot();
|
|
1009
1101
|
}
|
|
1010
1102
|
if (done) break;
|
|
1011
1103
|
await waitForEmission(seen, () => done);
|
|
@@ -1043,7 +1135,7 @@ export function createHandleStore(): HandleStore {
|
|
|
1043
1135
|
while (true) {
|
|
1044
1136
|
if (version > seen) {
|
|
1045
1137
|
seen = version;
|
|
1046
|
-
yield
|
|
1138
|
+
yield snapshot();
|
|
1047
1139
|
}
|
|
1048
1140
|
if (done) break;
|
|
1049
1141
|
await waitForEmission(seen, () => done);
|
|
@@ -1069,6 +1161,25 @@ export function createHandleStore(): HandleStore {
|
|
|
1069
1161
|
return result;
|
|
1070
1162
|
},
|
|
1071
1163
|
|
|
1164
|
+
getPushedBy(
|
|
1165
|
+
segmentId: string,
|
|
1166
|
+
excludeLoaderPushes?: boolean,
|
|
1167
|
+
): Record<string, (string | null)[]> | undefined {
|
|
1168
|
+
let result: Record<string, (string | null)[]> | undefined;
|
|
1169
|
+
for (const handleName in data) {
|
|
1170
|
+
const values = data[handleName][segmentId];
|
|
1171
|
+
if (!values) continue;
|
|
1172
|
+
const tags = slotTags.get(values);
|
|
1173
|
+
const by = pushedByOf(
|
|
1174
|
+
values,
|
|
1175
|
+
tags,
|
|
1176
|
+
excludeLoaderPushes ? (i) => !tags?.[i]?.loader : undefined,
|
|
1177
|
+
);
|
|
1178
|
+
if (by) (result ??= {})[handleName] = by;
|
|
1179
|
+
}
|
|
1180
|
+
return result;
|
|
1181
|
+
},
|
|
1182
|
+
|
|
1072
1183
|
getRecordOwners(
|
|
1073
1184
|
segmentId: string,
|
|
1074
1185
|
): Record<string, (string | null)[]> | undefined {
|
|
@@ -1117,4 +1228,20 @@ export function createHandleStore(): HandleStore {
|
|
|
1117
1228
|
signalEmission();
|
|
1118
1229
|
},
|
|
1119
1230
|
};
|
|
1231
|
+
// settled: the handler lane drained; fullySettled: the auxiliary lane too.
|
|
1232
|
+
defineSlot(store, SETTLEMENT_SLOT, (full: boolean): Promise<void> => {
|
|
1233
|
+
if (sealed && inflightCount === 0 && (!full || auxInflightCount === 0)) {
|
|
1234
|
+
return Promise.resolve();
|
|
1235
|
+
}
|
|
1236
|
+
return new Promise<void>((resolve) => {
|
|
1237
|
+
(full ? fullDrainWaiters : drainWaiters).push(resolve);
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
Object.defineProperty(store, "settled", SETTLEMENT_ACCESSORS.settled);
|
|
1241
|
+
Object.defineProperty(
|
|
1242
|
+
store,
|
|
1243
|
+
"fullySettled",
|
|
1244
|
+
SETTLEMENT_ACCESSORS.fullySettled,
|
|
1245
|
+
);
|
|
1246
|
+
return store as HandleStore;
|
|
1120
1247
|
}
|