@teleporthq/teleport-plugin-next-data-source 0.43.66 → 0.43.67
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/__tests__/array-mapper-cache-wiring.test.ts +11 -0
- package/__tests__/data-source-request-locale.test.ts +313 -0
- package/__tests__/gated-data-provider.test.ts +88 -0
- package/__tests__/localized-columns.test.ts +217 -0
- package/__tests__/numbered-pagination.test.ts +15 -0
- package/__tests__/product-filter-fields.ts +230 -0
- package/__tests__/product-filter-sql.fixture.json +7 -0
- package/__tests__/refetch-loading-state.test.ts +23 -0
- package/__tests__/search-clear-button.test.ts +122 -0
- package/dist/cjs/content-localization.d.ts +37 -0
- package/dist/cjs/content-localization.d.ts.map +1 -0
- package/dist/cjs/content-localization.js +29 -0
- package/dist/cjs/content-localization.js.map +1 -0
- package/dist/cjs/count-effect.d.ts +11 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +30 -2
- package/dist/cjs/count-effect.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +2 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +1 -1
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +10 -3
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +60 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/loading-state.d.ts +16 -1
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +69 -3
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/localized-columns.d.ts +56 -0
- package/dist/cjs/localized-columns.d.ts.map +1 -0
- package/dist/cjs/localized-columns.js +124 -0
- package/dist/cjs/localized-columns.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +123 -45
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-filter-fields.d.ts +87 -0
- package/dist/cjs/product-filter-fields.d.ts.map +1 -0
- package/dist/cjs/product-filter-fields.js +161 -0
- package/dist/cjs/product-filter-fields.js.map +1 -0
- package/dist/cjs/request-locale.d.ts +49 -0
- package/dist/cjs/request-locale.d.ts.map +1 -0
- package/dist/cjs/request-locale.js +130 -0
- package/dist/cjs/request-locale.js.map +1 -0
- package/dist/cjs/search-clear.d.ts +31 -0
- package/dist/cjs/search-clear.d.ts.map +1 -0
- package/dist/cjs/search-clear.js +103 -0
- package/dist/cjs/search-clear.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +16 -2
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +14 -2
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +11 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +59 -15
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/content-localization.d.ts +37 -0
- package/dist/esm/content-localization.d.ts.map +1 -0
- package/dist/esm/content-localization.js +24 -0
- package/dist/esm/content-localization.js.map +1 -0
- package/dist/esm/count-effect.d.ts +11 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +28 -1
- package/dist/esm/count-effect.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +1 -1
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +1 -1
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +1 -1
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +1 -1
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +1 -1
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +1 -1
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +2 -1
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +1 -1
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +10 -3
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +1 -1
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +55 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/loading-state.d.ts +16 -1
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +69 -3
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/localized-columns.d.ts +56 -0
- package/dist/esm/localized-columns.d.ts.map +1 -0
- package/dist/esm/localized-columns.js +119 -0
- package/dist/esm/localized-columns.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +124 -46
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-filter-fields.d.ts +87 -0
- package/dist/esm/product-filter-fields.d.ts.map +1 -0
- package/dist/esm/product-filter-fields.js +154 -0
- package/dist/esm/product-filter-fields.js.map +1 -0
- package/dist/esm/request-locale.d.ts +49 -0
- package/dist/esm/request-locale.d.ts.map +1 -0
- package/dist/esm/request-locale.js +98 -0
- package/dist/esm/request-locale.js.map +1 -0
- package/dist/esm/search-clear.d.ts +31 -0
- package/dist/esm/search-clear.d.ts.map +1 -0
- package/dist/esm/search-clear.js +74 -0
- package/dist/esm/search-clear.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +16 -2
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +14 -2
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +11 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +59 -15
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/content-localization.ts +53 -0
- package/src/count-effect.ts +53 -2
- package/src/fetchers/airtable.ts +4 -2
- package/src/fetchers/clickhouse.ts +4 -2
- package/src/fetchers/firestore.ts +4 -2
- package/src/fetchers/mariadb.ts +4 -2
- package/src/fetchers/mongodb.ts +4 -2
- package/src/fetchers/mysql.ts +4 -2
- package/src/fetchers/postgresql.ts +16 -2
- package/src/fetchers/redshift.ts +4 -2
- package/src/fetchers/teleport.ts +48 -9
- package/src/fetchers/turso.ts +4 -2
- package/src/index.ts +79 -11
- package/src/loading-state.ts +134 -3
- package/src/localized-columns.ts +133 -0
- package/src/pagination-plugin.ts +177 -53
- package/src/product-filter-fields.ts +173 -0
- package/src/request-locale.ts +113 -0
- package/src/search-clear.ts +102 -0
- package/src/transformations/index.ts +30 -5
- package/src/utils.ts +83 -15
package/src/pagination-plugin.ts
CHANGED
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
setJSXExpressionAttribute,
|
|
35
35
|
type PaginationControlKind,
|
|
36
36
|
} from './pagination-controls'
|
|
37
|
-
import { buildCountFetchEffect } from './count-effect'
|
|
37
|
+
import { buildCountFetchEffect, buildCountSeqDeclaration } from './count-effect'
|
|
38
|
+
import {
|
|
39
|
+
findAllSearchClearButtonsInJSX,
|
|
40
|
+
findSiblingSearchInput,
|
|
41
|
+
wireSearchClearButton,
|
|
42
|
+
} from './search-clear'
|
|
38
43
|
import {
|
|
39
44
|
buildPageTokensDeclaration,
|
|
40
45
|
wireNumberedPagination,
|
|
@@ -54,6 +59,14 @@ import {
|
|
|
54
59
|
type PageResetDeps,
|
|
55
60
|
} from './page-reset'
|
|
56
61
|
import { buildPageUrlRefDeclaration, buildPageUrlSyncEffect } from './page-url-sync'
|
|
62
|
+
import { isLocalizedProject } from './content-localization'
|
|
63
|
+
import {
|
|
64
|
+
appendClientLocaleParam,
|
|
65
|
+
buildClientLocaleExpression,
|
|
66
|
+
buildServerLocaleParam,
|
|
67
|
+
ensureRouterDeclaration,
|
|
68
|
+
hasLocaleParam,
|
|
69
|
+
} from './request-locale'
|
|
57
70
|
|
|
58
71
|
// ----- searchDefaultValue support -----
|
|
59
72
|
//
|
|
@@ -723,6 +736,8 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
723
736
|
combinedStateVar: string
|
|
724
737
|
setCombinedStateVar: string
|
|
725
738
|
skipDebounceRefVar: string
|
|
739
|
+
/** Ref numbering the count requests, so an overtaken count response is dropped. */
|
|
740
|
+
countSeqRefVar: string
|
|
726
741
|
propsPrefix: string
|
|
727
742
|
} {
|
|
728
743
|
const idx = usage.index
|
|
@@ -739,6 +754,7 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
739
754
|
combinedStateVar: `ds_${idx}_state`,
|
|
740
755
|
setCombinedStateVar: `setDs_${idx}_state`,
|
|
741
756
|
skipDebounceRefVar: `ds_${idx}_skipDebounce`,
|
|
757
|
+
countSeqRefVar: `ds_${idx}_countSeq`,
|
|
742
758
|
propsPrefix: `${usage.dataSourceIdentifier}_ds_${idx}`,
|
|
743
759
|
}
|
|
744
760
|
}
|
|
@@ -1110,6 +1126,10 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1110
1126
|
const getStaticPropsChunk = chunks.find((chunk) => chunk.name === 'getStaticProps')
|
|
1111
1127
|
const isPage = !!getStaticPropsChunk
|
|
1112
1128
|
|
|
1129
|
+
// In a multi-language project every fetch says which language it is for —
|
|
1130
|
+
// see `request-locale.ts`. Decided once for the whole component.
|
|
1131
|
+
const localized = isLocalizedProject(options)
|
|
1132
|
+
|
|
1113
1133
|
// Add React dependencies
|
|
1114
1134
|
if (!dependencies.useState) {
|
|
1115
1135
|
dependencies.useState = {
|
|
@@ -1408,6 +1428,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1408
1428
|
pushPropIdsAsDeps(countEffectDeps, countEffectSeen, usage.filterPropIds)
|
|
1409
1429
|
// Same goes for URL-driven filters (already documented above).
|
|
1410
1430
|
pushUrlSearchParamMemoDeps(countEffectDeps, usage)
|
|
1431
|
+
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1411
1432
|
effectStatements.push(
|
|
1412
1433
|
buildCountFetchEffect({
|
|
1413
1434
|
fileName,
|
|
@@ -1415,6 +1436,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1415
1436
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1416
1437
|
urlParams,
|
|
1417
1438
|
deps: countEffectDeps,
|
|
1439
|
+
countSeqRefVar: vars.countSeqRefVar,
|
|
1418
1440
|
})
|
|
1419
1441
|
)
|
|
1420
1442
|
|
|
@@ -1515,6 +1537,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1515
1537
|
pushPropIdsAsDeps(countDeps, countDepsSeen, usage.filterPropIds)
|
|
1516
1538
|
pushUrlSearchParamMemoDeps(countDeps, usage)
|
|
1517
1539
|
|
|
1540
|
+
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1518
1541
|
effectStatements.push(
|
|
1519
1542
|
buildCountFetchEffect({
|
|
1520
1543
|
fileName,
|
|
@@ -1522,6 +1545,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1522
1545
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1523
1546
|
urlParams: countUrlParams,
|
|
1524
1547
|
deps: countDeps,
|
|
1548
|
+
countSeqRefVar: vars.countSeqRefVar,
|
|
1525
1549
|
})
|
|
1526
1550
|
)
|
|
1527
1551
|
}
|
|
@@ -1658,43 +1682,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1658
1682
|
// `router.query` / `router.isReady` and call `router.replace`. Gated on the
|
|
1659
1683
|
// same predicate as the effects themselves so router is only injected when
|
|
1660
1684
|
// it is actually used.
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
// the dependency-resolver merges instead of emitting a second one.
|
|
1669
|
-
dependencies.useRouter = {
|
|
1670
|
-
type: 'library',
|
|
1671
|
-
path: 'next/router',
|
|
1672
|
-
version: '^12.1.10',
|
|
1673
|
-
meta: { namedImport: true },
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
const hasRouterDecl = blockStatement.body.some(
|
|
1677
|
-
(statement) =>
|
|
1678
|
-
statement.type === 'VariableDeclaration' &&
|
|
1679
|
-
statement.declarations.some(
|
|
1680
|
-
(decl) =>
|
|
1681
|
-
decl.id.type === 'Identifier' &&
|
|
1682
|
-
decl.id.name === 'router' &&
|
|
1683
|
-
decl.init?.type === 'CallExpression' &&
|
|
1684
|
-
decl.init.callee.type === 'Identifier' &&
|
|
1685
|
-
decl.init.callee.name === 'useRouter'
|
|
1686
|
-
)
|
|
1685
|
+
// A localized project reads `router.locale` into every wired provider's
|
|
1686
|
+
// params (each of them fetches client-side), so the router is needed as
|
|
1687
|
+
// soon as there is anything to wire.
|
|
1688
|
+
const needsUseRouter =
|
|
1689
|
+
localized ||
|
|
1690
|
+
registry.usages.some(
|
|
1691
|
+
(u) => hasUrlSearchParamFilters(u) || usageHasSearchUrlSync(u) || usageHasPageUrlSync(u)
|
|
1687
1692
|
)
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
types.variableDeclaration('const', [
|
|
1691
|
-
types.variableDeclarator(
|
|
1692
|
-
types.identifier('router'),
|
|
1693
|
-
types.callExpression(types.identifier('useRouter'), [])
|
|
1694
|
-
),
|
|
1695
|
-
])
|
|
1696
|
-
)
|
|
1697
|
-
}
|
|
1693
|
+
if (needsUseRouter) {
|
|
1694
|
+
ensureRouterDeclaration(blockStatement.body, dependencies)
|
|
1698
1695
|
}
|
|
1699
1696
|
|
|
1700
1697
|
// Both the search write-back and the page ⇄ URL effect write the URL through
|
|
@@ -1766,13 +1763,20 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1766
1763
|
|
|
1767
1764
|
// Update DataProvider based on category
|
|
1768
1765
|
if (usage.category === 'paginated+search') {
|
|
1769
|
-
updateDataProviderForPaginatedSearch(
|
|
1766
|
+
updateDataProviderForPaginatedSearch(
|
|
1767
|
+
dp,
|
|
1768
|
+
usage,
|
|
1769
|
+
vars,
|
|
1770
|
+
fileName,
|
|
1771
|
+
cacheDeclarations,
|
|
1772
|
+
localized
|
|
1773
|
+
)
|
|
1770
1774
|
} else if (usage.category === 'paginated-only') {
|
|
1771
|
-
updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations)
|
|
1775
|
+
updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations, localized)
|
|
1772
1776
|
} else if (usage.category === 'search-only') {
|
|
1773
|
-
updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations)
|
|
1777
|
+
updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations, localized)
|
|
1774
1778
|
} else if (usage.category === 'plain') {
|
|
1775
|
-
updateDataProviderForPlain(dp, fileName, usage)
|
|
1779
|
+
updateDataProviderForPlain(dp, fileName, usage, localized)
|
|
1776
1780
|
}
|
|
1777
1781
|
|
|
1778
1782
|
// Show the array mapper's loading state while a category/sort/search
|
|
@@ -1871,6 +1875,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1871
1875
|
const searchEnabledUsages = registry.usages.filter((u) => u.searchEnabled)
|
|
1872
1876
|
|
|
1873
1877
|
// Match by order - search input 0 -> searchEnabledUsages[0], etc.
|
|
1878
|
+
const usageBySearchInput = new Map<any, DataSourceUsage>()
|
|
1874
1879
|
searchInputs.forEach((input, idx) => {
|
|
1875
1880
|
if (idx >= searchEnabledUsages.length) {
|
|
1876
1881
|
return
|
|
@@ -1879,6 +1884,19 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1879
1884
|
const usage = searchEnabledUsages[idx]
|
|
1880
1885
|
const vars = getStateVarsForUsage(usage)
|
|
1881
1886
|
wireSearchInput(input.node, vars)
|
|
1887
|
+
usageBySearchInput.set(input.node, usage)
|
|
1888
|
+
})
|
|
1889
|
+
|
|
1890
|
+
// STEP 4.5: Wire the clear buttons, each to the input it shares a parent with.
|
|
1891
|
+
findAllSearchClearButtonsInJSX(blockStatement).forEach((button) => {
|
|
1892
|
+
const input = findSiblingSearchInput(
|
|
1893
|
+
button,
|
|
1894
|
+
searchInputs.map((entry) => entry.node)
|
|
1895
|
+
)
|
|
1896
|
+
const usage = input ? usageBySearchInput.get(input) : undefined
|
|
1897
|
+
if (usage) {
|
|
1898
|
+
wireSearchClearButton(button, getStateVarsForUsage(usage))
|
|
1899
|
+
}
|
|
1882
1900
|
})
|
|
1883
1901
|
|
|
1884
1902
|
// STEP 5: Wire pagination widgets
|
|
@@ -1908,7 +1926,13 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1908
1926
|
|
|
1909
1927
|
// STEP 6: Update getStaticProps if this is a page
|
|
1910
1928
|
if (isPage) {
|
|
1911
|
-
updateGetStaticProps(
|
|
1929
|
+
updateGetStaticProps(
|
|
1930
|
+
chunks,
|
|
1931
|
+
registry,
|
|
1932
|
+
dependencies,
|
|
1933
|
+
uidl.outputOptions?.folderPath,
|
|
1934
|
+
localized
|
|
1935
|
+
)
|
|
1912
1936
|
}
|
|
1913
1937
|
|
|
1914
1938
|
return structure
|
|
@@ -1964,6 +1988,20 @@ function insertSentinelAfterProvider(
|
|
|
1964
1988
|
if (node.alternate) {
|
|
1965
1989
|
traverse(node.alternate)
|
|
1966
1990
|
}
|
|
1991
|
+
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
1992
|
+
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
1993
|
+
// expression (or in a ternary's `test`), which is not a child, a body or
|
|
1994
|
+
// an argument. Left unvisited, the products list under the first tab of
|
|
1995
|
+
// a shop was never paginated or seeded, and rendered nothing.
|
|
1996
|
+
if (node.left) {
|
|
1997
|
+
traverse(node.left)
|
|
1998
|
+
}
|
|
1999
|
+
if (node.right) {
|
|
2000
|
+
traverse(node.right)
|
|
2001
|
+
}
|
|
2002
|
+
if (node.test) {
|
|
2003
|
+
traverse(node.test)
|
|
2004
|
+
}
|
|
1967
2005
|
if (node.expression) {
|
|
1968
2006
|
traverse(node.expression)
|
|
1969
2007
|
}
|
|
@@ -2007,6 +2045,20 @@ function findAllDataProvidersInJSX(blockStatement: types.BlockStatement): any[]
|
|
|
2007
2045
|
if (node.alternate) {
|
|
2008
2046
|
traverse(node.alternate)
|
|
2009
2047
|
}
|
|
2048
|
+
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2049
|
+
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
2050
|
+
// expression (or in a ternary's `test`), which is not a child, a body or
|
|
2051
|
+
// an argument. Left unvisited, the products list under the first tab of
|
|
2052
|
+
// a shop was never paginated or seeded, and rendered nothing.
|
|
2053
|
+
if (node.left) {
|
|
2054
|
+
traverse(node.left)
|
|
2055
|
+
}
|
|
2056
|
+
if (node.right) {
|
|
2057
|
+
traverse(node.right)
|
|
2058
|
+
}
|
|
2059
|
+
if (node.test) {
|
|
2060
|
+
traverse(node.test)
|
|
2061
|
+
}
|
|
2010
2062
|
|
|
2011
2063
|
if (node.expression) {
|
|
2012
2064
|
traverse(node.expression)
|
|
@@ -2110,6 +2162,20 @@ function findAllSearchInputsInJSX(
|
|
|
2110
2162
|
if (node.alternate) {
|
|
2111
2163
|
traverse(node.alternate)
|
|
2112
2164
|
}
|
|
2165
|
+
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2166
|
+
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
2167
|
+
// expression (or in a ternary's `test`), which is not a child, a body or
|
|
2168
|
+
// an argument. Left unvisited, the products list under the first tab of
|
|
2169
|
+
// a shop was never paginated or seeded, and rendered nothing.
|
|
2170
|
+
if (node.left) {
|
|
2171
|
+
traverse(node.left)
|
|
2172
|
+
}
|
|
2173
|
+
if (node.right) {
|
|
2174
|
+
traverse(node.right)
|
|
2175
|
+
}
|
|
2176
|
+
if (node.test) {
|
|
2177
|
+
traverse(node.test)
|
|
2178
|
+
}
|
|
2113
2179
|
if (node.expression) {
|
|
2114
2180
|
traverse(node.expression)
|
|
2115
2181
|
}
|
|
@@ -2173,6 +2239,20 @@ function findPaginationNodesWithOwners(
|
|
|
2173
2239
|
if (node.alternate) {
|
|
2174
2240
|
traverse(node.alternate)
|
|
2175
2241
|
}
|
|
2242
|
+
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2243
|
+
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
2244
|
+
// expression (or in a ternary's `test`), which is not a child, a body or
|
|
2245
|
+
// an argument. Left unvisited, the products list under the first tab of
|
|
2246
|
+
// a shop was never paginated or seeded, and rendered nothing.
|
|
2247
|
+
if (node.left) {
|
|
2248
|
+
traverse(node.left)
|
|
2249
|
+
}
|
|
2250
|
+
if (node.right) {
|
|
2251
|
+
traverse(node.right)
|
|
2252
|
+
}
|
|
2253
|
+
if (node.test) {
|
|
2254
|
+
traverse(node.test)
|
|
2255
|
+
}
|
|
2176
2256
|
if (node.expression) {
|
|
2177
2257
|
traverse(node.expression)
|
|
2178
2258
|
}
|
|
@@ -2239,7 +2319,8 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2239
2319
|
usage: DataSourceUsage,
|
|
2240
2320
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2241
2321
|
fileName: string,
|
|
2242
|
-
cacheDeclarations: types.Statement[]
|
|
2322
|
+
cacheDeclarations: types.Statement[],
|
|
2323
|
+
localized: boolean
|
|
2243
2324
|
): void {
|
|
2244
2325
|
const attrs = dp.openingElement.attributes
|
|
2245
2326
|
|
|
@@ -2293,6 +2374,9 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2293
2374
|
pushStateIdsAsDeps(memoDeps, seenDeps, usage.dynamicSort.depStateIds)
|
|
2294
2375
|
}
|
|
2295
2376
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2377
|
+
if (localized) {
|
|
2378
|
+
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2379
|
+
}
|
|
2296
2380
|
|
|
2297
2381
|
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
2298
2382
|
|
|
@@ -2416,7 +2500,8 @@ function updateDataProviderForPaginationOnly(
|
|
|
2416
2500
|
usage: DataSourceUsage,
|
|
2417
2501
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2418
2502
|
fileName: string,
|
|
2419
|
-
cacheDeclarations: types.Statement[]
|
|
2503
|
+
cacheDeclarations: types.Statement[],
|
|
2504
|
+
localized: boolean
|
|
2420
2505
|
): void {
|
|
2421
2506
|
const attrs = dp.openingElement.attributes
|
|
2422
2507
|
|
|
@@ -2448,6 +2533,9 @@ function updateDataProviderForPaginationOnly(
|
|
|
2448
2533
|
pushStateIdsAsDeps(memoDeps, seenDeps, usage.dynamicSort.depStateIds)
|
|
2449
2534
|
}
|
|
2450
2535
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2536
|
+
if (localized) {
|
|
2537
|
+
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2538
|
+
}
|
|
2451
2539
|
|
|
2452
2540
|
// Add params
|
|
2453
2541
|
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
@@ -2522,7 +2610,8 @@ function updateDataProviderForSearchOnly(
|
|
|
2522
2610
|
usage: DataSourceUsage,
|
|
2523
2611
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2524
2612
|
fileName: string,
|
|
2525
|
-
cacheDeclarations: types.Statement[]
|
|
2613
|
+
cacheDeclarations: types.Statement[],
|
|
2614
|
+
localized: boolean
|
|
2526
2615
|
): void {
|
|
2527
2616
|
const attrs = dp.openingElement.attributes
|
|
2528
2617
|
|
|
@@ -2564,6 +2653,9 @@ function updateDataProviderForSearchOnly(
|
|
|
2564
2653
|
pushStateIdsAsDeps(memoDeps, seenDeps, usage.dynamicSort.depStateIds)
|
|
2565
2654
|
}
|
|
2566
2655
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2656
|
+
if (localized) {
|
|
2657
|
+
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2658
|
+
}
|
|
2567
2659
|
|
|
2568
2660
|
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
2569
2661
|
|
|
@@ -2621,7 +2713,12 @@ function updateDataProviderForSearchOnly(
|
|
|
2621
2713
|
)
|
|
2622
2714
|
}
|
|
2623
2715
|
|
|
2624
|
-
function updateDataProviderForPlain(
|
|
2716
|
+
function updateDataProviderForPlain(
|
|
2717
|
+
dp: any,
|
|
2718
|
+
fileName: string,
|
|
2719
|
+
usage: DataSourceUsage,
|
|
2720
|
+
localized: boolean
|
|
2721
|
+
): void {
|
|
2625
2722
|
const attrs = dp.openingElement.attributes
|
|
2626
2723
|
|
|
2627
2724
|
const clientCache = usage.cache?.client ? usage.cache : undefined
|
|
@@ -2658,13 +2755,24 @@ function updateDataProviderForPlain(dp: any, fileName: string, usage: DataSource
|
|
|
2658
2755
|
)
|
|
2659
2756
|
}
|
|
2660
2757
|
|
|
2661
|
-
// Find the params attribute
|
|
2662
|
-
|
|
2758
|
+
// Find the params attribute. A plain list may have none at all — but the
|
|
2759
|
+
// fetch it was just given sends `params` regardless, and in a localized
|
|
2760
|
+
// project those have to carry the locale, so the attribute is created.
|
|
2761
|
+
let paramsAttrIndex = attrs.findIndex(
|
|
2663
2762
|
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'params'
|
|
2664
2763
|
)
|
|
2665
2764
|
|
|
2666
2765
|
if (paramsAttrIndex === -1) {
|
|
2667
|
-
|
|
2766
|
+
if (!localized) {
|
|
2767
|
+
return
|
|
2768
|
+
}
|
|
2769
|
+
attrs.push(
|
|
2770
|
+
types.jsxAttribute(
|
|
2771
|
+
types.jsxIdentifier('params'),
|
|
2772
|
+
types.jsxExpressionContainer(types.objectExpression([]))
|
|
2773
|
+
)
|
|
2774
|
+
)
|
|
2775
|
+
paramsAttrIndex = attrs.length - 1
|
|
2668
2776
|
}
|
|
2669
2777
|
|
|
2670
2778
|
const paramsAttr = attrs[paramsAttrIndex] as types.JSXAttribute
|
|
@@ -2695,6 +2803,9 @@ function updateDataProviderForPlain(dp: any, fileName: string, usage: DataSource
|
|
|
2695
2803
|
pushStateIdsAsDeps(memoDeps, memoSeen, usage.filterStateIds)
|
|
2696
2804
|
pushPropIdsAsDeps(memoDeps, memoSeen, usage.filterPropIds)
|
|
2697
2805
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2806
|
+
if (localized && paramsExpression.type === 'ObjectExpression') {
|
|
2807
|
+
appendClientLocaleParam(paramsExpression.properties, memoDeps)
|
|
2808
|
+
}
|
|
2698
2809
|
|
|
2699
2810
|
// Wrap params in useMemo with filter state dependencies
|
|
2700
2811
|
const memoizedParams = types.callExpression(types.identifier('useMemo'), [
|
|
@@ -2743,11 +2854,18 @@ function stabilizeDataProviderWithoutRepeater(dp: any): void {
|
|
|
2743
2854
|
return
|
|
2744
2855
|
}
|
|
2745
2856
|
|
|
2746
|
-
// Wrap params in useMemo with empty dependencies array
|
|
2747
|
-
//
|
|
2857
|
+
// Wrap params in useMemo with an empty dependencies array so the object
|
|
2858
|
+
// reference stays stable across re-renders. The one thing that may vary is
|
|
2859
|
+
// the locale a client-fetched item carries (put there by the data-source
|
|
2860
|
+
// plugin): a locale switch must rebuild the params, or the item would keep
|
|
2861
|
+
// its previous language.
|
|
2862
|
+
const memoDeps: types.Expression[] = []
|
|
2863
|
+
if (paramsExpression.type === 'ObjectExpression' && hasLocaleParam(paramsExpression.properties)) {
|
|
2864
|
+
memoDeps.push(buildClientLocaleExpression())
|
|
2865
|
+
}
|
|
2748
2866
|
const memoizedParams = types.callExpression(types.identifier('useMemo'), [
|
|
2749
2867
|
types.arrowFunctionExpression([], paramsExpression),
|
|
2750
|
-
types.arrayExpression(
|
|
2868
|
+
types.arrayExpression(memoDeps),
|
|
2751
2869
|
])
|
|
2752
2870
|
|
|
2753
2871
|
// Replace the params attribute
|
|
@@ -3377,7 +3495,8 @@ function updateGetStaticProps(
|
|
|
3377
3495
|
chunks: any[],
|
|
3378
3496
|
registry: StateRegistry,
|
|
3379
3497
|
dependencies: Record<string, any>,
|
|
3380
|
-
folderPath
|
|
3498
|
+
folderPath: string[] | undefined,
|
|
3499
|
+
localized: boolean
|
|
3381
3500
|
): void {
|
|
3382
3501
|
const getStaticPropsChunk = chunks.find((c) => c.name === 'getStaticProps')
|
|
3383
3502
|
if (!getStaticPropsChunk || getStaticPropsChunk.type !== ChunkType.AST) {
|
|
@@ -3549,6 +3668,11 @@ function updateGetStaticProps(
|
|
|
3549
3668
|
)
|
|
3550
3669
|
}
|
|
3551
3670
|
|
|
3671
|
+
// The prefetch is per locale — getStaticProps runs once per language.
|
|
3672
|
+
if (localized) {
|
|
3673
|
+
fetchParams.push(buildServerLocaleParam())
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3552
3676
|
// Check if this fetch already exists
|
|
3553
3677
|
const existingFetchIndex = arrayPattern.elements.findIndex(
|
|
3554
3678
|
(el: any) => el?.type === 'Identifier' && el.name === vars.propsPrefix
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filtering a products list on things that are not a plain column.
|
|
3
|
+
*
|
|
4
|
+
* The storefront's product filters ask questions the `teleport_products` row
|
|
5
|
+
* cannot answer with a single column comparison: "what does it cost right now"
|
|
6
|
+
* (`price` net of a live markdown), "how many stars" (an aggregate over the
|
|
7
|
+
* reviews table), "is it on sale", and "does it come in size XL" (a value
|
|
8
|
+
* inside the `variant_options` JSON). Like the discount-aware sort in
|
|
9
|
+
* `product-price-sort.ts`, these have to be answered by the DATABASE — the list
|
|
10
|
+
* is paginated, so filtering the fetched page would only filter 20 of N rows.
|
|
11
|
+
*
|
|
12
|
+
* The editor emits such a filter as an ordinary condition whose `source` is
|
|
13
|
+
* one of the VIRTUAL field names below. Every SQL builder that can receive a
|
|
14
|
+
* products filter rewrites them into the expressions here:
|
|
15
|
+
*
|
|
16
|
+
* - this file → the generated store's `teleport_products` API route
|
|
17
|
+
* (`fetchers/teleport.ts`, `fetchers/postgresql.ts`);
|
|
18
|
+
* - `database-provider-worker/src/lib/product-filter-fields.ts` → the editor
|
|
19
|
+
* canvas. ⛔ A byte-for-byte mirror of the SQL below; both repos pin the
|
|
20
|
+
* strings with a test, because a clause the canvas cannot build is an HTTP
|
|
21
|
+
* 500 the canvas draws as an EMPTY grid.
|
|
22
|
+
*
|
|
23
|
+
* ## The expressions cannot raise
|
|
24
|
+
*
|
|
25
|
+
* Same discipline as the sort: the JSON column is cast only when its text
|
|
26
|
+
* already looks like an array, the axis key is validated against a slug pattern
|
|
27
|
+
* before it is bound, and an unknown key filters to no rows instead of erroring
|
|
28
|
+
* — a filter that the database rejects would empty the whole products page.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
PRODUCTS_TABLE_NAME,
|
|
33
|
+
PRODUCT_PRICE_SORT_FIELD,
|
|
34
|
+
buildProductEffectivePriceSql,
|
|
35
|
+
supportsProductPriceSort,
|
|
36
|
+
} from './product-price-sort'
|
|
37
|
+
|
|
38
|
+
/** The reviews table the star-rating aggregate reads. Exists once reviews were enabled. */
|
|
39
|
+
export const PRODUCT_REVIEWS_TABLE_NAME = 'teleport_product_reviews'
|
|
40
|
+
|
|
41
|
+
/** Virtual field: the product's star bucket — `ROUND(AVG(approved rating))`, 1..5, NULL when unreviewed. */
|
|
42
|
+
export const PRODUCT_RATING_FILTER_FIELD = 'rating'
|
|
43
|
+
|
|
44
|
+
/** Virtual field: `'true'` while a markdown is live, else `'false'`. */
|
|
45
|
+
export const PRODUCT_ON_SALE_FILTER_FIELD = 'on_sale'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Virtual field family: `variant_options.<axisKey>` — the product offers the
|
|
49
|
+
* axis (e.g. `size`) in one of the given values (e.g. `xl`).
|
|
50
|
+
*/
|
|
51
|
+
export const PRODUCT_VARIANT_AXIS_FILTER_PREFIX = 'variant_options.'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* What an axis key may look like. Keys are produced by the editor's slug
|
|
55
|
+
* helper (`deriveOptionKey`), so this is what every real key matches; anything
|
|
56
|
+
* else is refused before it can reach the database.
|
|
57
|
+
*/
|
|
58
|
+
export const PRODUCT_VARIANT_AXIS_KEY_PATTERN = '^[a-z0-9][a-z0-9_-]{0,63}$'
|
|
59
|
+
|
|
60
|
+
/** Generic multi-value operand: the column equals ANY of the destination values. */
|
|
61
|
+
export const IN_OPERAND = 'in'
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `ROUND(AVG(rating))` over the product's APPROVED reviews. Only approved rows
|
|
65
|
+
* count — moderation is the merchant's veto — and the outer row is the
|
|
66
|
+
* unaliased `teleport_products` the fetchers select from.
|
|
67
|
+
*/
|
|
68
|
+
export const buildProductRatingBucketSql = (): string =>
|
|
69
|
+
`(SELECT ROUND(AVG(r.rating)) FROM ${PRODUCT_REVIEWS_TABLE_NAME} r WHERE r.product_id = ${PRODUCTS_TABLE_NAME}.id AND r.status = 'approved' AND r.rating IS NOT NULL)`
|
|
70
|
+
|
|
71
|
+
/** `'true'` when the price paid today is below the list price, i.e. a markdown is live. */
|
|
72
|
+
export const buildProductOnSaleSql = (): string =>
|
|
73
|
+
`(CASE WHEN ${buildProductEffectivePriceSql()} < "price"::numeric THEN 'true' ELSE 'false' END)`
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Only text that already looks like a JSON array is cast; NULL, empty or a
|
|
77
|
+
* hand-edited scalar reads as "no variant axes".
|
|
78
|
+
*/
|
|
79
|
+
export const SAFE_VARIANT_OPTIONS_JSONB = `CASE
|
|
80
|
+
WHEN "variant_options" IS NULL OR btrim("variant_options") NOT LIKE '[%' THEN '[]'::jsonb
|
|
81
|
+
ELSE "variant_options"::jsonb
|
|
82
|
+
END`
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* True when the product declares the axis bound to `keyParam` with at least one
|
|
86
|
+
* of the values bound to `valuesParam` (a `text[]`). Both are PARAMETERS, never
|
|
87
|
+
* interpolated text.
|
|
88
|
+
*/
|
|
89
|
+
export const buildProductVariantAxisExistsSql = (keyParam: string, valuesParam: string): string =>
|
|
90
|
+
`EXISTS (SELECT 1 FROM jsonb_array_elements(${SAFE_VARIANT_OPTIONS_JSONB}) AS ax, jsonb_array_elements(CASE WHEN jsonb_typeof(ax->'values') = 'array' THEN ax->'values' ELSE '[]'::jsonb END) AS v WHERE ax->>'key' = ${keyParam} AND v->>'value' = ANY(${valuesParam}::text[]))`
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The complete `productFilterClause(field, operand, value, pushParam)` helper a
|
|
94
|
+
* SQL fetcher embeds in its generated route.
|
|
95
|
+
*
|
|
96
|
+
* Returns a finished WHERE clause for a virtual (or rewritten) products field,
|
|
97
|
+
* or `null` for anything else so the fetcher's ordinary branches run. `pushParam`
|
|
98
|
+
* binds a value and answers its `$n` placeholder. By the time it is called an
|
|
99
|
+
* `in` filter has been normalised to `=` with an ARRAY value (see
|
|
100
|
+
* `normalizeInOperand` in `utils.ts`), which reads as "any of". A virtual field
|
|
101
|
+
* never answers `null` once matched — an operand it cannot express filters to
|
|
102
|
+
* no rows rather than falling into a branch that would name a column the table
|
|
103
|
+
* does not have.
|
|
104
|
+
*
|
|
105
|
+
* Emitted through `JSON.stringify` and string concatenation only — no backtick
|
|
106
|
+
* and no `${` may appear in the output, which is interpolated into a fetcher's
|
|
107
|
+
* template literal. On a table that is not the products table the helper still
|
|
108
|
+
* exists and always answers `null`, so a fetcher can embed it unconditionally.
|
|
109
|
+
*/
|
|
110
|
+
export const generateProductFilterClauseHelper = (tableName: string): string => {
|
|
111
|
+
if (!supportsProductPriceSort(tableName)) {
|
|
112
|
+
return [
|
|
113
|
+
'// Virtual products-table filter fields. This table has none, so every',
|
|
114
|
+
"// condition takes the fetcher's ordinary branches.",
|
|
115
|
+
'function productFilterClause() {',
|
|
116
|
+
' return null',
|
|
117
|
+
'}',
|
|
118
|
+
].join('\n')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const lines = [
|
|
122
|
+
"// Rewrites the products table's virtual filter fields - the price paid",
|
|
123
|
+
'// today, the star-rating bucket, the on-sale flag and a variant axis - into',
|
|
124
|
+
'// SQL. Answers null for a plain column so the ordinary branches run. An',
|
|
125
|
+
'// "in" filter arrives here already normalised to "=" with an ARRAY value.',
|
|
126
|
+
'function productFilterClause(field, operand, value, pushParam) {',
|
|
127
|
+
" var scalarOps = ['=', '!=', '>', '<', '>=', '<=']",
|
|
128
|
+
" var equalityOps = ['=', '!=']",
|
|
129
|
+
' var compare = function (expression, allowedOps, virtual) {',
|
|
130
|
+
' if (Array.isArray(value)) {',
|
|
131
|
+
" if (operand === '=') {",
|
|
132
|
+
" return expression + '::text = ANY(' + pushParam(value.map(String)) + '::text[])'",
|
|
133
|
+
' }',
|
|
134
|
+
" if (operand === '!=') {",
|
|
135
|
+
" return expression + '::text <> ALL(' + pushParam(value.map(String)) + '::text[])'",
|
|
136
|
+
' }',
|
|
137
|
+
" return virtual ? 'FALSE' : null",
|
|
138
|
+
' }',
|
|
139
|
+
" if (allowedOps.indexOf(operand) === -1) return virtual ? 'FALSE' : null",
|
|
140
|
+
" return expression + ' ' + operand + ' ' + pushParam(value)",
|
|
141
|
+
' }',
|
|
142
|
+
` if (field === ${JSON.stringify(PRODUCT_PRICE_SORT_FIELD)}) {`,
|
|
143
|
+
` return compare(${JSON.stringify(
|
|
144
|
+
'(' + buildProductEffectivePriceSql() + ')'
|
|
145
|
+
)}, scalarOps, false)`,
|
|
146
|
+
' }',
|
|
147
|
+
` if (field === ${JSON.stringify(PRODUCT_RATING_FILTER_FIELD)}) {`,
|
|
148
|
+
` return compare(${JSON.stringify(buildProductRatingBucketSql())}, scalarOps, true)`,
|
|
149
|
+
' }',
|
|
150
|
+
` if (field === ${JSON.stringify(PRODUCT_ON_SALE_FILTER_FIELD)}) {`,
|
|
151
|
+
` return compare(${JSON.stringify(buildProductOnSaleSql())}, equalityOps, true)`,
|
|
152
|
+
' }',
|
|
153
|
+
` if (field.indexOf(${JSON.stringify(PRODUCT_VARIANT_AXIS_FILTER_PREFIX)}) === 0) {`,
|
|
154
|
+
` var axisKey = field.slice(${PRODUCT_VARIANT_AXIS_FILTER_PREFIX.length})`,
|
|
155
|
+
` if (!new RegExp(${JSON.stringify(PRODUCT_VARIANT_AXIS_KEY_PATTERN)}).test(axisKey)) {`,
|
|
156
|
+
' // Not a key the editor could have written: no rows, never an error.',
|
|
157
|
+
" return 'FALSE'",
|
|
158
|
+
' }',
|
|
159
|
+
" if (operand !== '=') return 'FALSE'",
|
|
160
|
+
' var values = (Array.isArray(value) ? value : [value]).map(String)',
|
|
161
|
+
' var keyParam = pushParam(axisKey)',
|
|
162
|
+
' var valuesParam = pushParam(values)',
|
|
163
|
+
` return ${JSON.stringify(buildProductVariantAxisExistsSql('__KEY__', '__VALUES__'))}`,
|
|
164
|
+
// Function replacements: a "$1" inside a replacement STRING is a special
|
|
165
|
+
// pattern, so the placeholders are swapped by callback.
|
|
166
|
+
" .replace('__KEY__', function () { return keyParam })",
|
|
167
|
+
" .replace('__VALUES__', function () { return valuesParam })",
|
|
168
|
+
' }',
|
|
169
|
+
' return null',
|
|
170
|
+
'}',
|
|
171
|
+
]
|
|
172
|
+
return lines.join('\n')
|
|
173
|
+
}
|