@react-native-firebase/firestore 23.8.8 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/RNFBFirestore.podspec +2 -1
  3. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java +17 -4
  4. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java +2 -2
  5. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestorePipelineExecutor.java +1243 -0
  6. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestorePipelineNodeBuilder.java +3919 -0
  7. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestorePipelineParser.java +1735 -0
  8. package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java +1 -1
  9. package/dist/module/FieldPath.js +59 -0
  10. package/dist/module/FieldPath.js.map +1 -0
  11. package/dist/module/FieldValue.js +82 -0
  12. package/dist/module/FieldValue.js.map +1 -0
  13. package/{lib → dist/module}/FirestoreAggregate.js +31 -43
  14. package/dist/module/FirestoreAggregate.js.map +1 -0
  15. package/dist/module/FirestoreBlob.js +56 -0
  16. package/dist/module/FirestoreBlob.js.map +1 -0
  17. package/dist/module/FirestoreCollectionReference.js +70 -0
  18. package/dist/module/FirestoreCollectionReference.js.map +1 -0
  19. package/{lib → dist/module}/FirestoreDocumentChange.js +12 -15
  20. package/dist/module/FirestoreDocumentChange.js.map +1 -0
  21. package/dist/module/FirestoreDocumentReference.js +170 -0
  22. package/dist/module/FirestoreDocumentReference.js.map +1 -0
  23. package/dist/module/FirestoreDocumentSnapshot.js +88 -0
  24. package/dist/module/FirestoreDocumentSnapshot.js.map +1 -0
  25. package/dist/module/FirestoreFilter.js +146 -0
  26. package/dist/module/FirestoreFilter.js.map +1 -0
  27. package/dist/module/FirestoreGeoPoint.js +80 -0
  28. package/dist/module/FirestoreGeoPoint.js.map +1 -0
  29. package/{lib → dist/module}/FirestorePath.js +5 -12
  30. package/dist/module/FirestorePath.js.map +1 -0
  31. package/{lib → dist/module}/FirestorePersistentCacheIndexManager.js +11 -4
  32. package/dist/module/FirestorePersistentCacheIndexManager.js.map +1 -0
  33. package/dist/module/FirestoreQuery.js +298 -0
  34. package/dist/module/FirestoreQuery.js.map +1 -0
  35. package/{lib → dist/module}/FirestoreQueryModifiers.js +25 -136
  36. package/dist/module/FirestoreQueryModifiers.js.map +1 -0
  37. package/dist/module/FirestoreQuerySnapshot.js +98 -0
  38. package/dist/module/FirestoreQuerySnapshot.js.map +1 -0
  39. package/dist/module/FirestoreSnapshotMetadata.js +38 -0
  40. package/dist/module/FirestoreSnapshotMetadata.js.map +1 -0
  41. package/dist/module/FirestoreStatics.js +50 -0
  42. package/dist/module/FirestoreStatics.js.map +1 -0
  43. package/{lib → dist/module}/FirestoreTimestamp.js +39 -39
  44. package/dist/module/FirestoreTimestamp.js.map +1 -0
  45. package/dist/module/FirestoreTransaction.js +113 -0
  46. package/dist/module/FirestoreTransaction.js.map +1 -0
  47. package/dist/module/FirestoreTransactionHandler.js +137 -0
  48. package/dist/module/FirestoreTransactionHandler.js.map +1 -0
  49. package/dist/module/FirestoreVectorValue.js +75 -0
  50. package/dist/module/FirestoreVectorValue.js.map +1 -0
  51. package/dist/module/FirestoreWriteBatch.js +113 -0
  52. package/dist/module/FirestoreWriteBatch.js.map +1 -0
  53. package/dist/module/LoadBundleTask.js +70 -0
  54. package/dist/module/LoadBundleTask.js.map +1 -0
  55. package/dist/module/index.js +31 -0
  56. package/dist/module/index.js.map +1 -0
  57. package/dist/module/modular/Bytes.js +67 -0
  58. package/dist/module/modular/Bytes.js.map +1 -0
  59. package/dist/module/modular/FieldPath.js +25 -0
  60. package/dist/module/modular/FieldPath.js.map +1 -0
  61. package/dist/module/modular/FieldValue.js +37 -0
  62. package/dist/module/modular/FieldValue.js.map +1 -0
  63. package/dist/module/modular/GeoPoint.js +22 -0
  64. package/dist/module/modular/GeoPoint.js.map +1 -0
  65. package/dist/module/modular/Timestamp.js +22 -0
  66. package/dist/module/modular/Timestamp.js.map +1 -0
  67. package/dist/module/modular/VectorValue.js +25 -0
  68. package/dist/module/modular/VectorValue.js.map +1 -0
  69. package/dist/module/modular/query.js +222 -0
  70. package/dist/module/modular/query.js.map +1 -0
  71. package/dist/module/modular/snapshot.js +32 -0
  72. package/dist/module/modular/snapshot.js.map +1 -0
  73. package/dist/module/modular.js +229 -0
  74. package/dist/module/modular.js.map +1 -0
  75. package/dist/module/namespaced.js +298 -0
  76. package/dist/module/namespaced.js.map +1 -0
  77. package/dist/module/package.json +1 -0
  78. package/dist/module/pipelines/expressions.js +1273 -0
  79. package/dist/module/pipelines/expressions.js.map +1 -0
  80. package/dist/module/pipelines/index.js +32 -0
  81. package/dist/module/pipelines/index.js.map +1 -0
  82. package/dist/module/pipelines/pipeline-result.js +58 -0
  83. package/dist/module/pipelines/pipeline-result.js.map +1 -0
  84. package/dist/module/pipelines/pipeline-source.js +4 -0
  85. package/dist/module/pipelines/pipeline-source.js.map +1 -0
  86. package/dist/module/pipelines/pipeline.js +4 -0
  87. package/dist/module/pipelines/pipeline.js.map +1 -0
  88. package/dist/module/pipelines/pipeline_impl.js +42 -0
  89. package/dist/module/pipelines/pipeline_impl.js.map +1 -0
  90. package/dist/module/pipelines/pipeline_options.js +4 -0
  91. package/dist/module/pipelines/pipeline_options.js.map +1 -0
  92. package/dist/module/pipelines/pipeline_runtime.js +526 -0
  93. package/dist/module/pipelines/pipeline_runtime.js.map +1 -0
  94. package/dist/module/pipelines/pipeline_support.js +71 -0
  95. package/dist/module/pipelines/pipeline_support.js.map +1 -0
  96. package/dist/module/pipelines/pipeline_validate.js +183 -0
  97. package/dist/module/pipelines/pipeline_validate.js.map +1 -0
  98. package/dist/module/pipelines/stage_options.js +4 -0
  99. package/dist/module/pipelines/stage_options.js.map +1 -0
  100. package/dist/module/pipelines/types.js +2 -0
  101. package/dist/module/pipelines/types.js.map +1 -0
  102. package/dist/module/types/firestore.js +4 -0
  103. package/dist/module/types/firestore.js.map +1 -0
  104. package/dist/module/types/internal.js +4 -0
  105. package/dist/module/types/internal.js.map +1 -0
  106. package/dist/module/types/namespaced.js +338 -0
  107. package/dist/module/types/namespaced.js.map +1 -0
  108. package/{lib → dist/module}/utils/index.js +59 -114
  109. package/dist/module/utils/index.js.map +1 -0
  110. package/{lib → dist/module}/utils/serialize.js +58 -116
  111. package/dist/module/utils/serialize.js.map +1 -0
  112. package/{lib → dist/module}/utils/typemap.js +6 -20
  113. package/dist/module/utils/typemap.js.map +1 -0
  114. package/dist/module/version.js +5 -0
  115. package/dist/module/version.js.map +1 -0
  116. package/dist/module/web/RNFBFirestoreModule.android.js +5 -0
  117. package/dist/module/web/RNFBFirestoreModule.android.js.map +1 -0
  118. package/dist/module/web/RNFBFirestoreModule.ios.js +5 -0
  119. package/dist/module/web/RNFBFirestoreModule.ios.js.map +1 -0
  120. package/dist/module/web/RNFBFirestoreModule.js +387 -0
  121. package/dist/module/web/RNFBFirestoreModule.js.map +1 -0
  122. package/{lib → dist/module}/web/convert.js +60 -94
  123. package/dist/module/web/convert.js.map +1 -0
  124. package/dist/module/web/pipelines/pipeline.js +34 -0
  125. package/dist/module/web/pipelines/pipeline.js.map +1 -0
  126. package/dist/module/web/pipelines/pipeline_bridge_factory.js +217 -0
  127. package/dist/module/web/pipelines/pipeline_bridge_factory.js.map +1 -0
  128. package/dist/module/web/pipelines/pipeline_node_builder.js +294 -0
  129. package/dist/module/web/pipelines/pipeline_node_builder.js.map +1 -0
  130. package/dist/module/web/pipelines/pipeline_parser.js +21 -0
  131. package/dist/module/web/pipelines/pipeline_parser.js.map +1 -0
  132. package/dist/module/web/pipelines/pipeline_snapshot_serializer.js +89 -0
  133. package/dist/module/web/pipelines/pipeline_snapshot_serializer.js.map +1 -0
  134. package/dist/module/web/query.js +95 -0
  135. package/dist/module/web/query.js.map +1 -0
  136. package/dist/typescript/lib/FieldPath.d.ts +10 -0
  137. package/dist/typescript/lib/FieldPath.d.ts.map +1 -0
  138. package/dist/typescript/lib/FieldValue.d.ts +17 -0
  139. package/dist/typescript/lib/FieldValue.d.ts.map +1 -0
  140. package/dist/typescript/lib/FirestoreAggregate.d.ts +40 -0
  141. package/dist/typescript/lib/FirestoreAggregate.d.ts.map +1 -0
  142. package/dist/typescript/lib/FirestoreBlob.d.ts +11 -0
  143. package/dist/typescript/lib/FirestoreBlob.d.ts.map +1 -0
  144. package/dist/typescript/lib/FirestoreCollectionReference.d.ts +15 -0
  145. package/dist/typescript/lib/FirestoreCollectionReference.d.ts.map +1 -0
  146. package/dist/typescript/lib/FirestoreDocumentChange.d.ts +27 -0
  147. package/dist/typescript/lib/FirestoreDocumentChange.d.ts.map +1 -0
  148. package/dist/typescript/lib/FirestoreDocumentReference.d.ts +30 -0
  149. package/dist/typescript/lib/FirestoreDocumentReference.d.ts.map +1 -0
  150. package/dist/typescript/lib/FirestoreDocumentSnapshot.d.ts +30 -0
  151. package/dist/typescript/lib/FirestoreDocumentSnapshot.d.ts.map +1 -0
  152. package/dist/typescript/lib/FirestoreFilter.d.ts +52 -0
  153. package/dist/typescript/lib/FirestoreFilter.d.ts.map +1 -0
  154. package/dist/typescript/lib/FirestoreGeoPoint.d.ts +22 -0
  155. package/dist/typescript/lib/FirestoreGeoPoint.d.ts.map +1 -0
  156. package/dist/typescript/lib/FirestorePath.d.ts +12 -0
  157. package/dist/typescript/lib/FirestorePath.d.ts.map +1 -0
  158. package/dist/typescript/lib/FirestorePersistentCacheIndexManager.d.ts +16 -0
  159. package/dist/typescript/lib/FirestorePersistentCacheIndexManager.d.ts.map +1 -0
  160. package/dist/typescript/lib/FirestoreQuery.d.ts +39 -0
  161. package/dist/typescript/lib/FirestoreQuery.d.ts.map +1 -0
  162. package/dist/typescript/lib/FirestoreQueryModifiers.d.ts +59 -0
  163. package/dist/typescript/lib/FirestoreQueryModifiers.d.ts.map +1 -0
  164. package/dist/typescript/lib/FirestoreQuerySnapshot.d.ts +49 -0
  165. package/dist/typescript/lib/FirestoreQuerySnapshot.d.ts.map +1 -0
  166. package/dist/typescript/lib/FirestoreSnapshotMetadata.d.ts +8 -0
  167. package/dist/typescript/lib/FirestoreSnapshotMetadata.d.ts.map +1 -0
  168. package/dist/typescript/lib/FirestoreStatics.d.ts +23 -0
  169. package/dist/typescript/lib/FirestoreStatics.d.ts.map +1 -0
  170. package/dist/typescript/lib/FirestoreTimestamp.d.ts +33 -0
  171. package/dist/typescript/lib/FirestoreTimestamp.d.ts.map +1 -0
  172. package/dist/typescript/lib/FirestoreTransaction.d.ts +42 -0
  173. package/dist/typescript/lib/FirestoreTransaction.d.ts.map +1 -0
  174. package/dist/typescript/lib/FirestoreTransactionHandler.d.ts +26 -0
  175. package/dist/typescript/lib/FirestoreTransactionHandler.d.ts.map +1 -0
  176. package/dist/typescript/lib/FirestoreVectorValue.d.ts +17 -0
  177. package/dist/typescript/lib/FirestoreVectorValue.d.ts.map +1 -0
  178. package/dist/typescript/lib/FirestoreWriteBatch.d.ts +21 -0
  179. package/dist/typescript/lib/FirestoreWriteBatch.d.ts.map +1 -0
  180. package/dist/typescript/lib/LoadBundleTask.d.ts +16 -0
  181. package/dist/typescript/lib/LoadBundleTask.d.ts.map +1 -0
  182. package/dist/typescript/lib/index.d.ts +6 -0
  183. package/dist/typescript/lib/index.d.ts.map +1 -0
  184. package/dist/typescript/lib/modular/Bytes.d.ts +22 -0
  185. package/dist/typescript/lib/modular/Bytes.d.ts.map +1 -0
  186. package/dist/typescript/lib/modular/FieldPath.d.ts +4 -0
  187. package/dist/typescript/lib/modular/FieldPath.d.ts.map +1 -0
  188. package/dist/typescript/lib/modular/FieldValue.d.ts +8 -0
  189. package/dist/typescript/lib/modular/FieldValue.d.ts.map +1 -0
  190. package/dist/typescript/lib/modular/GeoPoint.d.ts +3 -0
  191. package/dist/typescript/lib/modular/GeoPoint.d.ts.map +1 -0
  192. package/dist/typescript/lib/modular/Timestamp.d.ts +3 -0
  193. package/dist/typescript/lib/modular/Timestamp.d.ts.map +1 -0
  194. package/dist/typescript/lib/modular/VectorValue.d.ts +4 -0
  195. package/dist/typescript/lib/modular/VectorValue.d.ts.map +1 -0
  196. package/dist/typescript/lib/modular/query.d.ts +93 -0
  197. package/dist/typescript/lib/modular/query.d.ts.map +1 -0
  198. package/dist/typescript/lib/modular/snapshot.d.ts +30 -0
  199. package/dist/typescript/lib/modular/snapshot.d.ts.map +1 -0
  200. package/dist/typescript/lib/modular.d.ts +69 -0
  201. package/dist/typescript/lib/modular.d.ts.map +1 -0
  202. package/dist/typescript/lib/namespaced.d.ts +13 -0
  203. package/dist/typescript/lib/namespaced.d.ts.map +1 -0
  204. package/dist/typescript/lib/pipelines/expressions.d.ts +723 -0
  205. package/dist/typescript/lib/pipelines/expressions.d.ts.map +1 -0
  206. package/dist/typescript/lib/pipelines/index.d.ts +31 -0
  207. package/dist/typescript/lib/pipelines/index.d.ts.map +1 -0
  208. package/dist/typescript/lib/pipelines/pipeline-result.d.ts +30 -0
  209. package/dist/typescript/lib/pipelines/pipeline-result.d.ts.map +1 -0
  210. package/dist/typescript/lib/pipelines/pipeline-source.d.ts +64 -0
  211. package/dist/typescript/lib/pipelines/pipeline-source.d.ts.map +1 -0
  212. package/dist/typescript/lib/pipelines/pipeline.d.ts +61 -0
  213. package/dist/typescript/lib/pipelines/pipeline.d.ts.map +1 -0
  214. package/dist/typescript/lib/pipelines/pipeline_impl.d.ts +21 -0
  215. package/dist/typescript/lib/pipelines/pipeline_impl.d.ts.map +1 -0
  216. package/dist/typescript/lib/pipelines/pipeline_options.d.ts +17 -0
  217. package/dist/typescript/lib/pipelines/pipeline_options.d.ts.map +1 -0
  218. package/dist/typescript/lib/pipelines/pipeline_runtime.d.ts +10 -0
  219. package/dist/typescript/lib/pipelines/pipeline_runtime.d.ts.map +1 -0
  220. package/dist/typescript/lib/pipelines/pipeline_support.d.ts +7 -0
  221. package/dist/typescript/lib/pipelines/pipeline_support.d.ts.map +1 -0
  222. package/dist/typescript/lib/pipelines/pipeline_validate.d.ts +9 -0
  223. package/dist/typescript/lib/pipelines/pipeline_validate.d.ts.map +1 -0
  224. package/dist/typescript/lib/pipelines/stage_options.d.ts +326 -0
  225. package/dist/typescript/lib/pipelines/stage_options.d.ts.map +1 -0
  226. package/dist/typescript/lib/pipelines/types.d.ts +10 -0
  227. package/dist/typescript/lib/pipelines/types.d.ts.map +1 -0
  228. package/dist/typescript/lib/types/firestore.d.ts +263 -0
  229. package/dist/typescript/lib/types/firestore.d.ts.map +1 -0
  230. package/dist/typescript/lib/types/internal.d.ts +483 -0
  231. package/dist/typescript/lib/types/internal.d.ts.map +1 -0
  232. package/dist/typescript/lib/types/namespaced.d.ts +2285 -0
  233. package/dist/typescript/lib/types/namespaced.d.ts.map +1 -0
  234. package/dist/typescript/lib/utils/index.d.ts +15 -0
  235. package/dist/typescript/lib/utils/index.d.ts.map +1 -0
  236. package/dist/typescript/lib/utils/serialize.d.ts +17 -0
  237. package/dist/typescript/lib/utils/serialize.d.ts.map +1 -0
  238. package/dist/typescript/lib/utils/typemap.d.ts +3 -0
  239. package/dist/typescript/lib/utils/typemap.d.ts.map +1 -0
  240. package/dist/typescript/lib/version.d.ts +2 -0
  241. package/dist/typescript/lib/version.d.ts.map +1 -0
  242. package/dist/typescript/lib/web/RNFBFirestoreModule.android.d.ts +3 -0
  243. package/dist/typescript/lib/web/RNFBFirestoreModule.android.d.ts.map +1 -0
  244. package/dist/typescript/lib/web/RNFBFirestoreModule.d.ts +75 -0
  245. package/dist/typescript/lib/web/RNFBFirestoreModule.d.ts.map +1 -0
  246. package/dist/typescript/lib/web/RNFBFirestoreModule.ios.d.ts +3 -0
  247. package/dist/typescript/lib/web/RNFBFirestoreModule.ios.d.ts.map +1 -0
  248. package/dist/typescript/lib/web/convert.d.ts +14 -0
  249. package/dist/typescript/lib/web/convert.d.ts.map +1 -0
  250. package/dist/typescript/lib/web/pipelines/pipeline.d.ts +4 -0
  251. package/dist/typescript/lib/web/pipelines/pipeline.d.ts.map +1 -0
  252. package/dist/typescript/lib/web/pipelines/pipeline_bridge_factory.d.ts +5 -0
  253. package/dist/typescript/lib/web/pipelines/pipeline_bridge_factory.d.ts.map +1 -0
  254. package/dist/typescript/lib/web/pipelines/pipeline_node_builder.d.ts +5 -0
  255. package/dist/typescript/lib/web/pipelines/pipeline_node_builder.d.ts.map +1 -0
  256. package/dist/typescript/lib/web/pipelines/pipeline_parser.d.ts +3 -0
  257. package/dist/typescript/lib/web/pipelines/pipeline_parser.d.ts.map +1 -0
  258. package/dist/typescript/lib/web/pipelines/pipeline_snapshot_serializer.d.ts +4 -0
  259. package/dist/typescript/lib/web/pipelines/pipeline_snapshot_serializer.d.ts.map +1 -0
  260. package/dist/typescript/lib/web/query.d.ts +23 -0
  261. package/dist/typescript/lib/web/query.d.ts.map +1 -0
  262. package/dist/typescript/package.json +1 -0
  263. package/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m +52 -2
  264. package/ios/RNFBFirestore/RNFBFirestorePipelineBridgeFactory.swift +384 -0
  265. package/ios/RNFBFirestore/RNFBFirestorePipelineCallHandler.swift +86 -0
  266. package/ios/RNFBFirestore/RNFBFirestorePipelineNodeBuilder.swift +1500 -0
  267. package/ios/RNFBFirestore/RNFBFirestorePipelineParser.swift +1352 -0
  268. package/ios/RNFBFirestore/RNFBFirestorePipelineSnapshotSerializer.swift +98 -0
  269. package/lib/{FirestoreFieldPath.js → FieldPath.ts} +10 -12
  270. package/lib/{FirestoreFieldValue.js → FieldValue.ts} +22 -19
  271. package/lib/FirestoreAggregate.ts +124 -0
  272. package/lib/FirestoreBlob.ts +73 -0
  273. package/lib/FirestoreCollectionReference.ts +99 -0
  274. package/lib/FirestoreDocumentChange.ts +71 -0
  275. package/lib/FirestoreDocumentReference.ts +310 -0
  276. package/lib/FirestoreDocumentSnapshot.ts +149 -0
  277. package/lib/FirestoreFilter.ts +232 -0
  278. package/lib/{FirestoreGeoPoint.js → FirestoreGeoPoint.ts} +48 -8
  279. package/lib/FirestorePath.ts +54 -0
  280. package/lib/FirestorePersistentCacheIndexManager.ts +46 -0
  281. package/lib/{FirestoreQuery.js → FirestoreQuery.ts} +208 -100
  282. package/lib/FirestoreQueryModifiers.ts +411 -0
  283. package/lib/{FirestoreQuerySnapshot.js → FirestoreQuerySnapshot.ts} +61 -32
  284. package/lib/{FirestoreSnapshotMetadata.js → FirestoreSnapshotMetadata.ts} +8 -6
  285. package/lib/{FirestoreStatics.js → FirestoreStatics.ts} +18 -11
  286. package/lib/FirestoreTimestamp.ts +161 -0
  287. package/lib/{FirestoreTransaction.js → FirestoreTransaction.ts} +64 -27
  288. package/lib/{FirestoreTransactionHandler.js → FirestoreTransactionHandler.ts} +54 -75
  289. package/lib/{FirestoreVectorValue.js → FirestoreVectorValue.ts} +36 -15
  290. package/lib/{FirestoreWriteBatch.js → FirestoreWriteBatch.ts} +45 -21
  291. package/lib/LoadBundleTask.ts +85 -0
  292. package/lib/index.ts +71 -0
  293. package/lib/modular/Bytes.ts +81 -0
  294. package/lib/modular/FieldPath.ts +24 -0
  295. package/lib/modular/FieldValue.ts +40 -0
  296. package/lib/modular/GeoPoint.ts +20 -0
  297. package/lib/modular/Timestamp.ts +20 -0
  298. package/lib/modular/VectorValue.ts +24 -0
  299. package/lib/modular/query.ts +368 -0
  300. package/lib/modular/snapshot.ts +137 -0
  301. package/lib/modular.ts +552 -0
  302. package/lib/{index.js → namespaced.ts} +170 -80
  303. package/lib/pipelines/expressions.ts +2321 -0
  304. package/lib/pipelines/index.ts +203 -0
  305. package/lib/pipelines/pipeline-result.ts +78 -0
  306. package/lib/pipelines/pipeline-source.ts +83 -0
  307. package/lib/pipelines/pipeline.ts +99 -0
  308. package/lib/pipelines/pipeline_impl.ts +46 -0
  309. package/lib/pipelines/pipeline_options.ts +32 -0
  310. package/lib/pipelines/pipeline_runtime.ts +863 -0
  311. package/lib/pipelines/pipeline_support.ts +134 -0
  312. package/lib/pipelines/pipeline_validate.ts +242 -0
  313. package/lib/pipelines/stage_options.ts +376 -0
  314. package/lib/pipelines/types.ts +26 -0
  315. package/lib/types/firestore.ts +477 -0
  316. package/lib/types/internal.ts +747 -0
  317. package/lib/{index.d.ts → types/namespaced.ts} +280 -79
  318. package/lib/utils/index.ts +244 -0
  319. package/lib/utils/serialize.ts +314 -0
  320. package/lib/utils/typemap.ts +65 -0
  321. package/lib/version.ts +2 -0
  322. package/lib/web/{RNFBFirestoreModule.js → RNFBFirestoreModule.ts} +222 -234
  323. package/lib/web/convert.ts +287 -0
  324. package/lib/web/pipelines/pipeline.ts +47 -0
  325. package/lib/web/pipelines/pipeline_bridge_factory.ts +377 -0
  326. package/lib/web/pipelines/pipeline_node_builder.ts +413 -0
  327. package/lib/web/pipelines/pipeline_parser.ts +23 -0
  328. package/lib/web/pipelines/pipeline_snapshot_serializer.ts +133 -0
  329. package/lib/web/query.ts +150 -0
  330. package/package.json +46 -7
  331. package/tsconfig.json +35 -0
  332. package/lib/FirestoreBlob.js +0 -107
  333. package/lib/FirestoreCollectionReference.js +0 -70
  334. package/lib/FirestoreDocumentReference.js +0 -222
  335. package/lib/FirestoreDocumentSnapshot.js +0 -132
  336. package/lib/FirestoreFilter.js +0 -156
  337. package/lib/modular/Bytes.d.ts +0 -11
  338. package/lib/modular/Bytes.js +0 -62
  339. package/lib/modular/FieldPath.d.ts +0 -20
  340. package/lib/modular/FieldPath.js +0 -7
  341. package/lib/modular/FieldValue.d.ts +0 -67
  342. package/lib/modular/FieldValue.js +0 -41
  343. package/lib/modular/GeoPoint.d.ts +0 -17
  344. package/lib/modular/GeoPoint.js +0 -3
  345. package/lib/modular/Timestamp.d.ts +0 -85
  346. package/lib/modular/Timestamp.js +0 -3
  347. package/lib/modular/VectorValue.d.ts +0 -30
  348. package/lib/modular/VectorValue.js +0 -11
  349. package/lib/modular/index.d.ts +0 -788
  350. package/lib/modular/index.js +0 -410
  351. package/lib/modular/query.d.ts +0 -370
  352. package/lib/modular/query.js +0 -233
  353. package/lib/modular/snapshot.d.ts +0 -256
  354. package/lib/modular/snapshot.js +0 -33
  355. package/lib/modular/utils/observer.js +0 -16
  356. package/lib/version.js +0 -2
  357. package/lib/web/query.js +0 -112
  358. /package/lib/web/{RNFBFirestoreModule.android.js → RNFBFirestoreModule.android.ts} +0 -0
  359. /package/lib/web/{RNFBFirestoreModule.ios.js → RNFBFirestoreModule.ios.ts} +0 -0
@@ -0,0 +1,1500 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import Foundation
18
+ import FirebaseFirestore
19
+
20
+ final class RNFBFirestorePipelineNodeBuilder {
21
+ private final class SerializedValueBox {
22
+ var value: Any?
23
+ }
24
+
25
+ private final class SerializedExpressionBox {
26
+ var value: Any?
27
+ }
28
+
29
+ private final class ExprBridgeBox {
30
+ var value: ExprBridge?
31
+ }
32
+
33
+ private final class RawParamBox {
34
+ var value: Any?
35
+ }
36
+
37
+ private enum ExpressionCoercionMode {
38
+ case expression
39
+ case booleanExpression
40
+ case expressionValue
41
+ case comparisonOperand
42
+ case vectorExpressionValue
43
+ }
44
+
45
+ private enum ExpressionCoercionFrame {
46
+ case enter(
47
+ Any,
48
+ String,
49
+ ExpressionCoercionMode,
50
+ ExprBridgeBox
51
+ )
52
+ case functionExit(
53
+ ExprBridgeBox,
54
+ String,
55
+ [ExprBridgeBox],
56
+ String
57
+ )
58
+ case conditionalExit(
59
+ ExprBridgeBox,
60
+ ExprBridgeBox,
61
+ ExprBridgeBox,
62
+ ExprBridgeBox,
63
+ String
64
+ )
65
+ case arrayExit(
66
+ ExprBridgeBox,
67
+ [ExprBridgeBox],
68
+ String
69
+ )
70
+ case mapLiteralExit(
71
+ ExprBridgeBox,
72
+ [(String, ExprBridgeBox)],
73
+ String
74
+ )
75
+ case mapPassthroughExit(
76
+ ExprBridgeBox,
77
+ [ExprBridgeBox],
78
+ String
79
+ )
80
+ case logicalOperatorExit(
81
+ ExprBridgeBox,
82
+ String,
83
+ [ExprBridgeBox],
84
+ String
85
+ )
86
+ case binaryOperatorExit(
87
+ ExprBridgeBox,
88
+ String,
89
+ String,
90
+ ExprBridgeBox,
91
+ String
92
+ )
93
+ }
94
+
95
+ private enum RawParamCoercionFrame {
96
+ case enter(
97
+ Any,
98
+ String,
99
+ RawParamBox
100
+ )
101
+ case listExit(
102
+ RawParamBox,
103
+ [RawParamBox]
104
+ )
105
+ case mapExit(
106
+ RawParamBox,
107
+ [(String, RawParamBox)]
108
+ )
109
+ }
110
+
111
+ private enum SerializationFrame {
112
+ case expressionEnter(
113
+ RNFBFirestoreParsedExpressionNode,
114
+ SerializedExpressionBox
115
+ )
116
+ case expressionFunctionExit(
117
+ SerializedExpressionBox,
118
+ String,
119
+ [SerializedValueBox]
120
+ )
121
+ case valueEnter(
122
+ RNFBFirestoreParsedValueNode,
123
+ SerializedValueBox
124
+ )
125
+ case valueListExit(
126
+ SerializedValueBox,
127
+ [SerializedValueBox]
128
+ )
129
+ case valueMapExit(
130
+ SerializedValueBox,
131
+ [(String, SerializedValueBox)]
132
+ )
133
+ case expressionConstantExit(
134
+ SerializedExpressionBox,
135
+ SerializedValueBox
136
+ )
137
+ case valueExpressionExit(
138
+ SerializedValueBox,
139
+ SerializedExpressionBox
140
+ )
141
+ }
142
+
143
+ private enum ConstantResolutionFrame {
144
+ case enter(
145
+ Any,
146
+ String,
147
+ SerializedValueBox
148
+ )
149
+ case exitList(
150
+ SerializedValueBox,
151
+ [SerializedValueBox]
152
+ )
153
+ case exitMap(
154
+ SerializedValueBox,
155
+ [(String, SerializedValueBox)]
156
+ )
157
+ }
158
+
159
+ func coerceExpression(
160
+ _ value: RNFBFirestoreParsedExpressionNode,
161
+ fieldName: String
162
+ ) throws -> ExprBridge {
163
+ try coerceExpression(serializeExpressionNode(value), fieldName: fieldName)
164
+ }
165
+
166
+ func coerceBooleanExpression(
167
+ _ value: RNFBFirestoreParsedExpressionNode,
168
+ fieldName: String
169
+ ) throws -> ExprBridge {
170
+ try coerceBooleanExpression(serializeExpressionNode(value), fieldName: fieldName)
171
+ }
172
+
173
+ func coerceNamedSelectables(
174
+ _ values: [RNFBFirestoreParsedSelectableNode],
175
+ fieldName: String
176
+ ) throws -> [String: ExprBridge] {
177
+ guard !values.isEmpty else {
178
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
179
+ }
180
+
181
+ var output: [String: ExprBridge] = [:]
182
+ for (index, value) in values.enumerated() {
183
+ let expression = try coerceExpression(value.expression, fieldName: "\(fieldName)[\(index)].expr")
184
+ let alias = coerceAlias(from: value) ?? expressionAlias(expression) ?? "field_\(index)"
185
+ output[alias] = expression
186
+ }
187
+ return output
188
+ }
189
+
190
+ func coerceOrderings(
191
+ _ values: [RNFBFirestoreParsedOrderingNode],
192
+ fieldName: String
193
+ ) throws -> [Any] {
194
+ guard !values.isEmpty else {
195
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
196
+ }
197
+
198
+ return try values.enumerated().map { index, value in
199
+ OrderingBridge(
200
+ expr: try coerceExpression(value.expression, fieldName: "\(fieldName)[\(index)].expr"),
201
+ direction: value.descending ? "descending" : "ascending"
202
+ )
203
+ }
204
+ }
205
+
206
+ func coerceAliasedAggregate(
207
+ _ value: RNFBFirestoreParsedAggregateNode,
208
+ fieldName: String
209
+ ) throws -> (alias: String, function: AggregateFunctionBridge) {
210
+ var aggregate: [String: Any] = ["kind": value.kind]
211
+ if let primaryValue = value.primaryValue {
212
+ aggregate["expr"] = serializeValueNode(primaryValue)
213
+ }
214
+ if !value.args.isEmpty {
215
+ aggregate["args"] = value.args.map(serializeValueNode)
216
+ }
217
+
218
+ let serializedAccumulator: [String: Any] = [
219
+ "alias": value.alias,
220
+ "aggregate": aggregate,
221
+ ]
222
+
223
+ return (
224
+ alias: value.alias,
225
+ function: try coerceAggregateFunction(serializedAccumulator, fieldName: fieldName)
226
+ )
227
+ }
228
+
229
+ func coerceVector(
230
+ _ value: RNFBFirestoreParsedValueNode,
231
+ fieldName: String
232
+ ) throws -> [Double] {
233
+ try coerceVector(serializeValueNode(value), fieldName: fieldName)
234
+ }
235
+
236
+ func coerceFieldPath(
237
+ _ value: RNFBFirestoreParsedExpressionNode,
238
+ fieldName: String
239
+ ) throws -> String {
240
+ try coerceFieldPath(serializeExpressionNode(value), fieldName: fieldName)
241
+ }
242
+
243
+ func coerceExpression(_ value: Any, fieldName: String) throws -> ExprBridge {
244
+ try coerceExpressionTree(value, fieldName: fieldName, mode: .expression)
245
+ }
246
+
247
+ func coerceBooleanExpression(_ value: Any, fieldName: String) throws -> ExprBridge {
248
+ try coerceExpressionTree(value, fieldName: fieldName, mode: .booleanExpression)
249
+ }
250
+
251
+ func coerceAggregateFunction(
252
+ _ accumulator: [String: Any],
253
+ fieldName: String
254
+ ) throws -> AggregateFunctionBridge {
255
+ let aggregate = (accumulator["aggregate"] as? [String: Any]) ?? accumulator
256
+ let kind = (aggregate["kind"] as? String)
257
+ ?? (aggregate["name"] as? String)
258
+ ?? (aggregate["function"] as? String)
259
+ ?? (aggregate["op"] as? String)
260
+
261
+ guard let kind, !kind.isEmpty else {
262
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to include an aggregate kind.")
263
+ }
264
+ let normalizedKind = normalizeAggregateKind(kind)
265
+
266
+ var args: [ExprBridge] = []
267
+ if let expr = aggregate["expr"] ?? aggregate["field"] ?? aggregate["value"] {
268
+ args.append(try coerceExpression(expr, fieldName: "\(fieldName).expr"))
269
+ }
270
+
271
+ if let extraArgs = aggregate["args"] as? [Any] {
272
+ args.append(contentsOf: try extraArgs.map {
273
+ try coerceExpression($0, fieldName: "\(fieldName).args")
274
+ })
275
+ }
276
+
277
+ return AggregateFunctionBridge(name: normalizedKind, args: args)
278
+ }
279
+
280
+ func coerceNamedSelectables(
281
+ _ values: [Any],
282
+ fieldName: String
283
+ ) throws -> [String: ExprBridge] {
284
+ guard !values.isEmpty else {
285
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
286
+ }
287
+
288
+ var output: [String: ExprBridge] = [:]
289
+ for (index, value) in values.enumerated() {
290
+ let expression = try coerceExpression(value, fieldName: "\(fieldName)[\(index)]")
291
+ let alias = coerceAlias(from: value) ?? expressionAlias(expression) ?? "field_\(index)"
292
+ output[alias] = expression
293
+ }
294
+ return output
295
+ }
296
+
297
+ func coerceOrderings(_ values: [Any], fieldName: String) throws -> [Any] {
298
+ guard !values.isEmpty else {
299
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
300
+ }
301
+
302
+ return try values.enumerated().map { index, value in
303
+ if let path = value as? String {
304
+ return OrderingBridge(expr: FieldBridge(name: path), direction: "asc")
305
+ }
306
+
307
+ guard let map = value as? [String: Any] else {
308
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName)[\(index)] to be a string or object.")
309
+ }
310
+
311
+ let direction = (map["direction"] as? String) ?? "asc"
312
+ let expressionValue = map["expression"] ?? map["expr"] ?? map["field"] ?? map["fieldPath"] ?? map["path"] ?? map
313
+ return OrderingBridge(
314
+ expr: try coerceExpression(expressionValue, fieldName: "\(fieldName)[\(index)]"),
315
+ direction: direction
316
+ )
317
+ }
318
+ }
319
+
320
+ func coerceRawParams(_ value: Any?, fieldName: String) throws -> [Any] {
321
+ guard let value else {
322
+ return []
323
+ }
324
+
325
+ if let values = value as? [Any] {
326
+ return try values.enumerated().map { index, nested in
327
+ try coerceRawParamValue(nested, fieldName: "\(fieldName)[\(index)]")
328
+ }
329
+ }
330
+
331
+ if let values = value as? [String: Any] {
332
+ return [try coerceRawParamDictionary(values, fieldName: fieldName)]
333
+ }
334
+
335
+ return [try coerceRawParamValue(value, fieldName: fieldName)]
336
+ }
337
+
338
+ func coerceRawOptions(_ options: [String: Any]?, fieldName: String) throws -> [String: ExprBridge]? {
339
+ guard let options else {
340
+ return nil
341
+ }
342
+
343
+ var output: [String: ExprBridge] = [:]
344
+ for (key, value) in options {
345
+ output[key] = try coerceExpression(value, fieldName: "\(fieldName).\(key)")
346
+ }
347
+ return output
348
+ }
349
+
350
+ func coerceVector(_ value: Any?, fieldName: String) throws -> [Double] {
351
+ if let map = value as? [String: Any] {
352
+ return try coerceVector(map["values"], fieldName: fieldName)
353
+ }
354
+
355
+ guard let values = value as? [Any] else {
356
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an array.")
357
+ }
358
+
359
+ return try values.map {
360
+ try coerceNumber($0, fieldName: fieldName)
361
+ }
362
+ }
363
+
364
+ func coerceFieldPath(_ value: Any, fieldName: String) throws -> String {
365
+ var currentValue: Any = value
366
+
367
+ while true {
368
+ if let string = currentValue as? String, !string.isEmpty {
369
+ return string
370
+ }
371
+
372
+ if let map = currentValue as? [String: Any] {
373
+ if let path = map["path"] as? String, !path.isEmpty {
374
+ return path
375
+ }
376
+
377
+ if let fieldPath = map["fieldPath"], !(fieldPath is [String: Any]) {
378
+ currentValue = fieldPath
379
+ continue
380
+ }
381
+
382
+ let segments = (map["segments"] as? [Any]) ?? (map["_segments"] as? [Any]) ?? []
383
+ if !segments.isEmpty {
384
+ let stringSegments = try segments.map { segment -> String in
385
+ guard let value = segment as? String else {
386
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) segment values to be strings.")
387
+ }
388
+ return value
389
+ }
390
+ let path = stringSegments.joined(separator: ".")
391
+ if !path.isEmpty {
392
+ return path
393
+ }
394
+ }
395
+ }
396
+
397
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to resolve to a field path string.")
398
+ }
399
+ }
400
+
401
+ func coerceStringArray(
402
+ _ values: [Any],
403
+ fieldName: String
404
+ ) throws -> [String] {
405
+ guard !values.isEmpty else {
406
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
407
+ }
408
+
409
+ return try values.enumerated().map { index, value in
410
+ guard let string = value as? String else {
411
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName)[\(index)] to be a string.")
412
+ }
413
+ return string
414
+ }
415
+ }
416
+
417
+ func coerceInt(_ value: Any?, fieldName: String) throws -> Int {
418
+ guard let value else {
419
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a number.")
420
+ }
421
+ return Int(try coerceNumber(value, fieldName: fieldName))
422
+ }
423
+
424
+ func requireValue(
425
+ _ map: [String: Any],
426
+ key: String,
427
+ fieldName: String
428
+ ) throws -> Any {
429
+ guard let value = map[key] else {
430
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
431
+ }
432
+ return value
433
+ }
434
+
435
+ func requireNonEmptyString(
436
+ _ map: [String: Any],
437
+ key: String,
438
+ fieldName: String
439
+ ) throws -> String {
440
+ guard let value = map[key] as? String else {
441
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a string.")
442
+ }
443
+ guard !value.isEmpty else {
444
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a non-empty string.")
445
+ }
446
+ return value
447
+ }
448
+
449
+ private func coerceBooleanFunctionExpression(
450
+ name: String,
451
+ args: [Any],
452
+ fieldName: String
453
+ ) throws -> ExprBridge {
454
+ return try coerceBooleanExpression([
455
+ "name": name,
456
+ "args": args,
457
+ ], fieldName: fieldName)
458
+ }
459
+
460
+ // NOTE: iOS pipeline function lowering lives in this builder.
461
+ //
462
+ // If a serialized JS pipeline function is not supported by the currently linked
463
+ // Firebase iOS pipeline runtime, add or document it here first.
464
+ //
465
+ // Some functions are intentionally blocked before reaching native on iOS
466
+ // (see `pipeline_support.ts` / `getIOSUnsupportedPipelineFunctions()`)
467
+ // because the installed iOS SDK/runtime currently rejects them with
468
+ // `invalid-argument` even though newer Firebase snippets may show them.
469
+ // When iOS support becomes available, implement the lowering here and then
470
+ // remove the corresponding JS-side unsupported-function guard.
471
+ private func coerceFunctionExpression(
472
+ name: String,
473
+ args: [Any],
474
+ fieldName: String
475
+ ) throws -> ExprBridge {
476
+ return try coerceExpression([
477
+ "name": name,
478
+ "args": args,
479
+ ], fieldName: fieldName)
480
+ }
481
+
482
+ private func buildArrayExpression(_ args: [Any], fieldName: String) throws -> ExprBridge {
483
+ let elements: [Any]
484
+ if args.count == 1, let unwrapped = try unwrapConstantArray(args[0], fieldName: "\(fieldName).args[0]") {
485
+ elements = unwrapped
486
+ } else {
487
+ elements = args
488
+ }
489
+
490
+ if !elements.contains(where: containsSerializedExpression) {
491
+ return ConstantBridge(try elements.enumerated().map { index, value in
492
+ try resolveConstantValue(value, fieldName: "\(fieldName).args[\(index)]")
493
+ })
494
+ }
495
+
496
+ return FunctionExprBridge(name: "array", args: try elements.enumerated().map { index, value in
497
+ try coerceExpressionValue(value, fieldName: "\(fieldName).args[\(index)]")
498
+ })
499
+ }
500
+
501
+ private func buildMapExpression(_ args: [Any], fieldName: String) throws -> ExprBridge {
502
+ guard args.count == 1 else {
503
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).map to include exactly 1 argument.")
504
+ }
505
+
506
+ guard let entries = try unwrapConstantMap(args[0], fieldName: "\(fieldName).args[0]") else {
507
+ return FunctionExprBridge(name: "map", args: try args.enumerated().map { index, value in
508
+ try coerceExpressionValue(value, fieldName: "\(fieldName).args[\(index)]")
509
+ })
510
+ }
511
+
512
+ if !entries.values.contains(where: containsSerializedExpression) {
513
+ var resolved: [String: Any] = [:]
514
+ for (key, value) in entries {
515
+ resolved[key] = try resolveConstantValue(value, fieldName: "\(fieldName).args[0].\(key)")
516
+ }
517
+ return ConstantBridge(resolved)
518
+ }
519
+
520
+ var expressionArgs: [ExprBridge] = []
521
+ for (key, value) in entries {
522
+ expressionArgs.append(ConstantBridge(key))
523
+ expressionArgs.append(try coerceExpressionValue(value, fieldName: "\(fieldName).args[0].\(key)"))
524
+ }
525
+ return FunctionExprBridge(name: "map", args: expressionArgs)
526
+ }
527
+
528
+ private func coerceBooleanOperatorExpression(
529
+ map: [String: Any],
530
+ operatorName: String,
531
+ fieldName: String
532
+ ) throws -> ExprBridge {
533
+ let normalized = operatorName.uppercased()
534
+ if normalized == "AND" || normalized == "OR" {
535
+ guard let queries = map["queries"] as? [Any], !queries.isEmpty else {
536
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).queries to contain boolean expressions.")
537
+ }
538
+
539
+ let args = try queries.map { try coerceBooleanExpression($0, fieldName: "\(fieldName).queries") }
540
+ return FunctionExprBridge(name: normalized == "AND" ? "and" : "or", args: args)
541
+ }
542
+
543
+ let fieldValue = map["fieldPath"] ?? map["field"]
544
+ guard let fieldValue else {
545
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).fieldPath to be provided.")
546
+ }
547
+
548
+ let left = FieldBridge(name: try coerceFieldPath(fieldValue, fieldName: "\(fieldName).fieldPath"))
549
+ let right = map["value"] ?? map["right"] ?? map["operand"] ?? NSNull()
550
+ let rightExpr = try coerceComparisonOperand(right, fieldName: "\(fieldName).value")
551
+ let fnName = mapOperatorToFunction(normalized)
552
+ return FunctionExprBridge(name: fnName, args: [left, rightExpr])
553
+ }
554
+
555
+ private func coerceComparisonOperand(_ value: Any, fieldName: String) throws -> ExprBridge {
556
+ try coerceExpressionTree(value, fieldName: fieldName, mode: .comparisonOperand)
557
+ }
558
+
559
+ private func coerceExpressionValue(_ value: Any, fieldName: String) throws -> ExprBridge {
560
+ try coerceExpressionTree(value, fieldName: fieldName, mode: .expressionValue)
561
+ }
562
+
563
+ private func coerceVectorExpressionValue(_ value: Any, fieldName: String) throws -> ExprBridge {
564
+ try coerceExpressionTree(value, fieldName: fieldName, mode: .vectorExpressionValue)
565
+ }
566
+
567
+ private func resolveConstantValue(_ value: Any, fieldName: String) throws -> Any {
568
+ let rootBox = SerializedValueBox()
569
+ var stack: [ConstantResolutionFrame] = [
570
+ .enter(value, fieldName, rootBox),
571
+ ]
572
+
573
+ while let frame = stack.popLast() {
574
+ switch frame {
575
+ case let .enter(value, currentFieldName, box):
576
+ var currentValue: Any = value
577
+
578
+ while let map = currentValue as? [String: Any],
579
+ let constantValue = try unwrapConstantValue(map, fieldName: currentFieldName) {
580
+ currentValue = constantValue
581
+ }
582
+
583
+ if let map = currentValue as? [String: Any] {
584
+ if isSerializedExpressionLike(map) {
585
+ box.value = try coerceExpression(map, fieldName: currentFieldName)
586
+ continue
587
+ }
588
+
589
+ let entries = map.map { (key: $0.key, box: SerializedValueBox(), value: $0.value) }
590
+ stack.append(.exitMap(box, entries.map { ($0.key, $0.box) }))
591
+ for entry in entries.reversed() {
592
+ stack.append(.enter(entry.value, "\(currentFieldName).\(entry.key)", entry.box))
593
+ }
594
+ continue
595
+ }
596
+
597
+ if let values = currentValue as? [Any] {
598
+ let childBoxes = values.map { _ in SerializedValueBox() }
599
+ stack.append(.exitList(box, childBoxes))
600
+ for index in values.indices.reversed() {
601
+ stack.append(.enter(values[index], "\(currentFieldName)[\(index)]", childBoxes[index]))
602
+ }
603
+ continue
604
+ }
605
+
606
+ box.value = currentValue
607
+ case let .exitList(box, childBoxes):
608
+ box.value = childBoxes.map { $0.value as Any }
609
+ case let .exitMap(box, entries):
610
+ var output: [String: Any] = [:]
611
+ for (key, childBox) in entries {
612
+ output[key] = childBox.value
613
+ }
614
+ box.value = output
615
+ }
616
+ }
617
+
618
+ return rootBox.value as Any
619
+ }
620
+
621
+ private func containsSerializedExpression(_ value: Any) -> Bool {
622
+ var stack: [Any] = [value]
623
+
624
+ while let value = stack.popLast() {
625
+ var currentValue: Any = value
626
+
627
+ while let map = currentValue as? [String: Any],
628
+ let constantValue = try? unwrapConstantValue(map, fieldName: "") {
629
+ currentValue = constantValue
630
+ }
631
+
632
+ if let map = currentValue as? [String: Any] {
633
+ if isSerializedExpressionLike(map) {
634
+ return true
635
+ }
636
+
637
+ for nestedValue in map.values {
638
+ stack.append(nestedValue)
639
+ }
640
+ continue
641
+ }
642
+
643
+ if let values = currentValue as? [Any] {
644
+ for nestedValue in values {
645
+ stack.append(nestedValue)
646
+ }
647
+ }
648
+ }
649
+
650
+ return false
651
+ }
652
+
653
+ private func unwrapConstantArray(_ value: Any, fieldName: String) throws -> [Any]? {
654
+ if let array = value as? [Any] {
655
+ return array
656
+ }
657
+
658
+ guard let map = value as? [String: Any],
659
+ let constantValue = try unwrapConstantValue(map, fieldName: fieldName) else {
660
+ return nil
661
+ }
662
+
663
+ return constantValue as? [Any]
664
+ }
665
+
666
+ private func unwrapConstantMap(_ value: Any, fieldName: String) throws -> [String: Any]? {
667
+ guard let map = value as? [String: Any] else {
668
+ return nil
669
+ }
670
+
671
+ if let constantValue = try unwrapConstantValue(map, fieldName: fieldName) {
672
+ return constantValue as? [String: Any]
673
+ }
674
+
675
+ return isSerializedExpressionLike(map) ? nil : map
676
+ }
677
+
678
+ private func unwrapConstantValue(_ map: [String: Any], fieldName: String) throws -> Any? {
679
+ guard let kind = (map["exprType"] as? String)?.lowercased(), kind == "constant" else {
680
+ return nil
681
+ }
682
+
683
+ guard let value = map["value"] else {
684
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).value to be provided.")
685
+ }
686
+
687
+ return value
688
+ }
689
+
690
+ private func isSerializedExpressionLike(_ map: [String: Any]) -> Bool {
691
+ map["exprType"] != nil || map["operator"] != nil || map["name"] != nil || map["expr"] != nil ||
692
+ map["expression"] != nil || map["fieldPath"] != nil || map["path"] != nil ||
693
+ map["segments"] != nil || map["_segments"] != nil
694
+ }
695
+
696
+ private func canonicalComparisonFunctionName(_ normalizedName: String) -> String {
697
+ switch normalizedName {
698
+ case "equal": return "equal"
699
+ case "notequal": return "not_equal"
700
+ case "greaterthan": return "greater_than"
701
+ case "greaterthanorequal": return "greater_than_or_equal"
702
+ case "lessthan": return "less_than"
703
+ case "lessthanorequal": return "less_than_or_equal"
704
+ case "arraycontains": return "array_contains"
705
+ case "arraycontainsany": return "array_contains_any"
706
+ case "arraycontainsall": return "array_contains_all"
707
+ case "equalany": return "equal_any"
708
+ case "notequalany": return "not_equal_any"
709
+ default: return normalizedName
710
+ }
711
+ }
712
+
713
+ private func canonicalizeFunctionName(_ name: String) -> String {
714
+ name.lowercased()
715
+ .replacingOccurrences(of: "_", with: "")
716
+ .replacingOccurrences(of: "-", with: "")
717
+ }
718
+
719
+ private func normalizeExpressionFunctionName(_ name: String) -> String {
720
+ let normalized = canonicalizeFunctionName(name)
721
+ switch normalized {
722
+ case "conditional":
723
+ return "cond"
724
+ case "logicalmaximum", "arraymaximum":
725
+ return "maximum"
726
+ case "logicalminimum", "arrayminimum":
727
+ return "minimum"
728
+ case "arraysum":
729
+ return "sum"
730
+ case "lower", "tolower":
731
+ return "to_lower"
732
+ case "upper", "toupper":
733
+ return "to_upper"
734
+ case "stringconcat":
735
+ return "string_concat"
736
+ case "startswith":
737
+ return "starts_with"
738
+ case "endswith":
739
+ return "ends_with"
740
+ case "timestampsubtract":
741
+ return "timestamp_sub"
742
+ case "timestamptruncate":
743
+ return "timestamp_trunc"
744
+ case "arraycontains":
745
+ return "array_contains"
746
+ case "arraycontainsany":
747
+ return "array_contains_any"
748
+ case "arraycontainsall":
749
+ return "array_contains_all"
750
+ case "charlength", "characterlength":
751
+ return "char_length"
752
+ case "bytelength":
753
+ return "byte_length"
754
+ case "greaterthan":
755
+ return "greater_than"
756
+ case "lessthan":
757
+ return "less_than"
758
+ case "greaterthanorequal":
759
+ return "greater_than_or_equal"
760
+ case "lessthanorequal":
761
+ return "less_than_or_equal"
762
+ case "notequal":
763
+ return "not_equal"
764
+ default:
765
+ return snakeCaseFunctionName(name)
766
+ }
767
+ }
768
+
769
+ private func normalizeAggregateKind(_ kind: String) -> String {
770
+ let normalized = canonicalizeFunctionName(kind)
771
+ switch normalized {
772
+ case "countall", "count_all":
773
+ return "count"
774
+ case "avg":
775
+ return "average"
776
+ case "min":
777
+ return "minimum"
778
+ case "max":
779
+ return "maximum"
780
+ case "countif", "count_if":
781
+ return "count_if"
782
+ case "countdistinct", "count_distinct":
783
+ return "count_distinct"
784
+ case "arrayagg", "array_agg":
785
+ return "array_agg"
786
+ case "arrayaggdistinct", "array_agg_distinct":
787
+ return "array_agg_distinct"
788
+ default:
789
+ return snakeCaseFunctionName(kind)
790
+ }
791
+ }
792
+
793
+ private func snakeCaseFunctionName(_ name: String) -> String {
794
+ guard !name.isEmpty else {
795
+ return name
796
+ }
797
+
798
+ var result = ""
799
+ for scalar in name.unicodeScalars {
800
+ let char = Character(scalar)
801
+ if CharacterSet.uppercaseLetters.contains(scalar) {
802
+ if !result.isEmpty {
803
+ result.append("_")
804
+ }
805
+ result.append(String(char).lowercased())
806
+ } else if char == "-" {
807
+ result.append("_")
808
+ } else {
809
+ result.append(String(char).lowercased())
810
+ }
811
+ }
812
+
813
+ return result
814
+ }
815
+
816
+ private func mapOperatorToFunction(_ operatorName: String) -> String {
817
+ switch operatorName {
818
+ case "==", "=", "EQUAL": return "equal"
819
+ case "!=", "<>", "NOT_EQUAL": return "not_equal"
820
+ case ">", "GREATER_THAN": return "greater_than"
821
+ case ">=", "GREATER_THAN_OR_EQUAL": return "greater_than_or_equal"
822
+ case "<", "LESS_THAN": return "less_than"
823
+ case "<=", "LESS_THAN_OR_EQUAL": return "less_than_or_equal"
824
+ case "ARRAY_CONTAINS", "ARRAY-CONTAINS": return "array_contains"
825
+ case "ARRAY_CONTAINS_ANY", "ARRAY-CONTAINS-ANY": return "array_contains_any"
826
+ case "ARRAY_CONTAINS_ALL", "ARRAY-CONTAINS-ALL": return "array_contains_all"
827
+ case "IN": return "equal_any"
828
+ case "NOT_IN": return "not_equal_any"
829
+ default: return operatorName.lowercased()
830
+ }
831
+ }
832
+
833
+ private func coerceRawParamDictionary(_ values: [String: Any], fieldName: String) throws -> [String: Any] {
834
+ var output: [String: Any] = [:]
835
+ for (key, nested) in values {
836
+ output[key] = try coerceRawParamValue(nested, fieldName: "\(fieldName).\(key)")
837
+ }
838
+ return output
839
+ }
840
+
841
+ private func coerceRawParamValue(_ value: Any, fieldName: String) throws -> Any {
842
+ let rootBox = RawParamBox()
843
+ var stack: [RawParamCoercionFrame] = [
844
+ .enter(value, fieldName, rootBox),
845
+ ]
846
+
847
+ while let frame = stack.popLast() {
848
+ switch frame {
849
+ case let .enter(value, currentFieldName, box):
850
+ if value is ExprBridge || value is AggregateFunctionBridge {
851
+ box.value = value
852
+ continue
853
+ }
854
+
855
+ if let map = value as? [String: Any] {
856
+ let entries = map.map { (key: $0.key, box: RawParamBox(), value: $0.value) }
857
+ stack.append(.mapExit(box, entries.map { ($0.key, $0.box) }))
858
+ for entry in entries.reversed() {
859
+ stack.append(.enter(entry.value, "\(currentFieldName).\(entry.key)", entry.box))
860
+ }
861
+ continue
862
+ }
863
+
864
+ if let array = value as? [Any] {
865
+ let childBoxes = array.map { _ in RawParamBox() }
866
+ stack.append(.listExit(box, childBoxes))
867
+ for index in array.indices.reversed() {
868
+ stack.append(.enter(array[index], "\(currentFieldName)[\(index)]", childBoxes[index]))
869
+ }
870
+ continue
871
+ }
872
+
873
+ box.value = try coerceExpression(value, fieldName: currentFieldName)
874
+ case let .listExit(box, childBoxes):
875
+ box.value = childBoxes.map { $0.value as Any }
876
+ case let .mapExit(box, entries):
877
+ var output: [String: Any] = [:]
878
+ for (key, childBox) in entries {
879
+ output[key] = childBox.value
880
+ }
881
+ box.value = output
882
+ }
883
+ }
884
+
885
+ return rootBox.value as Any
886
+ }
887
+
888
+ private func coerceExpressionTree(
889
+ _ value: Any,
890
+ fieldName: String,
891
+ mode: ExpressionCoercionMode
892
+ ) throws -> ExprBridge {
893
+ let comparisonFunctions: Set<String> = [
894
+ "equal", "notequal", "greaterthan", "greaterthanorequal", "lessthan", "lessthanorequal",
895
+ "arraycontains", "arraycontainsany", "arraycontainsall", "equalany", "notequalany",
896
+ ]
897
+
898
+ let rootBox = ExprBridgeBox()
899
+ var stack: [ExpressionCoercionFrame] = [
900
+ .enter(value, fieldName, mode, rootBox),
901
+ ]
902
+
903
+ while let frame = stack.popLast() {
904
+ switch frame {
905
+ case let .enter(value, currentFieldName, currentMode, box):
906
+ switch currentMode {
907
+ case .expressionValue:
908
+ if containsSerializedExpression(value) {
909
+ stack.append(.enter(value, currentFieldName, .expression, box))
910
+ } else {
911
+ box.value = ConstantBridge(try resolveConstantValue(value, fieldName: currentFieldName))
912
+ }
913
+ continue
914
+ case .comparisonOperand:
915
+ if let map = value as? [String: Any] {
916
+ stack.append(.enter(map, currentFieldName, .expression, box))
917
+ continue
918
+ }
919
+ if let values = value as? [Any] {
920
+ box.value = ConstantBridge(values)
921
+ continue
922
+ }
923
+ if let stringValue = value as? String {
924
+ box.value = ConstantBridge(stringValue)
925
+ continue
926
+ }
927
+ if isImmediateExpressionConstant(value) {
928
+ box.value = ConstantBridge(value)
929
+ continue
930
+ }
931
+ stack.append(.enter(value, currentFieldName, .expression, box))
932
+ continue
933
+ case .vectorExpressionValue:
934
+ var currentValue: Any = value
935
+ while let map = currentValue as? [String: Any],
936
+ let constantValue = try unwrapConstantValue(map, fieldName: currentFieldName) {
937
+ currentValue = constantValue
938
+ }
939
+
940
+ if let map = currentValue as? [String: Any], map["values"] != nil {
941
+ let vector = try coerceVector(map["values"], fieldName: currentFieldName)
942
+ box.value = ConstantBridge(VectorValue(__array: vector.map { NSNumber(value: $0) }))
943
+ continue
944
+ }
945
+
946
+ if currentValue is [Any] {
947
+ let vector = try coerceVector(currentValue, fieldName: currentFieldName)
948
+ box.value = ConstantBridge(VectorValue(__array: vector.map { NSNumber(value: $0) }))
949
+ continue
950
+ }
951
+
952
+ if containsSerializedExpression(currentValue) {
953
+ stack.append(.enter(currentValue, currentFieldName, .expression, box))
954
+ } else {
955
+ box.value = ConstantBridge(try resolveConstantValue(currentValue, fieldName: currentFieldName))
956
+ }
957
+ continue
958
+ case .expression, .booleanExpression:
959
+ var currentValue: Any = value
960
+ var currentField = currentFieldName
961
+
962
+ expressionLoop: while true {
963
+ if currentMode == .booleanExpression,
964
+ let conditionMap = currentValue as? [String: Any],
965
+ let nested = conditionMap["condition"] {
966
+ currentValue = nested
967
+ currentField = "\(currentField).condition"
968
+ continue
969
+ }
970
+
971
+ if let stringValue = currentValue as? String {
972
+ box.value = FieldBridge(name: stringValue)
973
+ break expressionLoop
974
+ }
975
+
976
+ if let expression = currentValue as? ExprBridge {
977
+ box.value = expression
978
+ break expressionLoop
979
+ }
980
+
981
+ if isImmediateExpressionConstant(currentValue) {
982
+ box.value = ConstantBridge(currentValue)
983
+ break expressionLoop
984
+ }
985
+
986
+ guard let map = currentValue as? [String: Any] else {
987
+ throw PipelineValidationError(
988
+ "pipelineExecute() could not convert \(currentField) into a pipeline expression.")
989
+ }
990
+
991
+ if let nested = map["expr"] {
992
+ currentValue = nested
993
+ currentField = "\(currentField).expr"
994
+ continue
995
+ }
996
+ if let nested = map["expression"] {
997
+ currentValue = nested
998
+ currentField = "\(currentField).expression"
999
+ continue
1000
+ }
1001
+
1002
+ if let operatorName = map["operator"] as? String {
1003
+ let normalizedOperator = operatorName.uppercased()
1004
+ if normalizedOperator == "AND" || normalizedOperator == "OR" {
1005
+ guard let queries = map["queries"] as? [Any], !queries.isEmpty else {
1006
+ throw PipelineValidationError(
1007
+ "pipelineExecute() expected \(currentField).queries to contain boolean expressions.")
1008
+ }
1009
+
1010
+ let queryBoxes = queries.map { _ in ExprBridgeBox() }
1011
+ stack.append(.logicalOperatorExit(
1012
+ box,
1013
+ normalizedOperator == "AND" ? "and" : "or",
1014
+ queryBoxes,
1015
+ currentField
1016
+ ))
1017
+ for index in queries.indices.reversed() {
1018
+ stack.append(.enter(
1019
+ queries[index],
1020
+ "\(currentField).queries[\(index)]",
1021
+ .booleanExpression,
1022
+ queryBoxes[index]
1023
+ ))
1024
+ }
1025
+ break expressionLoop
1026
+ }
1027
+
1028
+ let fieldValue = map["fieldPath"] ?? map["field"]
1029
+ guard let fieldValue else {
1030
+ throw PipelineValidationError("pipelineExecute() expected \(currentField).fieldPath to be provided.")
1031
+ }
1032
+
1033
+ let leftFieldPath = try coerceFieldPath(fieldValue, fieldName: "\(currentField).fieldPath")
1034
+ let right = map["value"] ?? map["right"] ?? map["operand"] ?? NSNull()
1035
+ let rightBox = ExprBridgeBox()
1036
+ stack.append(.binaryOperatorExit(
1037
+ box,
1038
+ mapOperatorToFunction(normalizedOperator),
1039
+ leftFieldPath,
1040
+ rightBox,
1041
+ currentField
1042
+ ))
1043
+ stack.append(.enter(right, "\(currentField).value", .comparisonOperand, rightBox))
1044
+ break expressionLoop
1045
+ }
1046
+
1047
+ if let name = map["name"] as? String {
1048
+ let rawArgs: [Any]
1049
+ if let args = map["args"] as? [Any] {
1050
+ rawArgs = args
1051
+ } else if let singleArg = map["args"] {
1052
+ rawArgs = [singleArg]
1053
+ } else {
1054
+ rawArgs = []
1055
+ }
1056
+
1057
+ let normalized = canonicalizeFunctionName(name)
1058
+
1059
+ if normalized == "array" {
1060
+ let elements: [Any]
1061
+ if rawArgs.count == 1,
1062
+ let unwrapped = try unwrapConstantArray(rawArgs[0], fieldName: "\(currentField).args[0]") {
1063
+ elements = unwrapped
1064
+ } else {
1065
+ elements = rawArgs
1066
+ }
1067
+
1068
+ if !elements.contains(where: containsSerializedExpression) {
1069
+ box.value = ConstantBridge(try elements.enumerated().map { index, element in
1070
+ try resolveConstantValue(element, fieldName: "\(currentField).args[\(index)]")
1071
+ })
1072
+ break expressionLoop
1073
+ }
1074
+
1075
+ let argBoxes = elements.map { _ in ExprBridgeBox() }
1076
+ stack.append(.arrayExit(box, argBoxes, currentField))
1077
+ for index in elements.indices.reversed() {
1078
+ stack.append(.enter(
1079
+ elements[index],
1080
+ "\(currentField).args[\(index)]",
1081
+ .expressionValue,
1082
+ argBoxes[index]
1083
+ ))
1084
+ }
1085
+ break expressionLoop
1086
+ }
1087
+
1088
+ if normalized == "map" {
1089
+ guard rawArgs.count == 1 else {
1090
+ throw PipelineValidationError(
1091
+ "pipelineExecute() expected \(currentField).map to include exactly 1 argument.")
1092
+ }
1093
+
1094
+ if let entries = try unwrapConstantMap(rawArgs[0], fieldName: "\(currentField).args[0]") {
1095
+ if !entries.values.contains(where: containsSerializedExpression) {
1096
+ var resolved: [String: Any] = [:]
1097
+ for (key, entryValue) in entries {
1098
+ resolved[key] = try resolveConstantValue(
1099
+ entryValue,
1100
+ fieldName: "\(currentField).args[0].\(key)"
1101
+ )
1102
+ }
1103
+ box.value = ConstantBridge(resolved)
1104
+ break expressionLoop
1105
+ }
1106
+
1107
+ let entryBoxes = entries.map { (key: $0.key, box: ExprBridgeBox(), value: $0.value) }
1108
+ stack.append(.mapLiteralExit(
1109
+ box,
1110
+ entryBoxes.map { ($0.key, $0.box) },
1111
+ currentField
1112
+ ))
1113
+ for entry in entryBoxes.reversed() {
1114
+ stack.append(.enter(
1115
+ entry.value,
1116
+ "\(currentField).args[0].\(entry.key)",
1117
+ .expressionValue,
1118
+ entry.box
1119
+ ))
1120
+ }
1121
+ break expressionLoop
1122
+ }
1123
+
1124
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1125
+ stack.append(.mapPassthroughExit(box, argBoxes, currentField))
1126
+ for index in rawArgs.indices.reversed() {
1127
+ stack.append(.enter(
1128
+ rawArgs[index],
1129
+ "\(currentField).args[\(index)]",
1130
+ .expressionValue,
1131
+ argBoxes[index]
1132
+ ))
1133
+ }
1134
+ break expressionLoop
1135
+ }
1136
+
1137
+ if normalized == "conditional" {
1138
+ guard rawArgs.count == 3 else {
1139
+ throw PipelineValidationError(
1140
+ "pipelineExecute() expected \(currentField).conditional to include exactly 3 arguments.")
1141
+ }
1142
+
1143
+ let conditionBox = ExprBridgeBox()
1144
+ let trueBox = ExprBridgeBox()
1145
+ let falseBox = ExprBridgeBox()
1146
+ stack.append(.conditionalExit(box, conditionBox, trueBox, falseBox, currentField))
1147
+ stack.append(.enter(rawArgs[2], "\(currentField).args[2]", .expressionValue, falseBox))
1148
+ stack.append(.enter(rawArgs[1], "\(currentField).args[1]", .expressionValue, trueBox))
1149
+ stack.append(.enter(rawArgs[0], "\(currentField).args[0]", .booleanExpression, conditionBox))
1150
+ break expressionLoop
1151
+ }
1152
+
1153
+ if normalized == "logicalmaximum" || normalized == "logicalminimum" {
1154
+ guard rawArgs.count >= 2 else {
1155
+ throw PipelineValidationError(
1156
+ "pipelineExecute() expected \(currentField).\(name) to include at least 2 arguments.")
1157
+ }
1158
+
1159
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1160
+ stack.append(.functionExit(box, normalizeExpressionFunctionName(name), argBoxes, currentField))
1161
+ for index in rawArgs.indices.reversed() {
1162
+ stack.append(.enter(
1163
+ rawArgs[index],
1164
+ "\(currentField).args[\(index)]",
1165
+ .expressionValue,
1166
+ argBoxes[index]
1167
+ ))
1168
+ }
1169
+ break expressionLoop
1170
+ }
1171
+
1172
+ if normalized == "cosinedistance" || normalized == "dotproduct" || normalized == "euclideandistance" {
1173
+ guard rawArgs.count == 2 else {
1174
+ throw PipelineValidationError(
1175
+ "pipelineExecute() expected \(currentField).\(name) to include exactly 2 arguments.")
1176
+ }
1177
+
1178
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1179
+ stack.append(.functionExit(box, normalizeExpressionFunctionName(name), argBoxes, currentField))
1180
+ stack.append(.enter(rawArgs[1], "\(currentField).args[1]", .vectorExpressionValue, argBoxes[1]))
1181
+ stack.append(.enter(rawArgs[0], "\(currentField).args[0]", .expressionValue, argBoxes[0]))
1182
+ break expressionLoop
1183
+ }
1184
+
1185
+ if normalized == "and" || normalized == "or" {
1186
+ guard !rawArgs.isEmpty else {
1187
+ throw PipelineValidationError(
1188
+ "pipelineExecute() expected \(currentField).args to contain boolean expressions.")
1189
+ }
1190
+
1191
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1192
+ stack.append(.functionExit(box, normalized, argBoxes, currentField))
1193
+ for index in rawArgs.indices.reversed() {
1194
+ stack.append(.enter(
1195
+ rawArgs[index],
1196
+ "\(currentField).args[\(index)]",
1197
+ .booleanExpression,
1198
+ argBoxes[index]
1199
+ ))
1200
+ }
1201
+ break expressionLoop
1202
+ }
1203
+
1204
+ if comparisonFunctions.contains(normalized) {
1205
+ guard rawArgs.count >= 2 else {
1206
+ throw PipelineValidationError(
1207
+ "pipelineExecute() expected \(currentField).args to include left and right operands.")
1208
+ }
1209
+
1210
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1211
+ stack.append(.functionExit(box, canonicalComparisonFunctionName(normalized), argBoxes, currentField))
1212
+ stack.append(.enter(rawArgs[1], "\(currentField).args[1]", .comparisonOperand, argBoxes[1]))
1213
+ stack.append(.enter(rawArgs[0], "\(currentField).args[0]", .expression, argBoxes[0]))
1214
+ break expressionLoop
1215
+ }
1216
+
1217
+ let argBoxes = rawArgs.map { _ in ExprBridgeBox() }
1218
+ stack.append(.functionExit(box, normalizeExpressionFunctionName(name), argBoxes, currentField))
1219
+ for index in rawArgs.indices.reversed() {
1220
+ stack.append(.enter(
1221
+ rawArgs[index],
1222
+ "\(currentField).args[\(index)]",
1223
+ .expressionValue,
1224
+ argBoxes[index]
1225
+ ))
1226
+ }
1227
+ break expressionLoop
1228
+ }
1229
+
1230
+ if map["fieldPath"] != nil || map["path"] != nil || map["segments"] != nil || map["_segments"] != nil {
1231
+ box.value = FieldBridge(name: try coerceFieldPath(map, fieldName: currentField))
1232
+ break expressionLoop
1233
+ }
1234
+
1235
+ if let kind = (map["exprType"] as? String)?.lowercased(), kind == "constant" {
1236
+ box.value = ConstantBridge(map["value"] as Any)
1237
+ break expressionLoop
1238
+ }
1239
+
1240
+ throw PipelineValidationError(
1241
+ "pipelineExecute() could not convert \(currentField) into a pipeline expression.")
1242
+ }
1243
+ }
1244
+ case let .functionExit(box, name, argBoxes, currentFieldName):
1245
+ box.value = FunctionExprBridge(
1246
+ name: name,
1247
+ args: try argBoxes.enumerated().map { index, argBox in
1248
+ try requireExpressionValue(argBox, fieldName: "\(currentFieldName).args[\(index)]")
1249
+ }
1250
+ )
1251
+ case let .conditionalExit(box, conditionBox, trueBox, falseBox, currentFieldName):
1252
+ box.value = FunctionExprBridge(name: "cond", args: [
1253
+ try requireExpressionValue(conditionBox, fieldName: "\(currentFieldName).args[0]"),
1254
+ try requireExpressionValue(trueBox, fieldName: "\(currentFieldName).args[1]"),
1255
+ try requireExpressionValue(falseBox, fieldName: "\(currentFieldName).args[2]"),
1256
+ ])
1257
+ case let .arrayExit(box, argBoxes, currentFieldName):
1258
+ box.value = FunctionExprBridge(
1259
+ name: "array",
1260
+ args: try argBoxes.enumerated().map { index, argBox in
1261
+ try requireExpressionValue(argBox, fieldName: "\(currentFieldName).args[\(index)]")
1262
+ }
1263
+ )
1264
+ case let .mapLiteralExit(box, entries, currentFieldName):
1265
+ var args: [ExprBridge] = []
1266
+ for (key, valueBox) in entries {
1267
+ args.append(ConstantBridge(key))
1268
+ args.append(try requireExpressionValue(valueBox, fieldName: "\(currentFieldName).args[0].\(key)"))
1269
+ }
1270
+ box.value = FunctionExprBridge(name: "map", args: args)
1271
+ case let .mapPassthroughExit(box, argBoxes, currentFieldName):
1272
+ box.value = FunctionExprBridge(
1273
+ name: "map",
1274
+ args: try argBoxes.enumerated().map { index, argBox in
1275
+ try requireExpressionValue(argBox, fieldName: "\(currentFieldName).args[\(index)]")
1276
+ }
1277
+ )
1278
+ case let .logicalOperatorExit(box, name, argBoxes, currentFieldName):
1279
+ box.value = FunctionExprBridge(
1280
+ name: name,
1281
+ args: try argBoxes.enumerated().map { index, argBox in
1282
+ try requireExpressionValue(argBox, fieldName: "\(currentFieldName).queries[\(index)]")
1283
+ }
1284
+ )
1285
+ case let .binaryOperatorExit(box, name, leftFieldPath, rightBox, currentFieldName):
1286
+ box.value = FunctionExprBridge(name: name, args: [
1287
+ FieldBridge(name: leftFieldPath),
1288
+ try requireExpressionValue(rightBox, fieldName: "\(currentFieldName).value"),
1289
+ ])
1290
+ }
1291
+ }
1292
+
1293
+ return try requireExpressionValue(rootBox, fieldName: fieldName)
1294
+ }
1295
+
1296
+ private func requireExpressionValue(_ box: ExprBridgeBox, fieldName: String) throws -> ExprBridge {
1297
+ guard let value = box.value else {
1298
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
1299
+ }
1300
+ return value
1301
+ }
1302
+
1303
+ private func isImmediateExpressionConstant(_ value: Any) -> Bool {
1304
+ value is NSNull || value is NSNumber || value is Date || value is Timestamp ||
1305
+ value is GeoPoint || value is DocumentReference || value is VectorValue
1306
+ }
1307
+
1308
+ func coerceAlias(from value: Any) -> String? {
1309
+ guard let map = value as? [String: Any] else {
1310
+ return nil
1311
+ }
1312
+ if let alias = map["alias"] as? String, !alias.isEmpty {
1313
+ return alias
1314
+ }
1315
+ if let alias = map["as"] as? String, !alias.isEmpty {
1316
+ return alias
1317
+ }
1318
+ return nil
1319
+ }
1320
+
1321
+ func coerceAlias(from value: RNFBFirestoreParsedSelectableNode) -> String? {
1322
+ if let alias = value.alias, !alias.isEmpty {
1323
+ return alias
1324
+ }
1325
+
1326
+ if case let .field(path) = value.expression, !path.isEmpty {
1327
+ return path
1328
+ }
1329
+
1330
+ return nil
1331
+ }
1332
+
1333
+ private func expressionAlias(_ expression: ExprBridge) -> String? {
1334
+ if let field = expression as? FieldBridge {
1335
+ return field.field_name()
1336
+ }
1337
+ return nil
1338
+ }
1339
+
1340
+ private func serializeExpressionNode(_ value: RNFBFirestoreParsedExpressionNode) -> Any {
1341
+ let rootBox = SerializedExpressionBox()
1342
+ var stack: [SerializationFrame] = [
1343
+ .expressionEnter(value, rootBox),
1344
+ ]
1345
+
1346
+ while let frame = stack.popLast() {
1347
+ switch frame {
1348
+ case let .expressionEnter(expression, box):
1349
+ switch expression {
1350
+ case let .field(path):
1351
+ box.value = [
1352
+ "__kind": "expression",
1353
+ "exprType": "Field",
1354
+ "path": path,
1355
+ ]
1356
+ case let .constant(constantValue):
1357
+ let valueBox = SerializedValueBox()
1358
+ stack.append(.expressionConstantExit(box, valueBox))
1359
+ stack.append(.valueEnter(constantValue, valueBox))
1360
+ case let .function(name, args):
1361
+ let argBoxes = args.map { _ in SerializedValueBox() }
1362
+ stack.append(.expressionFunctionExit(box, name, argBoxes))
1363
+ for index in args.indices.reversed() {
1364
+ stack.append(.valueEnter(args[index], argBoxes[index]))
1365
+ }
1366
+ }
1367
+ case let .expressionFunctionExit(box, name, argBoxes):
1368
+ box.value = [
1369
+ "__kind": "expression",
1370
+ "exprType": "Function",
1371
+ "name": name,
1372
+ "args": argBoxes.map { $0.value as Any },
1373
+ ]
1374
+ case let .valueEnter(value, box):
1375
+ switch value {
1376
+ case let .primitive(primitive):
1377
+ box.value = primitive
1378
+ case let .list(values):
1379
+ let childBoxes = values.map { _ in SerializedValueBox() }
1380
+ stack.append(.valueListExit(box, childBoxes))
1381
+ for index in values.indices.reversed() {
1382
+ stack.append(.valueEnter(values[index], childBoxes[index]))
1383
+ }
1384
+ case let .map(values):
1385
+ let entries = values.map { (key: $0.key, box: SerializedValueBox(), value: $0.value) }
1386
+ stack.append(.valueMapExit(box, entries.map { ($0.key, $0.box) }))
1387
+ for entry in entries.reversed() {
1388
+ stack.append(.valueEnter(entry.value, entry.box))
1389
+ }
1390
+ case let .expression(expression):
1391
+ let expressionBox = SerializedExpressionBox()
1392
+ stack.append(.valueExpressionExit(box, expressionBox))
1393
+ stack.append(.expressionEnter(expression, expressionBox))
1394
+ }
1395
+ case let .valueListExit(box, childBoxes):
1396
+ box.value = childBoxes.map { $0.value as Any }
1397
+ case let .valueMapExit(box, entries):
1398
+ var output: [String: Any] = [:]
1399
+ for (key, childBox) in entries {
1400
+ output[key] = childBox.value
1401
+ }
1402
+ box.value = output
1403
+ case let .expressionConstantExit(expressionBox, valueBox):
1404
+ expressionBox.value = [
1405
+ "__kind": "expression",
1406
+ "exprType": "constant",
1407
+ "value": valueBox.value as Any,
1408
+ ]
1409
+ case let .valueExpressionExit(valueBox, expressionBox):
1410
+ valueBox.value = expressionBox.value
1411
+ }
1412
+ }
1413
+
1414
+ return rootBox.value as Any
1415
+ }
1416
+
1417
+ private func serializeValueNode(_ value: RNFBFirestoreParsedValueNode) -> Any {
1418
+ let rootBox = SerializedValueBox()
1419
+ var stack: [SerializationFrame] = [
1420
+ .valueEnter(value, rootBox),
1421
+ ]
1422
+
1423
+ while let frame = stack.popLast() {
1424
+ switch frame {
1425
+ case let .valueEnter(value, box):
1426
+ switch value {
1427
+ case let .primitive(primitive):
1428
+ box.value = primitive
1429
+ case let .list(values):
1430
+ let childBoxes = values.map { _ in SerializedValueBox() }
1431
+ stack.append(.valueListExit(box, childBoxes))
1432
+ for index in values.indices.reversed() {
1433
+ stack.append(.valueEnter(values[index], childBoxes[index]))
1434
+ }
1435
+ case let .map(values):
1436
+ let entries = values.map { (key: $0.key, box: SerializedValueBox(), value: $0.value) }
1437
+ stack.append(.valueMapExit(box, entries.map { ($0.key, $0.box) }))
1438
+ for entry in entries.reversed() {
1439
+ stack.append(.valueEnter(entry.value, entry.box))
1440
+ }
1441
+ case let .expression(expression):
1442
+ let expressionBox = SerializedExpressionBox()
1443
+ stack.append(.valueExpressionExit(box, expressionBox))
1444
+ stack.append(.expressionEnter(expression, expressionBox))
1445
+ }
1446
+ case let .valueListExit(box, childBoxes):
1447
+ box.value = childBoxes.map { $0.value as Any }
1448
+ case let .valueMapExit(box, entries):
1449
+ var output: [String: Any] = [:]
1450
+ for (key, childBox) in entries {
1451
+ output[key] = childBox.value
1452
+ }
1453
+ box.value = output
1454
+ case let .expressionEnter(expression, box):
1455
+ switch expression {
1456
+ case let .field(path):
1457
+ box.value = [
1458
+ "__kind": "expression",
1459
+ "exprType": "Field",
1460
+ "path": path,
1461
+ ]
1462
+ case let .constant(constantValue):
1463
+ let valueBox = SerializedValueBox()
1464
+ stack.append(.expressionConstantExit(box, valueBox))
1465
+ stack.append(.valueEnter(constantValue, valueBox))
1466
+ case let .function(name, args):
1467
+ let argBoxes = args.map { _ in SerializedValueBox() }
1468
+ stack.append(.expressionFunctionExit(box, name, argBoxes))
1469
+ for index in args.indices.reversed() {
1470
+ stack.append(.valueEnter(args[index], argBoxes[index]))
1471
+ }
1472
+ }
1473
+ case let .expressionFunctionExit(box, name, argBoxes):
1474
+ box.value = [
1475
+ "__kind": "expression",
1476
+ "exprType": "Function",
1477
+ "name": name,
1478
+ "args": argBoxes.map { $0.value as Any },
1479
+ ]
1480
+ case let .expressionConstantExit(expressionBox, valueBox):
1481
+ expressionBox.value = [
1482
+ "__kind": "expression",
1483
+ "exprType": "constant",
1484
+ "value": valueBox.value as Any,
1485
+ ]
1486
+ case let .valueExpressionExit(valueBox, expressionBox):
1487
+ valueBox.value = expressionBox.value
1488
+ }
1489
+ }
1490
+
1491
+ return rootBox.value as Any
1492
+ }
1493
+
1494
+ private func coerceNumber(_ value: Any, fieldName: String) throws -> Double {
1495
+ if let number = value as? NSNumber {
1496
+ return number.doubleValue
1497
+ }
1498
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a number.")
1499
+ }
1500
+ }