@paramour-js/next 0.4.0 → 0.4.1
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/app.d.ts +48 -49
- package/dist/app.js +11 -12
- package/dist/cli-args.d.ts +4 -4
- package/dist/cli-args.js +4 -4
- package/dist/cli-inputs.d.ts +6 -7
- package/dist/cli-inputs.js +6 -7
- package/dist/cli.js +1 -1
- package/dist/collisions.d.ts +8 -8
- package/dist/collisions.js +9 -9
- package/dist/commands/generate.d.ts +7 -7
- package/dist/commands/generate.js +16 -16
- package/dist/commands/init.js +1 -1
- package/dist/config.d.ts +10 -10
- package/dist/config.js +4 -4
- package/dist/devtools-seam.d.ts +19 -19
- package/dist/devtools-seam.js +3 -3
- package/dist/doctor/checks.js +1 -1
- package/dist/emit.d.ts +12 -12
- package/dist/emit.js +13 -13
- package/dist/generate.d.ts +14 -14
- package/dist/generate.js +11 -11
- package/dist/list/discover-route-defs.d.ts +4 -4
- package/dist/list/discover-route-defs.js +4 -4
- package/dist/lock.d.ts +8 -9
- package/dist/lock.js +11 -12
- package/dist/navigation-adapter.d.ts +17 -18
- package/dist/navigation-adapter.js +4 -4
- package/dist/observe.d.ts +14 -14
- package/dist/observe.js +4 -4
- package/dist/pages.d.ts +30 -31
- package/dist/pages.js +10 -10
- package/dist/run-cli.d.ts +1 -1
- package/dist/run-cli.js +1 -1
- package/dist/scan-app.d.ts +10 -10
- package/dist/scan-app.js +30 -30
- package/dist/scan-pages.d.ts +6 -6
- package/dist/scan-pages.js +28 -26
- package/dist/scan.d.ts +13 -10
- package/dist/scan.js +7 -7
- package/dist/select.d.ts +40 -40
- package/dist/select.js +30 -30
- package/dist/testing.d.ts +28 -32
- package/dist/testing.js +15 -15
- package/dist/watch.d.ts +12 -12
- package/dist/watch.js +13 -13
- package/dist/with-typed-routes.d.ts +11 -10
- package/dist/with-typed-routes.js +42 -39
- package/package.json +2 -2
package/dist/select.d.ts
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
import type { AnyRoute, ParamsSource, SafeResult } from "paramour";
|
|
2
2
|
/**
|
|
3
|
-
* Shared internals of the read hooks' selector surface
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Shared internals of the read hooks' selector surface: the raw-slice
|
|
4
|
+
* stabilization layer and the selector layer. Deliberately NO `"use client"`
|
|
5
|
+
* directive: app.ts (which carries one) and pages.ts (which must not carry
|
|
6
|
+
* one) both import from here, and the directive belongs on the entry
|
|
7
|
+
* modules, not a shared leaf.
|
|
8
8
|
*
|
|
9
|
-
* Both layers are `useRef` caches mutated during render
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Both layers are `useRef` caches mutated during render — the Redux/TanStack
|
|
10
|
+
* selector pattern, and the one sanctioned departure from the hooks'
|
|
11
|
+
* pure-`useMemo` discipline: result equality needs memory across renders,
|
|
12
|
+
* which no pure memo can provide. Every cache is cleared BEFORE a compute
|
|
13
|
+
* that can throw, so a throwing decode or selector never strands a stale
|
|
14
|
+
* entry.
|
|
15
15
|
*/
|
|
16
16
|
/**
|
|
17
|
-
* Options bag accepted by every read hook
|
|
17
|
+
* Options bag accepted by every read hook.
|
|
18
18
|
*/
|
|
19
19
|
export interface SelectOptions<T, U> {
|
|
20
20
|
/**
|
|
21
|
-
* Result-equality mode for the selected value
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Result-equality mode for the selected value: `Object.is` by default —
|
|
22
|
+
* free and correct for primitive selections — with one-level `"shallow"`
|
|
23
|
+
* as the opt-in for tuple/object selections.
|
|
24
24
|
*/
|
|
25
25
|
readonly equality?: "shallow";
|
|
26
26
|
/**
|
|
27
|
-
* Pure projection of the decoded value; runs only on the success arm
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* Pure projection of the decoded value; runs only on the success arm.
|
|
28
|
+
* Identity is never compared, so inline arrows are fine — and when the
|
|
29
|
+
* underlying result is reference-stable the selector is NOT re-run, so it
|
|
30
|
+
* must not read changing outside state. A throw propagates to the nearest
|
|
31
|
+
* error boundary: a selector bug is a code bug, never the `SafeResult`
|
|
32
|
+
* error arm, which is reserved for URL data problems.
|
|
33
33
|
*/
|
|
34
34
|
readonly select: (value: T) => U;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Fingerprint of the pages hooks' pre-`isReady` state
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* Fingerprint of the pages hooks' pre-`isReady` state. Every real
|
|
38
|
+
* fingerprint is a `JSON.stringify`'d array (starts with `[`), so this can
|
|
39
|
+
* never collide with one.
|
|
40
40
|
*/
|
|
41
41
|
export declare const PENDING_FINGERPRINT = "pending";
|
|
42
42
|
/**
|
|
43
|
-
* Raw slice of a params source
|
|
44
|
-
*
|
|
43
|
+
* Raw slice of a params source: the route's dynamic segment names' raw
|
|
44
|
+
* values, from the define-time `~segments` token cache. Unknown keys —
|
|
45
45
|
* e.g. a parallel route's params in the same `useParams()` bag — never bust
|
|
46
46
|
* the fingerprint, because the decode never reads them.
|
|
47
47
|
*/
|
|
48
48
|
export declare function paramsFingerprint(route: AnyRoute, source: ParamsSource): string;
|
|
49
49
|
/**
|
|
50
|
-
* Raw slice of a pages `router.query` bag for the search half
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Raw slice of a pages `router.query` bag for the search half. A codec-map
|
|
51
|
+
* route reads exactly its declared keys (query junk and the route's own path
|
|
52
|
+
* params are invisible to the decode — the two namespaces are disjoint); a
|
|
53
53
|
* `rawSearch` route has no enumerable declared-key set — the schema sees
|
|
54
|
-
* every key except the route's path params (
|
|
55
|
-
* exactly that slice is fingerprinted, in sorted-key order for
|
|
56
|
-
* independence.
|
|
54
|
+
* every key except the route's path params (by subtracting them from the
|
|
55
|
+
* bag), so exactly that slice is fingerprinted, in sorted-key order for
|
|
56
|
+
* record-order independence.
|
|
57
57
|
*/
|
|
58
58
|
export declare function queryFingerprint(route: AnyRoute, query: ParamsSource): string;
|
|
59
59
|
/**
|
|
60
|
-
* Raw slice of an app `useSearchParams()` source
|
|
60
|
+
* Raw slice of an app `useSearchParams()` source: the declared keys'
|
|
61
61
|
* `[key, value]` pairs in wire order — order is load-bearing for repeated
|
|
62
62
|
* keys (array codecs decode in wire order, P5/S5), and iterating the live
|
|
63
63
|
* pairs preserves the relative order of declared entries while `?utm_*`
|
|
@@ -66,8 +66,8 @@ export declare function queryFingerprint(route: AnyRoute, query: ParamsSource):
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function searchParamsFingerprint(route: AnyRoute, source: URLSearchParams): string;
|
|
68
68
|
/**
|
|
69
|
-
* The selector layer for the safe hooks
|
|
70
|
-
* reference-stabilizes the projected WRAPPER by result equality
|
|
69
|
+
* The selector layer for the safe hooks: projects the success arm and
|
|
70
|
+
* reference-stabilizes the projected WRAPPER by result equality — a
|
|
71
71
|
* stable `data` inside a fresh wrapper would still churn every consumer.
|
|
72
72
|
* Error and pending arms pass through untouched; they are already
|
|
73
73
|
* reference-stabilized by {@link useStableResult}'s raw-slice layer.
|
|
@@ -79,16 +79,16 @@ export declare function useSelectedResult<T, U>(result: SafeResult<T> | {
|
|
|
79
79
|
status: "pending";
|
|
80
80
|
};
|
|
81
81
|
/**
|
|
82
|
-
* {@link useSelectedResult}'s twin for the `*OrThrow` hooks
|
|
83
|
-
*
|
|
82
|
+
* {@link useSelectedResult}'s twin for the `*OrThrow` hooks: same layering,
|
|
83
|
+
* no wrapper — the hook's return IS the (selected) value.
|
|
84
84
|
*/
|
|
85
85
|
export declare function useSelectedValue<T, U>(value: T, options: SelectOptions<T, U> | undefined): T | U;
|
|
86
86
|
/**
|
|
87
|
-
* The raw-slice stabilization layer
|
|
88
|
-
*
|
|
87
|
+
* The raw-slice stabilization layer: while `route` and `fingerprint` are
|
|
88
|
+
* unchanged from the previous render, the previous result — success OR
|
|
89
89
|
* error arm — is returned without recomputing, so a fresh `useSearchParams()`
|
|
90
90
|
* / `query` object whose DECLARED slice is unchanged (`?utm_source=` churn)
|
|
91
91
|
* costs neither a decode nor anyone's referential equality. This replaces
|
|
92
|
-
* the
|
|
92
|
+
* the earlier "memo keyed on Next's object reference" behavior.
|
|
93
93
|
*/
|
|
94
94
|
export declare function useStableResult<T>(route: AnyRoute, fingerprint: string, compute: () => T): T;
|
package/dist/select.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useRef } from "react";
|
|
2
2
|
/**
|
|
3
|
-
* Fingerprint of the pages hooks' pre-`isReady` state
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Fingerprint of the pages hooks' pre-`isReady` state. Every real
|
|
4
|
+
* fingerprint is a `JSON.stringify`'d array (starts with `[`), so this can
|
|
5
|
+
* never collide with one.
|
|
6
6
|
*/
|
|
7
7
|
export const PENDING_FINGERPRINT = "pending";
|
|
8
8
|
/**
|
|
9
|
-
* Raw slice of a params source
|
|
10
|
-
*
|
|
9
|
+
* Raw slice of a params source: the route's dynamic segment names' raw
|
|
10
|
+
* values, from the define-time `~segments` token cache. Unknown keys —
|
|
11
11
|
* e.g. a parallel route's params in the same `useParams()` bag — never bust
|
|
12
12
|
* the fingerprint, because the decode never reads them.
|
|
13
13
|
*/
|
|
@@ -15,13 +15,13 @@ export function paramsFingerprint(route, source) {
|
|
|
15
15
|
return recordFingerprint(dynamicSegmentNames(route), source);
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Raw slice of a pages `router.query` bag for the search half
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Raw slice of a pages `router.query` bag for the search half. A codec-map
|
|
19
|
+
* route reads exactly its declared keys (query junk and the route's own path
|
|
20
|
+
* params are invisible to the decode — the two namespaces are disjoint); a
|
|
21
21
|
* `rawSearch` route has no enumerable declared-key set — the schema sees
|
|
22
|
-
* every key except the route's path params (
|
|
23
|
-
* exactly that slice is fingerprinted, in sorted-key order for
|
|
24
|
-
* independence.
|
|
22
|
+
* every key except the route's path params (by subtracting them from the
|
|
23
|
+
* bag), so exactly that slice is fingerprinted, in sorted-key order for
|
|
24
|
+
* record-order independence.
|
|
25
25
|
*/
|
|
26
26
|
export function queryFingerprint(route, query) {
|
|
27
27
|
const declared = declaredSearchKeys(route);
|
|
@@ -34,7 +34,7 @@ export function queryFingerprint(route, query) {
|
|
|
34
34
|
return recordFingerprint(keys, query);
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Raw slice of an app `useSearchParams()` source
|
|
37
|
+
* Raw slice of an app `useSearchParams()` source: the declared keys'
|
|
38
38
|
* `[key, value]` pairs in wire order — order is load-bearing for repeated
|
|
39
39
|
* keys (array codecs decode in wire order, P5/S5), and iterating the live
|
|
40
40
|
* pairs preserves the relative order of declared entries while `?utm_*`
|
|
@@ -62,15 +62,15 @@ export function useSelectedResult(result, options) {
|
|
|
62
62
|
return result;
|
|
63
63
|
const previous = cache.current;
|
|
64
64
|
if (previous !== null && Object.is(previous.input, result.data)) {
|
|
65
|
-
// Reference-stable input ⇒ equal output by selector purity
|
|
66
|
-
//
|
|
65
|
+
// Reference-stable input ⇒ equal output by selector purity; the selector
|
|
66
|
+
// is deliberately not re-run.
|
|
67
67
|
return previous.wrapped;
|
|
68
68
|
}
|
|
69
|
-
const selected = options.select(result.data); // a throw propagates
|
|
69
|
+
const selected = options.select(result.data); // a throw propagates
|
|
70
70
|
if (previous !== null &&
|
|
71
71
|
selectedEquals(options.equality, previous.wrapped.data, selected)) {
|
|
72
|
-
// Same selection out of a new decode: keep the previous wrapper
|
|
73
|
-
//
|
|
72
|
+
// Same selection out of a new decode: keep the previous wrapper and
|
|
73
|
+
// re-key the cache so the next render takes the reference fast path.
|
|
74
74
|
previous.input = result.data;
|
|
75
75
|
return previous.wrapped;
|
|
76
76
|
}
|
|
@@ -82,8 +82,8 @@ export function useSelectedResult(result, options) {
|
|
|
82
82
|
return wrapped;
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
* {@link useSelectedResult}'s twin for the `*OrThrow` hooks
|
|
86
|
-
*
|
|
85
|
+
* {@link useSelectedResult}'s twin for the `*OrThrow` hooks: same layering,
|
|
86
|
+
* no wrapper — the hook's return IS the (selected) value.
|
|
87
87
|
*/
|
|
88
88
|
export function useSelectedValue(value, options) {
|
|
89
89
|
const cache = useRef(null);
|
|
@@ -91,9 +91,9 @@ export function useSelectedValue(value, options) {
|
|
|
91
91
|
return value;
|
|
92
92
|
const previous = cache.current;
|
|
93
93
|
if (previous !== null && Object.is(previous.input, value)) {
|
|
94
|
-
return previous.selected; //
|
|
94
|
+
return previous.selected; // selector purity: not re-run
|
|
95
95
|
}
|
|
96
|
-
const selected = options.select(value); // a throw propagates
|
|
96
|
+
const selected = options.select(value); // a throw propagates
|
|
97
97
|
if (previous !== null &&
|
|
98
98
|
selectedEquals(options.equality, previous.selected, selected)) {
|
|
99
99
|
previous.input = value;
|
|
@@ -103,12 +103,12 @@ export function useSelectedValue(value, options) {
|
|
|
103
103
|
return selected;
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* The raw-slice stabilization layer
|
|
107
|
-
*
|
|
106
|
+
* The raw-slice stabilization layer: while `route` and `fingerprint` are
|
|
107
|
+
* unchanged from the previous render, the previous result — success OR
|
|
108
108
|
* error arm — is returned without recomputing, so a fresh `useSearchParams()`
|
|
109
109
|
* / `query` object whose DECLARED slice is unchanged (`?utm_source=` churn)
|
|
110
110
|
* costs neither a decode nor anyone's referential equality. This replaces
|
|
111
|
-
* the
|
|
111
|
+
* the earlier "memo keyed on Next's object reference" behavior.
|
|
112
112
|
*/
|
|
113
113
|
export function useStableResult(route, fingerprint, compute) {
|
|
114
114
|
const cache = useRef(null);
|
|
@@ -120,7 +120,7 @@ export function useStableResult(route, fingerprint, compute) {
|
|
|
120
120
|
throw cached.outcome.thrown;
|
|
121
121
|
return cached.outcome.value;
|
|
122
122
|
}
|
|
123
|
-
// Cleared BEFORE computing
|
|
123
|
+
// Cleared BEFORE computing: no half-computed state may survive a
|
|
124
124
|
// throw, and a NEW fingerprint always recomputes — an error boundary
|
|
125
125
|
// reset after the URL is fixed can never be served a stale entry.
|
|
126
126
|
cache.current = null;
|
|
@@ -152,7 +152,7 @@ export function useStableResult(route, fingerprint, compute) {
|
|
|
152
152
|
* Declared search keys of a route's `~search` slot, or `null` for a
|
|
153
153
|
* `rawSearch` route (whose schema owns every key, so no declared subset
|
|
154
154
|
* exists). The `~kind` marker is unambiguous against a codec map, which
|
|
155
|
-
* never carries a top-level `~`-prefixed key (
|
|
155
|
+
* never carries a top-level `~`-prefixed key (SS2).
|
|
156
156
|
*/
|
|
157
157
|
function declaredSearchKeys(route) {
|
|
158
158
|
const config = route["~search"];
|
|
@@ -185,16 +185,16 @@ function recordFingerprint(keys, source) {
|
|
|
185
185
|
Object.hasOwn(source, key) ? (source[key] ?? null) : null,
|
|
186
186
|
]));
|
|
187
187
|
}
|
|
188
|
-
/**
|
|
188
|
+
/** Result equality: `Object.is`, widened one level by `"shallow"`. */
|
|
189
189
|
function selectedEquals(equality, a, b) {
|
|
190
190
|
if (Object.is(a, b))
|
|
191
191
|
return true;
|
|
192
192
|
return equality === "shallow" && shallowEqual(a, b);
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
|
-
* One-level equality for the `"shallow"` opt-in
|
|
196
|
-
*
|
|
197
|
-
* recursive (deep comparison in a render path is a non-goal
|
|
195
|
+
* One-level equality for the `"shallow"` opt-in: arrays element-wise, plain
|
|
196
|
+
* objects by own enumerable keys — `Object.is` at each leaf, nothing
|
|
197
|
+
* recursive (deep comparison in a render path is a non-goal).
|
|
198
198
|
*/
|
|
199
199
|
function shallowEqual(a, b) {
|
|
200
200
|
if (typeof a !== "object" ||
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
import type { ParamsSource } from "paramour";
|
|
2
2
|
import type { ReactElement, ReactNode } from "react";
|
|
3
3
|
/**
|
|
4
|
-
* `@paramour-js/next/testing
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* `@paramour-js/next/testing`: a provider that overrides the hooks'
|
|
5
|
+
* framework reads through the adapter seam, so client components calling
|
|
6
|
+
* `useSearch`/`useRouteParams` (either flavor) can be unit-tested without
|
|
7
|
+
* runner-specific `next/*` module mocking. One provider feeds BOTH flavor
|
|
8
|
+
* contexts — hybrid apps and pages components need no second import. Server
|
|
9
|
+
* code needs none of this: `parse`/`safeParse` and server components are
|
|
10
|
+
* pure functions over props.
|
|
11
11
|
*
|
|
12
12
|
* This module imports ONLY react and the (Next-free) adapter-seam module —
|
|
13
|
-
* no `next/*` specifier and no `@testing-library
|
|
14
|
-
*
|
|
15
|
-
* spell out the two Next specifiers
|
|
16
|
-
* app.ts.
|
|
13
|
+
* no `next/*` specifier and no `@testing-library/*`; dist.test.ts pins the
|
|
14
|
+
* bundle graph, and the hermeticity check there is why these docs never
|
|
15
|
+
* spell out the two Next specifiers. It carries `"use client"` like app.ts.
|
|
17
16
|
*
|
|
18
|
-
* Stability contract
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* with new props.
|
|
17
|
+
* Stability contract: the provider holds ONE adapter pair for its lifetime,
|
|
18
|
+
* created once and closing over a latest-props ref that is reassigned every
|
|
19
|
+
* render — prop changes mutate what the stable adapters RETURN, they never
|
|
20
|
+
* mint new adapters, so the hooks' context reads stay identity-stable and
|
|
21
|
+
* mid-test URL changes are driven by ordinary rerenders with new props.
|
|
24
22
|
*/
|
|
25
23
|
/**
|
|
26
|
-
* Input shape mirrors what Next hands the hooks
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* Input shape mirrors what Next hands the hooks — no `url` reverse-matching
|
|
25
|
+
* in v1 (deferred: needs a core matcher). `params: null` and
|
|
26
|
+
* `mounted: false` are first-class because they are the two states nobody
|
|
27
|
+
* hand-rolling a mock models.
|
|
30
28
|
*/
|
|
31
29
|
export interface ParamourTestingOptions {
|
|
32
30
|
/**
|
|
33
31
|
* Pages flavor only: `false` is the pre-hydration state of a
|
|
34
|
-
* statically-optimized page (`query` not yet populated — the
|
|
35
|
-
* `pending` arm). Defaults to `true`.
|
|
36
|
-
* but TA7 migrates the whole pages suite — which pins the pending arm —
|
|
37
|
-
* to this provider, so it was promoted from the deferred list.
|
|
32
|
+
* statically-optimized page (`query` not yet populated — the hooks'
|
|
33
|
+
* `pending` arm). Defaults to `true`.
|
|
38
34
|
*/
|
|
39
35
|
isReady?: boolean;
|
|
40
36
|
/**
|
|
41
37
|
* Pages flavor only: `false` reproduces the pages router's
|
|
42
|
-
* throw-on-unmounted state under `app/`
|
|
43
|
-
*
|
|
38
|
+
* throw-on-unmounted state under `app/` — pages.ts translates it to a
|
|
39
|
+
* `ParamourError` naming the actual mistake.
|
|
44
40
|
*/
|
|
45
41
|
mounted?: boolean;
|
|
46
42
|
/** Captures `replace(href)` from either flavor's router. */
|
|
@@ -56,16 +52,16 @@ export interface ParamourTestingOptions {
|
|
|
56
52
|
search?: string | URLSearchParams;
|
|
57
53
|
}
|
|
58
54
|
/**
|
|
59
|
-
* Renders BOTH flavor contexts' providers around `children
|
|
60
|
-
*
|
|
61
|
-
*
|
|
55
|
+
* Renders BOTH flavor contexts' providers around `children`. Exported for
|
|
56
|
+
* people composing their own wrappers (Storybook decorators, custom render
|
|
57
|
+
* helpers); testing-library users want {@link withParamourTesting}.
|
|
62
58
|
*/
|
|
63
59
|
export declare function ParamourTestingProvider(props: ParamourTestingOptions & {
|
|
64
60
|
children?: ReactNode;
|
|
65
61
|
}): ReactElement;
|
|
66
62
|
/**
|
|
67
|
-
* Wrapper-component form for testing-library's `wrapper` option
|
|
68
|
-
*
|
|
63
|
+
* Wrapper-component form for testing-library's `wrapper` option, mirroring
|
|
64
|
+
* `withNuqsTestingAdapter`.
|
|
69
65
|
*/
|
|
70
66
|
export declare function withParamourTesting(options?: ParamourTestingOptions): (props: {
|
|
71
67
|
children?: ReactNode;
|
package/dist/testing.js
CHANGED
|
@@ -3,21 +3,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
4
|
import { AppNavigationContext, PagesNavigationContext, } from "./navigation-adapter.js";
|
|
5
5
|
/**
|
|
6
|
-
* Renders BOTH flavor contexts' providers around `children
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Renders BOTH flavor contexts' providers around `children`. Exported for
|
|
7
|
+
* people composing their own wrappers (Storybook decorators, custom render
|
|
8
|
+
* helpers); testing-library users want {@link withParamourTesting}.
|
|
9
9
|
*/
|
|
10
10
|
export function ParamourTestingProvider(props) {
|
|
11
|
-
// Latest-ref pattern
|
|
12
|
-
//
|
|
11
|
+
// Latest-ref pattern: reassigned every render so the stable adapters
|
|
12
|
+
// below always read the CURRENT render's props.
|
|
13
13
|
const latest = useRef(props);
|
|
14
14
|
latest.current = props;
|
|
15
15
|
const [adapters] = useState(() => createAdapters(latest));
|
|
16
16
|
return (_jsx(AppNavigationContext.Provider, { value: adapters.app, children: _jsx(PagesNavigationContext.Provider, { value: adapters.pages, children: props.children }) }));
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Wrapper-component form for testing-library's `wrapper` option
|
|
20
|
-
*
|
|
19
|
+
* Wrapper-component form for testing-library's `wrapper` option, mirroring
|
|
20
|
+
* `withNuqsTestingAdapter`.
|
|
21
21
|
*/
|
|
22
22
|
export function withParamourTesting(options = {}) {
|
|
23
23
|
return function ParamourTestingWrapper({ children, }) {
|
|
@@ -25,10 +25,10 @@ export function withParamourTesting(options = {}) {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* The one adapter pair a provider instance ever holds
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* The one adapter pair a provider instance ever holds. Every read defers to
|
|
29
|
+
* `latest.current`, so the adapters are stable while their answers track
|
|
30
|
+
* prop updates. Fresh `URLSearchParams` per call is fine — the hooks
|
|
31
|
+
* fingerprint the declared slice, not the instance.
|
|
32
32
|
*/
|
|
33
33
|
function createAdapters(latest) {
|
|
34
34
|
const app = {
|
|
@@ -57,20 +57,20 @@ function createAdapters(latest) {
|
|
|
57
57
|
const options = latest.current;
|
|
58
58
|
if (options.mounted === false) {
|
|
59
59
|
// Verbatim prefix of next/router's real unmounted error — pages.ts
|
|
60
|
-
// matches on the message to translate it
|
|
60
|
+
// matches on the message to translate it.
|
|
61
61
|
throw new Error("NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted");
|
|
62
62
|
}
|
|
63
63
|
const search = normalizeSearch(options.search);
|
|
64
64
|
return {
|
|
65
|
-
// asPath derives from pathname + normalized search
|
|
65
|
+
// asPath derives from pathname + normalized search —
|
|
66
66
|
// basePath-relative, what the devtools navigate capability resolves
|
|
67
|
-
// against
|
|
67
|
+
// against.
|
|
68
68
|
asPath: (options.pathname ?? "/") + (search === "" ? "" : `?${search}`),
|
|
69
69
|
isReady: options.isReady ?? true,
|
|
70
70
|
query: mergedQuery(options),
|
|
71
71
|
replace(url) {
|
|
72
72
|
latest.current.onReplace?.(url);
|
|
73
|
-
// Real next/router resolves `true` on a completed replace
|
|
73
|
+
// Real next/router resolves `true` on a completed replace.
|
|
74
74
|
return Promise.resolve(true);
|
|
75
75
|
},
|
|
76
76
|
};
|
package/dist/watch.d.ts
CHANGED
|
@@ -9,31 +9,31 @@ export interface WatchRouteDirsOptions {
|
|
|
9
9
|
debounceMs?: number;
|
|
10
10
|
/**
|
|
11
11
|
* Absolute paths whose events are ignored — the artifact file, so a
|
|
12
|
-
* regeneration write can't re-trigger the watcher
|
|
12
|
+
* regeneration write can't re-trigger the watcher in a feedback loop.
|
|
13
13
|
*/
|
|
14
14
|
ignorePaths?: readonly string[];
|
|
15
15
|
/**
|
|
16
16
|
* Watcher startup/runtime failures and `onRescan` throws land here.
|
|
17
|
-
* Surfaced, not logged:
|
|
18
|
-
*
|
|
17
|
+
* Surfaced, not logged: the "log once, dev continues" behavior belongs to
|
|
18
|
+
* the composition points, not this module.
|
|
19
19
|
*/
|
|
20
20
|
onError?: (error: unknown) => void;
|
|
21
|
-
/** The regenerate callback — full rescan → write-if-changed
|
|
21
|
+
/** The regenerate callback — full rescan → write-if-changed. */
|
|
22
22
|
onRescan: () => void;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/** ~100 ms — long enough to coalesce an editor save storm. */
|
|
25
25
|
export declare const DEFAULT_DEBOUNCE_MS = 100;
|
|
26
26
|
/**
|
|
27
27
|
* Debounced full-rescan watcher over the route dirs — both of them in a
|
|
28
|
-
* hybrid project
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* hybrid project, sharing one debounce so an editor operation touching both
|
|
29
|
+
* coalesces into a single rescan. Because a scan is milliseconds, no event
|
|
30
|
+
* fidelity is needed: any event → debounce → `onRescan`. Native
|
|
31
31
|
* `fs.watch({ recursive: true })`, no chokidar; this start/close interface
|
|
32
32
|
* is the seam chokidar would drop in behind if a platform hole appears.
|
|
33
33
|
*
|
|
34
|
-
* A missing dir is skipped — not watched, not an error
|
|
35
|
-
*
|
|
36
|
-
* continuing in stale-types mode is exactly
|
|
37
|
-
* startup failures still surface through `onError`.
|
|
34
|
+
* A missing dir is skipped — not watched, not an error: callers pass the
|
|
35
|
+
* dirs discovery resolved, so absence here is a raced deletion, and dev
|
|
36
|
+
* continuing in stale-types mode is exactly the intended posture. Genuine
|
|
37
|
+
* watch startup failures still surface through `onError`.
|
|
38
38
|
*/
|
|
39
39
|
export declare function watchRouteDirs(dirs: readonly string[], options: WatchRouteDirsOptions): RouteDirsWatcher;
|
package/dist/watch.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { statSync, watch } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
/**
|
|
3
|
+
/** ~100 ms — long enough to coalesce an editor save storm. */
|
|
4
4
|
export const DEFAULT_DEBOUNCE_MS = 100;
|
|
5
5
|
/**
|
|
6
6
|
* Directory names whose subtrees are ignored if they ever fall under a
|
|
7
|
-
* watched root
|
|
7
|
+
* watched root.
|
|
8
8
|
*/
|
|
9
9
|
const IGNORED_SEGMENTS = new Set([".next", "node_modules"]);
|
|
10
10
|
/**
|
|
11
11
|
* Debounced full-rescan watcher over the route dirs — both of them in a
|
|
12
|
-
* hybrid project
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* hybrid project, sharing one debounce so an editor operation touching both
|
|
13
|
+
* coalesces into a single rescan. Because a scan is milliseconds, no event
|
|
14
|
+
* fidelity is needed: any event → debounce → `onRescan`. Native
|
|
15
15
|
* `fs.watch({ recursive: true })`, no chokidar; this start/close interface
|
|
16
16
|
* is the seam chokidar would drop in behind if a platform hole appears.
|
|
17
17
|
*
|
|
18
|
-
* A missing dir is skipped — not watched, not an error
|
|
19
|
-
*
|
|
20
|
-
* continuing in stale-types mode is exactly
|
|
21
|
-
* startup failures still surface through `onError`.
|
|
18
|
+
* A missing dir is skipped — not watched, not an error: callers pass the
|
|
19
|
+
* dirs discovery resolved, so absence here is a raced deletion, and dev
|
|
20
|
+
* continuing in stale-types mode is exactly the intended posture. Genuine
|
|
21
|
+
* watch startup failures still surface through `onError`.
|
|
22
22
|
*/
|
|
23
23
|
export function watchRouteDirs(dirs, options) {
|
|
24
24
|
const { debounceMs = DEFAULT_DEBOUNCE_MS, ignorePaths = [], onError, onRescan, } = options;
|
|
@@ -31,7 +31,7 @@ export function watchRouteDirs(dirs, options) {
|
|
|
31
31
|
onRescan();
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
|
-
// A throwing regeneration must not kill the watcher
|
|
34
|
+
// A throwing regeneration must not kill the watcher — non-fatal.
|
|
35
35
|
onError?.(error);
|
|
36
36
|
}
|
|
37
37
|
}, debounceMs);
|
|
@@ -50,7 +50,7 @@ export function watchRouteDirs(dirs, options) {
|
|
|
50
50
|
watcher = watch(dir, { recursive: true }, (_eventType, filename) => {
|
|
51
51
|
// `filename` can be null (platform-dependent); with nothing to
|
|
52
52
|
// filter on, err toward rescanning — a spurious pass is a no-op
|
|
53
|
-
// write
|
|
53
|
+
// write.
|
|
54
54
|
if (filename !== null) {
|
|
55
55
|
if (ignored.has(resolve(dir, filename)))
|
|
56
56
|
return;
|
|
@@ -63,8 +63,8 @@ export function watchRouteDirs(dirs, options) {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
catch (error) {
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// Watcher failure is non-fatal — dev continues in stale-types mode,
|
|
67
|
+
// and the other dir's watcher (if any) keeps running.
|
|
68
68
|
onError?.(error);
|
|
69
69
|
continue;
|
|
70
70
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/** Options for {@link withTypedRoutes}
|
|
1
|
+
/** Options for {@link withTypedRoutes}. */
|
|
2
2
|
export interface WithTypedRoutesOptions {
|
|
3
3
|
/**
|
|
4
4
|
* Artifact location, for monorepos where the Next app root isn't where the
|
|
5
|
-
* file should live
|
|
5
|
+
* file should live — the escape hatch. Relative paths resolve against the
|
|
6
6
|
* project root. Default: `paramour-env.d.ts` at the project root.
|
|
7
7
|
*/
|
|
8
8
|
outFile?: string;
|
|
9
9
|
/**
|
|
10
|
-
* Upgrade build-phase drift from a loud warning to a build failure
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Upgrade build-phase drift from a loud warning to a build failure — for
|
|
11
|
+
* teams that want the committed artifact to be the law. Default `false`,
|
|
12
|
+
* friendly to gitignored-file workflows and CI images.
|
|
13
13
|
*/
|
|
14
14
|
strict?: boolean;
|
|
15
15
|
}
|
|
@@ -24,21 +24,22 @@ export declare function devWatcherCountForTests(): number;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function resetDevWatchersForTests(): void;
|
|
26
26
|
/**
|
|
27
|
-
* Wrap a Next config with route-registry generation
|
|
27
|
+
* Wrap a Next config with route-registry generation. Returns the
|
|
28
28
|
* config-function form; Next's phase argument is the mode discriminator:
|
|
29
29
|
*
|
|
30
30
|
* - production build → one generation pass before the config is returned
|
|
31
31
|
* (the build type-checks against fresh routes); drift warns loudly, or
|
|
32
32
|
* fails the build under `strict: true`.
|
|
33
33
|
* - dev server → one immediate generation pass, then the debounced watcher
|
|
34
|
-
*
|
|
34
|
+
* behind both single-writer guards (the in-process singleton and the
|
|
35
|
+
* cross-process pidfile lock).
|
|
35
36
|
* - every other phase → pass-through, no generation.
|
|
36
37
|
*
|
|
37
38
|
* Two states throw during config evaluation instead of degrading to
|
|
38
39
|
* stale-types mode, both phases alike, because Next itself has no valid
|
|
39
|
-
* build for them: an app↔pages route collision
|
|
40
|
-
* populated-ignored-dir config error (
|
|
41
|
-
*
|
|
40
|
+
* build for them: an app↔pages route collision, and discovery's
|
|
41
|
+
* populated-ignored-dir config error (Next is silently serving none of those
|
|
42
|
+
* pages).
|
|
42
43
|
*/
|
|
43
44
|
export declare function withTypedRoutes<C extends object>(config: C | ConfigFunction<C>, options?: WithTypedRoutesOptions): ConfigFunction<C>;
|
|
44
45
|
export {};
|