@teleporthq/teleport-plugin-next-data-source 0.43.66 → 0.43.68

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.
Files changed (334) hide show
  1. package/__tests__/array-mapper-cache-wiring.test.ts +11 -0
  2. package/__tests__/array-overlap-sql.test.ts +53 -0
  3. package/__tests__/blog-post-page-enrichment.test.ts +214 -0
  4. package/__tests__/browser-row-policy.test.ts +503 -0
  5. package/__tests__/data-source-cache-wrapping.test.ts +18 -2
  6. package/__tests__/data-source-request-locale.test.ts +313 -0
  7. package/__tests__/ecommerce-product-options.test.ts +206 -0
  8. package/__tests__/ecommerce-product-pages.test.ts +82 -0
  9. package/__tests__/ecommerce-product-pre-options.golden.json +505 -0
  10. package/__tests__/ecommerce-product-storefront-contract.test.ts +293 -0
  11. package/__tests__/ecommerce-product-subscriptions.test.ts +121 -0
  12. package/__tests__/fetchers.test.ts +2 -2
  13. package/__tests__/gated-data-provider.test.ts +88 -0
  14. package/__tests__/items-path.test.ts +150 -0
  15. package/__tests__/localized-columns.test.ts +217 -0
  16. package/__tests__/numbered-pagination.test.ts +15 -0
  17. package/__tests__/product-filter-fields.ts +230 -0
  18. package/__tests__/product-filter-sql.fixture.json +7 -0
  19. package/__tests__/product-options-storefront.fixture.json +2510 -0
  20. package/__tests__/product-page-url-fixtures.json +25 -0
  21. package/__tests__/product-price-sort.ts +4 -2
  22. package/__tests__/raw-rows-mode.test.ts +111 -0
  23. package/__tests__/refetch-loading-state.test.ts +23 -0
  24. package/__tests__/rest-api-response-cache.test.ts +434 -0
  25. package/__tests__/search-clear-button.test.ts +122 -0
  26. package/__tests__/table-read-route-protected-tables.test.ts +493 -0
  27. package/dist/cjs/array-overlap-sql.d.ts +21 -0
  28. package/dist/cjs/array-overlap-sql.d.ts.map +1 -0
  29. package/dist/cjs/array-overlap-sql.js +25 -0
  30. package/dist/cjs/array-overlap-sql.js.map +1 -0
  31. package/dist/cjs/content-localization.d.ts +37 -0
  32. package/dist/cjs/content-localization.d.ts.map +1 -0
  33. package/dist/cjs/content-localization.js +29 -0
  34. package/dist/cjs/content-localization.js.map +1 -0
  35. package/dist/cjs/count-effect.d.ts +11 -1
  36. package/dist/cjs/count-effect.d.ts.map +1 -1
  37. package/dist/cjs/count-effect.js +30 -2
  38. package/dist/cjs/count-effect.js.map +1 -1
  39. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  40. package/dist/cjs/data-source-fetchers.js +10 -4
  41. package/dist/cjs/data-source-fetchers.js.map +1 -1
  42. package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
  43. package/dist/cjs/fetchers/airtable.js +1 -1
  44. package/dist/cjs/fetchers/airtable.js.map +1 -1
  45. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
  46. package/dist/cjs/fetchers/clickhouse.js +1 -1
  47. package/dist/cjs/fetchers/clickhouse.js.map +1 -1
  48. package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
  49. package/dist/cjs/fetchers/firestore.js +1 -1
  50. package/dist/cjs/fetchers/firestore.js.map +1 -1
  51. package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
  52. package/dist/cjs/fetchers/javascript.js +1 -1
  53. package/dist/cjs/fetchers/javascript.js.map +1 -1
  54. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
  55. package/dist/cjs/fetchers/mariadb.js +1 -1
  56. package/dist/cjs/fetchers/mariadb.js.map +1 -1
  57. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
  58. package/dist/cjs/fetchers/mongodb.js +1 -1
  59. package/dist/cjs/fetchers/mongodb.js.map +1 -1
  60. package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
  61. package/dist/cjs/fetchers/mysql.js +1 -1
  62. package/dist/cjs/fetchers/mysql.js.map +1 -1
  63. package/dist/cjs/fetchers/postgresql.d.ts +1 -1
  64. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  65. package/dist/cjs/fetchers/postgresql.js +9 -4
  66. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  67. package/dist/cjs/fetchers/raw-query.d.ts +8 -1
  68. package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
  69. package/dist/cjs/fetchers/raw-query.js +42 -4
  70. package/dist/cjs/fetchers/raw-query.js.map +1 -1
  71. package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
  72. package/dist/cjs/fetchers/redshift.js +1 -1
  73. package/dist/cjs/fetchers/redshift.js.map +1 -1
  74. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
  75. package/dist/cjs/fetchers/rest-api.js +7 -2
  76. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  77. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  78. package/dist/cjs/fetchers/teleport.js +16 -6
  79. package/dist/cjs/fetchers/teleport.js.map +1 -1
  80. package/dist/cjs/fetchers/turso.d.ts.map +1 -1
  81. package/dist/cjs/fetchers/turso.js +1 -1
  82. package/dist/cjs/fetchers/turso.js.map +1 -1
  83. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +43 -0
  84. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -0
  85. package/dist/cjs/fetchers/utils/browser-row-policy.js +66 -0
  86. package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -0
  87. package/dist/cjs/fetchers/utils/request-sql-guards.d.ts +8 -0
  88. package/dist/cjs/fetchers/utils/request-sql-guards.d.ts.map +1 -0
  89. package/dist/cjs/fetchers/utils/request-sql-guards.js +12 -0
  90. package/dist/cjs/fetchers/utils/request-sql-guards.js.map +1 -0
  91. package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts +40 -0
  92. package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
  93. package/dist/cjs/fetchers/utils/rest-api-response-cache.js +64 -0
  94. package/dist/cjs/fetchers/utils/rest-api-response-cache.js.map +1 -0
  95. package/dist/cjs/fetchers/utils/table-access-guard.d.ts +23 -0
  96. package/dist/cjs/fetchers/utils/table-access-guard.d.ts.map +1 -0
  97. package/dist/cjs/fetchers/utils/table-access-guard.js +34 -0
  98. package/dist/cjs/fetchers/utils/table-access-guard.js.map +1 -0
  99. package/dist/cjs/index.d.ts +5 -0
  100. package/dist/cjs/index.d.ts.map +1 -1
  101. package/dist/cjs/index.js +60 -8
  102. package/dist/cjs/index.js.map +1 -1
  103. package/dist/cjs/items-path.d.ts +10 -0
  104. package/dist/cjs/items-path.d.ts.map +1 -0
  105. package/dist/cjs/items-path.js +86 -0
  106. package/dist/cjs/items-path.js.map +1 -0
  107. package/dist/cjs/loading-state.d.ts +16 -1
  108. package/dist/cjs/loading-state.d.ts.map +1 -1
  109. package/dist/cjs/loading-state.js +69 -3
  110. package/dist/cjs/loading-state.js.map +1 -1
  111. package/dist/cjs/localized-columns.d.ts +56 -0
  112. package/dist/cjs/localized-columns.d.ts.map +1 -0
  113. package/dist/cjs/localized-columns.js +137 -0
  114. package/dist/cjs/localized-columns.js.map +1 -0
  115. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  116. package/dist/cjs/pagination-plugin.js +140 -50
  117. package/dist/cjs/pagination-plugin.js.map +1 -1
  118. package/dist/cjs/product-filter-fields.d.ts +87 -0
  119. package/dist/cjs/product-filter-fields.d.ts.map +1 -0
  120. package/dist/cjs/product-filter-fields.js +161 -0
  121. package/dist/cjs/product-filter-fields.js.map +1 -0
  122. package/dist/cjs/request-locale.d.ts +49 -0
  123. package/dist/cjs/request-locale.d.ts.map +1 -0
  124. package/dist/cjs/request-locale.js +130 -0
  125. package/dist/cjs/request-locale.js.map +1 -0
  126. package/dist/cjs/search-clear.d.ts +31 -0
  127. package/dist/cjs/search-clear.d.ts.map +1 -0
  128. package/dist/cjs/search-clear.js +103 -0
  129. package/dist/cjs/search-clear.js.map +1 -0
  130. package/dist/cjs/transformations/blog-adjacent-posts.d.ts +16 -0
  131. package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -0
  132. package/dist/cjs/transformations/blog-adjacent-posts.js +25 -0
  133. package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -0
  134. package/dist/cjs/transformations/blog-comments.d.ts +30 -0
  135. package/dist/cjs/transformations/blog-comments.d.ts.map +1 -0
  136. package/dist/cjs/transformations/blog-comments.js +34 -0
  137. package/dist/cjs/transformations/blog-comments.js.map +1 -0
  138. package/dist/cjs/transformations/blog-post.d.ts +2 -0
  139. package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
  140. package/dist/cjs/transformations/blog-post.js +4 -1
  141. package/dist/cjs/transformations/blog-post.js.map +1 -1
  142. package/dist/cjs/transformations/ecommerce-product.d.ts +9 -1
  143. package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
  144. package/dist/cjs/transformations/ecommerce-product.js +28 -1
  145. package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
  146. package/dist/cjs/transformations/heading-anchors.d.ts +25 -0
  147. package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -0
  148. package/dist/cjs/transformations/heading-anchors.js +29 -0
  149. package/dist/cjs/transformations/heading-anchors.js.map +1 -0
  150. package/dist/cjs/transformations/index.d.ts +61 -4
  151. package/dist/cjs/transformations/index.d.ts.map +1 -1
  152. package/dist/cjs/transformations/index.js +69 -19
  153. package/dist/cjs/transformations/index.js.map +1 -1
  154. package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
  155. package/dist/cjs/transformations/shared-utils.js +1 -1
  156. package/dist/cjs/transformations/shared-utils.js.map +1 -1
  157. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  158. package/dist/cjs/utils.d.ts +11 -1
  159. package/dist/cjs/utils.d.ts.map +1 -1
  160. package/dist/cjs/utils.js +60 -16
  161. package/dist/cjs/utils.js.map +1 -1
  162. package/dist/esm/array-overlap-sql.d.ts +21 -0
  163. package/dist/esm/array-overlap-sql.d.ts.map +1 -0
  164. package/dist/esm/array-overlap-sql.js +21 -0
  165. package/dist/esm/array-overlap-sql.js.map +1 -0
  166. package/dist/esm/content-localization.d.ts +37 -0
  167. package/dist/esm/content-localization.d.ts.map +1 -0
  168. package/dist/esm/content-localization.js +24 -0
  169. package/dist/esm/content-localization.js.map +1 -0
  170. package/dist/esm/count-effect.d.ts +11 -1
  171. package/dist/esm/count-effect.d.ts.map +1 -1
  172. package/dist/esm/count-effect.js +28 -1
  173. package/dist/esm/count-effect.js.map +1 -1
  174. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  175. package/dist/esm/data-source-fetchers.js +11 -5
  176. package/dist/esm/data-source-fetchers.js.map +1 -1
  177. package/dist/esm/fetchers/airtable.d.ts.map +1 -1
  178. package/dist/esm/fetchers/airtable.js +1 -1
  179. package/dist/esm/fetchers/airtable.js.map +1 -1
  180. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
  181. package/dist/esm/fetchers/clickhouse.js +1 -1
  182. package/dist/esm/fetchers/clickhouse.js.map +1 -1
  183. package/dist/esm/fetchers/firestore.d.ts.map +1 -1
  184. package/dist/esm/fetchers/firestore.js +1 -1
  185. package/dist/esm/fetchers/firestore.js.map +1 -1
  186. package/dist/esm/fetchers/javascript.d.ts.map +1 -1
  187. package/dist/esm/fetchers/javascript.js +1 -1
  188. package/dist/esm/fetchers/javascript.js.map +1 -1
  189. package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
  190. package/dist/esm/fetchers/mariadb.js +1 -1
  191. package/dist/esm/fetchers/mariadb.js.map +1 -1
  192. package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
  193. package/dist/esm/fetchers/mongodb.js +1 -1
  194. package/dist/esm/fetchers/mongodb.js.map +1 -1
  195. package/dist/esm/fetchers/mysql.d.ts.map +1 -1
  196. package/dist/esm/fetchers/mysql.js +1 -1
  197. package/dist/esm/fetchers/mysql.js.map +1 -1
  198. package/dist/esm/fetchers/postgresql.d.ts +1 -1
  199. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  200. package/dist/esm/fetchers/postgresql.js +9 -4
  201. package/dist/esm/fetchers/postgresql.js.map +1 -1
  202. package/dist/esm/fetchers/raw-query.d.ts +8 -1
  203. package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
  204. package/dist/esm/fetchers/raw-query.js +42 -4
  205. package/dist/esm/fetchers/raw-query.js.map +1 -1
  206. package/dist/esm/fetchers/redshift.d.ts.map +1 -1
  207. package/dist/esm/fetchers/redshift.js +1 -1
  208. package/dist/esm/fetchers/redshift.js.map +1 -1
  209. package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
  210. package/dist/esm/fetchers/rest-api.js +7 -2
  211. package/dist/esm/fetchers/rest-api.js.map +1 -1
  212. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  213. package/dist/esm/fetchers/teleport.js +16 -6
  214. package/dist/esm/fetchers/teleport.js.map +1 -1
  215. package/dist/esm/fetchers/turso.d.ts.map +1 -1
  216. package/dist/esm/fetchers/turso.js +1 -1
  217. package/dist/esm/fetchers/turso.js.map +1 -1
  218. package/dist/esm/fetchers/utils/browser-row-policy.d.ts +43 -0
  219. package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -0
  220. package/dist/esm/fetchers/utils/browser-row-policy.js +60 -0
  221. package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -0
  222. package/dist/esm/fetchers/utils/request-sql-guards.d.ts +8 -0
  223. package/dist/esm/fetchers/utils/request-sql-guards.d.ts.map +1 -0
  224. package/dist/esm/fetchers/utils/request-sql-guards.js +8 -0
  225. package/dist/esm/fetchers/utils/request-sql-guards.js.map +1 -0
  226. package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts +40 -0
  227. package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
  228. package/dist/esm/fetchers/utils/rest-api-response-cache.js +59 -0
  229. package/dist/esm/fetchers/utils/rest-api-response-cache.js.map +1 -0
  230. package/dist/esm/fetchers/utils/table-access-guard.d.ts +23 -0
  231. package/dist/esm/fetchers/utils/table-access-guard.d.ts.map +1 -0
  232. package/dist/esm/fetchers/utils/table-access-guard.js +29 -0
  233. package/dist/esm/fetchers/utils/table-access-guard.js.map +1 -0
  234. package/dist/esm/index.d.ts +5 -0
  235. package/dist/esm/index.d.ts.map +1 -1
  236. package/dist/esm/index.js +55 -7
  237. package/dist/esm/index.js.map +1 -1
  238. package/dist/esm/items-path.d.ts +10 -0
  239. package/dist/esm/items-path.d.ts.map +1 -0
  240. package/dist/esm/items-path.js +58 -0
  241. package/dist/esm/items-path.js.map +1 -0
  242. package/dist/esm/loading-state.d.ts +16 -1
  243. package/dist/esm/loading-state.d.ts.map +1 -1
  244. package/dist/esm/loading-state.js +69 -3
  245. package/dist/esm/loading-state.js.map +1 -1
  246. package/dist/esm/localized-columns.d.ts +56 -0
  247. package/dist/esm/localized-columns.d.ts.map +1 -0
  248. package/dist/esm/localized-columns.js +132 -0
  249. package/dist/esm/localized-columns.js.map +1 -0
  250. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  251. package/dist/esm/pagination-plugin.js +141 -51
  252. package/dist/esm/pagination-plugin.js.map +1 -1
  253. package/dist/esm/product-filter-fields.d.ts +87 -0
  254. package/dist/esm/product-filter-fields.d.ts.map +1 -0
  255. package/dist/esm/product-filter-fields.js +154 -0
  256. package/dist/esm/product-filter-fields.js.map +1 -0
  257. package/dist/esm/request-locale.d.ts +49 -0
  258. package/dist/esm/request-locale.d.ts.map +1 -0
  259. package/dist/esm/request-locale.js +98 -0
  260. package/dist/esm/request-locale.js.map +1 -0
  261. package/dist/esm/search-clear.d.ts +31 -0
  262. package/dist/esm/search-clear.d.ts.map +1 -0
  263. package/dist/esm/search-clear.js +74 -0
  264. package/dist/esm/search-clear.js.map +1 -0
  265. package/dist/esm/transformations/blog-adjacent-posts.d.ts +16 -0
  266. package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -0
  267. package/dist/esm/transformations/blog-adjacent-posts.js +21 -0
  268. package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -0
  269. package/dist/esm/transformations/blog-comments.d.ts +30 -0
  270. package/dist/esm/transformations/blog-comments.d.ts.map +1 -0
  271. package/dist/esm/transformations/blog-comments.js +30 -0
  272. package/dist/esm/transformations/blog-comments.js.map +1 -0
  273. package/dist/esm/transformations/blog-post.d.ts +2 -0
  274. package/dist/esm/transformations/blog-post.d.ts.map +1 -1
  275. package/dist/esm/transformations/blog-post.js +4 -1
  276. package/dist/esm/transformations/blog-post.js.map +1 -1
  277. package/dist/esm/transformations/ecommerce-product.d.ts +9 -1
  278. package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
  279. package/dist/esm/transformations/ecommerce-product.js +29 -2
  280. package/dist/esm/transformations/ecommerce-product.js.map +1 -1
  281. package/dist/esm/transformations/heading-anchors.d.ts +25 -0
  282. package/dist/esm/transformations/heading-anchors.d.ts.map +1 -0
  283. package/dist/esm/transformations/heading-anchors.js +25 -0
  284. package/dist/esm/transformations/heading-anchors.js.map +1 -0
  285. package/dist/esm/transformations/index.d.ts +61 -4
  286. package/dist/esm/transformations/index.d.ts.map +1 -1
  287. package/dist/esm/transformations/index.js +69 -19
  288. package/dist/esm/transformations/index.js.map +1 -1
  289. package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
  290. package/dist/esm/transformations/shared-utils.js +1 -1
  291. package/dist/esm/transformations/shared-utils.js.map +1 -1
  292. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  293. package/dist/esm/utils.d.ts +11 -1
  294. package/dist/esm/utils.d.ts.map +1 -1
  295. package/dist/esm/utils.js +60 -16
  296. package/dist/esm/utils.js.map +1 -1
  297. package/package.json +5 -5
  298. package/src/array-overlap-sql.ts +41 -0
  299. package/src/content-localization.ts +53 -0
  300. package/src/count-effect.ts +53 -2
  301. package/src/data-source-fetchers.ts +41 -5
  302. package/src/fetchers/airtable.ts +4 -2
  303. package/src/fetchers/clickhouse.ts +4 -2
  304. package/src/fetchers/firestore.ts +4 -2
  305. package/src/fetchers/javascript.ts +9 -1
  306. package/src/fetchers/mariadb.ts +4 -2
  307. package/src/fetchers/mongodb.ts +4 -2
  308. package/src/fetchers/mysql.ts +4 -2
  309. package/src/fetchers/postgresql.ts +53 -12
  310. package/src/fetchers/raw-query.ts +60 -5
  311. package/src/fetchers/redshift.ts +4 -2
  312. package/src/fetchers/rest-api.ts +27 -5
  313. package/src/fetchers/teleport.ts +85 -22
  314. package/src/fetchers/turso.ts +4 -2
  315. package/src/fetchers/utils/browser-row-policy.ts +212 -0
  316. package/src/fetchers/utils/request-sql-guards.ts +25 -0
  317. package/src/fetchers/utils/rest-api-response-cache.ts +188 -0
  318. package/src/fetchers/utils/table-access-guard.ts +39 -0
  319. package/src/index.ts +79 -11
  320. package/src/items-path.ts +74 -0
  321. package/src/loading-state.ts +134 -3
  322. package/src/localized-columns.ts +146 -0
  323. package/src/pagination-plugin.ts +206 -60
  324. package/src/product-filter-fields.ts +173 -0
  325. package/src/request-locale.ts +113 -0
  326. package/src/search-clear.ts +102 -0
  327. package/src/transformations/blog-adjacent-posts.ts +74 -0
  328. package/src/transformations/blog-comments.ts +132 -0
  329. package/src/transformations/blog-post.ts +38 -5
  330. package/src/transformations/ecommerce-product.ts +568 -5
  331. package/src/transformations/heading-anchors.ts +85 -0
  332. package/src/transformations/index.ts +191 -23
  333. package/src/transformations/shared-utils.ts +6 -0
  334. package/src/utils.ts +109 -19
@@ -0,0 +1,122 @@
1
+ import * as types from '@babel/types'
2
+ import generator from '@babel/generator'
3
+ import {
4
+ ChunkType,
5
+ FileType,
6
+ ComponentStructure,
7
+ ChunkDefinition,
8
+ } from '@teleporthq/teleport-types'
9
+ import { createNextArrayMapperPaginationPlugin } from '../src/pagination-plugin'
10
+ import { SEARCH_CLEAR_ATTR } from '../src/search-clear'
11
+
12
+ // tslint:disable:no-any
13
+
14
+ const element = (
15
+ tag: string,
16
+ attrs: Array<[string, string]>,
17
+ children: types.JSXElement['children'] = []
18
+ ): types.JSXElement =>
19
+ types.jsxElement(
20
+ types.jsxOpeningElement(
21
+ types.jsxIdentifier(tag),
22
+ attrs.map(([name, value]) =>
23
+ types.jsxAttribute(types.jsxIdentifier(name), types.stringLiteral(value))
24
+ ),
25
+ false
26
+ ),
27
+ types.jsxClosingElement(types.jsxIdentifier(tag)),
28
+ children,
29
+ false
30
+ )
31
+
32
+ const searchInput = (): types.JSXElement => element('input', [['className', 'search-input']])
33
+ const clearButton = (): types.JSXElement =>
34
+ element('button', [
35
+ ['type', 'button'],
36
+ [SEARCH_CLEAR_ATTR, 'true'],
37
+ ])
38
+
39
+ /**
40
+ * A component whose JSX is the field the editor builds — `<div><input
41
+ * class=search-input/><button data-tq-search-clear/></div>` — plus, when
42
+ * asked, a stray marked button somewhere else on the page.
43
+ */
44
+ const makeComponentChunk = (withStrayButton: boolean): ChunkDefinition => {
45
+ const field = element('div', [['data-tq-search-field', 'true']], [searchInput(), clearButton()])
46
+ const root = element(
47
+ 'div',
48
+ [],
49
+ withStrayButton ? [field, element('div', [], [clearButton()])] : [field]
50
+ )
51
+ const body = types.blockStatement([types.returnStatement(root)])
52
+ const arrow = types.arrowFunctionExpression([types.identifier('props')], body)
53
+ const declaration = types.variableDeclaration('const', [
54
+ types.variableDeclarator(types.identifier('TestComponent'), arrow),
55
+ ])
56
+ return {
57
+ name: 'jsx-component',
58
+ type: ChunkType.AST,
59
+ fileType: FileType.JS,
60
+ linkAfter: [],
61
+ content: declaration,
62
+ meta: {},
63
+ }
64
+ }
65
+
66
+ const makeUidlNode = (): any => ({
67
+ type: 'data-source-list',
68
+ content: {
69
+ renderPropIdentifier: 'items',
70
+ resourceDefinition: {
71
+ dataSourceId: 'ds1',
72
+ tableName: 'products',
73
+ dataSourceType: 'postgresql',
74
+ },
75
+ resource: { params: { queryColumns: { content: ['name'] } } },
76
+ nodes: {
77
+ success: {
78
+ type: 'cms-list-repeater',
79
+ content: {
80
+ renderPropIdentifier: 'product',
81
+ paginated: true,
82
+ perPage: 20,
83
+ searchEnabled: true,
84
+ searchDebounce: 300,
85
+ nodes: { list: { type: 'element', content: { elementType: 'div' } } },
86
+ },
87
+ },
88
+ },
89
+ },
90
+ })
91
+
92
+ const runPlugin = async (withStrayButton = false): Promise<string> => {
93
+ const chunk = makeComponentChunk(withStrayButton)
94
+ const structure: ComponentStructure = {
95
+ uidl: { name: 'TestComponent', node: makeUidlNode() },
96
+ chunks: [chunk],
97
+ dependencies: {},
98
+ options: { dataSources: {}, extractedResources: {} },
99
+ } as never
100
+ await createNextArrayMapperPaginationPlugin()(structure)
101
+ return generator(chunk.content as types.Node).code
102
+ }
103
+
104
+ const collapse = (code: string): string => code.replace(/\s+/g, ' ')
105
+
106
+ describe('pagination plugin — search clear button', () => {
107
+ it('empties the query on click and renders only while something is typed', async () => {
108
+ const flat = collapse(await runPlugin())
109
+ expect(flat).toContain('onClick={() => setDs_0_searchQuery("")}')
110
+ expect(flat).toContain('{ds_0_searchQuery ? <button type="button" data-tq-search-clear="true"')
111
+ expect(flat).toContain(': null}')
112
+ // The input beside it is the one the button clears.
113
+ expect(flat).toContain('value={ds_0_searchQuery}')
114
+ })
115
+
116
+ it('leaves a marked button that shares no parent with a search input alone', async () => {
117
+ const flat = collapse(await runPlugin(true))
118
+ expect(flat.match(/setDs_0_searchQuery\(""\)/g)).toHaveLength(1)
119
+ // The stray one keeps its markup, unwired and unconditional.
120
+ expect(flat).toContain('<div><button type="button" data-tq-search-clear="true"></button></div>')
121
+ })
122
+ })
@@ -0,0 +1,493 @@
1
+ /* tslint:disable:function-constructor */
2
+ import type { UIDLAuthentication } from '@teleporthq/teleport-types'
3
+ import { TableAccess } from '@teleporthq/teleport-shared'
4
+ import { generateDataSourceFetcherWithCore } from '../src/data-source-fetchers'
5
+ import { buildProductTransformOptions } from '../src/transformations'
6
+
7
+ /**
8
+ * Every table a page binds gets a read route (`/api/teleport-<table>-<source>`
9
+ * plus a `-count` twin) that serves the whole table, and any single SELECT
10
+ * through `?rawQuery=`, to whoever asks. For gift cards and voucher codes that
11
+ * is a list of cash. The route now refuses those tables to anyone but a server
12
+ * segment carrying the app secret or a signed-in member of a trusted role —
13
+ * the generated admin — and the verdict is a status code from the executed
14
+ * route, not a substring of its source.
15
+ */
16
+
17
+ const SECRET = 'server-secret'
18
+ const ADMIN_ROLES = ['admin']
19
+
20
+ interface RouteResult {
21
+ status: number
22
+ body: any
23
+ queries: string[]
24
+ }
25
+
26
+ interface FakeSession {
27
+ role?: string
28
+ }
29
+
30
+ type RouteRunner = (params: {
31
+ handler: 'handler' | 'getCount'
32
+ query?: Record<string, string>
33
+ headers?: Record<string, string>
34
+ session?: FakeSession | null
35
+ }) => Promise<RouteResult>
36
+
37
+ type BootedRoute = RouteRunner & {
38
+ serverCall: (name: 'fetchData' | 'fetchCount') => Promise<unknown>
39
+ }
40
+
41
+ function bootTableRoute(
42
+ tableName: string,
43
+ trustedReaderRoles: string[],
44
+ options: { appSecret?: string | null; rows?: any[] } = {}
45
+ ): BootedRoute {
46
+ const appSecret = options.appSecret === undefined ? SECRET : options.appSecret
47
+ const dataSource = {
48
+ id: 'ds-1',
49
+ name: 'db',
50
+ type: 'teleport' as const,
51
+ config: {
52
+ host: 'teleporthq.secrets.TELEPORT_DB_HOST',
53
+ port: 5432,
54
+ user: 'teleporthq.secrets.TELEPORT_DB_USER',
55
+ password: 'teleporthq.secrets.TELEPORT_DB_PASSWORD',
56
+ database: 'teleporthq.secrets.TELEPORT_DB_NAME',
57
+ selectedTables: { [tableName]: { columns: [{ name: 'id', type: 'uuid' }] } },
58
+ },
59
+ }
60
+ const esm = generateDataSourceFetcherWithCore(dataSource as never, tableName, false, {
61
+ trustedReaderRoles,
62
+ })
63
+ // The route is an ES module; run it as a script with the same bindings.
64
+ const code = esm
65
+ .replace("import { Client } from 'pg'", "const { Client } = require('pg')")
66
+ .replace(/^export default async function handler/m, 'async function handler')
67
+ .replace(/^export \{[^}]*\}\s*$/m, '')
68
+ .replace(/^export default \{[^}]*\}\s*$/m, '')
69
+ .concat(
70
+ '\nmodule.exports = { handler: handler, getCount: getCount, fetchData: fetchData, fetchCount: fetchCount }'
71
+ )
72
+ expect(code).not.toMatch(/^export /m)
73
+
74
+ const queries: string[] = []
75
+ let session: FakeSession | null = null
76
+ class FakeClient {
77
+ public async connect(): Promise<void> {
78
+ return
79
+ }
80
+ public async end(): Promise<void> {
81
+ return
82
+ }
83
+ public async query(sql: string): Promise<{ rows: any[]; rowCount: number }> {
84
+ queries.push(sql)
85
+ if (/information_schema/i.test(sql)) {
86
+ return { rows: [], rowCount: 0 }
87
+ }
88
+ if (/^SELECT COUNT/i.test(sql)) {
89
+ return { rows: [{ count: '0' }], rowCount: 1 }
90
+ }
91
+ const rows = options.rows || []
92
+ return { rows, rowCount: rows.length }
93
+ }
94
+ }
95
+ const fakeRequire = (name: string): any => {
96
+ if (name === 'pg') {
97
+ return { Client: FakeClient }
98
+ }
99
+ if (name === 'next-auth/jwt') {
100
+ return { getToken: async () => (session ? { sub: 'u1', role: session.role } : null) }
101
+ }
102
+ throw new Error('unexpected require: ' + name)
103
+ }
104
+ const moduleObj: { exports: any } = { exports: {} }
105
+ new Function('require', 'module', 'exports', 'process', 'console', code)(
106
+ fakeRequire,
107
+ moduleObj,
108
+ moduleObj.exports,
109
+ {
110
+ env: {
111
+ ...(appSecret ? { NEXTAUTH_SECRET: appSecret } : {}),
112
+ TELEPORT_DB_HOST: 'h',
113
+ TELEPORT_DB_USER: 'u',
114
+ TELEPORT_DB_PASSWORD: 'p',
115
+ TELEPORT_DB_NAME: 'd',
116
+ },
117
+ },
118
+ { error: () => undefined, warn: () => undefined, log: () => undefined }
119
+ )
120
+
121
+ const serverCall = async (name: 'fetchData' | 'fetchCount'): Promise<unknown> => {
122
+ queries.length = 0
123
+ session = null
124
+ return moduleObj.exports[name]({})
125
+ }
126
+
127
+ const run: RouteRunner = async ({
128
+ handler,
129
+ query = {},
130
+ headers = {},
131
+ session: requestSession = null,
132
+ }) => {
133
+ queries.length = 0
134
+ session = requestSession
135
+ let status = 0
136
+ let responseBody: any = null
137
+ const res = {
138
+ status(statusCode: number) {
139
+ status = statusCode
140
+ return this
141
+ },
142
+ json(payload: any) {
143
+ responseBody = payload
144
+ return this
145
+ },
146
+ }
147
+ const req = {
148
+ method: 'GET',
149
+ query,
150
+ headers: {
151
+ cookie: requestSession ? 'next-auth.session-token=signed' : '',
152
+ ...headers,
153
+ },
154
+ }
155
+ await moduleObj.exports[handler](req, res)
156
+ return { status, body: responseBody, queries: queries.slice() }
157
+ }
158
+ const booted = run as BootedRoute
159
+ booted.serverCall = serverCall
160
+ return booted
161
+ }
162
+
163
+ const internal = { 'x-internal-data-secret': SECRET }
164
+
165
+ describe('per-table read routes — money and customer tables are read only by the admin or the server', () => {
166
+ const giftCards = bootTableRoute('teleport_gift_cards', ADMIN_ROLES)
167
+ const vouchers = bootTableRoute('teleport_vouchers', ADMIN_ROLES)
168
+ const products = bootTableRoute('teleport_products', ADMIN_ROLES)
169
+ // The customers' own records: only the generated admin lists them from a
170
+ // browser; a buyer's order page reads through a server segment.
171
+ const orders = bootTableRoute('teleport_orders', ADMIN_ROLES)
172
+ const users = bootTableRoute('users', ADMIN_ROLES)
173
+ const reviews = bootTableRoute('teleport_product_reviews', ADMIN_ROLES)
174
+
175
+ it('refuses a guest before the database is touched, on the list and the count', async () => {
176
+ for (const route of [giftCards, vouchers, orders, users, reviews]) {
177
+ const list = await route({ handler: 'handler' })
178
+ expect(list.status).toBe(401)
179
+ expect(list.queries).toEqual([])
180
+ const count = await route({ handler: 'getCount' })
181
+ expect(count.status).toBe(401)
182
+ expect(count.queries).toEqual([])
183
+ }
184
+ })
185
+
186
+ it('refuses a signed-in shopper, and serves a signed-in administrator', async () => {
187
+ for (const route of [giftCards, orders, users]) {
188
+ expect((await route({ handler: 'handler', session: { role: 'user' } })).status).toBe(403)
189
+ expect((await route({ handler: 'getCount', session: { role: 'user' } })).status).toBe(403)
190
+ }
191
+
192
+ const admin = await giftCards({ handler: 'handler', session: { role: 'admin' } })
193
+ expect(admin.status).toBe(200)
194
+ expect(admin.queries.some((sql) => /FROM teleport_gift_cards/i.test(sql))).toBe(true)
195
+ expect((await giftCards({ handler: 'getCount', session: { role: 'admin' } })).status).toBe(200)
196
+ expect((await orders({ handler: 'handler', session: { role: 'admin' } })).status).toBe(200)
197
+ expect((await users({ handler: 'getCount', session: { role: 'admin' } })).status).toBe(200)
198
+ })
199
+
200
+ it('serves a server segment that presents the app secret', async () => {
201
+ expect((await giftCards({ handler: 'handler', headers: internal })).status).toBe(200)
202
+ expect((await vouchers({ handler: 'getCount', headers: internal })).status).toBe(200)
203
+ })
204
+
205
+ it("serves the module's own server-side fetchData, which getStaticProps and page-load workflows call", async () => {
206
+ await expect(giftCards.serverCall('fetchData')).resolves.toEqual([])
207
+ await expect(vouchers.serverCall('fetchCount')).resolves.toBe(0)
208
+ })
209
+
210
+ it('refuses a browser every raw statement, whatever it names, once the app has a secret', async () => {
211
+ // Generated pages never send one: their raw queries run in getStaticProps
212
+ // (the module's own fetchData) or in server segments. So no statement scan
213
+ // has to be right about what a browser sends — admin sessions included.
214
+ for (const rawQuery of [
215
+ 'SELECT code FROM teleport_gift_cards',
216
+ "SELECT 1 FROM teleport_products WHERE name = 'teleport_gift_cards'",
217
+ 'SELECT code AS "x\'", balance FROM teleport_gift_cards -- \'',
218
+ ]) {
219
+ for (const session of [null, { role: 'user' }, { role: 'admin' }]) {
220
+ const result = await products({ handler: 'handler', query: { rawQuery }, session })
221
+ expect(result.status).toBe(403)
222
+ expect(result.queries).toEqual([])
223
+ }
224
+ expect(
225
+ (await products({ handler: 'handler', query: { rawQuery }, headers: internal })).status
226
+ ).toBe(200)
227
+ }
228
+ })
229
+
230
+ it('refuses a column the query string made up, before the database is touched', async () => {
231
+ // Filter and sort columns are spliced into the statement (values are
232
+ // bound): a sub-select there would read any table, and the error message
233
+ // Postgres returns would carry what it read.
234
+ const smuggled = "CAST((SELECT string_agg(code, ',') FROM teleport_gift_cards) AS int)"
235
+ for (const query of [
236
+ { filters: JSON.stringify([{ source: smuggled, operand: '=', destination: 1 }]) },
237
+ { sorts: JSON.stringify([{ field: smuggled, order: 'asc' }]) },
238
+ { sortBy: smuggled },
239
+ ]) {
240
+ const result = await products({ handler: 'handler', query })
241
+ expect(result.status).toBe(400)
242
+ expect(result.queries).toEqual([])
243
+ }
244
+ // A row count is a number or nothing: the sub-select never reaches SQL.
245
+ const limited = await products({
246
+ handler: 'handler',
247
+ query: { limit: '(SELECT 1 FROM teleport_vouchers)' },
248
+ })
249
+ expect(limited.status).toBe(200)
250
+ expect(limited.queries.join('\n')).not.toMatch(/teleport_vouchers|LIMIT/)
251
+ const count = await products({
252
+ handler: 'getCount',
253
+ query: { filters: JSON.stringify([{ source: smuggled, operand: '=', destination: 1 }]) },
254
+ })
255
+ expect(count.status).toBe(400)
256
+ expect(count.queries).toEqual([])
257
+ // Real columns still filter and sort.
258
+ const plain = await products({
259
+ handler: 'handler',
260
+ query: {
261
+ filters: JSON.stringify([{ source: 'status', operand: '=', destination: 'active' }]),
262
+ sorts: JSON.stringify([{ field: 'created_at', order: 'desc' }]),
263
+ limit: '12',
264
+ },
265
+ })
266
+ expect(plain.status).toBe(200)
267
+ expect(plain.queries.join('\n')).toMatch(/WHERE status = \$1/)
268
+ expect(plain.queries.join('\n')).toMatch(/LIMIT 12/)
269
+ })
270
+
271
+ it('keeps scanning a raw statement in an app with no secret to tell callers apart', async () => {
272
+ // No authentication, hence no store and no admin: the old scan still
273
+ // stands between a raw statement and any protected table.
274
+ const open = bootTableRoute('teleport_products', ADMIN_ROLES, { appSecret: null })
275
+ for (const rawQuery of [
276
+ 'SELECT code FROM teleport_gift_cards',
277
+ 'SELECT * FROM "teleport_vouchers"',
278
+ 'WITH c AS (SELECT id FROM public.teleport_voucher_redemptions) SELECT * FROM c',
279
+ "SELECT 1 /* teleport_products */ FROM Teleport_Gift_Card_Transactions -- 'x'",
280
+ // The rendered gift-card email carries the full code.
281
+ 'SELECT body_html FROM teleport_sent_emails',
282
+ // ⛔ A dollar-quoted body holding a lone apostrophe used to open a
283
+ // "literal" that swallowed the rest of the statement, table name
284
+ // included — Postgres runs it happily.
285
+ "SELECT $$it's$$, code FROM teleport_gift_cards",
286
+ "SELECT $tag$don't$tag$ AS a, balance FROM teleport_gift_cards",
287
+ // An escape string, and an unterminated literal (unreadable ⇒ refused).
288
+ "SELECT E'\\'' AS a, code FROM teleport_gift_cards",
289
+ "SELECT 'unterminated, code FROM teleport_products",
290
+ // Spellings a name scan cannot see: a Unicode-escaped identifier (\\005f
291
+ // is the underscore), and functions that run SQL text or dump tables.
292
+ 'SELECT code FROM U&"teleport\\005fgift_cards"',
293
+ "SELECT query_to_xml('select code from teleport_gift_cards', true, false, '')",
294
+ "SELECT database_to_xml(true, false, '')",
295
+ // Quote and comment markers inside a quoted identifier, `$` and `E`
296
+ // inside an identifier, and the planner statistics.
297
+ 'SELECT code AS "x\'", balance FROM teleport_gift_cards -- \'',
298
+ 'SELECT 1 AS "--", code, balance FROM teleport_gift_cards',
299
+ 'SELECT code AS a$b$, balance FROM teleport_gift_cards AS g$b$',
300
+ "SELECT CASE WHEN true THEN 'a' ELSE'\\' END, code FROM teleport_vouchers -- '",
301
+ "SELECT histogram_bounds::text FROM pg_stats WHERE tablename = 'teleport_vouchers'",
302
+ ]) {
303
+ const guest = await open({ handler: 'handler', query: { rawQuery } })
304
+ expect(guest.status).toBe(401)
305
+ expect(guest.queries).toEqual([])
306
+ }
307
+ // A readable statement that names nothing protected still passes, dollar
308
+ // quotes and nested comments included.
309
+ for (const rawQuery of [
310
+ "SELECT 1 FROM team_members WHERE name = 'teleport_gift_cards'",
311
+ "SELECT $$it's fine$$ /* a /* nested */ note */ FROM team_members",
312
+ ]) {
313
+ expect((await open({ handler: 'handler', query: { rawQuery } })).status).toBe(200)
314
+ }
315
+ // A statement over a table a visitor only reads narrowed (the published
316
+ // posts, the active products) cannot be narrowed, so it is refused whole.
317
+ for (const rawQuery of [
318
+ "SELECT 1 FROM teleport_products WHERE name = 'teleport_gift_cards'",
319
+ 'SELECT title, author_email FROM teleport_blog_posts',
320
+ ]) {
321
+ const refused = await open({ handler: 'handler', query: { rawQuery } })
322
+ expect(refused.status).toBe(403)
323
+ expect(refused.queries).toEqual([])
324
+ }
325
+ })
326
+
327
+ it('leaves every other read exactly as it was', async () => {
328
+ expect((await products({ handler: 'handler' })).status).toBe(200)
329
+ expect((await products({ handler: 'getCount' })).status).toBe(200)
330
+ })
331
+
332
+ it('serves the automatic-discount table to no browser but the admin — its one public shape is the data API feed', async () => {
333
+ const discounts = bootTableRoute('teleport_discounts', ADMIN_ROLES)
334
+ const guest = await discounts({ handler: 'handler' })
335
+ expect(guest.status).toBe(401)
336
+ expect(guest.queries).toEqual([])
337
+ expect((await discounts({ handler: 'getCount', session: { role: 'user' } })).status).toBe(403)
338
+ expect((await discounts({ handler: 'handler', session: { role: 'admin' } })).status).toBe(200)
339
+ // Nor through another table's raw statement.
340
+ const rawQuery = "SELECT name, usage_count FROM teleport_discounts WHERE status = 'draft'"
341
+ expect((await products({ handler: 'handler', query: { rawQuery } })).status).toBe(403)
342
+ expect(
343
+ (await products({ handler: 'handler', query: { rawQuery }, session: { role: 'user' } }))
344
+ .status
345
+ ).toBe(403)
346
+ })
347
+
348
+ it('admits nobody from a browser when the project has no admin panel', async () => {
349
+ const noAdmin = bootTableRoute('teleport_gift_cards', [])
350
+ expect((await noAdmin({ handler: 'handler', session: { role: 'admin' } })).status).toBe(403)
351
+ expect((await noAdmin({ handler: 'handler', headers: internal })).status).toBe(200)
352
+ })
353
+ })
354
+
355
+ const ADMIN_PANEL_AUTH: UIDLAuthentication = {
356
+ enabled: true,
357
+ dataSourceId: 'ds-1',
358
+ dataSourceType: 'teleport',
359
+ passwordAuthEnabled: true,
360
+ providers: [],
361
+ roles: ['admin', 'user'],
362
+ tables: {},
363
+ pageProtection: {},
364
+ folderProtection: {
365
+ f1: {
366
+ requiresAuth: true,
367
+ allowedRoles: ['admin'],
368
+ folderName: 'admin-panel',
369
+ parentId: null,
370
+ children: {},
371
+ },
372
+ },
373
+ authPages: {},
374
+ callbackBaseUrl: '',
375
+ envKeys: {},
376
+ customUserProperties: [],
377
+ }
378
+
379
+ describe('per-table read routes — the users table never serves its credentials', () => {
380
+ const userRow = {
381
+ id: 'u1',
382
+ name: 'Ada',
383
+ password: '$2b$10$hash',
384
+ access_token: 'ya29.token',
385
+ refresh_token: '1//refresh',
386
+ id_token: 'eyJ.id',
387
+ session_state: 's',
388
+ token_type: 'Bearer',
389
+ scope: 'email',
390
+ expires_at: 1790000000,
391
+ }
392
+
393
+ it("keeps the profile and drops the password and tokens, for a browser and for a page's props", async () => {
394
+ // Whatever fetchData returns is serialized into the page, so the server
395
+ // path strips them too.
396
+ const users = bootTableRoute('users', ADMIN_ROLES, { rows: [userRow] })
397
+ const read = await users({ handler: 'handler', session: { role: 'admin' } })
398
+ expect(read.status).toBe(200)
399
+ expect(read.body.data).toEqual([{ id: 'u1', name: 'Ada', __rowNumber: 1 }])
400
+ const props = (await users.serverCall('fetchData')) as Array<Record<string, unknown>>
401
+ expect(props.map((row) => Object.keys(row).sort())).toEqual([['__rowNumber', 'id', 'name']])
402
+ })
403
+
404
+ it('drops the unambiguous credential names from a raw statement, whatever table it read', async () => {
405
+ const products = bootTableRoute('teleport_products', ADMIN_ROLES, {
406
+ rows: [{ id: 'p1', expires_at: '2026-12-31', password: 'x', access_token: 'y' }],
407
+ })
408
+ const result = await products({
409
+ handler: 'handler',
410
+ query: { rawQuery: 'SELECT p.*, u.password FROM teleport_products p' },
411
+ headers: internal,
412
+ })
413
+ expect(result.body.data).toEqual([{ id: 'p1', expires_at: '2026-12-31' }])
414
+ })
415
+ })
416
+
417
+ describe('buildProductTransformOptions — the roles reach the emitted module', () => {
418
+ it("reads the project's auth under BOTH names, because a ProjectUIDL spells it `authentication`", () => {
419
+ // The project plugins (data-source utility, global-state API routes, page
420
+ // state fetchers) hand this the UIDL itself.
421
+ expect(buildProductTransformOptions({ authentication: ADMIN_PANEL_AUTH })).toMatchObject({
422
+ trustedReaderRoles: ['admin'],
423
+ })
424
+ expect(buildProductTransformOptions({ auth: ADMIN_PANEL_AUTH })).toMatchObject({
425
+ trustedReaderRoles: ['admin'],
426
+ })
427
+ expect(buildProductTransformOptions({})).toMatchObject({ trustedReaderRoles: [] })
428
+ })
429
+
430
+ it("emits a money table's route with a role list its admin panel can pass", async () => {
431
+ const options = buildProductTransformOptions({ authentication: ADMIN_PANEL_AUTH })
432
+ const giftCards = bootTableRoute('teleport_gift_cards', options.trustedReaderRoles || [])
433
+ expect((await giftCards({ handler: 'handler', session: { role: 'admin' } })).status).toBe(200)
434
+ expect((await giftCards({ handler: 'handler', session: { role: 'user' } })).status).toBe(403)
435
+ })
436
+ })
437
+
438
+ describe('resolveTrustedReaderRoles', () => {
439
+ it('reads the roles off the admin folder and the admin pages, and nothing else', () => {
440
+ const roles = TableAccess.resolveTrustedReaderRoles({
441
+ enabled: true,
442
+ dataSourceId: 'ds-1',
443
+ dataSourceType: 'teleport',
444
+ passwordAuthEnabled: true,
445
+ providers: [],
446
+ roles: ['admin', 'staff', 'user'],
447
+ tables: {},
448
+ pageProtection: {
449
+ p1: {
450
+ requiresAuth: true,
451
+ allowedRoles: ['admin'],
452
+ pageName: 'dashboard',
453
+ route: '/admin/dashboard',
454
+ },
455
+ p2: {
456
+ requiresAuth: true,
457
+ allowedRoles: ['staff'],
458
+ pageName: 'orders',
459
+ route: '/admin/orders',
460
+ },
461
+ p3: { requiresAuth: true, allowedRoles: ['user'], pageName: 'profile', route: '/profile' },
462
+ p4: {
463
+ requiresAuth: true,
464
+ allowedRoles: ['user'],
465
+ pageName: 'administration',
466
+ route: '/administration',
467
+ },
468
+ },
469
+ folderProtection: {
470
+ f1: {
471
+ requiresAuth: true,
472
+ allowedRoles: ['admin'],
473
+ folderName: 'admin-panel',
474
+ parentId: null,
475
+ children: {},
476
+ },
477
+ f2: {
478
+ requiresAuth: true,
479
+ allowedRoles: ['user'],
480
+ folderName: 'members',
481
+ parentId: null,
482
+ children: {},
483
+ },
484
+ },
485
+ authPages: {},
486
+ callbackBaseUrl: '',
487
+ envKeys: {},
488
+ customUserProperties: [],
489
+ })
490
+ expect(roles).toEqual(['admin', 'staff'])
491
+ expect(TableAccess.resolveTrustedReaderRoles(undefined)).toEqual([])
492
+ })
493
+ })
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The SQL an `array_overlap` filter compiles to: "the row's list shares any
3
+ * value with these".
4
+ *
5
+ * A list column is TEXT in the platform tables, and holds whatever wrote it:
6
+ *
7
+ * - a JSON array — `["react","css"]` — which is what the editor writes;
8
+ * - a comma-separated list — `react, css` — which is what a person types
9
+ * into the generated admin's plain text field;
10
+ * - a PostgreSQL array literal — `{react,css}` — from a native array column.
11
+ *
12
+ * Casting every cell to `jsonb` (what this used to do) threw on the second
13
+ * kind, and one hand-typed row failed the WHOLE listing query — every visitor
14
+ * saw an empty blog. Each form is now read as the text array it means, and a
15
+ * blank cell as no list at all (it matches nothing, fails nothing).
16
+ *
17
+ * Emitted as plain source with no backtick, no `${` and no backslash, because
18
+ * the fetchers interpolate it into their own template literals.
19
+ */
20
+ export declare const generateArrayOverlapSqlCode: () => string;
21
+ //# sourceMappingURL=array-overlap-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array-overlap-sql.d.ts","sourceRoot":"","sources":["../../src/array-overlap-sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,2BAA2B,QAAO,MAqB9C,CAAA"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateArrayOverlapSqlCode = void 0;
4
+ /**
5
+ * The SQL an `array_overlap` filter compiles to: "the row's list shares any
6
+ * value with these".
7
+ *
8
+ * A list column is TEXT in the platform tables, and holds whatever wrote it:
9
+ *
10
+ * - a JSON array — `["react","css"]` — which is what the editor writes;
11
+ * - a comma-separated list — `react, css` — which is what a person types
12
+ * into the generated admin's plain text field;
13
+ * - a PostgreSQL array literal — `{react,css}` — from a native array column.
14
+ *
15
+ * Casting every cell to `jsonb` (what this used to do) threw on the second
16
+ * kind, and one hand-typed row failed the WHOLE listing query — every visitor
17
+ * saw an empty blog. Each form is now read as the text array it means, and a
18
+ * blank cell as no list at all (it matches nothing, fails nothing).
19
+ *
20
+ * Emitted as plain source with no backtick, no `${` and no backslash, because
21
+ * the fetchers interpolate it into their own template literals.
22
+ */
23
+ var generateArrayOverlapSqlCode = function () { return "\nfunction listColumnSql(column) {\n var cell = \"BTRIM(COALESCE(\" + column + \"::text, ''))\"\n return (\n '(CASE' +\n \" WHEN \" + cell + \" LIKE '[%' THEN ARRAY(SELECT jsonb_array_elements_text(\" + cell + '::jsonb))' +\n \" WHEN \" + cell + \" LIKE '{%' THEN \" + cell + '::text[]' +\n \" ELSE regexp_split_to_array(NULLIF(\" + cell + \", ''), '[[:space:]]*,[[:space:]]*')\" +\n ' END)'\n )\n}\n\n// Any of the columns (a translatable list and its per-language copies) sharing\n// a value with the parameter.\nfunction arrayOverlapSql(columns, param) {\n var clauses = []\n for (var i = 0; i < columns.length; i++) {\n clauses.push(listColumnSql(columns[i]) + ' && ' + param + '::text[]')\n }\n return clauses.length === 1 ? clauses[0] : '(' + clauses.join(' OR ') + ')'\n}\n"; };
24
+ exports.generateArrayOverlapSqlCode = generateArrayOverlapSqlCode;
25
+ //# sourceMappingURL=array-overlap-sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array-overlap-sql.js","sourceRoot":"","sources":["../../src/array-overlap-sql.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACI,IAAM,2BAA2B,GAAG,cAAc,OAAA,uyBAqBxD,EArBwD,CAqBxD,CAAA;AArBY,QAAA,2BAA2B,+BAqBvC"}