@react-native-firebase/firestore 23.8.6 → 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 +35 -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,1352 @@
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
+
19
+ struct RNFBFirestoreParsedPipelineRequest {
20
+ let source: RNFBFirestoreParsedPipelineSource
21
+ let stages: [RNFBFirestoreParsedPipelineStage]
22
+ let options: RNFBFirestoreParsedPipelineExecuteOptions
23
+ }
24
+
25
+ struct RNFBFirestoreParsedPipelineSource {
26
+ let sourceType: String
27
+ let path: String?
28
+ let collectionId: String?
29
+ let documents: [String]
30
+ let queryType: String?
31
+ let query: RNFBFirestoreParsedQuerySource?
32
+ let rawOptions: [String: Any]?
33
+ }
34
+
35
+ struct RNFBFirestoreParsedQuerySource {
36
+ let filters: [RNFBFirestoreParsedValueNode]
37
+ let orders: [RNFBFirestoreParsedValueNode]
38
+ let options: [String: RNFBFirestoreParsedValueNode]
39
+ }
40
+
41
+ indirect enum RNFBFirestoreParsedExpressionNode {
42
+ case field(path: String)
43
+ case constant(RNFBFirestoreParsedValueNode)
44
+ case function(name: String, args: [RNFBFirestoreParsedValueNode])
45
+ }
46
+
47
+ indirect enum RNFBFirestoreParsedValueNode {
48
+ case primitive(Any)
49
+ case list([RNFBFirestoreParsedValueNode])
50
+ case map([String: RNFBFirestoreParsedValueNode])
51
+ case expression(RNFBFirestoreParsedExpressionNode)
52
+ }
53
+
54
+ struct RNFBFirestoreParsedSelectableNode {
55
+ let expression: RNFBFirestoreParsedExpressionNode
56
+ let alias: String?
57
+ let isFlatFieldAlias: Bool
58
+ }
59
+
60
+ struct RNFBFirestoreParsedOrderingNode {
61
+ let expression: RNFBFirestoreParsedExpressionNode
62
+ let descending: Bool
63
+ let fieldShortcut: Bool
64
+ }
65
+
66
+ struct RNFBFirestoreParsedAggregateNode {
67
+ let kind: String
68
+ let alias: String
69
+ let primaryValue: RNFBFirestoreParsedValueNode?
70
+ let args: [RNFBFirestoreParsedValueNode]
71
+ }
72
+
73
+ enum RNFBFirestoreParsedPipelineStage {
74
+ case whereStage(RNFBFirestoreParsedWhereStage)
75
+ case selectStage(RNFBFirestoreParsedSelectStage)
76
+ case addFieldsStage(RNFBFirestoreParsedAddFieldsStage)
77
+ case removeFieldsStage(RNFBFirestoreParsedRemoveFieldsStage)
78
+ case sortStage(RNFBFirestoreParsedSortStage)
79
+ case limitStage(RNFBFirestoreParsedLimitStage)
80
+ case offsetStage(RNFBFirestoreParsedOffsetStage)
81
+ case aggregateStage(RNFBFirestoreParsedAggregateStage)
82
+ case distinctStage(RNFBFirestoreParsedDistinctStage)
83
+ case findNearestStage(RNFBFirestoreParsedFindNearestStage)
84
+ case replaceWithStage(RNFBFirestoreParsedReplaceWithStage)
85
+ case sampleStage(RNFBFirestoreParsedSampleStage)
86
+ case unionStage(RNFBFirestoreParsedUnionStage)
87
+ case unnestStage(RNFBFirestoreParsedUnnestStage)
88
+ case rawStage(RNFBFirestoreParsedRawStage)
89
+
90
+ var stageName: String {
91
+ switch self {
92
+ case .whereStage: return "where"
93
+ case .selectStage: return "select"
94
+ case .addFieldsStage: return "addFields"
95
+ case .removeFieldsStage: return "removeFields"
96
+ case .sortStage: return "sort"
97
+ case .limitStage: return "limit"
98
+ case .offsetStage: return "offset"
99
+ case .aggregateStage: return "aggregate"
100
+ case .distinctStage: return "distinct"
101
+ case .findNearestStage: return "findNearest"
102
+ case .replaceWithStage: return "replaceWith"
103
+ case .sampleStage: return "sample"
104
+ case .unionStage: return "union"
105
+ case .unnestStage: return "unnest"
106
+ case .rawStage: return "rawStage"
107
+ }
108
+ }
109
+ }
110
+
111
+ struct RNFBFirestoreParsedPipelineExecuteOptions {
112
+ let indexMode: String?
113
+ let rawOptions: [String: Any]?
114
+
115
+ var isEmpty: Bool {
116
+ indexMode == nil && rawOptions == nil
117
+ }
118
+ }
119
+
120
+ struct RNFBFirestoreParsedWhereStage {
121
+ let condition: RNFBFirestoreParsedExpressionNode
122
+ }
123
+
124
+ struct RNFBFirestoreParsedSelectStage {
125
+ let selections: [RNFBFirestoreParsedSelectableNode]
126
+ }
127
+
128
+ struct RNFBFirestoreParsedAddFieldsStage {
129
+ let fields: [RNFBFirestoreParsedSelectableNode]
130
+ }
131
+
132
+ struct RNFBFirestoreParsedRemoveFieldsStage {
133
+ let fields: [String]
134
+ }
135
+
136
+ struct RNFBFirestoreParsedSortStage {
137
+ let orderings: [RNFBFirestoreParsedOrderingNode]
138
+ }
139
+
140
+ struct RNFBFirestoreParsedLimitStage {
141
+ let limit: NSNumber
142
+ }
143
+
144
+ struct RNFBFirestoreParsedOffsetStage {
145
+ let offset: NSNumber
146
+ }
147
+
148
+ struct RNFBFirestoreParsedAggregateStage {
149
+ let accumulators: [RNFBFirestoreParsedAggregateNode]
150
+ let groups: [RNFBFirestoreParsedSelectableNode]
151
+ }
152
+
153
+ struct RNFBFirestoreParsedDistinctStage {
154
+ let groups: [RNFBFirestoreParsedSelectableNode]
155
+ }
156
+
157
+ struct RNFBFirestoreParsedFindNearestStage {
158
+ let field: RNFBFirestoreParsedExpressionNode
159
+ let vectorValue: RNFBFirestoreParsedValueNode
160
+ let distanceMeasure: String
161
+ let limit: NSNumber?
162
+ let distanceField: RNFBFirestoreParsedExpressionNode?
163
+ }
164
+
165
+ struct RNFBFirestoreParsedReplaceWithStage {
166
+ let value: RNFBFirestoreParsedExpressionNode
167
+ }
168
+
169
+ struct RNFBFirestoreParsedSampleStage {
170
+ let documents: NSNumber?
171
+ let percentage: NSNumber?
172
+ }
173
+
174
+ struct RNFBFirestoreParsedUnionStage {
175
+ let other: RNFBFirestoreParsedPipelineRequest
176
+ }
177
+
178
+ struct RNFBFirestoreParsedUnnestStage {
179
+ let selectable: RNFBFirestoreParsedSelectableNode
180
+ let indexField: RNFBFirestoreParsedExpressionNode?
181
+ }
182
+
183
+ struct RNFBFirestoreParsedRawStage {
184
+ let name: String
185
+ let params: Any?
186
+ let options: [String: Any]?
187
+ }
188
+
189
+ enum RNFBFirestorePipelineParser {
190
+ private static let sourceTypes: Set<String> = [
191
+ "collection", "collectionGroup", "database", "documents", "query",
192
+ ]
193
+
194
+ private static let knownStages: Set<String> = [
195
+ "where", "select", "addFields", "removeFields", "sort", "limit", "offset",
196
+ "aggregate", "distinct", "findNearest", "replaceWith", "sample", "union", "unnest", "rawStage",
197
+ ]
198
+
199
+ private final class ParsedPipelineRequestBox {
200
+ var value: RNFBFirestoreParsedPipelineRequest?
201
+ }
202
+
203
+ private final class ParsedValueNodeBox {
204
+ var value: RNFBFirestoreParsedValueNode?
205
+ }
206
+
207
+ private final class ParsedExpressionNodeBox {
208
+ var value: RNFBFirestoreParsedExpressionNode?
209
+ }
210
+
211
+ private enum PendingParsedStage {
212
+ case ready(RNFBFirestoreParsedPipelineStage)
213
+ case union(ParsedPipelineRequestBox)
214
+ }
215
+
216
+ private enum PipelineParseFrame {
217
+ case enter(
218
+ pipeline: [String: Any],
219
+ options: [String: Any]?,
220
+ box: ParsedPipelineRequestBox
221
+ )
222
+ case exit(
223
+ source: RNFBFirestoreParsedPipelineSource,
224
+ stages: [PendingParsedStage],
225
+ options: RNFBFirestoreParsedPipelineExecuteOptions,
226
+ box: ParsedPipelineRequestBox
227
+ )
228
+ }
229
+
230
+ private enum QuerySourceValueParseFrame {
231
+ case enter(
232
+ Any,
233
+ ParsedValueNodeBox
234
+ )
235
+ case exitList(
236
+ ParsedValueNodeBox,
237
+ [ParsedValueNodeBox]
238
+ )
239
+ case exitMap(
240
+ ParsedValueNodeBox,
241
+ [(String, ParsedValueNodeBox)]
242
+ )
243
+ }
244
+
245
+ private enum ExpressionValueParseFrame {
246
+ case expressionEnter(
247
+ Any,
248
+ ParsedExpressionNodeBox,
249
+ String
250
+ )
251
+ case expressionValueExit(
252
+ ParsedValueNodeBox,
253
+ ParsedExpressionNodeBox,
254
+ String
255
+ )
256
+ case expressionConstantExit(
257
+ ParsedExpressionNodeBox,
258
+ ParsedValueNodeBox,
259
+ String
260
+ )
261
+ case expressionFunctionExit(
262
+ ParsedExpressionNodeBox,
263
+ String,
264
+ [ParsedValueNodeBox],
265
+ String
266
+ )
267
+ case expressionOperatorLogicalExit(
268
+ ParsedExpressionNodeBox,
269
+ String,
270
+ [ParsedExpressionNodeBox],
271
+ String
272
+ )
273
+ case expressionOperatorBinaryExit(
274
+ ParsedExpressionNodeBox,
275
+ String,
276
+ ParsedExpressionNodeBox,
277
+ ParsedValueNodeBox,
278
+ String
279
+ )
280
+ case valueEnter(
281
+ Any,
282
+ ParsedValueNodeBox,
283
+ String
284
+ )
285
+ case valueListExit(
286
+ ParsedValueNodeBox,
287
+ [ParsedValueNodeBox],
288
+ String
289
+ )
290
+ case valueMapExit(
291
+ ParsedValueNodeBox,
292
+ [(String, ParsedValueNodeBox)],
293
+ String
294
+ )
295
+ }
296
+
297
+ private struct StageDescriptor {
298
+ let name: String
299
+ let options: [String: Any]
300
+ }
301
+
302
+ static func parse(
303
+ pipeline: NSDictionary?,
304
+ options: NSDictionary?
305
+ ) throws -> RNFBFirestoreParsedPipelineRequest {
306
+ guard let pipeline = pipeline as? [String: Any] else {
307
+ throw PipelineValidationError("pipelineExecute() expected a pipeline object.")
308
+ }
309
+
310
+ return try parsePipelineMap(pipeline, options: options as? [String: Any])
311
+ }
312
+
313
+ private static func parsePipelineMap(
314
+ _ pipeline: [String: Any],
315
+ options: [String: Any]?
316
+ ) throws -> RNFBFirestoreParsedPipelineRequest {
317
+ let rootBox = ParsedPipelineRequestBox()
318
+ var stack: [PipelineParseFrame] = [
319
+ .enter(pipeline: pipeline, options: options, box: rootBox),
320
+ ]
321
+
322
+ while let frame = stack.popLast() {
323
+ switch frame {
324
+ case let .enter(pipeline, options, box):
325
+ let source = try parseSource(requireMap(pipeline, key: "source", fieldName: "pipeline.source"))
326
+ let stageMaps = try requireStageArray(pipeline, key: "stages", fieldName: "pipeline.stages")
327
+ let executeOptions = try parseOptions(options)
328
+ var stages: [PendingParsedStage] = []
329
+ var nestedPipelines: [([String: Any], ParsedPipelineRequestBox)] = []
330
+ stages.reserveCapacity(stageMaps.count)
331
+
332
+ for (index, stage) in stageMaps.enumerated() {
333
+ let fieldName = "pipeline.stages[\(index)]"
334
+ let descriptor = try parseStageDescriptor(stage, fieldName: fieldName)
335
+ if descriptor.name == "union" {
336
+ let childBox = ParsedPipelineRequestBox()
337
+ stages.append(.union(childBox))
338
+ nestedPipelines.append((
339
+ try requireMap(descriptor.options, key: "other", fieldName: "\(fieldName).options.other"),
340
+ childBox
341
+ ))
342
+ } else {
343
+ stages.append(.ready(try parseStage(
344
+ stageName: descriptor.name,
345
+ options: descriptor.options,
346
+ fieldName: fieldName
347
+ )))
348
+ }
349
+ }
350
+
351
+ stack.append(.exit(
352
+ source: source,
353
+ stages: stages,
354
+ options: executeOptions,
355
+ box: box
356
+ ))
357
+
358
+ for (nestedPipeline, childBox) in nestedPipelines.reversed() {
359
+ stack.append(.enter(pipeline: nestedPipeline, options: nil, box: childBox))
360
+ }
361
+ case let .exit(source, pendingStages, options, box):
362
+ var stages: [RNFBFirestoreParsedPipelineStage] = []
363
+ stages.reserveCapacity(pendingStages.count)
364
+
365
+ for pendingStage in pendingStages {
366
+ switch pendingStage {
367
+ case let .ready(stage):
368
+ stages.append(stage)
369
+ case let .union(childBox):
370
+ guard let other = childBox.value else {
371
+ throw PipelineValidationError("pipelineExecute() failed to parse nested union pipeline.")
372
+ }
373
+ stages.append(.unionStage(RNFBFirestoreParsedUnionStage(other: other)))
374
+ }
375
+ }
376
+
377
+ box.value = RNFBFirestoreParsedPipelineRequest(source: source, stages: stages, options: options)
378
+ }
379
+ }
380
+
381
+ guard let request = rootBox.value else {
382
+ throw PipelineValidationError("pipelineExecute() expected a pipeline object.")
383
+ }
384
+
385
+ return request
386
+ }
387
+
388
+ private static func parseSource(_ source: [String: Any]) throws -> RNFBFirestoreParsedPipelineSource {
389
+ let sourceType = try requireNonEmptyString(source, key: "source", fieldName: "pipeline.source.source")
390
+
391
+ guard sourceTypes.contains(sourceType) else {
392
+ throw PipelineValidationError("pipelineExecute() received an unknown source type.")
393
+ }
394
+
395
+ switch sourceType {
396
+ case "collection":
397
+ return RNFBFirestoreParsedPipelineSource(
398
+ sourceType: sourceType,
399
+ path: try requireNonEmptyString(source, key: "path", fieldName: "pipeline.source.path"),
400
+ collectionId: nil,
401
+ documents: [],
402
+ queryType: nil,
403
+ query: nil,
404
+ rawOptions: try parseUnsupportedSourceRawOptions(source, sourceType: sourceType)
405
+ )
406
+ case "collectionGroup":
407
+ return RNFBFirestoreParsedPipelineSource(
408
+ sourceType: sourceType,
409
+ path: nil,
410
+ collectionId: try requireNonEmptyString(source, key: "collectionId", fieldName: "pipeline.source.collectionId"),
411
+ documents: [],
412
+ queryType: nil,
413
+ query: nil,
414
+ rawOptions: try parseUnsupportedSourceRawOptions(source, sourceType: sourceType)
415
+ )
416
+ case "database":
417
+ return RNFBFirestoreParsedPipelineSource(
418
+ sourceType: sourceType,
419
+ path: nil,
420
+ collectionId: nil,
421
+ documents: [],
422
+ queryType: nil,
423
+ query: nil,
424
+ rawOptions: try parseUnsupportedSourceRawOptions(source, sourceType: sourceType)
425
+ )
426
+ case "documents":
427
+ let documents = try requireArray(source, key: "documents", fieldName: "pipeline.source.documents")
428
+ guard !documents.isEmpty else {
429
+ throw PipelineValidationError(
430
+ "pipelineExecute() expected pipeline.source.documents to contain at least one document path.")
431
+ }
432
+
433
+ let parsedDocuments = try documents.enumerated().map { index, value in
434
+ guard let documentPath = value as? String else {
435
+ throw PipelineValidationError(
436
+ "pipelineExecute() expected pipeline.source.documents entries to be strings.")
437
+ }
438
+ return documentPath
439
+ }
440
+
441
+ return RNFBFirestoreParsedPipelineSource(
442
+ sourceType: sourceType,
443
+ path: nil,
444
+ collectionId: nil,
445
+ documents: parsedDocuments,
446
+ queryType: nil,
447
+ query: nil,
448
+ rawOptions: nil
449
+ )
450
+ case "query":
451
+ return RNFBFirestoreParsedPipelineSource(
452
+ sourceType: sourceType,
453
+ path: try requireNonEmptyString(source, key: "path", fieldName: "pipeline.source.path"),
454
+ collectionId: nil,
455
+ documents: [],
456
+ queryType: try requireNonEmptyString(source, key: "queryType", fieldName: "pipeline.source.queryType"),
457
+ query: try parseQuerySource(source),
458
+ rawOptions: nil
459
+ )
460
+ default:
461
+ throw PipelineValidationError("pipelineExecute() received an unknown source type.")
462
+ }
463
+ }
464
+
465
+ private static func parseQuerySource(_ source: [String: Any]) throws -> RNFBFirestoreParsedQuerySource {
466
+ let filters = try requireArray(source, key: "filters", fieldName: "pipeline.source.filters")
467
+ let orders = try requireArray(source, key: "orders", fieldName: "pipeline.source.orders")
468
+ let options = try requireMap(source, key: "options", fieldName: "pipeline.source.options")
469
+
470
+ return RNFBFirestoreParsedQuerySource(
471
+ filters: try filters.enumerated().map { index, value in
472
+ try parseQuerySourceValueNode(value, fieldName: "pipeline.source.filters[\(index)]")
473
+ },
474
+ orders: try orders.enumerated().map { index, value in
475
+ try parseQuerySourceValueNode(value, fieldName: "pipeline.source.orders[\(index)]")
476
+ },
477
+ options: try options.reduce(into: [String: RNFBFirestoreParsedValueNode]()) { result, entry in
478
+ result[entry.key] = try parseQuerySourceValueNode(
479
+ entry.value,
480
+ fieldName: "pipeline.source.options.\(entry.key)"
481
+ )
482
+ }
483
+ )
484
+ }
485
+
486
+ private static func parseStageDescriptor(
487
+ _ stage: [String: Any],
488
+ fieldName: String
489
+ ) throws -> StageDescriptor {
490
+ guard let stageName = stage["stage"] as? String else {
491
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).stage to be a string.")
492
+ }
493
+
494
+ guard !stageName.isEmpty else {
495
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).stage to be a non-empty string.")
496
+ }
497
+
498
+ guard knownStages.contains(stageName) else {
499
+ throw PipelineValidationError("pipelineExecute() received an unknown stage: \(stageName).")
500
+ }
501
+
502
+ let options = try requireMap(stage, key: "options", fieldName: "\(fieldName).options")
503
+ return StageDescriptor(name: stageName, options: options)
504
+ }
505
+
506
+ private static func parseStage(
507
+ stageName: String,
508
+ options: [String: Any],
509
+ fieldName: String
510
+ ) throws -> RNFBFirestoreParsedPipelineStage {
511
+ switch stageName {
512
+ case "where":
513
+ return .whereStage(RNFBFirestoreParsedWhereStage(
514
+ condition: try parseExpressionNode(
515
+ requireValue(options, key: "condition", fieldName: "\(fieldName).options.condition"),
516
+ fieldName: "\(fieldName).options.condition"
517
+ )
518
+ ))
519
+ case "select":
520
+ return .selectStage(RNFBFirestoreParsedSelectStage(
521
+ selections: try parseSelectableNodes(
522
+ requireArray(options, key: "selections", fieldName: "\(fieldName).options.selections"),
523
+ fieldName: "\(fieldName).options.selections"
524
+ )
525
+ ))
526
+ case "addFields":
527
+ return .addFieldsStage(RNFBFirestoreParsedAddFieldsStage(
528
+ fields: try parseSelectableNodes(
529
+ requireArray(options, key: "fields", fieldName: "\(fieldName).options.fields"),
530
+ fieldName: "\(fieldName).options.fields"
531
+ )
532
+ ))
533
+ case "removeFields":
534
+ return .removeFieldsStage(RNFBFirestoreParsedRemoveFieldsStage(
535
+ fields: try requireStringArray(options, key: "fields", fieldName: "\(fieldName).options.fields")
536
+ ))
537
+ case "sort":
538
+ return .sortStage(RNFBFirestoreParsedSortStage(
539
+ orderings: try parseOrderingNodes(
540
+ requireArray(options, key: "orderings", fieldName: "\(fieldName).options.orderings"),
541
+ fieldName: "\(fieldName).options.orderings"
542
+ )
543
+ ))
544
+ case "limit":
545
+ return .limitStage(RNFBFirestoreParsedLimitStage(
546
+ limit: try requireNumber(options, key: "limit", fieldName: "\(fieldName).options.limit")
547
+ ))
548
+ case "offset":
549
+ return .offsetStage(RNFBFirestoreParsedOffsetStage(
550
+ offset: try requireNumber(options, key: "offset", fieldName: "\(fieldName).options.offset")
551
+ ))
552
+ case "aggregate":
553
+ return .aggregateStage(try parseAggregateStage(options, fieldName: "\(fieldName).options"))
554
+ case "distinct":
555
+ return .distinctStage(RNFBFirestoreParsedDistinctStage(
556
+ groups: try parseSelectableNodes(
557
+ requireArray(options, key: "groups", fieldName: "\(fieldName).options.groups"),
558
+ fieldName: "\(fieldName).options.groups"
559
+ )
560
+ ))
561
+ case "findNearest":
562
+ return .findNearestStage(try parseFindNearestStage(options, fieldName: "\(fieldName).options"))
563
+ case "replaceWith":
564
+ return .replaceWithStage(RNFBFirestoreParsedReplaceWithStage(
565
+ value: try parseExpressionNode(
566
+ requireValue(options, key: "map", fieldName: "\(fieldName).options.map"),
567
+ fieldName: "\(fieldName).options.map"
568
+ )
569
+ ))
570
+ case "sample":
571
+ return .sampleStage(try parseSampleStage(options, fieldName: "\(fieldName).options"))
572
+ case "union":
573
+ throw PipelineValidationError("pipelineExecute() failed to parse nested union pipeline.")
574
+ case "unnest":
575
+ return .unnestStage(RNFBFirestoreParsedUnnestStage(
576
+ selectable: try parseSelectableNode(
577
+ requireValue(options, key: "selectable", fieldName: "\(fieldName).options.selectable"),
578
+ fieldName: "\(fieldName).options.selectable"
579
+ ),
580
+ indexField: try optionalExpressionNode(options, key: "indexField", fieldName: "\(fieldName).options.indexField")
581
+ ))
582
+ case "rawStage":
583
+ return .rawStage(RNFBFirestoreParsedRawStage(
584
+ name: try requireNonEmptyString(options, key: "name", fieldName: "\(fieldName).options.name"),
585
+ params: options["params"],
586
+ options: try optionalMap(options, key: "options", fieldName: "\(fieldName).options.options")
587
+ ))
588
+ default:
589
+ throw PipelineValidationError("pipelineExecute() received an unknown stage: \(stageName).")
590
+ }
591
+ }
592
+
593
+ private static func parseOptions(_ options: [String: Any]?) throws -> RNFBFirestoreParsedPipelineExecuteOptions {
594
+ guard let options else {
595
+ return RNFBFirestoreParsedPipelineExecuteOptions(indexMode: nil, rawOptions: nil)
596
+ }
597
+
598
+ if let indexMode = options["indexMode"], !(indexMode is String) {
599
+ throw PipelineValidationError("pipelineExecute() expected options.indexMode to be a string.")
600
+ }
601
+
602
+ if let indexMode = options["indexMode"] as? String {
603
+ guard indexMode == "recommended" else {
604
+ throw PipelineValidationError("pipelineExecute() only supports options.indexMode=\"recommended\".")
605
+ }
606
+
607
+ throw PipelineValidationError(
608
+ "pipelineExecute() does not support options.indexMode on iOS because the native Firestore pipeline SDK does not expose execute options.")
609
+ }
610
+
611
+ if let rawOptions = options["rawOptions"] {
612
+ guard rawOptions is [String: Any] else {
613
+ throw PipelineValidationError("pipelineExecute() expected options.rawOptions to be an object.")
614
+ }
615
+
616
+ throw PipelineValidationError(
617
+ "pipelineExecute() does not support options.rawOptions on iOS because the native Firestore pipeline SDK does not expose execute options.")
618
+ }
619
+
620
+ return RNFBFirestoreParsedPipelineExecuteOptions(indexMode: nil, rawOptions: nil)
621
+ }
622
+
623
+ private static func parseUnsupportedSourceRawOptions(
624
+ _ source: [String: Any],
625
+ sourceType: String
626
+ ) throws -> [String: Any]? {
627
+ guard let rawOptions = source["rawOptions"] else {
628
+ return nil
629
+ }
630
+
631
+ guard rawOptions is [String: Any] else {
632
+ throw PipelineValidationError("pipelineExecute() expected pipeline.source.rawOptions to be an object.")
633
+ }
634
+
635
+ throw PipelineValidationError(
636
+ "pipelineExecute() does not support pipeline.source.rawOptions for \(sourceType) on iOS because the native Firestore pipeline SDK does not expose source options.")
637
+ }
638
+
639
+ private static func requireMap(
640
+ _ map: [String: Any],
641
+ key: String,
642
+ fieldName: String
643
+ ) throws -> [String: Any] {
644
+ guard let value = map[key] as? [String: Any] else {
645
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an object.")
646
+ }
647
+
648
+ return value
649
+ }
650
+
651
+ private static func requireStageArray(
652
+ _ map: [String: Any],
653
+ key: String,
654
+ fieldName: String
655
+ ) throws -> [[String: Any]] {
656
+ guard let value = map[key] as? [[String: Any]] else {
657
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an array.")
658
+ }
659
+
660
+ return value
661
+ }
662
+
663
+ private static func requireArray(
664
+ _ map: [String: Any],
665
+ key: String,
666
+ fieldName: String
667
+ ) throws -> [Any] {
668
+ guard let value = map[key] as? [Any] else {
669
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an array.")
670
+ }
671
+
672
+ return value
673
+ }
674
+
675
+ private static func requireNonEmptyString(
676
+ _ map: [String: Any],
677
+ key: String,
678
+ fieldName: String
679
+ ) throws -> String {
680
+ guard let value = map[key] as? String else {
681
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a string.")
682
+ }
683
+ guard !value.isEmpty else {
684
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a non-empty string.")
685
+ }
686
+ return value
687
+ }
688
+
689
+ private static func requireValue(
690
+ _ map: [String: Any],
691
+ key: String,
692
+ fieldName: String
693
+ ) throws -> Any {
694
+ guard let value = map[key] else {
695
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
696
+ }
697
+
698
+ return value
699
+ }
700
+
701
+ private static func optionalMap(
702
+ _ map: [String: Any],
703
+ key: String,
704
+ fieldName: String
705
+ ) throws -> [String: Any]? {
706
+ guard let value = map[key] else {
707
+ return nil
708
+ }
709
+
710
+ guard let value = value as? [String: Any] else {
711
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an object.")
712
+ }
713
+
714
+ return value
715
+ }
716
+
717
+ private static func optionalExpressionNode(
718
+ _ map: [String: Any],
719
+ key: String,
720
+ fieldName: String
721
+ ) throws -> RNFBFirestoreParsedExpressionNode? {
722
+ guard let value = map[key] else {
723
+ return nil
724
+ }
725
+
726
+ return try parseExpressionNode(value, fieldName: fieldName)
727
+ }
728
+
729
+ private static func requireNumber(
730
+ _ map: [String: Any],
731
+ key: String,
732
+ fieldName: String
733
+ ) throws -> NSNumber {
734
+ guard let value = map[key] as? NSNumber else {
735
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a number.")
736
+ }
737
+
738
+ return value
739
+ }
740
+
741
+ private static func optionalNumber(
742
+ _ map: [String: Any],
743
+ key: String,
744
+ fieldName: String
745
+ ) throws -> NSNumber? {
746
+ guard let value = map[key] else {
747
+ return nil
748
+ }
749
+
750
+ guard let number = value as? NSNumber else {
751
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a number.")
752
+ }
753
+
754
+ return number
755
+ }
756
+
757
+ private static func requireStringArray(
758
+ _ map: [String: Any],
759
+ key: String,
760
+ fieldName: String
761
+ ) throws -> [String] {
762
+ let values = try requireArray(map, key: key, fieldName: fieldName)
763
+ guard !values.isEmpty else {
764
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
765
+ }
766
+
767
+ return try values.enumerated().map { index, value in
768
+ guard let stringValue = value as? String else {
769
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName)[\(index)] to be a string.")
770
+ }
771
+ return stringValue
772
+ }
773
+ }
774
+
775
+ private static func parseAggregateStage(
776
+ _ options: [String: Any],
777
+ fieldName: String
778
+ ) throws -> RNFBFirestoreParsedAggregateStage {
779
+ let accumulators = try parseAggregateNodes(
780
+ requireArray(options, key: "accumulators", fieldName: "\(fieldName).accumulators"),
781
+ fieldName: "\(fieldName).accumulators"
782
+ )
783
+
784
+ let groups: [RNFBFirestoreParsedSelectableNode]
785
+ if options["groups"] != nil {
786
+ groups = try parseSelectableNodes(
787
+ requireArray(options, key: "groups", fieldName: "\(fieldName).groups"),
788
+ fieldName: "\(fieldName).groups",
789
+ requireNonEmpty: false
790
+ )
791
+ } else {
792
+ groups = []
793
+ }
794
+
795
+ return RNFBFirestoreParsedAggregateStage(accumulators: accumulators, groups: groups)
796
+ }
797
+
798
+ private static func parseFindNearestStage(
799
+ _ options: [String: Any],
800
+ fieldName: String
801
+ ) throws -> RNFBFirestoreParsedFindNearestStage {
802
+ let distanceMeasure = try requireNonEmptyString(
803
+ options,
804
+ key: "distanceMeasure",
805
+ fieldName: "\(fieldName).distanceMeasure"
806
+ )
807
+
808
+ return RNFBFirestoreParsedFindNearestStage(
809
+ field: try parseExpressionNode(
810
+ requireValue(options, key: "field", fieldName: "\(fieldName).field"),
811
+ fieldName: "\(fieldName).field"
812
+ ),
813
+ vectorValue: try parseValueNode(
814
+ requireValue(options, key: "vectorValue", fieldName: "\(fieldName).vectorValue"),
815
+ fieldName: "\(fieldName).vectorValue"
816
+ ),
817
+ distanceMeasure: distanceMeasure,
818
+ limit: try optionalNumber(options, key: "limit", fieldName: "\(fieldName).limit"),
819
+ distanceField: try optionalExpressionNode(options, key: "distanceField", fieldName: "\(fieldName).distanceField")
820
+ )
821
+ }
822
+
823
+ private static func parseSampleStage(
824
+ _ options: [String: Any],
825
+ fieldName: String
826
+ ) throws -> RNFBFirestoreParsedSampleStage {
827
+ let documents = options["documents"]
828
+ let percentage = options["percentage"]
829
+
830
+ if documents == nil && percentage == nil {
831
+ throw PipelineValidationError("pipelineExecute() expected sample stage to include documents or percentage.")
832
+ }
833
+
834
+ return RNFBFirestoreParsedSampleStage(
835
+ documents: try optionalNumber(options, key: "documents", fieldName: "\(fieldName).documents"),
836
+ percentage: try optionalNumber(options, key: "percentage", fieldName: "\(fieldName).percentage")
837
+ )
838
+ }
839
+
840
+ private static func parseSelectableNodes(
841
+ _ values: [Any],
842
+ fieldName: String,
843
+ requireNonEmpty: Bool = true
844
+ ) throws -> [RNFBFirestoreParsedSelectableNode] {
845
+ if requireNonEmpty && values.isEmpty {
846
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
847
+ }
848
+
849
+ return try values.enumerated().map { index, value in
850
+ try parseSelectableNode(value, fieldName: "\(fieldName)[\(index)]")
851
+ }
852
+ }
853
+
854
+ private static func parseSelectableNode(
855
+ _ value: Any,
856
+ fieldName: String
857
+ ) throws -> RNFBFirestoreParsedSelectableNode {
858
+ if value is String {
859
+ return RNFBFirestoreParsedSelectableNode(
860
+ expression: try parseExpressionNode(value, fieldName: fieldName),
861
+ alias: nil,
862
+ isFlatFieldAlias: false
863
+ )
864
+ }
865
+
866
+ guard let map = value as? [String: Any] else {
867
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a selectable expression.")
868
+ }
869
+
870
+ let alias = firstString(map["alias"], map["as"], map["name"])
871
+ if let alias, !alias.isEmpty {
872
+ if map["path"] != nil || map["fieldPath"] != nil || map["segments"] != nil {
873
+ return RNFBFirestoreParsedSelectableNode(
874
+ expression: .field(path: try coerceFieldPath(value, fieldName: "\(fieldName).path")),
875
+ alias: alias,
876
+ isFlatFieldAlias: true
877
+ )
878
+ }
879
+
880
+ let expressionValue = firstNonNil(map["expr"], map["expression"], map["field"]) ?? value
881
+ return RNFBFirestoreParsedSelectableNode(
882
+ expression: try parseExpressionNode(expressionValue, fieldName: "\(fieldName).expr"),
883
+ alias: alias,
884
+ isFlatFieldAlias: false
885
+ )
886
+ }
887
+
888
+ return RNFBFirestoreParsedSelectableNode(
889
+ expression: try parseExpressionNode(value, fieldName: fieldName),
890
+ alias: nil,
891
+ isFlatFieldAlias: false
892
+ )
893
+ }
894
+
895
+ private static func parseOrderingNodes(
896
+ _ values: [Any],
897
+ fieldName: String
898
+ ) throws -> [RNFBFirestoreParsedOrderingNode] {
899
+ guard !values.isEmpty else {
900
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
901
+ }
902
+
903
+ return try values.enumerated().map { index, value in
904
+ try parseOrderingNode(value, fieldName: "\(fieldName)[\(index)]")
905
+ }
906
+ }
907
+
908
+ private static func parseOrderingNode(
909
+ _ value: Any,
910
+ fieldName: String
911
+ ) throws -> RNFBFirestoreParsedOrderingNode {
912
+ if value is String {
913
+ return RNFBFirestoreParsedOrderingNode(
914
+ expression: try parseExpressionNode(value, fieldName: fieldName),
915
+ descending: false,
916
+ fieldShortcut: true
917
+ )
918
+ }
919
+
920
+ guard let map = value as? [String: Any] else {
921
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be a string or object.")
922
+ }
923
+
924
+ let direction = (map["direction"] as? String) ?? "asc"
925
+ let expressionValue = firstNonNil(
926
+ map["expression"],
927
+ map["expr"],
928
+ map["field"],
929
+ map["fieldPath"],
930
+ map["path"],
931
+ value
932
+ ) as Any
933
+
934
+ return RNFBFirestoreParsedOrderingNode(
935
+ expression: try parseExpressionNode(expressionValue, fieldName: fieldName),
936
+ descending: isDescendingDirection(direction),
937
+ fieldShortcut: false
938
+ )
939
+ }
940
+
941
+ private static func parseAggregateNodes(
942
+ _ values: [Any],
943
+ fieldName: String
944
+ ) throws -> [RNFBFirestoreParsedAggregateNode] {
945
+ guard !values.isEmpty else {
946
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to contain at least one value.")
947
+ }
948
+
949
+ return try values.enumerated().map { index, value in
950
+ try parseAggregateNode(value, fieldName: "\(fieldName)[\(index)]")
951
+ }
952
+ }
953
+
954
+ private static func parseAggregateNode(
955
+ _ value: Any,
956
+ fieldName: String
957
+ ) throws -> RNFBFirestoreParsedAggregateNode {
958
+ guard let map = value as? [String: Any] else {
959
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be an object.")
960
+ }
961
+
962
+ let aggregateValue = map["aggregate"] ?? value
963
+ guard let aggregateMap = aggregateValue as? [String: Any] else {
964
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).aggregate to be an object.")
965
+ }
966
+
967
+ guard let kind = firstString(
968
+ aggregateMap["kind"],
969
+ aggregateMap["name"],
970
+ aggregateMap["function"],
971
+ aggregateMap["op"]
972
+ ), !kind.isEmpty else {
973
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to include an aggregate kind.")
974
+ }
975
+
976
+ let alias = firstString(map["alias"], map["as"], map["name"]) ?? kind.lowercased()
977
+ let primaryExpression = firstNonNil(aggregateMap["expr"], aggregateMap["field"]) ?? aggregateMap["value"]
978
+ let primaryValue = try primaryExpression.map {
979
+ try parseValueNode($0, fieldName: "\(fieldName).expr")
980
+ }
981
+ let args = try parseArgumentValueNodes(aggregateMap["args"], fieldName: "\(fieldName).args")
982
+
983
+ return RNFBFirestoreParsedAggregateNode(kind: kind, alias: alias, primaryValue: primaryValue, args: args)
984
+ }
985
+
986
+ private static func parseExpressionNode(
987
+ _ value: Any,
988
+ fieldName: String
989
+ ) throws -> RNFBFirestoreParsedExpressionNode {
990
+ let rootBox = ParsedExpressionNodeBox()
991
+ try parseExpressionValueTree(.expressionEnter(value, rootBox, fieldName))
992
+
993
+ guard let expression = rootBox.value else {
994
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
995
+ }
996
+
997
+ return expression
998
+ }
999
+
1000
+ private static func parseArgumentValueNodes(
1001
+ _ argsValue: Any?,
1002
+ fieldName: String
1003
+ ) throws -> [RNFBFirestoreParsedValueNode] {
1004
+ guard let argsValue else {
1005
+ return []
1006
+ }
1007
+
1008
+ if let rawArgs = argsValue as? [Any] {
1009
+ return try rawArgs.enumerated().map { index, value in
1010
+ try parseValueNode(value, fieldName: "\(fieldName)[\(index)]")
1011
+ }
1012
+ }
1013
+
1014
+ return [try parseValueNode(argsValue, fieldName: "\(fieldName)[0]")]
1015
+ }
1016
+
1017
+ private static func parseValueNode(
1018
+ _ value: Any,
1019
+ fieldName: String
1020
+ ) throws -> RNFBFirestoreParsedValueNode {
1021
+ let rootBox = ParsedValueNodeBox()
1022
+ try parseExpressionValueTree(.valueEnter(value, rootBox, fieldName))
1023
+
1024
+ guard let parsedValue = rootBox.value else {
1025
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
1026
+ }
1027
+
1028
+ return parsedValue
1029
+ }
1030
+
1031
+ private static func parseQuerySourceValueNode(
1032
+ _ value: Any,
1033
+ fieldName: String
1034
+ ) throws -> RNFBFirestoreParsedValueNode {
1035
+ let rootBox = ParsedValueNodeBox()
1036
+ var stack: [QuerySourceValueParseFrame] = [
1037
+ .enter(value, rootBox),
1038
+ ]
1039
+
1040
+ while let frame = stack.popLast() {
1041
+ switch frame {
1042
+ case let .enter(value, box):
1043
+ if let map = value as? [String: Any] {
1044
+ let entries = map.map { (key: $0.key, value: $0.value, box: ParsedValueNodeBox()) }
1045
+ stack.append(.exitMap(box, entries.map { ($0.key, $0.box) }))
1046
+ for entry in entries.reversed() {
1047
+ stack.append(.enter(entry.value, entry.box))
1048
+ }
1049
+ continue
1050
+ }
1051
+
1052
+ if let list = value as? [Any] {
1053
+ let childBoxes = list.map { _ in ParsedValueNodeBox() }
1054
+ stack.append(.exitList(box, childBoxes))
1055
+ for index in list.indices.reversed() {
1056
+ stack.append(.enter(list[index], childBoxes[index]))
1057
+ }
1058
+ continue
1059
+ }
1060
+
1061
+ box.value = .primitive(value)
1062
+ case let .exitList(box, childBoxes):
1063
+ box.value = .list(try childBoxes.enumerated().map { index, childBox in
1064
+ guard let value = childBox.value else {
1065
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName)[\(index)] to be provided.")
1066
+ }
1067
+ return value
1068
+ })
1069
+ case let .exitMap(box, entries):
1070
+ box.value = .map(try entries.reduce(into: [String: RNFBFirestoreParsedValueNode]()) { result, entry in
1071
+ guard let value = entry.1.value else {
1072
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).\(entry.0) to be provided.")
1073
+ }
1074
+ result[entry.0] = value
1075
+ })
1076
+ }
1077
+ }
1078
+
1079
+ guard let parsedValue = rootBox.value else {
1080
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
1081
+ }
1082
+
1083
+ return parsedValue
1084
+ }
1085
+
1086
+ private static func isExpressionLike(_ map: [String: Any]) -> Bool {
1087
+ map["exprType"] != nil || map["operator"] != nil || map["name"] != nil || map["expr"] != nil ||
1088
+ map["expression"] != nil || map["fieldPath"] != nil || map["path"] != nil ||
1089
+ map["segments"] != nil || map["_segments"] != nil
1090
+ }
1091
+
1092
+ private static func coerceFieldPath(
1093
+ _ value: Any,
1094
+ fieldName: String
1095
+ ) throws -> String {
1096
+ var currentValue: Any = value
1097
+
1098
+ while true {
1099
+ if let fieldPath = currentValue as? String, !fieldPath.isEmpty {
1100
+ return fieldPath
1101
+ }
1102
+
1103
+ if let map = currentValue as? [String: Any] {
1104
+ let path = firstNonNil(map["path"], map["fieldPath"])
1105
+ if let path, !(path is [String: Any]) {
1106
+ currentValue = path
1107
+ continue
1108
+ }
1109
+
1110
+ let segments = (map["segments"] as? [Any]) ?? (map["_segments"] as? [Any])
1111
+ if let segments {
1112
+ let stringSegments = try segments.enumerated().map { _, segment -> String in
1113
+ guard let stringSegment = segment as? String else {
1114
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) segment values to be strings.")
1115
+ }
1116
+ return stringSegment
1117
+ }
1118
+
1119
+ let pathValue = stringSegments.joined(separator: ".")
1120
+ if !pathValue.isEmpty {
1121
+ return pathValue
1122
+ }
1123
+ }
1124
+ }
1125
+
1126
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to resolve to a field path string.")
1127
+ }
1128
+ }
1129
+
1130
+ private static func parseExpressionValueTree(
1131
+ _ initialFrame: ExpressionValueParseFrame
1132
+ ) throws {
1133
+ var stack: [ExpressionValueParseFrame] = [initialFrame]
1134
+
1135
+ while let frame = stack.popLast() {
1136
+ switch frame {
1137
+ case let .expressionEnter(value, box, fieldName):
1138
+ if let stringValue = value as? String {
1139
+ box.value = .field(path: stringValue)
1140
+ continue
1141
+ }
1142
+
1143
+ if let map = value as? [String: Any] {
1144
+ if let nested = map["expr"] {
1145
+ stack.append(.expressionEnter(nested, box, "\(fieldName).expr"))
1146
+ continue
1147
+ }
1148
+ if let nested = map["expression"] {
1149
+ stack.append(.expressionEnter(nested, box, "\(fieldName).expression"))
1150
+ continue
1151
+ }
1152
+
1153
+ if let operatorName = map["operator"] as? String {
1154
+ let normalizedOperator = operatorName.uppercased()
1155
+ if normalizedOperator == "AND" || normalizedOperator == "OR" {
1156
+ guard let queries = map["queries"] as? [Any], !queries.isEmpty else {
1157
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).queries to contain boolean expressions.")
1158
+ }
1159
+
1160
+ let queryBoxes = queries.map { _ in ParsedExpressionNodeBox() }
1161
+ stack.append(.expressionOperatorLogicalExit(box, normalizedOperator, queryBoxes, fieldName))
1162
+ for index in queries.indices.reversed() {
1163
+ stack.append(.expressionEnter(queries[index], queryBoxes[index], "\(fieldName).queries[\(index)]"))
1164
+ }
1165
+ continue
1166
+ }
1167
+
1168
+ let fieldValue = map["fieldPath"] ?? map["field"]
1169
+ guard let fieldValue else {
1170
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).fieldPath to be provided.")
1171
+ }
1172
+
1173
+ let rightValue = map["value"] ?? map["right"] ?? map["operand"] ?? NSNull()
1174
+ let fieldBox = ParsedExpressionNodeBox()
1175
+ let valueBox = ParsedValueNodeBox()
1176
+ stack.append(.expressionOperatorBinaryExit(box, normalizedOperator, fieldBox, valueBox, fieldName))
1177
+ stack.append(.valueEnter(rightValue, valueBox, "\(fieldName).value"))
1178
+ stack.append(.expressionEnter(fieldValue, fieldBox, "\(fieldName).fieldPath"))
1179
+ continue
1180
+ }
1181
+
1182
+ if let exprType = map["exprType"] as? String {
1183
+ let normalizedType = exprType.lowercased()
1184
+ if normalizedType == "field" {
1185
+ box.value = .field(path: try coerceFieldPath(value, fieldName: fieldName))
1186
+ continue
1187
+ }
1188
+ if normalizedType == "constant" {
1189
+ let valueBox = ParsedValueNodeBox()
1190
+ stack.append(.expressionConstantExit(box, valueBox, fieldName))
1191
+ stack.append(.valueEnter(map["value"] as Any, valueBox, "\(fieldName).value"))
1192
+ continue
1193
+ }
1194
+ }
1195
+
1196
+ if map["name"] != nil {
1197
+ guard let nameValue = map["name"] as? String, !nameValue.isEmpty else {
1198
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).name to be a non-empty string.")
1199
+ }
1200
+
1201
+ let rawArgs: [Any]
1202
+ if let args = map["args"] as? [Any] {
1203
+ rawArgs = args
1204
+ } else if let singleArg = map["args"] {
1205
+ rawArgs = [singleArg]
1206
+ } else {
1207
+ rawArgs = []
1208
+ }
1209
+
1210
+ let argBoxes = rawArgs.map { _ in ParsedValueNodeBox() }
1211
+ stack.append(.expressionFunctionExit(box, nameValue, argBoxes, fieldName))
1212
+ for index in rawArgs.indices.reversed() {
1213
+ stack.append(.valueEnter(rawArgs[index], argBoxes[index], "\(fieldName).args[\(index)]"))
1214
+ }
1215
+ continue
1216
+ }
1217
+
1218
+ if map["fieldPath"] != nil || map["path"] != nil || map["segments"] != nil || map["_segments"] != nil {
1219
+ box.value = .field(path: try coerceFieldPath(value, fieldName: fieldName))
1220
+ continue
1221
+ }
1222
+ }
1223
+
1224
+ let valueBox = ParsedValueNodeBox()
1225
+ stack.append(.expressionConstantExit(box, valueBox, fieldName))
1226
+ stack.append(.valueEnter(value, valueBox, fieldName))
1227
+ case let .expressionValueExit(box, expressionBox, fieldName):
1228
+ guard let expression = expressionBox.value else {
1229
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
1230
+ }
1231
+ box.value = .expression(expression)
1232
+ case let .expressionConstantExit(box, valueBox, fieldName):
1233
+ guard let value = valueBox.value else {
1234
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName) to be provided.")
1235
+ }
1236
+ box.value = .constant(value)
1237
+ case let .expressionFunctionExit(box, name, argBoxes, fieldName):
1238
+ let args = try argBoxes.enumerated().map { index, argBox in
1239
+ guard let value = argBox.value else {
1240
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).args[\(index)] to be provided.")
1241
+ }
1242
+ return value
1243
+ }
1244
+ box.value = .function(name: name, args: args)
1245
+ case let .expressionOperatorLogicalExit(box, normalizedOperator, queryBoxes, fieldName):
1246
+ let args = try queryBoxes.enumerated().map { index, queryBox in
1247
+ guard let expression = queryBox.value else {
1248
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).queries[\(index)] to be provided.")
1249
+ }
1250
+ return RNFBFirestoreParsedValueNode.expression(expression)
1251
+ }
1252
+ box.value = .function(name: normalizedOperator == "AND" ? "and" : "or", args: args)
1253
+ case let .expressionOperatorBinaryExit(box, normalizedOperator, fieldBox, valueBox, fieldName):
1254
+ guard let fieldExpression = fieldBox.value else {
1255
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).fieldPath to be provided.")
1256
+ }
1257
+ guard let rightValue = valueBox.value else {
1258
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).value to be provided.")
1259
+ }
1260
+ box.value = .function(name: mapOperatorToFunction(normalizedOperator), args: [
1261
+ .expression(fieldExpression),
1262
+ rightValue,
1263
+ ])
1264
+ case let .valueEnter(value, box, fieldName):
1265
+ if let map = value as? [String: Any] {
1266
+ if isExpressionLike(map) {
1267
+ let expressionBox = ParsedExpressionNodeBox()
1268
+ stack.append(.expressionValueExit(box, expressionBox, fieldName))
1269
+ stack.append(.expressionEnter(value, expressionBox, fieldName))
1270
+ continue
1271
+ }
1272
+
1273
+ let entries = map.map { (key: $0.key, value: $0.value, box: ParsedValueNodeBox()) }
1274
+ stack.append(.valueMapExit(box, entries.map { ($0.key, $0.box) }, fieldName))
1275
+ for entry in entries.reversed() {
1276
+ stack.append(.valueEnter(entry.value, entry.box, "\(fieldName).\(entry.key)"))
1277
+ }
1278
+ continue
1279
+ }
1280
+
1281
+ if let list = value as? [Any] {
1282
+ let childBoxes = list.map { _ in ParsedValueNodeBox() }
1283
+ stack.append(.valueListExit(box, childBoxes, fieldName))
1284
+ for index in list.indices.reversed() {
1285
+ stack.append(.valueEnter(list[index], childBoxes[index], "\(fieldName)[\(index)]"))
1286
+ }
1287
+ continue
1288
+ }
1289
+
1290
+ box.value = .primitive(value)
1291
+ case let .valueListExit(box, childBoxes, fieldName):
1292
+ box.value = .list(try childBoxes.enumerated().map { index, childBox in
1293
+ guard let value = childBox.value else {
1294
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName)[\(index)] to be provided.")
1295
+ }
1296
+ return value
1297
+ })
1298
+ case let .valueMapExit(box, entries, fieldName):
1299
+ box.value = .map(try entries.reduce(into: [String: RNFBFirestoreParsedValueNode]()) { result, entry in
1300
+ guard let value = entry.1.value else {
1301
+ throw PipelineValidationError("pipelineExecute() expected \(fieldName).\(entry.0) to be provided.")
1302
+ }
1303
+ result[entry.0] = value
1304
+ })
1305
+ }
1306
+ }
1307
+ }
1308
+
1309
+ private static func firstString(_ values: Any?...) -> String? {
1310
+ for value in values {
1311
+ if let stringValue = value as? String, !stringValue.isEmpty {
1312
+ return stringValue
1313
+ }
1314
+ }
1315
+
1316
+ return nil
1317
+ }
1318
+
1319
+ private static func firstNonNil(_ values: Any?...) -> Any? {
1320
+ for value in values where value != nil {
1321
+ return value
1322
+ }
1323
+
1324
+ return nil
1325
+ }
1326
+
1327
+ private static func isDescendingDirection(_ direction: String?) -> Bool {
1328
+ guard let direction else {
1329
+ return false
1330
+ }
1331
+
1332
+ let normalized = direction.lowercased()
1333
+ return normalized == "desc" || normalized == "descending"
1334
+ }
1335
+
1336
+ private static func mapOperatorToFunction(_ operatorName: String) -> String {
1337
+ switch operatorName {
1338
+ case "==", "=", "EQUAL": return "equal"
1339
+ case "!=", "<>", "NOT_EQUAL": return "not_equal"
1340
+ case ">", "GREATER_THAN": return "greater_than"
1341
+ case ">=", "GREATER_THAN_OR_EQUAL": return "greater_than_or_equal"
1342
+ case "<", "LESS_THAN": return "less_than"
1343
+ case "<=", "LESS_THAN_OR_EQUAL": return "less_than_or_equal"
1344
+ case "ARRAY_CONTAINS", "ARRAY-CONTAINS": return "array_contains"
1345
+ case "ARRAY_CONTAINS_ANY", "ARRAY-CONTAINS-ANY": return "array_contains_any"
1346
+ case "ARRAY_CONTAINS_ALL", "ARRAY-CONTAINS-ALL": return "array_contains_all"
1347
+ case "IN": return "equal_any"
1348
+ case "NOT_IN": return "not_equal_any"
1349
+ default: return operatorName.lowercased()
1350
+ }
1351
+ }
1352
+ }