@teleporthq/teleport-plugin-common 0.43.14 → 0.43.22

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.
@@ -0,0 +1,177 @@
1
+ import * as types from '@babel/types';
2
+ // Shared builders for two-way `<state> ⇄ ?key=` URL search-param syncing.
3
+ //
4
+ // Used by BOTH:
5
+ // • `createNextUrlSearchParamsPlugin` (teleport-project-generator-next) for
6
+ // page/component state defs that declare `urlSearchParamBinding` — e.g.
7
+ // the products-list `selectedCategory` / `sortBy` dropdowns.
8
+ // • `createNextArrayMapperPaginationPlugin` (teleport-plugin-next-data-source)
9
+ // for the products-list search input, whose query lives in a
10
+ // pagination-managed state (`ds_N_searchQuery` / `ds_N_state.debouncedQuery`)
11
+ // rather than a UIDL state def, so it cannot ride the plugin above.
12
+ //
13
+ // Both emit the SAME loop-free read-back / write-back effect shapes, so the
14
+ // builders live here to keep the two call sites byte-identical and the
15
+ // loop-prevention reasoning in one place.
16
+ // Strict JS identifier (Latin only is fine: UIDL URL keys are limited to the
17
+ // subset Next.js supports as a query param key, which the GUI guarantees to
18
+ // be at minimum URL-safe; but the *generated* code must be valid JS, and
19
+ // `__nextQuery.foo-bar` parses as a subtraction). Anything off-pattern goes
20
+ // through bracket notation: `__nextQuery['foo-bar']`.
21
+ var VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
22
+ // `object.<paramKey>` for identifier-safe keys, `object['param-key']` otherwise,
23
+ // so the emitted code is always valid JS regardless of the URL key shape.
24
+ var accessKeyOf = function (object, paramKey) {
25
+ if (VALID_JS_IDENTIFIER.test(paramKey)) {
26
+ return types.memberExpression(object, types.identifier(paramKey));
27
+ }
28
+ return types.memberExpression(object, types.stringLiteral(paramKey), true);
29
+ };
30
+ var routerQueryAccess = function (paramKey) {
31
+ return accessKeyOf(types.memberExpression(types.identifier('router'), types.identifier('query')), paramKey);
32
+ };
33
+ // Clones an expression so it can be slotted into multiple positions of the
34
+ // emitted AST — Babel does not support sharing the same node reference across
35
+ // the tree, and callers pass arbitrary value/dependency expressions.
36
+ var clone = function (expr) {
37
+ return types.cloneNode(expr, /* deep */ true);
38
+ };
39
+ // Builds the state→URL write-back useEffect. Emitted shape (for a value of
40
+ // `selectedCategory` bound to URL key `categoryFilter`):
41
+ //
42
+ // useEffect(() => {
43
+ // if (typeof window === 'undefined') return
44
+ // if (!router.isReady) return
45
+ // const __nextQuery = { ...router.query }
46
+ // if (selectedCategory === '' || selectedCategory == null) {
47
+ // delete __nextQuery.categoryFilter
48
+ // } else {
49
+ // __nextQuery.categoryFilter = String(selectedCategory)
50
+ // }
51
+ // if (__nextQuery.categoryFilter === router.query.categoryFilter) return
52
+ // router.replace(
53
+ // { pathname: router.pathname, query: __nextQuery },
54
+ // undefined,
55
+ // { shallow: true }
56
+ // )
57
+ // }, [selectedCategory, router.isReady])
58
+ //
59
+ // `valueExpr` is the expression read each render and pushed to the URL (a bare
60
+ // state identifier for dropdowns, `ds_N_state.debouncedQuery` for the search
61
+ // input). `depExpr` is the effect dependency that re-triggers the write-back
62
+ // when that value changes — usually the same expression as `valueExpr`.
63
+ //
64
+ // Notes:
65
+ // 1. `router.isReady` gate: on the very first SSG render `router.query` is
66
+ // empty, so without this gate any value initialized from
67
+ // `window.location.search` would immediately `router.replace` itself with
68
+ // the same value, racing with Next's own hydration. `router.isReady` is
69
+ // also in the deps array — once it flips to true, the effect re-runs with
70
+ // the now-hydrated `router.query` and decides correctly whether to replace.
71
+ // 2. The equality check after building `__nextQuery` short-circuits replaces
72
+ // that would not change the URL — important when the user re-enters the
73
+ // same value, when the value mounts equal to the URL, and when the
74
+ // read-back effect fires its setter with the value just pushed.
75
+ // 3. `shallow: true` keeps `getStaticProps` from re-running just because the
76
+ // URL bar changed; the data-source `useMemo` reacts to the state, not the
77
+ // URL.
78
+ // 4. Empty / null value deletes the key entirely so the URL never keeps a
79
+ // sticky `?key=` empty param.
80
+ export var buildUrlWriteBackEffect = function (paramKey, valueExpr, depExpr) {
81
+ var effectBody = types.blockStatement([
82
+ // if (typeof window === 'undefined') return
83
+ types.ifStatement(types.binaryExpression('===', types.unaryExpression('typeof', types.identifier('window')), types.stringLiteral('undefined')), types.returnStatement(null)),
84
+ // if (!router.isReady) return
85
+ types.ifStatement(types.unaryExpression('!', types.memberExpression(types.identifier('router'), types.identifier('isReady'))), types.returnStatement(null)),
86
+ // const __nextQuery = { ...router.query }
87
+ types.variableDeclaration('const', [
88
+ types.variableDeclarator(types.identifier('__nextQuery'), types.objectExpression([
89
+ types.spreadElement(types.memberExpression(types.identifier('router'), types.identifier('query'))),
90
+ ])),
91
+ ]),
92
+ // if (value === '' || value == null) { delete __nextQuery.key }
93
+ // else { __nextQuery.key = String(value) }
94
+ types.ifStatement(types.logicalExpression('||', types.binaryExpression('===', clone(valueExpr), types.stringLiteral('')), types.binaryExpression('==', clone(valueExpr), types.nullLiteral())), types.blockStatement([
95
+ types.expressionStatement(types.unaryExpression('delete', accessKeyOf(types.identifier('__nextQuery'), paramKey))),
96
+ ]), types.blockStatement([
97
+ types.expressionStatement(types.assignmentExpression('=', accessKeyOf(types.identifier('__nextQuery'), paramKey), types.callExpression(types.identifier('String'), [clone(valueExpr)]))),
98
+ ])),
99
+ // if (__nextQuery.key === router.query.key) return
100
+ types.ifStatement(types.binaryExpression('===', accessKeyOf(types.identifier('__nextQuery'), paramKey), routerQueryAccess(paramKey)), types.returnStatement(null)),
101
+ // router.replace({ pathname: router.pathname, query: __nextQuery }, undefined, { shallow: true })
102
+ types.expressionStatement(types.callExpression(types.memberExpression(types.identifier('router'), types.identifier('replace')), [
103
+ types.objectExpression([
104
+ types.objectProperty(types.identifier('pathname'), types.memberExpression(types.identifier('router'), types.identifier('pathname'))),
105
+ types.objectProperty(types.identifier('query'), types.identifier('__nextQuery')),
106
+ ]),
107
+ types.identifier('undefined'),
108
+ types.objectExpression([
109
+ types.objectProperty(types.identifier('shallow'), types.booleanLiteral(true)),
110
+ ]),
111
+ ])),
112
+ ]);
113
+ return types.expressionStatement(types.callExpression(types.identifier('useEffect'), [
114
+ types.arrowFunctionExpression([], effectBody),
115
+ types.arrayExpression([
116
+ clone(depExpr),
117
+ types.memberExpression(types.identifier('router'), types.identifier('isReady')),
118
+ ]),
119
+ ]));
120
+ };
121
+ // Builds the URL→state read-back useEffect. Emitted shape (for setter
122
+ // `setSelectedCategory` bound to URL key `categoryFilter`):
123
+ //
124
+ // useEffect(() => {
125
+ // if (!router.isReady) return
126
+ // const __urlValue = router.query.categoryFilter
127
+ // const __nextValue =
128
+ // typeof __urlValue === 'string' ? __urlValue : Array.isArray(__urlValue) ? (__urlValue[0] || '') : ''
129
+ // setSelectedCategory((prev) => (prev === __nextValue ? prev : __nextValue))
130
+ // }, [router.query.categoryFilter, router.isReady])
131
+ //
132
+ // Why pair this with the write-back effect:
133
+ // - On `<Link>` / `router.push` shallow navigations that land on the same path
134
+ // with a different query (or browser back/forward, which can change the query
135
+ // without remounting the page), `router.query` updates but the state hook does
136
+ // not. Without this effect, the control would be stuck on whatever the user
137
+ // last picked even though the URL has moved on.
138
+ // - The functional `setState((prev) => prev === next ? prev : next)` form
139
+ // guarantees React bails out when the URL value matches state, which prevents
140
+ // an infinite write-back ↔ read-back loop: when the user picks a new value,
141
+ // state→URL fires once, URL→state observes the matching value and returns
142
+ // `prev`, so React does not re-render or fire any deps.
143
+ // - `router.query[key]` can be either `string` (single value) or `string[]`
144
+ // (duplicate keys) per Next.js's typings; the helper normalizes both to a
145
+ // string before comparing, defaulting to `''` for the missing-key case so the
146
+ // comparison aligns with the write-back's `value === ''` empty-check.
147
+ export var buildUrlReadBackEffect = function (paramKey, setterName) {
148
+ var normalizeArg = types.conditionalExpression(
149
+ // typeof __urlValue === 'string' ? __urlValue
150
+ types.binaryExpression('===', types.unaryExpression('typeof', types.identifier('__urlValue')), types.stringLiteral('string')), types.identifier('__urlValue'), types.conditionalExpression(
151
+ // Array.isArray(__urlValue) ? (__urlValue[0] || '') : ''
152
+ types.callExpression(types.memberExpression(types.identifier('Array'), types.identifier('isArray')), [types.identifier('__urlValue')]), types.logicalExpression('||', types.memberExpression(types.identifier('__urlValue'), types.numericLiteral(0), true), types.stringLiteral('')), types.stringLiteral('')));
153
+ var effectBody = types.blockStatement([
154
+ // if (!router.isReady) return
155
+ types.ifStatement(types.unaryExpression('!', types.memberExpression(types.identifier('router'), types.identifier('isReady'))), types.returnStatement(null)),
156
+ // const __urlValue = router.query.<paramKey>
157
+ types.variableDeclaration('const', [
158
+ types.variableDeclarator(types.identifier('__urlValue'), routerQueryAccess(paramKey)),
159
+ ]),
160
+ // const __nextValue = (normalized)
161
+ types.variableDeclaration('const', [
162
+ types.variableDeclarator(types.identifier('__nextValue'), normalizeArg),
163
+ ]),
164
+ // setState((prev) => prev === __nextValue ? prev : __nextValue)
165
+ types.expressionStatement(types.callExpression(types.identifier(setterName), [
166
+ types.arrowFunctionExpression([types.identifier('prev')], types.conditionalExpression(types.binaryExpression('===', types.identifier('prev'), types.identifier('__nextValue')), types.identifier('prev'), types.identifier('__nextValue'))),
167
+ ])),
168
+ ]);
169
+ return types.expressionStatement(types.callExpression(types.identifier('useEffect'), [
170
+ types.arrowFunctionExpression([], effectBody),
171
+ types.arrayExpression([
172
+ routerQueryAccess(paramKey),
173
+ types.memberExpression(types.identifier('router'), types.identifier('isReady')),
174
+ ]),
175
+ ]));
176
+ };
177
+ //# sourceMappingURL=url-search-param-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-search-param-sync.js","sourceRoot":"","sources":["../../../src/utils/url-search-param-sync.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teleporthq/teleport-plugin-common",
3
- "version": "0.43.14",
3
+ "version": "0.43.22",
4
4
  "description": "Common building and modelating functions for ASTs and HASTs",
5
5
  "author": "teleportHQ",
6
6
  "license": "MIT",
@@ -27,10 +27,10 @@
27
27
  "@babel/core": "^7.5.5",
28
28
  "@babel/preset-react": "^7.22.5",
29
29
  "@babel/types": "^7.5.5",
30
- "@teleporthq/teleport-shared": "^0.43.14",
31
- "@teleporthq/teleport-types": "^0.43.14",
30
+ "@teleporthq/teleport-shared": "^0.43.22",
31
+ "@teleporthq/teleport-types": "^0.43.22",
32
32
  "jss": "^10.0.0",
33
33
  "jss-preset-default": "^10.0.0"
34
34
  },
35
- "gitHead": "dab51af409fb5b93dbee9c54ddcadfd87bf8f41e"
35
+ "gitHead": "4bac1ec64c3c7e9f14a84a260331206d5797b773"
36
36
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import * as HASTBuilders from './builders/hast-builders'
5
5
  import * as ASTUtils from './utils/ast-utils'
6
6
  import * as StyleUtils from './utils/style-utils'
7
7
  import * as HASTUtils from './utils/hast-utils'
8
+ import * as URLSearchParamSync from './utils/url-search-param-sync'
8
9
  import ParsedASTNode from './utils/parsed-ast'
9
10
 
10
11
  import { createBinaryExpression } from './node-handlers/node-to-jsx/utils'
@@ -16,6 +17,7 @@ export {
16
17
  ASTUtils,
17
18
  StyleUtils,
18
19
  HASTUtils,
20
+ URLSearchParamSync,
19
21
  ParsedASTNode,
20
22
  createBinaryExpression,
21
23
  }
@@ -1381,16 +1381,21 @@ const generateParamsAST = (
1381
1381
  }
1382
1382
 
1383
1383
  const generateRESTHeadersAST = (headers: UIDLResourceItem['headers']): types.ObjectProperty[] => {
1384
- return Object.keys(headers)
1385
- .filter((header) => header !== 'authToken')
1386
- .map((header) => {
1387
- const headerResolved = resolveResourceValue(headers[header])
1388
- const value =
1389
- headers[header].type === 'static'
1390
- ? types.stringLiteral(String(headerResolved))
1391
- : types.identifier(String(headerResolved))
1392
- return types.objectProperty(types.stringLiteral(header), value)
1393
- })
1384
+ return (
1385
+ Object.keys(headers)
1386
+ // `authToken` is rendered as the `Authorization` header; `authScheme` is a
1387
+ // generator-only directive for that header's prefix — neither is emitted as
1388
+ // its own literal HTTP header.
1389
+ .filter((header) => header !== 'authToken' && header !== 'authScheme')
1390
+ .map((header) => {
1391
+ const headerResolved = resolveResourceValue(headers[header])
1392
+ const value =
1393
+ headers[header].type === 'static'
1394
+ ? types.stringLiteral(String(headerResolved))
1395
+ : types.identifier(String(headerResolved))
1396
+ return types.objectProperty(types.stringLiteral(header), value)
1397
+ })
1398
+ )
1394
1399
  }
1395
1400
 
1396
1401
  export const generateMemberExpressionASTFromBase = (
@@ -1529,14 +1534,28 @@ const computeAuthorizationHeaderAST = (headers: UIDLResourceItem['headers']) =>
1529
1534
 
1530
1535
  const authTokenType = headers.authToken?.type
1531
1536
 
1537
+ // The auth scheme prefix defaults to `Bearer ` (back-compatible with every CMS
1538
+ // and data-source that stores a bare bearer token). A resource may override it
1539
+ // via a `headers.authScheme` static value — e.g. WordPress Basic / Application
1540
+ // Password auth sets `Basic ` so the rendered header is `Basic <base64>`
1541
+ // instead of the wrong `Bearer <base64>`. `authScheme` is consumed here and
1542
+ // filtered out of the emitted REST headers (see generateRESTHeadersAST).
1543
+ const authSchemeValue = headers.authScheme
1544
+ const schemePrefix =
1545
+ authSchemeValue &&
1546
+ authSchemeValue.type === 'static' &&
1547
+ typeof authSchemeValue.content === 'string'
1548
+ ? authSchemeValue.content
1549
+ : 'Bearer '
1550
+
1532
1551
  return types.objectProperty(
1533
1552
  types.identifier('Authorization'),
1534
1553
  types.templateLiteral(
1535
1554
  [
1536
1555
  types.templateElement(
1537
1556
  {
1538
- cooked: authTokenType === 'static' ? `Bearer ${authToken}` : 'Bearer ',
1539
- raw: authTokenType === 'static' ? `Bearer ${authToken}` : 'Bearer ',
1557
+ cooked: authTokenType === 'static' ? `${schemePrefix}${authToken}` : schemePrefix,
1558
+ raw: authTokenType === 'static' ? `${schemePrefix}${authToken}` : schemePrefix,
1540
1559
  },
1541
1560
  false
1542
1561
  ),
@@ -0,0 +1,286 @@
1
+ import * as types from '@babel/types'
2
+
3
+ // Shared builders for two-way `<state> ⇄ ?key=` URL search-param syncing.
4
+ //
5
+ // Used by BOTH:
6
+ // • `createNextUrlSearchParamsPlugin` (teleport-project-generator-next) for
7
+ // page/component state defs that declare `urlSearchParamBinding` — e.g.
8
+ // the products-list `selectedCategory` / `sortBy` dropdowns.
9
+ // • `createNextArrayMapperPaginationPlugin` (teleport-plugin-next-data-source)
10
+ // for the products-list search input, whose query lives in a
11
+ // pagination-managed state (`ds_N_searchQuery` / `ds_N_state.debouncedQuery`)
12
+ // rather than a UIDL state def, so it cannot ride the plugin above.
13
+ //
14
+ // Both emit the SAME loop-free read-back / write-back effect shapes, so the
15
+ // builders live here to keep the two call sites byte-identical and the
16
+ // loop-prevention reasoning in one place.
17
+
18
+ // Strict JS identifier (Latin only is fine: UIDL URL keys are limited to the
19
+ // subset Next.js supports as a query param key, which the GUI guarantees to
20
+ // be at minimum URL-safe; but the *generated* code must be valid JS, and
21
+ // `__nextQuery.foo-bar` parses as a subtraction). Anything off-pattern goes
22
+ // through bracket notation: `__nextQuery['foo-bar']`.
23
+ const VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
24
+
25
+ // `object.<paramKey>` for identifier-safe keys, `object['param-key']` otherwise,
26
+ // so the emitted code is always valid JS regardless of the URL key shape.
27
+ const accessKeyOf = (object: types.Expression, paramKey: string): types.MemberExpression => {
28
+ if (VALID_JS_IDENTIFIER.test(paramKey)) {
29
+ return types.memberExpression(object, types.identifier(paramKey))
30
+ }
31
+ return types.memberExpression(object, types.stringLiteral(paramKey), true)
32
+ }
33
+
34
+ const routerQueryAccess = (paramKey: string): types.MemberExpression =>
35
+ accessKeyOf(
36
+ types.memberExpression(types.identifier('router'), types.identifier('query')),
37
+ paramKey
38
+ )
39
+
40
+ // Clones an expression so it can be slotted into multiple positions of the
41
+ // emitted AST — Babel does not support sharing the same node reference across
42
+ // the tree, and callers pass arbitrary value/dependency expressions.
43
+ const clone = (expr: types.Expression): types.Expression =>
44
+ types.cloneNode(expr, /* deep */ true) as types.Expression
45
+
46
+ // Builds the state→URL write-back useEffect. Emitted shape (for a value of
47
+ // `selectedCategory` bound to URL key `categoryFilter`):
48
+ //
49
+ // useEffect(() => {
50
+ // if (typeof window === 'undefined') return
51
+ // if (!router.isReady) return
52
+ // const __nextQuery = { ...router.query }
53
+ // if (selectedCategory === '' || selectedCategory == null) {
54
+ // delete __nextQuery.categoryFilter
55
+ // } else {
56
+ // __nextQuery.categoryFilter = String(selectedCategory)
57
+ // }
58
+ // if (__nextQuery.categoryFilter === router.query.categoryFilter) return
59
+ // router.replace(
60
+ // { pathname: router.pathname, query: __nextQuery },
61
+ // undefined,
62
+ // { shallow: true }
63
+ // )
64
+ // }, [selectedCategory, router.isReady])
65
+ //
66
+ // `valueExpr` is the expression read each render and pushed to the URL (a bare
67
+ // state identifier for dropdowns, `ds_N_state.debouncedQuery` for the search
68
+ // input). `depExpr` is the effect dependency that re-triggers the write-back
69
+ // when that value changes — usually the same expression as `valueExpr`.
70
+ //
71
+ // Notes:
72
+ // 1. `router.isReady` gate: on the very first SSG render `router.query` is
73
+ // empty, so without this gate any value initialized from
74
+ // `window.location.search` would immediately `router.replace` itself with
75
+ // the same value, racing with Next's own hydration. `router.isReady` is
76
+ // also in the deps array — once it flips to true, the effect re-runs with
77
+ // the now-hydrated `router.query` and decides correctly whether to replace.
78
+ // 2. The equality check after building `__nextQuery` short-circuits replaces
79
+ // that would not change the URL — important when the user re-enters the
80
+ // same value, when the value mounts equal to the URL, and when the
81
+ // read-back effect fires its setter with the value just pushed.
82
+ // 3. `shallow: true` keeps `getStaticProps` from re-running just because the
83
+ // URL bar changed; the data-source `useMemo` reacts to the state, not the
84
+ // URL.
85
+ // 4. Empty / null value deletes the key entirely so the URL never keeps a
86
+ // sticky `?key=` empty param.
87
+ export const buildUrlWriteBackEffect = (
88
+ paramKey: string,
89
+ valueExpr: types.Expression,
90
+ depExpr: types.Expression
91
+ ): types.ExpressionStatement => {
92
+ const effectBody = types.blockStatement([
93
+ // if (typeof window === 'undefined') return
94
+ types.ifStatement(
95
+ types.binaryExpression(
96
+ '===',
97
+ types.unaryExpression('typeof', types.identifier('window')),
98
+ types.stringLiteral('undefined')
99
+ ),
100
+ types.returnStatement(null)
101
+ ),
102
+ // if (!router.isReady) return
103
+ types.ifStatement(
104
+ types.unaryExpression(
105
+ '!',
106
+ types.memberExpression(types.identifier('router'), types.identifier('isReady'))
107
+ ),
108
+ types.returnStatement(null)
109
+ ),
110
+ // const __nextQuery = { ...router.query }
111
+ types.variableDeclaration('const', [
112
+ types.variableDeclarator(
113
+ types.identifier('__nextQuery'),
114
+ types.objectExpression([
115
+ types.spreadElement(
116
+ types.memberExpression(types.identifier('router'), types.identifier('query'))
117
+ ),
118
+ ])
119
+ ),
120
+ ]),
121
+ // if (value === '' || value == null) { delete __nextQuery.key }
122
+ // else { __nextQuery.key = String(value) }
123
+ types.ifStatement(
124
+ types.logicalExpression(
125
+ '||',
126
+ types.binaryExpression('===', clone(valueExpr), types.stringLiteral('')),
127
+ types.binaryExpression('==', clone(valueExpr), types.nullLiteral())
128
+ ),
129
+ types.blockStatement([
130
+ types.expressionStatement(
131
+ types.unaryExpression(
132
+ 'delete',
133
+ accessKeyOf(types.identifier('__nextQuery'), paramKey)
134
+ ) as unknown as types.Expression
135
+ ),
136
+ ]),
137
+ types.blockStatement([
138
+ types.expressionStatement(
139
+ types.assignmentExpression(
140
+ '=',
141
+ accessKeyOf(types.identifier('__nextQuery'), paramKey),
142
+ types.callExpression(types.identifier('String'), [clone(valueExpr)])
143
+ )
144
+ ),
145
+ ])
146
+ ),
147
+ // if (__nextQuery.key === router.query.key) return
148
+ types.ifStatement(
149
+ types.binaryExpression(
150
+ '===',
151
+ accessKeyOf(types.identifier('__nextQuery'), paramKey),
152
+ routerQueryAccess(paramKey)
153
+ ),
154
+ types.returnStatement(null)
155
+ ),
156
+ // router.replace({ pathname: router.pathname, query: __nextQuery }, undefined, { shallow: true })
157
+ types.expressionStatement(
158
+ types.callExpression(
159
+ types.memberExpression(types.identifier('router'), types.identifier('replace')),
160
+ [
161
+ types.objectExpression([
162
+ types.objectProperty(
163
+ types.identifier('pathname'),
164
+ types.memberExpression(types.identifier('router'), types.identifier('pathname'))
165
+ ),
166
+ types.objectProperty(types.identifier('query'), types.identifier('__nextQuery')),
167
+ ]),
168
+ types.identifier('undefined'),
169
+ types.objectExpression([
170
+ types.objectProperty(types.identifier('shallow'), types.booleanLiteral(true)),
171
+ ]),
172
+ ]
173
+ )
174
+ ),
175
+ ])
176
+
177
+ return types.expressionStatement(
178
+ types.callExpression(types.identifier('useEffect'), [
179
+ types.arrowFunctionExpression([], effectBody),
180
+ types.arrayExpression([
181
+ clone(depExpr),
182
+ types.memberExpression(types.identifier('router'), types.identifier('isReady')),
183
+ ]),
184
+ ])
185
+ )
186
+ }
187
+
188
+ // Builds the URL→state read-back useEffect. Emitted shape (for setter
189
+ // `setSelectedCategory` bound to URL key `categoryFilter`):
190
+ //
191
+ // useEffect(() => {
192
+ // if (!router.isReady) return
193
+ // const __urlValue = router.query.categoryFilter
194
+ // const __nextValue =
195
+ // typeof __urlValue === 'string' ? __urlValue : Array.isArray(__urlValue) ? (__urlValue[0] || '') : ''
196
+ // setSelectedCategory((prev) => (prev === __nextValue ? prev : __nextValue))
197
+ // }, [router.query.categoryFilter, router.isReady])
198
+ //
199
+ // Why pair this with the write-back effect:
200
+ // - On `<Link>` / `router.push` shallow navigations that land on the same path
201
+ // with a different query (or browser back/forward, which can change the query
202
+ // without remounting the page), `router.query` updates but the state hook does
203
+ // not. Without this effect, the control would be stuck on whatever the user
204
+ // last picked even though the URL has moved on.
205
+ // - The functional `setState((prev) => prev === next ? prev : next)` form
206
+ // guarantees React bails out when the URL value matches state, which prevents
207
+ // an infinite write-back ↔ read-back loop: when the user picks a new value,
208
+ // state→URL fires once, URL→state observes the matching value and returns
209
+ // `prev`, so React does not re-render or fire any deps.
210
+ // - `router.query[key]` can be either `string` (single value) or `string[]`
211
+ // (duplicate keys) per Next.js's typings; the helper normalizes both to a
212
+ // string before comparing, defaulting to `''` for the missing-key case so the
213
+ // comparison aligns with the write-back's `value === ''` empty-check.
214
+ export const buildUrlReadBackEffect = (
215
+ paramKey: string,
216
+ setterName: string
217
+ ): types.ExpressionStatement => {
218
+ const normalizeArg = types.conditionalExpression(
219
+ // typeof __urlValue === 'string' ? __urlValue
220
+ types.binaryExpression(
221
+ '===',
222
+ types.unaryExpression('typeof', types.identifier('__urlValue')),
223
+ types.stringLiteral('string')
224
+ ),
225
+ types.identifier('__urlValue'),
226
+ types.conditionalExpression(
227
+ // Array.isArray(__urlValue) ? (__urlValue[0] || '') : ''
228
+ types.callExpression(
229
+ types.memberExpression(types.identifier('Array'), types.identifier('isArray')),
230
+ [types.identifier('__urlValue')]
231
+ ),
232
+ types.logicalExpression(
233
+ '||',
234
+ types.memberExpression(types.identifier('__urlValue'), types.numericLiteral(0), true),
235
+ types.stringLiteral('')
236
+ ),
237
+ types.stringLiteral('')
238
+ )
239
+ )
240
+
241
+ const effectBody = types.blockStatement([
242
+ // if (!router.isReady) return
243
+ types.ifStatement(
244
+ types.unaryExpression(
245
+ '!',
246
+ types.memberExpression(types.identifier('router'), types.identifier('isReady'))
247
+ ),
248
+ types.returnStatement(null)
249
+ ),
250
+ // const __urlValue = router.query.<paramKey>
251
+ types.variableDeclaration('const', [
252
+ types.variableDeclarator(types.identifier('__urlValue'), routerQueryAccess(paramKey)),
253
+ ]),
254
+ // const __nextValue = (normalized)
255
+ types.variableDeclaration('const', [
256
+ types.variableDeclarator(types.identifier('__nextValue'), normalizeArg),
257
+ ]),
258
+ // setState((prev) => prev === __nextValue ? prev : __nextValue)
259
+ types.expressionStatement(
260
+ types.callExpression(types.identifier(setterName), [
261
+ types.arrowFunctionExpression(
262
+ [types.identifier('prev')],
263
+ types.conditionalExpression(
264
+ types.binaryExpression(
265
+ '===',
266
+ types.identifier('prev'),
267
+ types.identifier('__nextValue')
268
+ ),
269
+ types.identifier('prev'),
270
+ types.identifier('__nextValue')
271
+ )
272
+ ),
273
+ ])
274
+ ),
275
+ ])
276
+
277
+ return types.expressionStatement(
278
+ types.callExpression(types.identifier('useEffect'), [
279
+ types.arrowFunctionExpression([], effectBody),
280
+ types.arrayExpression([
281
+ routerQueryAccess(paramKey),
282
+ types.memberExpression(types.identifier('router'), types.identifier('isReady')),
283
+ ]),
284
+ ])
285
+ )
286
+ }