@astrogate/sdk 0.0.0-stage → 0.1.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 (440) hide show
  1. package/README.md +6 -2
  2. package/THIRD_PARTY_NOTICES.md +17 -0
  3. package/dist/browser.d.ts +85 -0
  4. package/dist/browser.js +16 -0
  5. package/dist/database-binding.d.ts +7 -0
  6. package/dist/database-binding.js +11 -0
  7. package/dist/hosted-session.d.ts +1 -0
  8. package/dist/hosted-session.js +11 -0
  9. package/dist/http.d.ts +24 -0
  10. package/dist/http.js +92 -0
  11. package/dist/inventory.d.ts +7 -0
  12. package/dist/inventory.js +18 -0
  13. package/dist/management-read.d.ts +17 -0
  14. package/dist/management-read.js +104 -0
  15. package/dist/management.d.ts +347 -0
  16. package/dist/management.js +137 -0
  17. package/dist/operator.d.ts +136 -0
  18. package/dist/operator.js +53 -0
  19. package/dist/react.d.ts +16 -0
  20. package/dist/react.js +10 -0
  21. package/dist/server.d.ts +27 -0
  22. package/dist/server.js +30 -0
  23. package/dist/services.d.ts +7 -0
  24. package/dist/services.js +53 -0
  25. package/dist/storage.d.ts +68 -0
  26. package/dist/storage.js +30 -0
  27. package/dist/tasks.d.ts +42 -0
  28. package/dist/tasks.js +24 -0
  29. package/dist/wire.gen.d.ts +7107 -0
  30. package/dist/wire.gen.js +5 -0
  31. package/native-provenance.json +15 -0
  32. package/node_modules/@instantdb/admin/LICENSE +202 -0
  33. package/node_modules/@instantdb/admin/dist/esm/index.d.ts +607 -0
  34. package/node_modules/@instantdb/admin/dist/esm/index.js +1036 -0
  35. package/node_modules/@instantdb/admin/dist/esm/polyfill.d.ts +16 -0
  36. package/node_modules/@instantdb/admin/dist/esm/polyfill.js +21 -0
  37. package/node_modules/@instantdb/admin/dist/esm/subscribe.d.ts +38 -0
  38. package/node_modules/@instantdb/admin/dist/esm/subscribe.js +301 -0
  39. package/node_modules/@instantdb/admin/dist/esm/version.d.ts +2 -0
  40. package/node_modules/@instantdb/admin/dist/esm/version.js +2 -0
  41. package/node_modules/@instantdb/admin/package.json +23 -0
  42. package/node_modules/@instantdb/core/LICENSE +202 -0
  43. package/node_modules/@instantdb/core/dist/esm/Connection.d.ts +92 -0
  44. package/node_modules/@instantdb/core/dist/esm/Connection.js +188 -0
  45. package/node_modules/@instantdb/core/dist/esm/InMemoryStorage.d.ts +10 -0
  46. package/node_modules/@instantdb/core/dist/esm/InMemoryStorage.js +25 -0
  47. package/node_modules/@instantdb/core/dist/esm/IndexedDBStorage.d.ts +17 -0
  48. package/node_modules/@instantdb/core/dist/esm/IndexedDBStorage.js +309 -0
  49. package/node_modules/@instantdb/core/dist/esm/InstantError.d.ts +6 -0
  50. package/node_modules/@instantdb/core/dist/esm/InstantError.js +23 -0
  51. package/node_modules/@instantdb/core/dist/esm/Reactor.d.ts +430 -0
  52. package/node_modules/@instantdb/core/dist/esm/Reactor.js +2369 -0
  53. package/node_modules/@instantdb/core/dist/esm/StorageAPI.d.ts +39 -0
  54. package/node_modules/@instantdb/core/dist/esm/StorageAPI.js +65 -0
  55. package/node_modules/@instantdb/core/dist/esm/Stream.d.ts +120 -0
  56. package/node_modules/@instantdb/core/dist/esm/Stream.js +811 -0
  57. package/node_modules/@instantdb/core/dist/esm/SyncTable.d.ts +144 -0
  58. package/node_modules/@instantdb/core/dist/esm/SyncTable.js +523 -0
  59. package/node_modules/@instantdb/core/dist/esm/WindowNetworkListener.d.ts +4 -0
  60. package/node_modules/@instantdb/core/dist/esm/WindowNetworkListener.js +19 -0
  61. package/node_modules/@instantdb/core/dist/esm/attrTypes.d.ts +25 -0
  62. package/node_modules/@instantdb/core/dist/esm/attrTypes.js +1 -0
  63. package/node_modules/@instantdb/core/dist/esm/authAPI.d.ts +64 -0
  64. package/node_modules/@instantdb/core/dist/esm/authAPI.js +103 -0
  65. package/node_modules/@instantdb/core/dist/esm/clientTypes.d.ts +40 -0
  66. package/node_modules/@instantdb/core/dist/esm/clientTypes.js +1 -0
  67. package/node_modules/@instantdb/core/dist/esm/coreTypes.d.ts +33 -0
  68. package/node_modules/@instantdb/core/dist/esm/coreTypes.js +1 -0
  69. package/node_modules/@instantdb/core/dist/esm/createRouteHandler.d.ts +7 -0
  70. package/node_modules/@instantdb/core/dist/esm/createRouteHandler.js +51 -0
  71. package/node_modules/@instantdb/core/dist/esm/datalog.d.ts +7 -0
  72. package/node_modules/@instantdb/core/dist/esm/datalog.js +99 -0
  73. package/node_modules/@instantdb/core/dist/esm/devtool.d.ts +3 -0
  74. package/node_modules/@instantdb/core/dist/esm/devtool.js +144 -0
  75. package/node_modules/@instantdb/core/dist/esm/framework.d.ts +80 -0
  76. package/node_modules/@instantdb/core/dist/esm/framework.js +243 -0
  77. package/node_modules/@instantdb/core/dist/esm/helperTypes.d.ts +75 -0
  78. package/node_modules/@instantdb/core/dist/esm/helperTypes.js +1 -0
  79. package/node_modules/@instantdb/core/dist/esm/index.d.ts +597 -0
  80. package/node_modules/@instantdb/core/dist/esm/index.js +649 -0
  81. package/node_modules/@instantdb/core/dist/esm/infiniteQuery.d.ts +25 -0
  82. package/node_modules/@instantdb/core/dist/esm/infiniteQuery.js +430 -0
  83. package/node_modules/@instantdb/core/dist/esm/instaml.d.ts +17 -0
  84. package/node_modules/@instantdb/core/dist/esm/instaml.js +576 -0
  85. package/node_modules/@instantdb/core/dist/esm/instaql.d.ts +12 -0
  86. package/node_modules/@instantdb/core/dist/esm/instaql.js +632 -0
  87. package/node_modules/@instantdb/core/dist/esm/instatx.d.ts +127 -0
  88. package/node_modules/@instantdb/core/dist/esm/instatx.js +93 -0
  89. package/node_modules/@instantdb/core/dist/esm/model/instaqlResult.d.ts +7 -0
  90. package/node_modules/@instantdb/core/dist/esm/model/instaqlResult.js +24 -0
  91. package/node_modules/@instantdb/core/dist/esm/parseSchemaFromJSON.d.ts +2 -0
  92. package/node_modules/@instantdb/core/dist/esm/parseSchemaFromJSON.js +142 -0
  93. package/node_modules/@instantdb/core/dist/esm/presence.d.ts +50 -0
  94. package/node_modules/@instantdb/core/dist/esm/presence.js +50 -0
  95. package/node_modules/@instantdb/core/dist/esm/presenceTypes.d.ts +9 -0
  96. package/node_modules/@instantdb/core/dist/esm/presenceTypes.js +3 -0
  97. package/node_modules/@instantdb/core/dist/esm/queryTypes.d.ts +271 -0
  98. package/node_modules/@instantdb/core/dist/esm/queryTypes.js +3 -0
  99. package/node_modules/@instantdb/core/dist/esm/queryValidation.d.ts +5 -0
  100. package/node_modules/@instantdb/core/dist/esm/queryValidation.js +287 -0
  101. package/node_modules/@instantdb/core/dist/esm/reactorTypes.d.ts +29 -0
  102. package/node_modules/@instantdb/core/dist/esm/reactorTypes.js +1 -0
  103. package/node_modules/@instantdb/core/dist/esm/routeHandlerProtocol.d.ts +19 -0
  104. package/node_modules/@instantdb/core/dist/esm/routeHandlerProtocol.js +1 -0
  105. package/node_modules/@instantdb/core/dist/esm/rulesTypes.d.ts +85 -0
  106. package/node_modules/@instantdb/core/dist/esm/rulesTypes.js +1 -0
  107. package/node_modules/@instantdb/core/dist/esm/sanityCheckQueries.d.ts +18 -0
  108. package/node_modules/@instantdb/core/dist/esm/sanityCheckQueries.js +152 -0
  109. package/node_modules/@instantdb/core/dist/esm/schema.d.ts +72 -0
  110. package/node_modules/@instantdb/core/dist/esm/schema.js +128 -0
  111. package/node_modules/@instantdb/core/dist/esm/schemaTypes.d.ts +244 -0
  112. package/node_modules/@instantdb/core/dist/esm/schemaTypes.js +106 -0
  113. package/node_modules/@instantdb/core/dist/esm/store.d.ts +89 -0
  114. package/node_modules/@instantdb/core/dist/esm/store.js +722 -0
  115. package/node_modules/@instantdb/core/dist/esm/transactionValidation.d.ts +7 -0
  116. package/node_modules/@instantdb/core/dist/esm/transactionValidation.js +130 -0
  117. package/node_modules/@instantdb/core/dist/esm/utils/Deferred.d.ts +8 -0
  118. package/node_modules/@instantdb/core/dist/esm/utils/Deferred.js +17 -0
  119. package/node_modules/@instantdb/core/dist/esm/utils/PersistedObject.d.ts +80 -0
  120. package/node_modules/@instantdb/core/dist/esm/utils/PersistedObject.js +448 -0
  121. package/node_modules/@instantdb/core/dist/esm/utils/dates.d.ts +1 -0
  122. package/node_modules/@instantdb/core/dist/esm/utils/dates.js +211 -0
  123. package/node_modules/@instantdb/core/dist/esm/utils/error.d.ts +2 -0
  124. package/node_modules/@instantdb/core/dist/esm/utils/error.js +8 -0
  125. package/node_modules/@instantdb/core/dist/esm/utils/fetch.d.ts +125 -0
  126. package/node_modules/@instantdb/core/dist/esm/utils/fetch.js +31 -0
  127. package/node_modules/@instantdb/core/dist/esm/utils/flags.d.ts +4 -0
  128. package/node_modules/@instantdb/core/dist/esm/utils/flags.js +10 -0
  129. package/node_modules/@instantdb/core/dist/esm/utils/id.d.ts +3 -0
  130. package/node_modules/@instantdb/core/dist/esm/utils/id.js +25 -0
  131. package/node_modules/@instantdb/core/dist/esm/utils/linkIndex.d.ts +7 -0
  132. package/node_modules/@instantdb/core/dist/esm/utils/linkIndex.js +17 -0
  133. package/node_modules/@instantdb/core/dist/esm/utils/log.d.ts +6 -0
  134. package/node_modules/@instantdb/core/dist/esm/utils/log.js +13 -0
  135. package/node_modules/@instantdb/core/dist/esm/utils/object.d.ts +16 -0
  136. package/node_modules/@instantdb/core/dist/esm/utils/object.js +143 -0
  137. package/node_modules/@instantdb/core/dist/esm/utils/pgtime.d.ts +197 -0
  138. package/node_modules/@instantdb/core/dist/esm/utils/pgtime.js +197 -0
  139. package/node_modules/@instantdb/core/dist/esm/utils/pick.d.ts +1 -0
  140. package/node_modules/@instantdb/core/dist/esm/utils/pick.js +9 -0
  141. package/node_modules/@instantdb/core/dist/esm/utils/strings.d.ts +1 -0
  142. package/node_modules/@instantdb/core/dist/esm/utils/strings.js +15 -0
  143. package/node_modules/@instantdb/core/dist/esm/utils/weakHash.d.ts +15 -0
  144. package/node_modules/@instantdb/core/dist/esm/utils/weakHash.js +71 -0
  145. package/node_modules/@instantdb/core/dist/esm/utils/weakHashLegacy.d.ts +7 -0
  146. package/node_modules/@instantdb/core/dist/esm/utils/weakHashLegacy.js +72 -0
  147. package/node_modules/@instantdb/core/dist/esm/version.d.ts +2 -0
  148. package/node_modules/@instantdb/core/dist/esm/version.js +2 -0
  149. package/node_modules/@instantdb/core/dist/esm/warningToggle.d.ts +2 -0
  150. package/node_modules/@instantdb/core/dist/esm/warningToggle.js +4 -0
  151. package/node_modules/@instantdb/core/package.json +20 -0
  152. package/node_modules/@instantdb/eventsource/LICENSE +22 -0
  153. package/node_modules/@instantdb/eventsource/README.md +226 -0
  154. package/node_modules/@instantdb/eventsource/dist/index.cjs +279 -0
  155. package/node_modules/@instantdb/eventsource/dist/index.cjs.map +1 -0
  156. package/node_modules/@instantdb/eventsource/dist/index.d.cts +346 -0
  157. package/node_modules/@instantdb/eventsource/dist/index.d.ts +346 -0
  158. package/node_modules/@instantdb/eventsource/dist/index.js +279 -0
  159. package/node_modules/@instantdb/eventsource/dist/index.js.map +1 -0
  160. package/node_modules/@instantdb/eventsource/package.json +128 -0
  161. package/node_modules/@instantdb/eventsource/src/EventSource.ts +614 -0
  162. package/node_modules/@instantdb/eventsource/src/errors.ts +141 -0
  163. package/node_modules/@instantdb/eventsource/src/index.ts +3 -0
  164. package/node_modules/@instantdb/eventsource/src/types.ts +164 -0
  165. package/node_modules/@instantdb/react/LICENSE +202 -0
  166. package/node_modules/@instantdb/react/dist/esm/Cursors.d.ts +18 -0
  167. package/node_modules/@instantdb/react/dist/esm/Cursors.js +103 -0
  168. package/node_modules/@instantdb/react/dist/esm/InstantReactWebDatabase.d.ts +5 -0
  169. package/node_modules/@instantdb/react/dist/esm/InstantReactWebDatabase.js +5 -0
  170. package/node_modules/@instantdb/react/dist/esm/index.d.ts +6 -0
  171. package/node_modules/@instantdb/react/dist/esm/index.js +22 -0
  172. package/node_modules/@instantdb/react/dist/esm/init.d.ts +40 -0
  173. package/node_modules/@instantdb/react/dist/esm/init.js +49 -0
  174. package/node_modules/@instantdb/react/dist/esm/next-ssr/HydrationStreamProvider.d.ts +65 -0
  175. package/node_modules/@instantdb/react/dist/esm/next-ssr/HydrationStreamProvider.js +96 -0
  176. package/node_modules/@instantdb/react/dist/esm/next-ssr/InstantNextDatabase.d.ts +11 -0
  177. package/node_modules/@instantdb/react/dist/esm/next-ssr/InstantNextDatabase.js +31 -0
  178. package/node_modules/@instantdb/react/dist/esm/next-ssr/InstantSuspenseProvider.d.ts +24 -0
  179. package/node_modules/@instantdb/react/dist/esm/next-ssr/InstantSuspenseProvider.js +162 -0
  180. package/node_modules/@instantdb/react/dist/esm/next-ssr/getUserFromInstantCookie.d.ts +9 -0
  181. package/node_modules/@instantdb/react/dist/esm/next-ssr/getUserFromInstantCookie.js +16 -0
  182. package/node_modules/@instantdb/react/dist/esm/next-ssr/htmlescape.d.ts +2 -0
  183. package/node_modules/@instantdb/react/dist/esm/next-ssr/htmlescape.js +20 -0
  184. package/node_modules/@instantdb/react/dist/esm/next-ssr/index.d.ts +29 -0
  185. package/node_modules/@instantdb/react/dist/esm/next-ssr/index.js +34 -0
  186. package/node_modules/@instantdb/react/dist/esm/version.d.ts +2 -0
  187. package/node_modules/@instantdb/react/dist/esm/version.js +2 -0
  188. package/node_modules/@instantdb/react/package.json +24 -0
  189. package/node_modules/@instantdb/react-common/LICENSE +202 -0
  190. package/node_modules/@instantdb/react-common/dist/esm/InstantReactAbstractDatabase.d.ts +282 -0
  191. package/node_modules/@instantdb/react-common/dist/esm/InstantReactAbstractDatabase.js +386 -0
  192. package/node_modules/@instantdb/react-common/dist/esm/InstantReactRoom.d.ts +182 -0
  193. package/node_modules/@instantdb/react-common/dist/esm/InstantReactRoom.js +280 -0
  194. package/node_modules/@instantdb/react-common/dist/esm/index.d.ts +4 -0
  195. package/node_modules/@instantdb/react-common/dist/esm/index.js +3 -0
  196. package/node_modules/@instantdb/react-common/dist/esm/useInfiniteQuerySubscription.d.ts +27 -0
  197. package/node_modules/@instantdb/react-common/dist/esm/useInfiniteQuerySubscription.js +53 -0
  198. package/node_modules/@instantdb/react-common/dist/esm/useQuery.d.ts +6 -0
  199. package/node_modules/@instantdb/react-common/dist/esm/useQuery.js +75 -0
  200. package/node_modules/@instantdb/react-common/dist/esm/useTimeout.d.ts +4 -0
  201. package/node_modules/@instantdb/react-common/dist/esm/useTimeout.js +17 -0
  202. package/node_modules/@instantdb/react-common/dist/esm/version.d.ts +2 -0
  203. package/node_modules/@instantdb/react-common/dist/esm/version.js +2 -0
  204. package/node_modules/@instantdb/react-common/package.json +22 -0
  205. package/node_modules/@instantdb/version/LICENSE +202 -0
  206. package/node_modules/@instantdb/version/dist/esm/index.d.ts +2 -0
  207. package/node_modules/@instantdb/version/dist/esm/index.js +2 -0
  208. package/node_modules/@instantdb/version/dist/esm/version.d.ts +2 -0
  209. package/node_modules/@instantdb/version/dist/esm/version.js +5 -0
  210. package/node_modules/@instantdb/version/package.json +16 -0
  211. package/node_modules/@instantdb/webhooks/LICENSE +202 -0
  212. package/node_modules/@instantdb/webhooks/dist/esm/index.d.ts +587 -0
  213. package/node_modules/@instantdb/webhooks/dist/esm/index.js +748 -0
  214. package/node_modules/@instantdb/webhooks/package.json +18 -0
  215. package/node_modules/cookie/LICENSE +24 -0
  216. package/node_modules/cookie/README.md +295 -0
  217. package/node_modules/cookie/dist/index.d.ts +143 -0
  218. package/node_modules/cookie/dist/index.js +355 -0
  219. package/node_modules/cookie/dist/index.js.map +1 -0
  220. package/node_modules/cookie/package.json +57 -0
  221. package/node_modules/eventsource/LICENSE +22 -0
  222. package/node_modules/eventsource/README.md +252 -0
  223. package/node_modules/eventsource/dist/EventSource.d.ts +128 -0
  224. package/node_modules/eventsource/dist/EventSource.d.ts.map +1 -0
  225. package/node_modules/eventsource/dist/EventSource.js +556 -0
  226. package/node_modules/eventsource/dist/EventSource.js.map +1 -0
  227. package/node_modules/eventsource/dist/errors.d.ts +53 -0
  228. package/node_modules/eventsource/dist/errors.d.ts.map +1 -0
  229. package/node_modules/eventsource/dist/errors.js +120 -0
  230. package/node_modules/eventsource/dist/errors.js.map +1 -0
  231. package/node_modules/eventsource/dist/index.d.ts +4 -0
  232. package/node_modules/eventsource/dist/index.d.ts.map +1 -0
  233. package/node_modules/eventsource/dist/index.js +3 -0
  234. package/node_modules/eventsource/dist/index.js.map +1 -0
  235. package/node_modules/eventsource/dist/types.d.ts +156 -0
  236. package/node_modules/eventsource/dist/types.d.ts.map +1 -0
  237. package/node_modules/eventsource/dist/types.js +2 -0
  238. package/node_modules/eventsource/dist/types.js.map +1 -0
  239. package/node_modules/eventsource/node_modules/eventsource-parser/LICENSE +21 -0
  240. package/node_modules/eventsource/node_modules/eventsource-parser/README.md +177 -0
  241. package/node_modules/eventsource/node_modules/eventsource-parser/dist/errors.d.ts +35 -0
  242. package/node_modules/eventsource/node_modules/eventsource-parser/dist/errors.d.ts.map +1 -0
  243. package/node_modules/eventsource/node_modules/eventsource-parser/dist/errors.js +16 -0
  244. package/node_modules/eventsource/node_modules/eventsource-parser/dist/errors.js.map +1 -0
  245. package/node_modules/eventsource/node_modules/eventsource-parser/dist/index.d.ts +4 -0
  246. package/node_modules/eventsource/node_modules/eventsource-parser/dist/index.d.ts.map +1 -0
  247. package/node_modules/eventsource/node_modules/eventsource-parser/dist/index.js +3 -0
  248. package/node_modules/eventsource/node_modules/eventsource-parser/dist/index.js.map +1 -0
  249. package/node_modules/eventsource/node_modules/eventsource-parser/dist/parse.d.ts +12 -0
  250. package/node_modules/eventsource/node_modules/eventsource-parser/dist/parse.d.ts.map +1 -0
  251. package/node_modules/eventsource/node_modules/eventsource-parser/dist/parse.js +509 -0
  252. package/node_modules/eventsource/node_modules/eventsource-parser/dist/parse.js.map +1 -0
  253. package/node_modules/eventsource/node_modules/eventsource-parser/dist/stream.d.ts +76 -0
  254. package/node_modules/eventsource/node_modules/eventsource-parser/dist/stream.d.ts.map +1 -0
  255. package/node_modules/eventsource/node_modules/eventsource-parser/dist/stream.js +59 -0
  256. package/node_modules/eventsource/node_modules/eventsource-parser/dist/stream.js.map +1 -0
  257. package/node_modules/eventsource/node_modules/eventsource-parser/dist/types.d.ts +130 -0
  258. package/node_modules/eventsource/node_modules/eventsource-parser/dist/types.d.ts.map +1 -0
  259. package/node_modules/eventsource/node_modules/eventsource-parser/dist/types.js +1 -0
  260. package/node_modules/eventsource/node_modules/eventsource-parser/dist/types.js.map +1 -0
  261. package/node_modules/eventsource/node_modules/eventsource-parser/package.json +96 -0
  262. package/node_modules/eventsource/node_modules/eventsource-parser/src/errors.ts +44 -0
  263. package/node_modules/eventsource/node_modules/eventsource-parser/src/index.ts +3 -0
  264. package/node_modules/eventsource/node_modules/eventsource-parser/src/parse.ts +576 -0
  265. package/node_modules/eventsource/node_modules/eventsource-parser/src/stream.ts +118 -0
  266. package/node_modules/eventsource/node_modules/eventsource-parser/src/types.ts +138 -0
  267. package/node_modules/eventsource/package.json +103 -0
  268. package/node_modules/eventsource/src/EventSource.ts +844 -0
  269. package/node_modules/eventsource/src/errors.ts +141 -0
  270. package/node_modules/eventsource/src/index.ts +3 -0
  271. package/node_modules/eventsource/src/types.ts +166 -0
  272. package/node_modules/eventsource-parser/LICENSE +21 -0
  273. package/node_modules/eventsource-parser/README.md +157 -0
  274. package/node_modules/eventsource-parser/dist/index.cjs +177 -0
  275. package/node_modules/eventsource-parser/dist/index.cjs.map +1 -0
  276. package/node_modules/eventsource-parser/dist/index.d.cts +169 -0
  277. package/node_modules/eventsource-parser/dist/index.d.ts +169 -0
  278. package/node_modules/eventsource-parser/dist/index.js +177 -0
  279. package/node_modules/eventsource-parser/dist/index.js.map +1 -0
  280. package/node_modules/eventsource-parser/dist/stream.cjs +29 -0
  281. package/node_modules/eventsource-parser/dist/stream.cjs.map +1 -0
  282. package/node_modules/eventsource-parser/dist/stream.d.cts +134 -0
  283. package/node_modules/eventsource-parser/dist/stream.d.ts +134 -0
  284. package/node_modules/eventsource-parser/dist/stream.js +30 -0
  285. package/node_modules/eventsource-parser/dist/stream.js.map +1 -0
  286. package/node_modules/eventsource-parser/package.json +92 -0
  287. package/node_modules/eventsource-parser/src/errors.ts +44 -0
  288. package/node_modules/eventsource-parser/src/index.ts +8 -0
  289. package/node_modules/eventsource-parser/src/parse.ts +439 -0
  290. package/node_modules/eventsource-parser/src/stream.ts +107 -0
  291. package/node_modules/eventsource-parser/src/types.ts +122 -0
  292. package/node_modules/eventsource-parser/stream.js +2 -0
  293. package/node_modules/mutative/LICENSE +21 -0
  294. package/node_modules/mutative/README.md +870 -0
  295. package/node_modules/mutative/dist/apply.d.ts +31 -0
  296. package/node_modules/mutative/dist/array.d.ts +45 -0
  297. package/node_modules/mutative/dist/constant.d.ts +8 -0
  298. package/node_modules/mutative/dist/create.d.ts +46 -0
  299. package/node_modules/mutative/dist/current.d.ts +23 -0
  300. package/node_modules/mutative/dist/draft.d.ts +3 -0
  301. package/node_modules/mutative/dist/draftify.d.ts +2 -0
  302. package/node_modules/mutative/dist/error.d.ts +54 -0
  303. package/node_modules/mutative/dist/index.d.mts +1 -0
  304. package/node_modules/mutative/dist/index.d.ts +12 -0
  305. package/node_modules/mutative/dist/index.js +8 -0
  306. package/node_modules/mutative/dist/index.mjs +3 -0
  307. package/node_modules/mutative/dist/interface.d.ts +200 -0
  308. package/node_modules/mutative/dist/internal.d.ts +4 -0
  309. package/node_modules/mutative/dist/makeCreator.d.ts +54 -0
  310. package/node_modules/mutative/dist/map.d.ts +15 -0
  311. package/node_modules/mutative/dist/mutative.cjs.development.js +2126 -0
  312. package/node_modules/mutative/dist/mutative.cjs.development.js.map +1 -0
  313. package/node_modules/mutative/dist/mutative.cjs.production.min.js +2 -0
  314. package/node_modules/mutative/dist/mutative.cjs.production.min.js.map +1 -0
  315. package/node_modules/mutative/dist/mutative.esm.js +2123 -0
  316. package/node_modules/mutative/dist/mutative.esm.js.map +1 -0
  317. package/node_modules/mutative/dist/mutative.esm.mjs +2123 -0
  318. package/node_modules/mutative/dist/mutative.esm.mjs.map +1 -0
  319. package/node_modules/mutative/dist/mutative.esm.production.min.mjs +2 -0
  320. package/node_modules/mutative/dist/mutative.esm.production.min.mjs.map +1 -0
  321. package/node_modules/mutative/dist/mutative.umd.development.js +2132 -0
  322. package/node_modules/mutative/dist/mutative.umd.development.js.map +1 -0
  323. package/node_modules/mutative/dist/mutative.umd.production.min.js +2 -0
  324. package/node_modules/mutative/dist/mutative.umd.production.min.js.map +1 -0
  325. package/node_modules/mutative/dist/original.d.ts +19 -0
  326. package/node_modules/mutative/dist/patch.d.ts +2 -0
  327. package/node_modules/mutative/dist/rawReturn.d.ts +19 -0
  328. package/node_modules/mutative/dist/set.d.ts +14 -0
  329. package/node_modules/mutative/dist/unsafe.d.ts +34 -0
  330. package/node_modules/mutative/dist/utils/cast.d.ts +13 -0
  331. package/node_modules/mutative/dist/utils/copy.d.ts +5 -0
  332. package/node_modules/mutative/dist/utils/deepFreeze.d.ts +1 -0
  333. package/node_modules/mutative/dist/utils/draft.d.ts +27 -0
  334. package/node_modules/mutative/dist/utils/finalize.d.ts +13 -0
  335. package/node_modules/mutative/dist/utils/forEach.d.ts +1 -0
  336. package/node_modules/mutative/dist/utils/index.d.ts +7 -0
  337. package/node_modules/mutative/dist/utils/mark.d.ts +2 -0
  338. package/node_modules/mutative/dist/utils/marker.d.ts +5 -0
  339. package/node_modules/mutative/dist/utils/proto.d.ts +10 -0
  340. package/node_modules/mutative/package.json +142 -0
  341. package/node_modules/mutative/src/apply.ts +239 -0
  342. package/node_modules/mutative/src/array.ts +679 -0
  343. package/node_modules/mutative/src/constant.ts +17 -0
  344. package/node_modules/mutative/src/create.ts +27 -0
  345. package/node_modules/mutative/src/current.ts +206 -0
  346. package/node_modules/mutative/src/draft.ts +489 -0
  347. package/node_modules/mutative/src/draftify.ts +57 -0
  348. package/node_modules/mutative/src/error.ts +129 -0
  349. package/node_modules/mutative/src/index.ts +21 -0
  350. package/node_modules/mutative/src/interface.ts +321 -0
  351. package/node_modules/mutative/src/internal.ts +5 -0
  352. package/node_modules/mutative/src/makeCreator.ts +382 -0
  353. package/node_modules/mutative/src/map.ts +160 -0
  354. package/node_modules/mutative/src/original.ts +28 -0
  355. package/node_modules/mutative/src/patch.ts +386 -0
  356. package/node_modules/mutative/src/rawReturn.ts +41 -0
  357. package/node_modules/mutative/src/set.ts +201 -0
  358. package/node_modules/mutative/src/unsafe.ts +66 -0
  359. package/node_modules/mutative/src/utils/cast.ts +22 -0
  360. package/node_modules/mutative/src/utils/copy.ts +224 -0
  361. package/node_modules/mutative/src/utils/deepFreeze.ts +103 -0
  362. package/node_modules/mutative/src/utils/draft.ts +189 -0
  363. package/node_modules/mutative/src/utils/finalize.ts +232 -0
  364. package/node_modules/mutative/src/utils/forEach.ts +24 -0
  365. package/node_modules/mutative/src/utils/index.ts +7 -0
  366. package/node_modules/mutative/src/utils/mark.ts +11 -0
  367. package/node_modules/mutative/src/utils/marker.ts +34 -0
  368. package/node_modules/mutative/src/utils/proto.ts +38 -0
  369. package/node_modules/uuid/LICENSE.md +9 -0
  370. package/node_modules/uuid/README.md +491 -0
  371. package/node_modules/uuid/dist/index.d.ts +15 -0
  372. package/node_modules/uuid/dist/index.js +14 -0
  373. package/node_modules/uuid/dist/max.d.ts +2 -0
  374. package/node_modules/uuid/dist/max.js +1 -0
  375. package/node_modules/uuid/dist/md5.d.ts +2 -0
  376. package/node_modules/uuid/dist/md5.js +135 -0
  377. package/node_modules/uuid/dist/nil.d.ts +2 -0
  378. package/node_modules/uuid/dist/nil.js +1 -0
  379. package/node_modules/uuid/dist/parse.d.ts +3 -0
  380. package/node_modules/uuid/dist/parse.js +9 -0
  381. package/node_modules/uuid/dist/regex.d.ts +2 -0
  382. package/node_modules/uuid/dist/regex.js +1 -0
  383. package/node_modules/uuid/dist/rng.d.ts +1 -0
  384. package/node_modules/uuid/dist/rng.js +4 -0
  385. package/node_modules/uuid/dist/sha1.d.ts +2 -0
  386. package/node_modules/uuid/dist/sha1.js +70 -0
  387. package/node_modules/uuid/dist/stringify.d.ts +3 -0
  388. package/node_modules/uuid/dist/stringify.js +35 -0
  389. package/node_modules/uuid/dist/types.d.ts +22 -0
  390. package/node_modules/uuid/dist/types.js +1 -0
  391. package/node_modules/uuid/dist/uuid-bin.d.ts +1 -0
  392. package/node_modules/uuid/dist/uuid-bin.js +70 -0
  393. package/node_modules/uuid/dist/v1.d.ts +11 -0
  394. package/node_modules/uuid/dist/v1.js +87 -0
  395. package/node_modules/uuid/dist/v1ToV6.d.ts +3 -0
  396. package/node_modules/uuid/dist/v1ToV6.js +10 -0
  397. package/node_modules/uuid/dist/v3.d.ts +9 -0
  398. package/node_modules/uuid/dist/v3.js +9 -0
  399. package/node_modules/uuid/dist/v35.d.ts +7 -0
  400. package/node_modules/uuid/dist/v35.js +39 -0
  401. package/node_modules/uuid/dist/v4.d.ts +4 -0
  402. package/node_modules/uuid/dist/v4.js +29 -0
  403. package/node_modules/uuid/dist/v5.d.ts +9 -0
  404. package/node_modules/uuid/dist/v5.js +9 -0
  405. package/node_modules/uuid/dist/v6.d.ts +4 -0
  406. package/node_modules/uuid/dist/v6.js +20 -0
  407. package/node_modules/uuid/dist/v6ToV1.d.ts +2 -0
  408. package/node_modules/uuid/dist/v6ToV1.js +10 -0
  409. package/node_modules/uuid/dist/v7.d.ts +9 -0
  410. package/node_modules/uuid/dist/v7.js +68 -0
  411. package/node_modules/uuid/dist/validate.d.ts +2 -0
  412. package/node_modules/uuid/dist/validate.js +5 -0
  413. package/node_modules/uuid/dist/version.d.ts +2 -0
  414. package/node_modules/uuid/dist/version.js +8 -0
  415. package/node_modules/uuid/dist-node/bin/uuid +2 -0
  416. package/node_modules/uuid/dist-node/index.js +14 -0
  417. package/node_modules/uuid/dist-node/max.js +1 -0
  418. package/node_modules/uuid/dist-node/md5.js +11 -0
  419. package/node_modules/uuid/dist-node/nil.js +1 -0
  420. package/node_modules/uuid/dist-node/parse.js +9 -0
  421. package/node_modules/uuid/dist-node/regex.js +1 -0
  422. package/node_modules/uuid/dist-node/rng.js +4 -0
  423. package/node_modules/uuid/dist-node/sha1.js +11 -0
  424. package/node_modules/uuid/dist-node/stringify.js +35 -0
  425. package/node_modules/uuid/dist-node/types.js +1 -0
  426. package/node_modules/uuid/dist-node/uuid-bin.js +70 -0
  427. package/node_modules/uuid/dist-node/v1.js +87 -0
  428. package/node_modules/uuid/dist-node/v1ToV6.js +10 -0
  429. package/node_modules/uuid/dist-node/v3.js +9 -0
  430. package/node_modules/uuid/dist-node/v35.js +39 -0
  431. package/node_modules/uuid/dist-node/v4.js +29 -0
  432. package/node_modules/uuid/dist-node/v5.js +9 -0
  433. package/node_modules/uuid/dist-node/v6.js +20 -0
  434. package/node_modules/uuid/dist-node/v6ToV1.js +10 -0
  435. package/node_modules/uuid/dist-node/v7.js +68 -0
  436. package/node_modules/uuid/dist-node/validate.js +5 -0
  437. package/node_modules/uuid/dist-node/version.js +8 -0
  438. package/node_modules/uuid/package.json +115 -0
  439. package/package.json +80 -4
  440. package/release.json +78 -0
@@ -0,0 +1,2369 @@
1
+ // @ts-check
2
+ import weakHash from './utils/weakHash.js';
3
+ import weakHashLegacy from './utils/weakHashLegacy.js';
4
+ import instaql from './instaql.js';
5
+ import * as instaml from './instaml.js';
6
+ import * as s from './store.js';
7
+ import uuid from './utils/id.js';
8
+ import IndexedDBStorage from './IndexedDBStorage.js';
9
+ import WindowNetworkListener from './WindowNetworkListener.js';
10
+ import * as authAPI from './authAPI.js';
11
+ import * as StorageApi from './StorageAPI.js';
12
+ import * as flags from './utils/flags.js';
13
+ import { buildPresenceSlice, hasPresenceResponseChanged } from './presence.js';
14
+ import { Deferred } from './utils/Deferred.js';
15
+ import { PersistedObject, StoreInterface } from './utils/PersistedObject.js';
16
+ import { extractTriples } from './model/instaqlResult.js';
17
+ import { areObjectsDeepEqual, assocInMutative, dissocInMutative, insertInMutative, } from './utils/object.js';
18
+ import { createLinkIndex } from './utils/linkIndex.js';
19
+ import version from './version.js';
20
+ import { create } from 'mutative';
21
+ import createLogger from './utils/log.js';
22
+ import { validateQuery } from './queryValidation.js';
23
+ import { validateTransactions } from './transactionValidation.js';
24
+ import { InstantError } from './InstantError.js';
25
+ import { InstantAPIError } from './utils/fetch.js';
26
+ import { validate as validateUUID } from 'uuid';
27
+ import { WSConnection, SSEConnection } from './Connection.js';
28
+ import { SyncTable } from './SyncTable.js';
29
+ import { InstantStream } from './Stream.js';
30
+ /** @typedef {import('./utils/log.ts').Logger} Logger */
31
+ /** @typedef {import('./Connection.ts').Connection<WebSocket | import('./Connection.ts').EventSourceType>} Connection */
32
+ /** @typedef {import('./Connection.ts').TransportType} TransportType */
33
+ /** @typedef {import('./Connection.ts').EventSourceConstructor} EventSourceConstructor */
34
+ /** @typedef {import('./reactorTypes.ts').QuerySub} QuerySub */
35
+ /** @typedef {import('./reactorTypes.ts').QuerySubInStorage} QuerySubInStorage */
36
+ /** @typedef {import('./clientTypes.ts').User} User */
37
+ /** @typedef {import('./clientTypes.ts').AuthState} AuthState */
38
+ /** @typedef {import('./framework.ts').FrameworkClient} FrameworkClient */
39
+ export const STATUS = {
40
+ CONNECTING: 'connecting',
41
+ OPENED: 'opened',
42
+ AUTHENTICATED: 'authenticated',
43
+ CLOSED: 'closed',
44
+ ERRORED: 'errored',
45
+ };
46
+ const QUERY_ONCE_TIMEOUT = 30_000;
47
+ const PENDING_TX_CLEANUP_TIMEOUT = 30_000;
48
+ const PENDING_MUTATION_CLEANUP_THRESHOLD = 200;
49
+ const ONE_MIN_MS = 1_000 * 60;
50
+ const ONE_DAY_MS = ONE_MIN_MS * 60 * 24;
51
+ export const COOKIE_SYNC_LAST_UPDATED_KEY = 'lastSyncedUserCookie';
52
+ const defaultConfig = {
53
+ apiURI: 'https://api.instantdb.com',
54
+ websocketURI: 'wss://api.instantdb.com/runtime/session',
55
+ };
56
+ // Param that the backend adds if this is an oauth redirect
57
+ const OAUTH_REDIRECT_PARAM = '_instant_oauth_redirect';
58
+ const OAUTH_EXTRA_FIELDS_ID_PARAM = '_instant_extra_fields_id';
59
+ const oauthExtraFieldsKey = 'oauthExtraFields';
60
+ const currentUserKey = `currentUser`;
61
+ /**
62
+ * @param {Object} config
63
+ * @param {TransportType} config.transportType
64
+ * @param {string} config.appId
65
+ * @param {string} config.apiURI
66
+ * @param {string} config.wsURI
67
+ * @param {EventSourceConstructor} config.EventSourceImpl
68
+ * @returns {WSConnection | SSEConnection}
69
+ */
70
+ function createTransport({ transportType, appId, apiURI, wsURI, EventSourceImpl, }) {
71
+ if (!EventSourceImpl) {
72
+ return new WSConnection(`${wsURI}?app_id=${appId}`);
73
+ }
74
+ switch (transportType) {
75
+ case 'ws':
76
+ return new WSConnection(`${wsURI}?app_id=${appId}`);
77
+ case 'sse':
78
+ return new SSEConnection(EventSourceImpl, `${apiURI}/runtime/sse?app_id=${appId}`);
79
+ default:
80
+ throw new Error('Unknown transport type ' + transportType);
81
+ }
82
+ }
83
+ function isClient() {
84
+ const hasWindow = typeof window !== 'undefined';
85
+ // this checks if we are running in a chrome extension
86
+ // @ts-expect-error
87
+ const isChrome = typeof chrome !== 'undefined';
88
+ return hasWindow || isChrome;
89
+ }
90
+ const ignoreLogging = {
91
+ 'set-presence': true,
92
+ 'set-presence-ok': true,
93
+ 'refresh-presence': true,
94
+ 'patch-presence': true,
95
+ };
96
+ /**
97
+ * @param {QuerySubInStorage} x
98
+ * @param {boolean | null} useDateObjects
99
+ * @returns {QuerySub}
100
+ */
101
+ function querySubFromStorage(x, useDateObjects) {
102
+ const v = typeof x === 'string' ? JSON.parse(x) : x;
103
+ if (v?.result?.store) {
104
+ const attrsStore = s.attrsStoreFromJSON(v.result.attrsStore, v.result.store);
105
+ if (attrsStore) {
106
+ const storeJSON = v.result.store;
107
+ v.result.store = s.fromJSON(attrsStore, {
108
+ ...storeJSON,
109
+ useDateObjects: useDateObjects,
110
+ });
111
+ v.result.attrsStore = attrsStore;
112
+ }
113
+ }
114
+ return v;
115
+ }
116
+ /**
117
+ *
118
+ * @param {string} _key
119
+ * @param {QuerySub} sub
120
+ * @returns QuerySubInStorage
121
+ */
122
+ function querySubToStorage(_key, sub) {
123
+ const { result, ...rest } = sub;
124
+ const jsonSub = /** @type {import('./reactorTypes.ts').QuerySubInStorage} */ (rest);
125
+ if (result) {
126
+ /** @type {import('./reactorTypes.ts').QuerySubResultInStorage} */
127
+ const jsonResult = {
128
+ ...result,
129
+ store: s.toJSON(result.store),
130
+ attrsStore: result.attrsStore.toJSON(),
131
+ };
132
+ jsonSub.result = jsonResult;
133
+ }
134
+ return jsonSub;
135
+ }
136
+ function kvFromStorage(key, x) {
137
+ switch (key) {
138
+ case 'pendingMutations':
139
+ return new Map(typeof x === 'string' ? JSON.parse(x) : x);
140
+ default:
141
+ return x;
142
+ }
143
+ }
144
+ function kvToStorage(key, x) {
145
+ switch (key) {
146
+ case 'pendingMutations':
147
+ return [...x.entries()];
148
+ default:
149
+ return x;
150
+ }
151
+ }
152
+ function onMergeQuerySub(_k, storageSub, inMemorySub) {
153
+ const storageResult = storageSub?.result;
154
+ const memoryResult = inMemorySub?.result;
155
+ if (storageResult && !memoryResult && inMemorySub) {
156
+ inMemorySub.result = storageResult;
157
+ }
158
+ return inMemorySub || storageSub;
159
+ }
160
+ function sortedMutationEntries(entries) {
161
+ return [...entries].sort((a, b) => {
162
+ const [ka, muta] = a;
163
+ const [kb, mutb] = b;
164
+ const a_order = muta.order || 0;
165
+ const b_order = mutb.order || 0;
166
+ if (a_order == b_order) {
167
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
168
+ }
169
+ return a_order - b_order;
170
+ });
171
+ }
172
+ /**
173
+ * @template {import('./presence.ts').RoomSchemaShape} [RoomSchema = {}]
174
+ */
175
+ export default class Reactor {
176
+ /** @type {s.AttrsStore | undefined} */
177
+ attrs;
178
+ _isOnline = true;
179
+ _isShutdown = false;
180
+ status = STATUS.CONNECTING;
181
+ /** @type {PersistedObject<string, QuerySub, QuerySubInStorage>} */
182
+ querySubs;
183
+ /** @type {PersistedObject} */
184
+ kv;
185
+ /** @type {SyncTable} */
186
+ _syncTable;
187
+ /** @type {InstantStream} */
188
+ _instantStream;
189
+ /** @type {Record<string, Array<{ q: any, cb: (data: any) => any }>>} */
190
+ queryCbs = {};
191
+ /** @type {Record<string, Array<{ q: any, eventId: string, dfd: Deferred }>>} */
192
+ queryOnceDfds = {};
193
+ authCbs = [];
194
+ attrsCbs = [];
195
+ mutationErrorCbs = [];
196
+ connectionStatusCbs = [];
197
+ appStatusCbs = [];
198
+ /**
199
+ * Carries the raw server status off the public type: `disabled` is an
200
+ * operator-level state we don't surface to apps.
201
+ * @type {import('./clientTypes.ts').AppStatusState & { status?: 'active' | 'read-only' | 'disabled' }}
202
+ */
203
+ _appStatusState = { isLoading: true, isReadOnly: undefined };
204
+ config;
205
+ mutationDeferredStore = new Map();
206
+ /** @type {ReturnType<typeof setTimeout> | null} */
207
+ _reconnectTimeoutId = null;
208
+ _reconnectTimeoutMs = 0;
209
+ _networkUnsubscribe;
210
+ /** @type {Connection} */
211
+ _transport;
212
+ /** @type {TransportType} */
213
+ _transportType = 'ws';
214
+ /** @type {EventSourceConstructor} */
215
+ _EventSource;
216
+ /** @type {boolean | null} */
217
+ _wsOk = null;
218
+ _localIdPromises = {};
219
+ _errorMessage = null;
220
+ /** @type {Promise<null | {error: {message: string}}> | null}**/
221
+ _oauthCallbackResponse = null;
222
+ /** @type {null | import('./utils/linkIndex.ts').LinkIndex}} */
223
+ _linkIndex = null;
224
+ /** @type BroadcastChannel | undefined */
225
+ _broadcastChannel;
226
+ /** @type {Record<string, {roomType: string; isConnected: boolean; error: any}>} */
227
+ _rooms = {};
228
+ /** @type {Record<string, boolean>} */
229
+ _roomsPendingLeave = {};
230
+ _presence = {};
231
+ _broadcastQueue = [];
232
+ _broadcastSubs = {};
233
+ /** @type {{isLoading: boolean; error: any | undefined, user: any | undefined}} */
234
+ _currentUserCached = { isLoading: true, error: undefined, user: undefined };
235
+ _beforeUnloadCbs = [];
236
+ _dataForQueryCache = {};
237
+ /** @type {Logger} */
238
+ _log;
239
+ _pendingTxCleanupTimeout;
240
+ _pendingMutationCleanupThreshold;
241
+ _inFlightMutationEventIds = new Set();
242
+ /** @type FrameworkClient | null */
243
+ _frameworkClient = null;
244
+ constructor(config, Storage = IndexedDBStorage, NetworkListener = WindowNetworkListener, versions, EventSourceConstructor) {
245
+ this._EventSource = EventSourceConstructor;
246
+ this.config = { ...defaultConfig, ...config };
247
+ this.queryCacheLimit = this.config.queryCacheLimit ?? 10;
248
+ this._pendingTxCleanupTimeout =
249
+ this.config.pendingTxCleanupTimeout ?? PENDING_TX_CLEANUP_TIMEOUT;
250
+ this._pendingMutationCleanupThreshold =
251
+ this.config.pendingMutationCleanupThreshold ??
252
+ PENDING_MUTATION_CLEANUP_THRESHOLD;
253
+ this._log = createLogger(config.verbose || flags.devBackend || flags.instantLogs, () => this._reactorStats(), config.logger);
254
+ this.versions = { ...(versions || {}), '@instantdb/core': version };
255
+ if (this.config.schema) {
256
+ this._linkIndex = createLinkIndex(this.config.schema);
257
+ }
258
+ // This is to protect us against running
259
+ // server-side.
260
+ if (!isClient()) {
261
+ return;
262
+ }
263
+ if (!config.appId) {
264
+ throw new Error('Instant must be initialized with an appId.');
265
+ }
266
+ if (!validateUUID(config.appId)) {
267
+ throw new Error(`Instant must be initialized with a valid appId. \`${config.appId}\` is not a valid uuid.`);
268
+ }
269
+ if (typeof BroadcastChannel === 'function') {
270
+ this._broadcastChannel = new BroadcastChannel('@instantdb');
271
+ this._broadcastChannel.addEventListener('message', async (e) => {
272
+ try {
273
+ if (e.data?.type === 'auth') {
274
+ const res = await this.getCurrentUser({
275
+ forceReadFromStorage: true,
276
+ });
277
+ await this.updateUser(res.user).catch((error) => {
278
+ this._log.error('[error] update user', error);
279
+ });
280
+ }
281
+ }
282
+ catch (e) {
283
+ this._log.error('[error] handle broadcast channel', e);
284
+ }
285
+ });
286
+ }
287
+ this._initStorage(Storage);
288
+ this._syncTable = new SyncTable(this._trySendAuthed.bind(this), new Storage(this.config.appId, 'syncSubs'), {
289
+ useDateObjects: this.config.useDateObjects,
290
+ }, this._log, (triples) => {
291
+ return s.createStore(this.ensureAttrs(), triples, this.config.enableCardinalityInference, this.config.useDateObjects);
292
+ }, () => this.ensureAttrs());
293
+ this._instantStream = new InstantStream({
294
+ WStream: this.config.WritableStream || WritableStream,
295
+ RStream: this.config.ReadableStream || ReadableStream,
296
+ trySend: this._trySendAuthed.bind(this),
297
+ log: this._log,
298
+ });
299
+ this._oauthCallbackResponse = this._oauthLoginInit();
300
+ this.setupUserSyncTimer();
301
+ NetworkListener.getIsOnline().then((isOnline) => {
302
+ if (this._isShutdown)
303
+ return;
304
+ this._isOnline = isOnline;
305
+ this._startSocket();
306
+ this._networkUnsubscribe = NetworkListener.listen((isOnline) => {
307
+ // We do this because react native's NetInfo
308
+ // fires multiple online events.
309
+ // We only want to handle one state change
310
+ if (isOnline === this._isOnline) {
311
+ return;
312
+ }
313
+ this._log.info('[network] online =', isOnline);
314
+ this._isOnline = isOnline;
315
+ if (this._isOnline) {
316
+ this._startSocket();
317
+ }
318
+ else {
319
+ this._log.info('Changing status from', this.status, 'to', STATUS.CLOSED);
320
+ this._setStatus(STATUS.CLOSED);
321
+ }
322
+ });
323
+ });
324
+ if (typeof addEventListener !== 'undefined') {
325
+ this._beforeUnload = this._beforeUnload.bind(this);
326
+ addEventListener('beforeunload', this._beforeUnload);
327
+ }
328
+ }
329
+ getFrameworkClient() {
330
+ return this._frameworkClient;
331
+ }
332
+ /** @param {FrameworkClient} client */
333
+ setFrameworkClient(client) {
334
+ this._frameworkClient = client;
335
+ }
336
+ ensureAttrs() {
337
+ if (!this.attrs) {
338
+ throw new Error('attrs have not loaded.');
339
+ }
340
+ return this.attrs;
341
+ }
342
+ updateSchema(schema) {
343
+ this.config = {
344
+ ...this.config,
345
+ schema: schema,
346
+ cardinalityInference: Boolean(schema),
347
+ };
348
+ this._linkIndex = schema ? createLinkIndex(this.config.schema) : null;
349
+ }
350
+ _reactorStats() {
351
+ return {
352
+ inFlightMutationCount: this._inFlightMutationEventIds.size,
353
+ storedMutationCount: this._pendingMutations().size,
354
+ transportType: this._transportType,
355
+ };
356
+ }
357
+ _onQuerySubLoaded(hash) {
358
+ this.kv
359
+ .waitForKeyToLoad('pendingMutations')
360
+ .then(() => this.notifyOne(hash));
361
+ }
362
+ _initStorage(Storage) {
363
+ this.querySubs = new PersistedObject({
364
+ persister: new Storage(this.config.appId, 'querySubs'),
365
+ merge: onMergeQuerySub,
366
+ serialize: querySubToStorage,
367
+ parse: (_key, x) => querySubFromStorage(x, this.config.useDateObjects),
368
+ // objectSize
369
+ objectSize: (x) => x?.result?.store?.triples?.length ?? 0,
370
+ logger: this._log,
371
+ preloadEntryCount: 10,
372
+ gc: {
373
+ maxAgeMs: 1000 * 60 * 60 * 24 * 7 * 52, // 1 year
374
+ maxEntries: 1000,
375
+ // Size of each query is the number of triples
376
+ maxSize: 1_000_000, // 1 million triples
377
+ },
378
+ });
379
+ this.querySubs.onKeyLoaded = (k) => this._onQuerySubLoaded(k);
380
+ this.kv = new PersistedObject({
381
+ persister: new Storage(this.config.appId, 'kv'),
382
+ merge: this._onMergeKv,
383
+ serialize: kvToStorage,
384
+ parse: kvFromStorage,
385
+ objectSize: () => 0,
386
+ logger: this._log,
387
+ saveThrottleMs: 100,
388
+ idleCallbackMaxWaitMs: 100,
389
+ // Don't GC the kv store
390
+ gc: null,
391
+ });
392
+ this.kv.onKeyLoaded = (k) => {
393
+ if (k === 'pendingMutations') {
394
+ this.notifyAll();
395
+ }
396
+ };
397
+ // Trigger immediate load for pendingMutations and currentUser
398
+ this.kv.waitForKeyToLoad('pendingMutations');
399
+ this.kv.waitForKeyToLoad(currentUserKey);
400
+ this._beforeUnloadCbs.push(() => {
401
+ this.kv.flush();
402
+ this.querySubs.flush();
403
+ });
404
+ }
405
+ _beforeUnload() {
406
+ for (const cb of this._beforeUnloadCbs) {
407
+ cb();
408
+ }
409
+ this._syncTable.beforeUnload();
410
+ }
411
+ /**
412
+ * @param {'enqueued' | 'pending' | 'synced' | 'timeout' | 'error' } status
413
+ * @param {string} eventId
414
+ * @param {{message?: string, type?: string, status?: number, hint?: unknown, traceId?: string}} [errorMsg]
415
+ */
416
+ _finishTransaction(status, eventId, errorMsg) {
417
+ const dfd = this.mutationDeferredStore.get(eventId);
418
+ this.mutationDeferredStore.delete(eventId);
419
+ const ok = status !== 'error' && status !== 'timeout';
420
+ if (!dfd && !ok) {
421
+ // console.erroring here, as there are no listeners to let know
422
+ console.error('Mutation failed', { status, eventId, ...errorMsg });
423
+ }
424
+ if (!dfd) {
425
+ return;
426
+ }
427
+ if (ok) {
428
+ dfd.resolve({ status, eventId });
429
+ }
430
+ else {
431
+ // Check if error comes from server or client
432
+ if (errorMsg?.type) {
433
+ const { status, ...body } = errorMsg;
434
+ dfd.reject(new InstantAPIError({
435
+ // @ts-expect-error body.type is not constant typed
436
+ body,
437
+ status: status ?? 0,
438
+ }));
439
+ }
440
+ else {
441
+ dfd.reject(new InstantError(errorMsg?.message || 'Unknown error', errorMsg?.hint, errorMsg?.traceId));
442
+ }
443
+ }
444
+ }
445
+ _setStatus(status, err) {
446
+ this.status = status;
447
+ this._errorMessage = err;
448
+ this.notifyConnectionStatusSubs(status);
449
+ this._instantStream.onConnectionStatusChange(status);
450
+ }
451
+ _onMergeKv = (key, storageV, inMemoryV) => {
452
+ switch (key) {
453
+ case 'pendingMutations': {
454
+ const storageEntries = storageV?.entries() ?? [];
455
+ const inMemoryEntries = inMemoryV?.entries() ?? [];
456
+ const muts = new Map([...storageEntries, ...inMemoryEntries]);
457
+ const rewrittenStorageMuts = storageV
458
+ ? this._rewriteMutationsSorted(this.attrs, storageV)
459
+ : [];
460
+ rewrittenStorageMuts.forEach(([k, mut]) => {
461
+ if (!inMemoryV?.pendingMutations?.has(k) && !mut['tx-id']) {
462
+ this._sendMutation(k, mut);
463
+ }
464
+ });
465
+ return muts;
466
+ }
467
+ default:
468
+ return inMemoryV || storageV;
469
+ }
470
+ };
471
+ _flushEnqueuedRoomData(roomId) {
472
+ const enqueuedUserPresence = this._presence[roomId]?.result?.user;
473
+ const enqueuedBroadcasts = this._broadcastQueue[roomId];
474
+ this._broadcastQueue[roomId] = [];
475
+ if (enqueuedUserPresence) {
476
+ this._trySetPresence(roomId, enqueuedUserPresence);
477
+ }
478
+ if (enqueuedBroadcasts) {
479
+ for (const item of enqueuedBroadcasts) {
480
+ const { topic, roomType, data } = item;
481
+ this._tryBroadcast(roomId, roomType, topic, data);
482
+ }
483
+ }
484
+ }
485
+ /**
486
+ * Does the same thing as add-query-ok
487
+ * but called as a result of receiving query info from ssr
488
+ * @param {any} q
489
+ * @param {{ triples: any; pageInfo: any; }} result
490
+ * @param {boolean} enableCardinalityInference
491
+ */
492
+ _addQueryData(q, result, enableCardinalityInference) {
493
+ if (!this.attrs) {
494
+ throw new Error('Attrs in reactor have not been set');
495
+ }
496
+ const queryHash = weakHash(q);
497
+ const attrsStore = this.ensureAttrs();
498
+ const store = s.createStore(this.attrs, result.triples, enableCardinalityInference, this.config.useDateObjects);
499
+ this.querySubs.updateInPlace((prev) => {
500
+ prev[queryHash] = {
501
+ result: {
502
+ store,
503
+ attrsStore,
504
+ pageInfo: result.pageInfo,
505
+ processedTxId: undefined,
506
+ isExternal: true,
507
+ },
508
+ q,
509
+ };
510
+ });
511
+ this._cleanupPendingMutationsQueries();
512
+ this.notifyOne(queryHash);
513
+ this.notifyOneQueryOnce(queryHash);
514
+ this._cleanupPendingMutationsTimeout();
515
+ }
516
+ _handleReceive(connId, msg) {
517
+ // opt-out, enabled by default if schema
518
+ const enableCardinalityInference = Boolean(this.config.schema) &&
519
+ ('cardinalityInference' in this.config
520
+ ? Boolean(this.config.cardinalityInference)
521
+ : true);
522
+ if (!ignoreLogging[msg.op]) {
523
+ this._log.info('[receive]', connId, msg.op, msg);
524
+ }
525
+ switch (msg.op) {
526
+ case 'init-ok': {
527
+ this._setStatus(STATUS.AUTHENTICATED);
528
+ this._reconnectTimeoutMs = 0;
529
+ this._setAppStatus(msg['app-status']?.status ?? 'active');
530
+ this._setAttrs(msg.attrs);
531
+ this._flushPendingMessages();
532
+ // (EPH): set session-id, so we know
533
+ // which item is us
534
+ this._sessionId = msg['session-id'];
535
+ for (const roomId of Object.keys(this._rooms)) {
536
+ const enqueuedUserPresence = this._presence[roomId]?.result?.user;
537
+ const roomType = this._rooms[roomId]?.roomType;
538
+ this._tryJoinRoom(roomType, roomId, enqueuedUserPresence);
539
+ }
540
+ break;
541
+ }
542
+ case 'add-query-exists': {
543
+ this.notifyOneQueryOnce(weakHash(msg.q));
544
+ break;
545
+ }
546
+ case 'add-query-ok': {
547
+ const { q, result } = msg;
548
+ const hash = weakHash(q);
549
+ if (!this._hasQueryListeners() && !this.querySubs.currentValue[hash]) {
550
+ break;
551
+ }
552
+ const pageInfo = result?.[0]?.data?.['page-info'];
553
+ const aggregate = result?.[0]?.data?.['aggregate'];
554
+ const triples = extractTriples(result);
555
+ const attrsStore = this.ensureAttrs();
556
+ const store = s.createStore(attrsStore, triples, enableCardinalityInference, this.config.useDateObjects);
557
+ this.querySubs.updateInPlace((prev) => {
558
+ if (!prev[hash]) {
559
+ this._log.info('Missing value in querySubs', { hash, q });
560
+ return;
561
+ }
562
+ prev[hash].result = {
563
+ store,
564
+ attrsStore,
565
+ pageInfo,
566
+ aggregate,
567
+ processedTxId: msg['processed-tx-id'],
568
+ };
569
+ });
570
+ this._cleanupPendingMutationsQueries();
571
+ this.notifyOne(hash);
572
+ this.notifyOneQueryOnce(hash);
573
+ this._cleanupPendingMutationsTimeout();
574
+ break;
575
+ }
576
+ case 'start-sync-ok': {
577
+ this._syncTable.onStartSyncOk(msg);
578
+ break;
579
+ }
580
+ case 'sync-load-batch': {
581
+ this._syncTable.onSyncLoadBatch(msg);
582
+ break;
583
+ }
584
+ case 'sync-init-finish': {
585
+ this._syncTable.onSyncInitFinish(msg);
586
+ break;
587
+ }
588
+ case 'sync-update-triples': {
589
+ this._syncTable.onSyncUpdateTriples(msg);
590
+ break;
591
+ }
592
+ case 'start-stream-ok': {
593
+ this._instantStream.onStartStreamOk(msg);
594
+ break;
595
+ }
596
+ case 'stream-flushed': {
597
+ this._instantStream.onStreamFlushed(msg);
598
+ break;
599
+ }
600
+ case 'append-failed': {
601
+ this._instantStream.onAppendFailed(msg);
602
+ break;
603
+ }
604
+ case 'stream-append': {
605
+ this._instantStream.onStreamAppend(msg);
606
+ break;
607
+ }
608
+ case 'refresh-ok': {
609
+ const { computations, attrs } = msg;
610
+ const processedTxId = msg['processed-tx-id'];
611
+ if (attrs) {
612
+ this._setAttrs(attrs);
613
+ }
614
+ this._cleanupPendingMutationsTimeout();
615
+ const rewrittenMutations = this._rewriteMutations(this.ensureAttrs(), this._pendingMutations(), processedTxId);
616
+ if (rewrittenMutations !== this._pendingMutations()) {
617
+ // We know we've changed the mutations to fix the attr ids and removed
618
+ // processed attrs, so we'll persist those changes to prevent optimisticAttrs
619
+ // from using old attr definitions
620
+ this.kv.updateInPlace((prev) => {
621
+ prev.pendingMutations = rewrittenMutations;
622
+ });
623
+ }
624
+ const mutations = sortedMutationEntries(rewrittenMutations.entries());
625
+ const updates = computations.map((x) => {
626
+ const q = x['instaql-query'];
627
+ const result = x['instaql-result'];
628
+ const hash = weakHash(q);
629
+ const triples = extractTriples(result);
630
+ const attrsStore = this.ensureAttrs();
631
+ const store = s.createStore(attrsStore, triples, enableCardinalityInference, this.config.useDateObjects);
632
+ const { store: newStore, attrsStore: newAttrsStore } = this._applyOptimisticUpdates(store, attrsStore, mutations, processedTxId);
633
+ const pageInfo = result?.[0]?.data?.['page-info'];
634
+ const aggregate = result?.[0]?.data?.['aggregate'];
635
+ return {
636
+ q,
637
+ hash,
638
+ store: newStore,
639
+ attrsStore: newAttrsStore,
640
+ pageInfo,
641
+ aggregate,
642
+ };
643
+ });
644
+ updates.forEach(({ hash, q, store, attrsStore, pageInfo, aggregate }) => {
645
+ this.querySubs.updateInPlace((prev) => {
646
+ if (!prev[hash]) {
647
+ this._log.error('Missing value in querySubs', { hash, q });
648
+ return;
649
+ }
650
+ prev[hash].result = {
651
+ store,
652
+ attrsStore,
653
+ pageInfo,
654
+ aggregate,
655
+ processedTxId,
656
+ };
657
+ });
658
+ });
659
+ this._cleanupPendingMutationsQueries();
660
+ updates.forEach(({ hash }) => {
661
+ this.notifyOne(hash);
662
+ });
663
+ break;
664
+ }
665
+ case 'transact-ok': {
666
+ const { 'client-event-id': eventId, 'tx-id': txId } = msg;
667
+ this._inFlightMutationEventIds.delete(eventId);
668
+ const muts = this._rewriteMutations(this.ensureAttrs(), this._pendingMutations());
669
+ const prevMutation = muts.get(eventId);
670
+ if (!prevMutation) {
671
+ break;
672
+ }
673
+ // update pendingMutation with server-side tx-id
674
+ this._updatePendingMutations((prev) => {
675
+ prev.set(eventId, {
676
+ ...prev.get(eventId),
677
+ 'tx-id': txId,
678
+ confirmed: Date.now(),
679
+ });
680
+ });
681
+ const newAttrs = [];
682
+ for (const step of prevMutation['tx-steps']) {
683
+ if (step[0] === 'add-attr') {
684
+ const attr = step[1];
685
+ newAttrs.push(attr);
686
+ }
687
+ }
688
+ if (newAttrs.length) {
689
+ const existingAttrs = Object.values(this.ensureAttrs().attrs);
690
+ this._setAttrs([...existingAttrs, ...newAttrs]);
691
+ }
692
+ this._finishTransaction('synced', eventId);
693
+ this._cleanupPendingMutationsTimeout();
694
+ break;
695
+ }
696
+ case 'patch-presence': {
697
+ const roomId = msg['room-id'];
698
+ this._trySetRoomConnected(roomId, true);
699
+ this._patchPresencePeers(roomId, msg['edits']);
700
+ this._notifyPresenceSubs(roomId);
701
+ break;
702
+ }
703
+ case 'refresh-presence': {
704
+ const roomId = msg['room-id'];
705
+ this._trySetRoomConnected(roomId, true);
706
+ this._setPresencePeers(roomId, msg['data']);
707
+ this._notifyPresenceSubs(roomId);
708
+ break;
709
+ }
710
+ case 'server-broadcast': {
711
+ const room = msg['room-id'];
712
+ const topic = msg.topic;
713
+ this._trySetRoomConnected(room, true);
714
+ this._notifyBroadcastSubs(room, topic, msg);
715
+ break;
716
+ }
717
+ case 'join-room-ok': {
718
+ const loadingRoomId = msg['room-id'];
719
+ const joinedRoom = this._rooms[loadingRoomId];
720
+ if (!joinedRoom) {
721
+ if (this._roomsPendingLeave[loadingRoomId]) {
722
+ this._tryLeaveRoom(loadingRoomId);
723
+ delete this._roomsPendingLeave[loadingRoomId];
724
+ }
725
+ break;
726
+ }
727
+ this._trySetRoomConnected(loadingRoomId, true);
728
+ this._flushEnqueuedRoomData(loadingRoomId);
729
+ break;
730
+ }
731
+ case 'leave-room-ok': {
732
+ const roomId = msg['room-id'];
733
+ this._trySetRoomConnected(roomId, false);
734
+ break;
735
+ }
736
+ case 'app-status-changed': {
737
+ const prevStatus = this._appStatusState.status;
738
+ this._setAppStatus(msg.status);
739
+ if (msg.status === 'disabled' && prevStatus !== 'disabled') {
740
+ // The server has stopped serving reads; surface the typed error on
741
+ // every registered query immediately instead of waiting for the
742
+ // next round trip.
743
+ const error = {
744
+ message: 'This app is currently disabled.',
745
+ hint: { status: 'disabled' },
746
+ };
747
+ Object.keys(this.queryCbs).forEach((hash) => this.notifyQueryError(hash, error));
748
+ }
749
+ if (prevStatus === 'disabled' && msg.status !== 'disabled') {
750
+ // Leaving `disabled`: refreshes were paused server-side, so restart
751
+ // the socket to re-add every query and get fresh results.
752
+ this._startSocket();
753
+ }
754
+ break;
755
+ }
756
+ case 'join-room-error':
757
+ const errorRoomId = msg['room-id'];
758
+ const errorRoom = this._rooms[errorRoomId];
759
+ if (errorRoom) {
760
+ errorRoom.error = msg['error'];
761
+ }
762
+ this._notifyPresenceSubs(errorRoomId);
763
+ break;
764
+ case 'error':
765
+ this._handleReceiveError(msg);
766
+ break;
767
+ default:
768
+ this._log.info('Unknown op', msg.op, msg);
769
+ break;
770
+ }
771
+ }
772
+ createWriteStream(opts) {
773
+ return this._instantStream.createWriteStream(opts);
774
+ }
775
+ createReadStream(opts) {
776
+ return this._instantStream.createReadStream(opts);
777
+ }
778
+ _pendingMutations() {
779
+ return this.kv.currentValue.pendingMutations ?? new Map();
780
+ }
781
+ _updatePendingMutations(f) {
782
+ this.kv.updateInPlace((prev) => {
783
+ const muts = prev.pendingMutations ?? new Map();
784
+ prev.pendingMutations = muts;
785
+ f(muts);
786
+ });
787
+ }
788
+ /**
789
+ * @param {'timeout' | 'error'} status
790
+ * @param {string} eventId
791
+ * @param {{message?: string, type?: string, status?: number, hint?: unknown, traceId?: string}} errorMsg
792
+ */
793
+ _handleMutationError(status, eventId, errorMsg) {
794
+ const mut = this._pendingMutations().get(eventId);
795
+ if (mut && (status !== 'timeout' || !mut['tx-id'])) {
796
+ this._updatePendingMutations((prev) => {
797
+ prev.delete(eventId);
798
+ return prev;
799
+ });
800
+ this._inFlightMutationEventIds.delete(eventId);
801
+ const errDetails = {
802
+ message: errorMsg.message,
803
+ hint: errorMsg.hint,
804
+ traceId: errorMsg.traceId,
805
+ };
806
+ this.notifyAll();
807
+ this.notifyAttrsSubs();
808
+ this.notifyMutationErrorSubs(errDetails);
809
+ this._finishTransaction(status, eventId, errorMsg);
810
+ }
811
+ }
812
+ _handleReceiveError(msg) {
813
+ this._log.info('error', msg);
814
+ const eventId = msg['client-event-id'];
815
+ // This might not be a mutation, but it can't hurt to delete it
816
+ this._inFlightMutationEventIds.delete(eventId);
817
+ const prevMutation = this._pendingMutations().get(eventId);
818
+ const errorMessage = {
819
+ message: msg.message || 'Uh-oh, something went wrong. Ping Joe & Stopa.',
820
+ };
821
+ if (msg.hint) {
822
+ errorMessage.hint = msg.hint;
823
+ }
824
+ if (msg['trace-id'] || msg['original-event']?.['trace-id']) {
825
+ const traceIds = [msg['trace-id'], msg['original-event']?.['trace-id']];
826
+ const traceId = traceIds.filter(Boolean).join(',');
827
+ errorMessage.traceId = traceId;
828
+ }
829
+ if (prevMutation) {
830
+ this._handleMutationError('error', eventId, errorMessage);
831
+ return;
832
+ }
833
+ if (msg['original-event']?.hasOwnProperty('q') &&
834
+ msg['original-event']?.op === 'add-query') {
835
+ const q = msg['original-event']?.q;
836
+ const hash = weakHash(q);
837
+ this.notifyQueryError(weakHash(q), errorMessage);
838
+ this.notifyQueryOnceError(q, hash, eventId, errorMessage);
839
+ return;
840
+ }
841
+ const isInitError = msg['original-event']?.op === 'init';
842
+ if (isInitError) {
843
+ if (msg.type === 'record-not-found' &&
844
+ msg.hint?.['record-type'] === 'app-user') {
845
+ // User has been logged out
846
+ this.changeCurrentUser(null);
847
+ return;
848
+ }
849
+ // We failed to init
850
+ this._setStatus(STATUS.ERRORED, errorMessage);
851
+ this.notifyAll();
852
+ return;
853
+ }
854
+ switch (msg['original-event']?.op) {
855
+ case 'resync-table': {
856
+ this._syncTable.onResyncError(msg);
857
+ return;
858
+ }
859
+ case 'start-sync': {
860
+ this._syncTable.onStartSyncError(msg);
861
+ return;
862
+ }
863
+ case 'start-stream':
864
+ case 'append-stream':
865
+ case 'subscribe-stream':
866
+ case 'unsubscribe-stream': {
867
+ this._instantStream.onRecieveError(msg);
868
+ return;
869
+ }
870
+ }
871
+ // We've caught some error which has no corresponding listener.
872
+ // Let's console.error to let the user know.
873
+ const errorObj = { ...msg };
874
+ delete errorObj.message;
875
+ delete errorObj.hint;
876
+ console.error(msg.message, errorObj);
877
+ if (msg.hint) {
878
+ console.error('This error comes with some debugging information. Here it is: \n', msg.hint);
879
+ }
880
+ }
881
+ notifyQueryOnceError(q, hash, eventId, e) {
882
+ const r = this.queryOnceDfds[hash]?.find((r) => r.eventId === eventId);
883
+ if (!r)
884
+ return;
885
+ r.dfd.reject(e);
886
+ this._completeQueryOnce(q, hash, r.dfd);
887
+ }
888
+ _setAttrs(attrs) {
889
+ this.attrs = new s.AttrsStoreClass(attrs.reduce((acc, attr) => {
890
+ acc[attr.id] = attr;
891
+ return acc;
892
+ }, {}), this._linkIndex);
893
+ this.notifyAttrsSubs();
894
+ }
895
+ // ---------------------------
896
+ // Queries
897
+ getPreviousResult = (q) => {
898
+ const hash = weakHash(q);
899
+ return this.dataForQuery(hash)?.data;
900
+ };
901
+ _startQuerySub(q, hash) {
902
+ const eventId = uuid();
903
+ this.querySubs.updateInPlace((prev) => {
904
+ if (!prev[hash]) {
905
+ // Legacy hash migration (sync): if a preloaded entry sits under the
906
+ // old hash, move it to the new key so the placeholder line below
907
+ // becomes a no-op and currentValue[hash] keeps its cached result.
908
+ const legacyHash = weakHashLegacy(q);
909
+ if (legacyHash !== hash && prev[legacyHash]) {
910
+ prev[hash] = prev[legacyHash];
911
+ delete prev[legacyHash];
912
+ }
913
+ }
914
+ prev[hash] = prev[hash] || { q, result: null, eventId };
915
+ prev[hash].lastAccessed = Date.now();
916
+ });
917
+ this._tryMigrateLegacyQuerySub(q, hash);
918
+ this._trySendAuthed(eventId, { op: 'add-query', q });
919
+ return eventId;
920
+ }
921
+ // Legacy hash migration (async): covers entries that weren't preloaded
922
+ // into currentValue. Actively loads the legacy key from storage, moves
923
+ // its result onto the new key, and notifies subscribers. Safe to remove
924
+ // a few releases after v1.0.39.
925
+ async _tryMigrateLegacyQuerySub(q, newHash) {
926
+ const legacyHash = weakHashLegacy(q);
927
+ if (legacyHash === newHash)
928
+ return;
929
+ await this.querySubs.waitForKeyToLoad(legacyHash);
930
+ const legacy = this.querySubs.currentValue[legacyHash];
931
+ if (!legacy)
932
+ return;
933
+ let migratedResult = false;
934
+ this.querySubs.updateInPlace((prev) => {
935
+ const existing = prev[newHash];
936
+ if (existing && !existing.result && legacy.result) {
937
+ existing.result = legacy.result;
938
+ migratedResult = true;
939
+ }
940
+ else if (!existing) {
941
+ prev[newHash] = legacy;
942
+ migratedResult = !!legacy.result;
943
+ }
944
+ delete prev[legacyHash];
945
+ });
946
+ if (migratedResult)
947
+ this.notifyOne(newHash);
948
+ }
949
+ subscribeTable(q, cb) {
950
+ return this._syncTable.subscribe(q, cb);
951
+ }
952
+ /**
953
+ * When a user subscribes to a query the following side effects occur:
954
+ *
955
+ * - We update querySubs to include the new query
956
+ * - We update queryCbs to include the new cb
957
+ * - If we already have a result for the query we call cb immediately
958
+ * - We send the server an `add-query` message
959
+ *
960
+ * Returns an unsubscribe function
961
+ */
962
+ subscribeQuery(q, cb, opts) {
963
+ if (!this.config.disableValidation) {
964
+ validateQuery(q, this.config.schema);
965
+ }
966
+ if (opts && 'ruleParams' in opts) {
967
+ q = { $$ruleParams: opts['ruleParams'], ...q };
968
+ }
969
+ const hash = weakHash(q);
970
+ const prevResult = this.getPreviousResult(q);
971
+ if (prevResult) {
972
+ cb(prevResult);
973
+ }
974
+ this.queryCbs[hash] = this.queryCbs[hash] ?? [];
975
+ this.queryCbs[hash].push({ q, cb });
976
+ this._startQuerySub(q, hash);
977
+ return () => {
978
+ this._unsubQuery(q, hash, cb);
979
+ };
980
+ }
981
+ queryOnce(q, opts) {
982
+ if (!this.config.disableValidation) {
983
+ validateQuery(q, this.config.schema);
984
+ }
985
+ if (opts && 'ruleParams' in opts) {
986
+ q = { $$ruleParams: opts['ruleParams'], ...q };
987
+ }
988
+ const dfd = new Deferred();
989
+ if (!this._isOnline) {
990
+ dfd.reject(new Error("We can't run `queryOnce`, because the device is offline."));
991
+ return dfd.promise;
992
+ }
993
+ if (!this.querySubs) {
994
+ dfd.reject(new Error("We can't run `queryOnce` on the backend. Use adminAPI.query instead: https://www.instantdb.com/docs/backend#query"));
995
+ return dfd.promise;
996
+ }
997
+ const hash = weakHash(q);
998
+ const eventId = this._startQuerySub(q, hash);
999
+ this.queryOnceDfds[hash] = this.queryOnceDfds[hash] ?? [];
1000
+ this.queryOnceDfds[hash].push({ q, dfd, eventId });
1001
+ setTimeout(() => dfd.reject(new Error('Query timed out')), QUERY_ONCE_TIMEOUT);
1002
+ return dfd.promise;
1003
+ }
1004
+ _completeQueryOnce(q, hash, dfd) {
1005
+ if (!this.queryOnceDfds[hash])
1006
+ return;
1007
+ this.queryOnceDfds[hash] = this.queryOnceDfds[hash].filter((r) => r.dfd !== dfd);
1008
+ this._cleanupQuery(q, hash);
1009
+ }
1010
+ _unsubQuery(q, hash, cb) {
1011
+ if (!this.queryCbs[hash])
1012
+ return;
1013
+ this.queryCbs[hash] = this.queryCbs[hash].filter((r) => r.cb !== cb);
1014
+ this._cleanupQuery(q, hash);
1015
+ }
1016
+ _hasQueryListeners(hash) {
1017
+ return !!(this.queryCbs[hash]?.length || this.queryOnceDfds[hash]?.length);
1018
+ }
1019
+ _cleanupQuery(q, hash) {
1020
+ const hasListeners = this._hasQueryListeners(hash);
1021
+ if (hasListeners)
1022
+ return;
1023
+ delete this.queryCbs[hash];
1024
+ delete this.queryOnceDfds[hash];
1025
+ delete this._dataForQueryCache[hash];
1026
+ this.querySubs.unloadKey(hash);
1027
+ this._trySendAuthed(uuid(), { op: 'remove-query', q });
1028
+ }
1029
+ // When we `pushTx`, it's possible that we don't yet have `this.attrs`
1030
+ // This means that `tx-steps` in `pendingMutations` will include `add-attr`
1031
+ // commands for attrs that already exist.
1032
+ //
1033
+ // This will also affect `add-triple` and `retract-triple` which
1034
+ // reference attr-ids that do not match the server.
1035
+ //
1036
+ // We fix this by rewriting `tx-steps` in each `pendingMutation`.
1037
+ // We remove `add-attr` commands for attrs that already exist.
1038
+ // We update `add-triple` and `retract-triple` commands to use the
1039
+ // server attr-ids.
1040
+ /**
1041
+ *
1042
+ * @param {s.AttrsStore} attrs
1043
+ * @param {any} muts
1044
+ * @param {number} [processedTxId]
1045
+ */
1046
+ _rewriteMutations(attrs, muts, processedTxId) {
1047
+ if (!attrs)
1048
+ return muts;
1049
+ if (!muts)
1050
+ return new Map();
1051
+ const findExistingAttr = (attr) => {
1052
+ const [_, etype, label] = attr['forward-identity'];
1053
+ const existing = s.getAttrByFwdIdentName(attrs, etype, label);
1054
+ return existing;
1055
+ };
1056
+ const findReverseAttr = (attr) => {
1057
+ const [_, etype, label] = attr['forward-identity'];
1058
+ const revAttr = s.getAttrByReverseIdentName(attrs, etype, label);
1059
+ return revAttr;
1060
+ };
1061
+ const mapping = { attrIdMap: {}, refSwapAttrIds: new Set() };
1062
+ let mappingChanged = false;
1063
+ const rewriteTxSteps = (txSteps, txId) => {
1064
+ const retTxSteps = [];
1065
+ for (const txStep of txSteps) {
1066
+ const [action] = txStep;
1067
+ // Handles add-attr
1068
+ // If existing, we drop it, and track it
1069
+ // to update add/retract triples
1070
+ if (action === 'add-attr') {
1071
+ const [_action, attr] = txStep;
1072
+ const existing = findExistingAttr(attr);
1073
+ if (existing && attr.id !== existing.id) {
1074
+ mapping.attrIdMap[attr.id] = existing.id;
1075
+ mappingChanged = true;
1076
+ continue;
1077
+ }
1078
+ if (attr['value-type'] === 'ref') {
1079
+ const revAttr = findReverseAttr(attr);
1080
+ if (revAttr) {
1081
+ mapping.attrIdMap[attr.id] = revAttr.id;
1082
+ mapping.refSwapAttrIds.add(attr.id);
1083
+ mappingChanged = true;
1084
+ continue;
1085
+ }
1086
+ }
1087
+ }
1088
+ if ((processedTxId &&
1089
+ txId &&
1090
+ processedTxId >= txId &&
1091
+ action === 'add-attr') ||
1092
+ action === 'update-attr' ||
1093
+ action === 'delete-attr') {
1094
+ mappingChanged = true;
1095
+ // Don't add this step because we already have the newer attrs
1096
+ continue;
1097
+ }
1098
+ // Handles add-triple|retract-triple
1099
+ // If in mapping, we update the attr-id
1100
+ const newTxStep = mappingChanged
1101
+ ? instaml.rewriteStep(mapping, txStep)
1102
+ : txStep;
1103
+ retTxSteps.push(newTxStep);
1104
+ }
1105
+ return mappingChanged ? retTxSteps : txSteps;
1106
+ };
1107
+ const rewritten = new Map();
1108
+ for (const [k, mut] of muts.entries()) {
1109
+ rewritten.set(k, {
1110
+ ...mut,
1111
+ 'tx-steps': rewriteTxSteps(mut['tx-steps'], mut['tx-id']),
1112
+ });
1113
+ }
1114
+ if (!mappingChanged) {
1115
+ return muts;
1116
+ }
1117
+ return rewritten;
1118
+ }
1119
+ _rewriteMutationsSorted(attrs, muts) {
1120
+ return sortedMutationEntries(this._rewriteMutations(attrs, muts).entries());
1121
+ }
1122
+ // ---------------------------
1123
+ // Transact
1124
+ /**
1125
+ * @returns {s.AttrsStore}
1126
+ */
1127
+ optimisticAttrs() {
1128
+ const pendingMutationSteps = [...this._pendingMutations().values()] // hack due to Map()
1129
+ .flatMap((x) => x['tx-steps']);
1130
+ const deletedAttrIds = new Set(pendingMutationSteps
1131
+ .filter(([action, _attr]) => action === 'delete-attr')
1132
+ .map(([_action, id]) => id));
1133
+ const pendingAttrs = [];
1134
+ for (const [_action, attr] of pendingMutationSteps) {
1135
+ if (_action === 'add-attr') {
1136
+ pendingAttrs.push(attr);
1137
+ }
1138
+ else if (_action === 'update-attr' &&
1139
+ attr.id &&
1140
+ this.attrs?.getAttr(attr.id)) {
1141
+ const fullAttr = { ...this.attrs.getAttr(attr.id), ...attr };
1142
+ pendingAttrs.push(fullAttr);
1143
+ }
1144
+ }
1145
+ if (!deletedAttrIds.size && !pendingAttrs.length) {
1146
+ return this.attrs || new s.AttrsStoreClass({}, this._linkIndex);
1147
+ }
1148
+ const attrs = { ...(this.attrs?.attrs || {}) };
1149
+ for (const attr of pendingAttrs) {
1150
+ attrs[attr.id] = attr;
1151
+ }
1152
+ for (const id of deletedAttrIds) {
1153
+ delete attrs[id];
1154
+ }
1155
+ return new s.AttrsStoreClass(attrs, this._linkIndex);
1156
+ }
1157
+ /** Runs instaql on a query and a store */
1158
+ dataForQuery(hash, applyOptimistic = true) {
1159
+ const errorMessage = this._errorMessage;
1160
+ if (errorMessage) {
1161
+ return { error: errorMessage };
1162
+ }
1163
+ if (!this.querySubs)
1164
+ return;
1165
+ if (!this.kv.currentValue.pendingMutations)
1166
+ return;
1167
+ const querySubVersion = this.querySubs.version();
1168
+ const querySubs = this.querySubs.currentValue;
1169
+ const pendingMutationsVersion = this.kv.version();
1170
+ const pendingMutations = this._pendingMutations();
1171
+ const { q, result } = querySubs[hash] || {};
1172
+ if (!result)
1173
+ return;
1174
+ const cached = this._dataForQueryCache[hash];
1175
+ if (cached &&
1176
+ querySubVersion === cached.querySubVersion &&
1177
+ pendingMutationsVersion === cached.pendingMutationsVersion) {
1178
+ return cached;
1179
+ }
1180
+ let store = result.store;
1181
+ let attrsStore = result.attrsStore;
1182
+ const { pageInfo, aggregate, processedTxId } = result;
1183
+ const mutations = this._rewriteMutationsSorted(attrsStore, pendingMutations);
1184
+ if (applyOptimistic) {
1185
+ const optimisticResult = this._applyOptimisticUpdates(store, attrsStore, mutations, processedTxId);
1186
+ store = optimisticResult.store;
1187
+ attrsStore = optimisticResult.attrsStore;
1188
+ }
1189
+ const resp = instaql({ store: store, attrsStore: attrsStore, pageInfo, aggregate }, q);
1190
+ return { data: resp, querySubVersion, pendingMutationsVersion };
1191
+ }
1192
+ _applyOptimisticUpdates(store, attrsStore, mutations, processedTxId) {
1193
+ for (const [_, mut] of mutations) {
1194
+ if (!mut['tx-id'] || (processedTxId && mut['tx-id'] > processedTxId)) {
1195
+ const result = s.transact(store, attrsStore, mut['tx-steps']);
1196
+ store = result.store;
1197
+ attrsStore = result.attrsStore;
1198
+ }
1199
+ }
1200
+ return { store, attrsStore };
1201
+ }
1202
+ /** Re-run instaql and call all callbacks with new data */
1203
+ notifyOne = (hash) => {
1204
+ const cbs = this.queryCbs[hash] ?? [];
1205
+ const prevData = this._dataForQueryCache[hash]?.data;
1206
+ const resp = this.dataForQuery(hash);
1207
+ if (!resp?.data)
1208
+ return;
1209
+ this._dataForQueryCache[hash] = resp;
1210
+ if (areObjectsDeepEqual(resp.data, prevData))
1211
+ return;
1212
+ cbs.forEach((r) => r.cb(resp.data));
1213
+ };
1214
+ notifyOneQueryOnce = (hash) => {
1215
+ const dfds = this.queryOnceDfds[hash] ?? [];
1216
+ const data = this.dataForQuery(hash)?.data;
1217
+ dfds.forEach((r) => {
1218
+ this._completeQueryOnce(r.q, hash, r.dfd);
1219
+ r.dfd.resolve(data);
1220
+ });
1221
+ };
1222
+ notifyQueryError = (hash, error) => {
1223
+ // Clear the dedupe cache so the next successful result re-notifies
1224
+ // subscribers even if the data is unchanged from before the error
1225
+ delete this._dataForQueryCache[hash];
1226
+ const cbs = this.queryCbs[hash] || [];
1227
+ cbs.forEach((r) => r.cb({ error }));
1228
+ };
1229
+ /** Re-compute all subscriptions */
1230
+ notifyAll() {
1231
+ Object.keys(this.queryCbs).forEach((hash) => {
1232
+ this.querySubs
1233
+ .waitForKeyToLoad(hash)
1234
+ .then(() => this.notifyOne(hash))
1235
+ .catch(() => this.notifyOne(hash));
1236
+ });
1237
+ }
1238
+ loadedNotifyAll() {
1239
+ this.kv
1240
+ .waitForKeyToLoad('pendingMutations')
1241
+ .then(() => this.notifyAll())
1242
+ .catch(() => this.notifyAll());
1243
+ }
1244
+ /** Applies transactions locally and sends transact message to server */
1245
+ pushTx = (chunks) => {
1246
+ // The server rejects every write while read-only or disabled, so reject
1247
+ // locally instead of applying an optimistic update
1248
+ if (this._appStatusState.isReadOnly) {
1249
+ return Promise.reject(this._appStatusState.status === 'disabled'
1250
+ ? new InstantError('This app is currently disabled.', {
1251
+ status: 'disabled',
1252
+ })
1253
+ : new InstantError('This app is in read-only mode.', {
1254
+ status: 'read-only',
1255
+ }));
1256
+ }
1257
+ // Throws if transactions are invalid
1258
+ if (!this.config.disableValidation) {
1259
+ validateTransactions(chunks, this.config.schema);
1260
+ }
1261
+ try {
1262
+ const txSteps = instaml.transform({
1263
+ attrsStore: this.optimisticAttrs(),
1264
+ schema: this.config.schema,
1265
+ stores: Object.values(this.querySubs.currentValue).map((sub) => sub?.result?.store),
1266
+ useDateObjects: this.config.useDateObjects,
1267
+ }, chunks);
1268
+ return this.pushOps(txSteps);
1269
+ }
1270
+ catch (e) {
1271
+ return this.pushOps([], e);
1272
+ }
1273
+ };
1274
+ /**
1275
+ * @param {*} txSteps
1276
+ * @param {*} [error]
1277
+ * @returns
1278
+ */
1279
+ pushOps = (txSteps, error) => {
1280
+ const eventId = uuid();
1281
+ const mutations = [...this._pendingMutations().values()];
1282
+ const order = Math.max(0, ...mutations.map((mut) => mut.order || 0)) + 1;
1283
+ const mutation = {
1284
+ op: 'transact',
1285
+ 'tx-steps': txSteps,
1286
+ created: Date.now(),
1287
+ error,
1288
+ order,
1289
+ };
1290
+ this._updatePendingMutations((prev) => {
1291
+ prev.set(eventId, mutation);
1292
+ });
1293
+ const dfd = new Deferred();
1294
+ this.mutationDeferredStore.set(eventId, dfd);
1295
+ this._sendMutation(eventId, mutation);
1296
+ this.notifyAll();
1297
+ return dfd.promise;
1298
+ };
1299
+ shutdown() {
1300
+ this._log.info('[shutdown]', this.config.appId);
1301
+ this._isShutdown = true;
1302
+ if (this._reconnectTimeoutId !== null)
1303
+ clearTimeout(this._reconnectTimeoutId);
1304
+ this._reconnectTimeoutId = null;
1305
+ this._networkUnsubscribe?.();
1306
+ this._networkUnsubscribe = undefined;
1307
+ this._broadcastChannel?.close();
1308
+ this._broadcastChannel = undefined;
1309
+ if (typeof removeEventListener !== 'undefined') {
1310
+ removeEventListener('beforeunload', this._beforeUnload);
1311
+ }
1312
+ this._transport?.close();
1313
+ }
1314
+ /**
1315
+ * Sends mutation to server and schedules a timeout to cancel it if
1316
+ * we don't hear back in time.
1317
+ * Note: If we're offline we don't schedule a timeout, we'll schedule it
1318
+ * later once we're back online and send the mutation again
1319
+ *
1320
+ */
1321
+ _sendMutation(eventId, mutation) {
1322
+ if (mutation.error) {
1323
+ this._handleMutationError('error', eventId, {
1324
+ message: mutation.error.message,
1325
+ });
1326
+ return;
1327
+ }
1328
+ if (this.status !== STATUS.AUTHENTICATED) {
1329
+ this._finishTransaction('enqueued', eventId);
1330
+ return;
1331
+ }
1332
+ const timeoutMs = Math.max(6000, Math.min(this._inFlightMutationEventIds.size + 1,
1333
+ // Defensive code in case we don't clean up in flight mutation event ids
1334
+ this._pendingMutations().size + 1) * 6000);
1335
+ if (!this._isOnline) {
1336
+ this._finishTransaction('enqueued', eventId);
1337
+ }
1338
+ else {
1339
+ this._trySend(eventId, mutation);
1340
+ setTimeout(() => {
1341
+ if (!this._isOnline) {
1342
+ return;
1343
+ }
1344
+ // If we are here, this means that we have sent this mutation, we are online
1345
+ // but we have not received a response. If it's this long, something must be wrong,
1346
+ // so we error with a timeout.
1347
+ this._handleMutationError('timeout', eventId, {
1348
+ message: 'transaction timed out',
1349
+ });
1350
+ }, timeoutMs);
1351
+ }
1352
+ }
1353
+ // ---------------------------
1354
+ // Websocket
1355
+ /** Send messages we accumulated while we were connecting */
1356
+ _flushPendingMessages() {
1357
+ const subs = Object.keys(this.queryCbs).map((hash) => {
1358
+ return this.querySubs.currentValue[hash];
1359
+ });
1360
+ // Note: we should not have any nulls in subs, but we're
1361
+ // doing this defensively just in case.
1362
+ const safeSubs = subs.filter((x) => x);
1363
+ safeSubs.forEach(({ eventId, q }) => {
1364
+ this._trySendAuthed(eventId, { op: 'add-query', q });
1365
+ });
1366
+ Object.values(this.queryOnceDfds)
1367
+ .flat()
1368
+ .forEach(({ eventId, q }) => {
1369
+ this._trySendAuthed(eventId, { op: 'add-query', q });
1370
+ });
1371
+ const rewrittenMutations = this._rewriteMutations(this.ensureAttrs(), this._pendingMutations());
1372
+ if (rewrittenMutations !== this._pendingMutations()) {
1373
+ // Persist rewritten mutations to avoid stale attr ids in future txs.
1374
+ this.kv.updateInPlace((prev) => {
1375
+ prev.pendingMutations = rewrittenMutations;
1376
+ });
1377
+ }
1378
+ const muts = sortedMutationEntries(rewrittenMutations.entries());
1379
+ muts.forEach(([eventId, mut]) => {
1380
+ if (!mut['tx-id']) {
1381
+ this._sendMutation(eventId, mut);
1382
+ }
1383
+ });
1384
+ this._syncTable.flushPending();
1385
+ }
1386
+ /**
1387
+ * Clean up pendingMutations that all queries have seen
1388
+ */
1389
+ _cleanupPendingMutationsQueries() {
1390
+ let minProcessedTxId = Number.MAX_SAFE_INTEGER;
1391
+ for (const { result } of Object.values(this.querySubs.currentValue)) {
1392
+ if (result?.processedTxId) {
1393
+ minProcessedTxId = Math.min(minProcessedTxId, result?.processedTxId);
1394
+ }
1395
+ }
1396
+ this._updatePendingMutations((prev) => {
1397
+ for (const [eventId, mut] of Array.from(prev.entries())) {
1398
+ if (mut['tx-id'] && mut['tx-id'] <= minProcessedTxId) {
1399
+ prev.delete(eventId);
1400
+ }
1401
+ }
1402
+ });
1403
+ }
1404
+ /**
1405
+ * After mutations is confirmed by server, we give each query 30 sec
1406
+ * to update its results. If that doesn't happen, we assume query is
1407
+ * unaffected by this mutation and it’s safe to delete it from local queue
1408
+ */
1409
+ _cleanupPendingMutationsTimeout() {
1410
+ if (this._pendingMutations().size < this._pendingMutationCleanupThreshold) {
1411
+ return;
1412
+ }
1413
+ const now = Date.now();
1414
+ this._updatePendingMutations((prev) => {
1415
+ for (const [eventId, mut] of Array.from(prev.entries())) {
1416
+ if (mut.confirmed &&
1417
+ mut.confirmed + this._pendingTxCleanupTimeout < now) {
1418
+ prev.delete(eventId);
1419
+ }
1420
+ }
1421
+ });
1422
+ }
1423
+ _trySendAuthed(...args) {
1424
+ if (this.status !== STATUS.AUTHENTICATED) {
1425
+ return;
1426
+ }
1427
+ this._trySend(...args);
1428
+ }
1429
+ _trySend(eventId, msg, opts) {
1430
+ if (!this._transport.isOpen()) {
1431
+ return;
1432
+ }
1433
+ if (!ignoreLogging[msg.op]) {
1434
+ this._log.info('[send]', this._transport.id, msg.op, {
1435
+ 'client-event-id': eventId,
1436
+ ...msg,
1437
+ });
1438
+ }
1439
+ switch (msg.op) {
1440
+ case 'transact': {
1441
+ this._inFlightMutationEventIds.add(eventId);
1442
+ break;
1443
+ }
1444
+ case 'init': {
1445
+ // New connection, so we can't have any mutations in flight
1446
+ this._inFlightMutationEventIds.clear();
1447
+ }
1448
+ }
1449
+ this._transport.send({ 'client-event-id': eventId, ...msg });
1450
+ }
1451
+ _transportOnOpen = (e) => {
1452
+ const targetTransport = e.target;
1453
+ if (this._transport !== targetTransport) {
1454
+ this._log.info('[socket][open]', targetTransport.id, 'skip; this is no longer the current transport');
1455
+ return;
1456
+ }
1457
+ this._log.info('[socket][open]', this._transport.id);
1458
+ this._setStatus(STATUS.OPENED);
1459
+ this.getCurrentUser()
1460
+ .then((resp) => {
1461
+ this._trySend(uuid(), {
1462
+ op: 'init',
1463
+ 'app-id': this.config.appId,
1464
+ 'refresh-token': resp.user?.['refresh_token'],
1465
+ versions: this.versions,
1466
+ // If an admin token is provided for an app, we will
1467
+ // skip all permission checks. This is an advanced feature,
1468
+ // to let users write internal tools
1469
+ // This option is not exposed in `Config`, as it's
1470
+ // not ready for prime time
1471
+ '__admin-token': this.config.__adminToken,
1472
+ });
1473
+ })
1474
+ .catch((e) => {
1475
+ this._log.error('[socket][error]', targetTransport.id, e);
1476
+ });
1477
+ };
1478
+ _transportOnMessage = (e) => {
1479
+ const targetTransport = e.target;
1480
+ const m = e.message;
1481
+ if (this._transport !== targetTransport) {
1482
+ this._log.info('[socket][message]', targetTransport.id, m, 'skip; this is no longer the current transport');
1483
+ return;
1484
+ }
1485
+ if (!this._wsOk && targetTransport.type === 'ws') {
1486
+ this._wsOk = true;
1487
+ }
1488
+ // Try to reconnect via websocket the next time we connect
1489
+ this._transportType = 'ws';
1490
+ if (Array.isArray(e.message)) {
1491
+ for (const msg of e.message) {
1492
+ this._handleReceive(targetTransport.id, msg);
1493
+ }
1494
+ }
1495
+ else {
1496
+ this._handleReceive(targetTransport.id, e.message);
1497
+ }
1498
+ };
1499
+ _transportOnError = (e) => {
1500
+ const targetTransport = e.target;
1501
+ if (this._transport !== targetTransport) {
1502
+ this._log.info('[socket][error]', targetTransport.id, 'skip; this is no longer the current transport');
1503
+ return;
1504
+ }
1505
+ this._log.error('[socket][error]', targetTransport.id, e);
1506
+ };
1507
+ _scheduleReconnect = () => {
1508
+ if (this._isShutdown)
1509
+ return;
1510
+ // If we couldn't connect with a websocket last time, try sse
1511
+ if (!this._wsOk && this._transportType !== 'sse') {
1512
+ this._transportType = 'sse';
1513
+ this._reconnectTimeoutMs = 0;
1514
+ }
1515
+ this._reconnectTimeoutId = setTimeout(() => {
1516
+ this._reconnectTimeoutId = null;
1517
+ if (this._isShutdown)
1518
+ return;
1519
+ this._reconnectTimeoutMs = Math.min(this._reconnectTimeoutMs + 1000, 10000);
1520
+ if (!this._isOnline) {
1521
+ this._log.info('[socket][close]', this._transport.id, 'we are offline, no need to start socket');
1522
+ return;
1523
+ }
1524
+ this._startSocket();
1525
+ }, this._reconnectTimeoutMs);
1526
+ };
1527
+ _transportOnClose = (e) => {
1528
+ const targetTransport = e.target;
1529
+ if (this._transport !== targetTransport) {
1530
+ this._log.info('[socket][close]', targetTransport.id, 'skip; this is no longer the current transport');
1531
+ return;
1532
+ }
1533
+ this._setStatus(STATUS.CLOSED);
1534
+ for (const room of Object.values(this._rooms)) {
1535
+ room.isConnected = false;
1536
+ }
1537
+ if (this._isShutdown) {
1538
+ this._log.info('[socket][close]', targetTransport.id, 'Reactor has been shut down and will not reconnect');
1539
+ return;
1540
+ }
1541
+ this._log.info('[socket][close]', targetTransport.id, 'schedule reconnect, ms =', this._reconnectTimeoutMs);
1542
+ this._scheduleReconnect();
1543
+ };
1544
+ _startSocket() {
1545
+ // Reset whether we support websockets each time we connect
1546
+ // new networks may not support websockets
1547
+ this._wsOk = null;
1548
+ if (this._isShutdown) {
1549
+ this._log.info('[socket][start]', this.config.appId, 'Reactor has been shut down and will not start a new socket');
1550
+ return;
1551
+ }
1552
+ if (this._transport && this._transport.isConnecting()) {
1553
+ // Our current websocket is in a 'connecting' state.
1554
+ // There's no need to start another one, as the socket is
1555
+ // effectively fresh.
1556
+ this._log.info('[socket][start]', this._transport.id, 'maintained as current transport, we were still in a connecting state');
1557
+ return;
1558
+ }
1559
+ const prevTransport = this._transport;
1560
+ this._transport = createTransport({
1561
+ transportType: this._transportType,
1562
+ appId: this.config.appId,
1563
+ apiURI: this.config.apiURI,
1564
+ wsURI: this.config.websocketURI,
1565
+ EventSourceImpl: this._EventSource,
1566
+ });
1567
+ this._transport.onopen = this._transportOnOpen;
1568
+ this._transport.onmessage = this._transportOnMessage;
1569
+ this._transport.onclose = this._transportOnClose;
1570
+ this._transport.onerror = this._transportOnError;
1571
+ this._log.info('[socket][start]', this._transport.id);
1572
+ if (prevTransport?.isOpen()) {
1573
+ // When the network dies, it doesn't always mean that our
1574
+ // socket connection will fire a close event.
1575
+ //
1576
+ // We _could_ re-use the old socket, if the network drop was a
1577
+ // few seconds. But, to be safe right now we always create a new socket.
1578
+ //
1579
+ // This means that we have to make sure to kill the previous one ourselves.
1580
+ // c.f https://issues.chromium.org/issues/41343684
1581
+ this._log.info('[socket][start]', this._transport.id, 'close previous transport id = ', prevTransport.id);
1582
+ prevTransport.close();
1583
+ }
1584
+ }
1585
+ /**
1586
+ * Given a key, returns a stable local id, unique to this device and app.
1587
+ *
1588
+ * This can be useful if you want to create guest ids for example.
1589
+ *
1590
+ * Note: If the user deletes their local storage, this id will change.
1591
+ *
1592
+ */
1593
+ async getLocalId(name) {
1594
+ const k = `localToken_${name}`;
1595
+ if (this.kv.currentValue[k]) {
1596
+ return this.kv.currentValue[k];
1597
+ }
1598
+ const current = await this.kv.waitForKeyToLoad(k);
1599
+ if (current) {
1600
+ return current;
1601
+ }
1602
+ const newId = uuid();
1603
+ this.kv.updateInPlace((prev) => {
1604
+ if (prev[k])
1605
+ return;
1606
+ prev[k] = newId;
1607
+ });
1608
+ return await this.kv.waitForKeyToLoad(k);
1609
+ }
1610
+ // ----
1611
+ // Auth
1612
+ _replaceUrlAfterOAuth() {
1613
+ if (typeof URL === 'undefined') {
1614
+ return;
1615
+ }
1616
+ const url = new URL(window.location.href);
1617
+ if (url.searchParams.get(OAUTH_REDIRECT_PARAM)) {
1618
+ const startUrl = url.toString();
1619
+ url.searchParams.delete(OAUTH_REDIRECT_PARAM);
1620
+ url.searchParams.delete(OAUTH_EXTRA_FIELDS_ID_PARAM);
1621
+ url.searchParams.delete('code');
1622
+ url.searchParams.delete('error');
1623
+ const newPath = url.pathname +
1624
+ (url.searchParams.size ? '?' + url.searchParams : '') +
1625
+ url.hash;
1626
+ // Note: In next.js, this will revert to the old state if user navigates
1627
+ // back. We would need to allow framework specific routing to work
1628
+ // around that problem.
1629
+ history.replaceState(history.state, '', newPath);
1630
+ // navigation is part of the HTML spec, but not supported by Safari
1631
+ // or Firefox yet:
1632
+ // https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility
1633
+ if (
1634
+ // @ts-ignore (waiting for ts support)
1635
+ typeof navigation === 'object' &&
1636
+ // @ts-ignore (waiting for ts support)
1637
+ typeof navigation.addEventListener === 'function' &&
1638
+ // @ts-ignore (waiting for ts support)
1639
+ typeof navigation.removeEventListener === 'function') {
1640
+ let ran = false;
1641
+ // The next.js app router will reset the URL when the router loads.
1642
+ // This puts it back after the router loads.
1643
+ const listener = (e) => {
1644
+ if (!ran) {
1645
+ ran = true;
1646
+ // @ts-ignore (waiting for ts support)
1647
+ navigation.removeEventListener('navigate', listener);
1648
+ if (!e.userInitiated &&
1649
+ e.navigationType === 'replace' &&
1650
+ e.destination?.url === startUrl) {
1651
+ history.replaceState(history.state, '', newPath);
1652
+ }
1653
+ }
1654
+ };
1655
+ // @ts-ignore (waiting for ts support)
1656
+ navigation.addEventListener('navigate', listener);
1657
+ }
1658
+ }
1659
+ }
1660
+ /**
1661
+ *
1662
+ * @returns Promise<null | {error: {message: string}}>
1663
+ */
1664
+ async _oauthLoginInit() {
1665
+ if (typeof window === 'undefined' ||
1666
+ typeof window.location === 'undefined' ||
1667
+ typeof URLSearchParams === 'undefined') {
1668
+ return null;
1669
+ }
1670
+ const params = new URLSearchParams(window.location.search);
1671
+ if (!params.get(OAUTH_REDIRECT_PARAM)) {
1672
+ return null;
1673
+ }
1674
+ const error = params.get('error');
1675
+ if (error) {
1676
+ this._replaceUrlAfterOAuth();
1677
+ return { error: { message: error } };
1678
+ }
1679
+ const code = params.get('code');
1680
+ if (!code) {
1681
+ return null;
1682
+ }
1683
+ const extraFieldsId = params.get(OAUTH_EXTRA_FIELDS_ID_PARAM);
1684
+ this._replaceUrlAfterOAuth();
1685
+ try {
1686
+ let extraFields;
1687
+ const stored = await this.kv.waitForKeyToLoad(oauthExtraFieldsKey);
1688
+ if (extraFieldsId && stored) {
1689
+ extraFields = stored[extraFieldsId];
1690
+ }
1691
+ // Clean up all stored extraFields after login
1692
+ if (stored) {
1693
+ this.kv.updateInPlace((prev) => {
1694
+ delete prev[oauthExtraFieldsKey];
1695
+ });
1696
+ }
1697
+ const currentUser = await this._getCurrentUser();
1698
+ const isGuest = currentUser?.type === 'guest';
1699
+ const { user } = await authAPI.exchangeCodeForToken({
1700
+ apiURI: this.config.apiURI,
1701
+ appId: this.config.appId,
1702
+ code,
1703
+ refreshToken: isGuest ? currentUser.refresh_token : undefined,
1704
+ extraFields,
1705
+ });
1706
+ this.setCurrentUser(user);
1707
+ return null;
1708
+ }
1709
+ catch (e) {
1710
+ if (e?.body?.type === 'record-not-found' &&
1711
+ e?.body?.hint?.['record-type'] === 'app-oauth-code' &&
1712
+ (await this._hasCurrentUser())) {
1713
+ // We probably just weren't able to clean up the URL, so
1714
+ // let's just ignore this error
1715
+ return null;
1716
+ }
1717
+ const message = e?.body?.message || 'Error logging in.';
1718
+ return { error: { message } };
1719
+ }
1720
+ }
1721
+ async _waitForOAuthCallbackResponse() {
1722
+ return await this._oauthCallbackResponse;
1723
+ }
1724
+ __subscribeMutationErrors(cb) {
1725
+ this.mutationErrorCbs.push(cb);
1726
+ return () => {
1727
+ this.mutationErrorCbs = this.mutationErrorCbs.filter((x) => x !== cb);
1728
+ };
1729
+ }
1730
+ subscribeAuth(cb) {
1731
+ this.authCbs.push(cb);
1732
+ const currUserCached = this._currentUserCached;
1733
+ if (!currUserCached.isLoading) {
1734
+ cb(this._currentUserCached);
1735
+ }
1736
+ let unsubbed = false;
1737
+ this.getCurrentUser().then((resp) => {
1738
+ if (unsubbed)
1739
+ return;
1740
+ if (areObjectsDeepEqual(resp, currUserCached))
1741
+ return;
1742
+ cb(resp);
1743
+ });
1744
+ return () => {
1745
+ unsubbed = true;
1746
+ this.authCbs = this.authCbs.filter((x) => x !== cb);
1747
+ };
1748
+ }
1749
+ async getAuth() {
1750
+ const { user, error } = await this.getCurrentUser();
1751
+ if (error) {
1752
+ throw new InstantError('Could not get current user: ' + error.message);
1753
+ }
1754
+ return user ?? null;
1755
+ }
1756
+ subscribeConnectionStatus(cb) {
1757
+ this.connectionStatusCbs.push(cb);
1758
+ return () => {
1759
+ this.connectionStatusCbs = this.connectionStatusCbs.filter((x) => x !== cb);
1760
+ };
1761
+ }
1762
+ /**
1763
+ * @param {'active' | 'read-only' | 'disabled' | undefined} status
1764
+ */
1765
+ _setAppStatus(status) {
1766
+ if (!status || status === this._appStatusState.status)
1767
+ return;
1768
+ this._appStatusState = {
1769
+ isLoading: false,
1770
+ isReadOnly: status !== 'active',
1771
+ status,
1772
+ };
1773
+ this.notifyAppStatusSubs(this._appStatusState);
1774
+ }
1775
+ subscribeAppStatus(cb) {
1776
+ this.appStatusCbs.push(cb);
1777
+ cb(this._appStatusState);
1778
+ return () => {
1779
+ this.appStatusCbs = this.appStatusCbs.filter((x) => x !== cb);
1780
+ };
1781
+ }
1782
+ subscribeAttrs(cb) {
1783
+ this.attrsCbs.push(cb);
1784
+ if (this.attrs) {
1785
+ cb(this.attrs.attrs);
1786
+ }
1787
+ return () => {
1788
+ this.attrsCbs = this.attrsCbs.filter((x) => x !== cb);
1789
+ };
1790
+ }
1791
+ notifyAuthSubs(user) {
1792
+ this.authCbs.forEach((cb) => cb(user));
1793
+ }
1794
+ notifyMutationErrorSubs(error) {
1795
+ this.mutationErrorCbs.forEach((cb) => cb(error));
1796
+ }
1797
+ notifyAttrsSubs() {
1798
+ if (!this.attrs)
1799
+ return;
1800
+ const oas = this.optimisticAttrs();
1801
+ this.attrsCbs.forEach((cb) => cb(oas.attrs));
1802
+ }
1803
+ notifyConnectionStatusSubs(status) {
1804
+ this.connectionStatusCbs.forEach((cb) => cb(status));
1805
+ }
1806
+ notifyAppStatusSubs(state) {
1807
+ this.appStatusCbs.forEach((cb) => cb(state));
1808
+ }
1809
+ async setCurrentUser(user) {
1810
+ this.kv.updateInPlace((prev) => {
1811
+ prev[currentUserKey] = user;
1812
+ });
1813
+ await this.kv.waitForKeyToLoad(currentUserKey);
1814
+ }
1815
+ getCurrentUserCached() {
1816
+ return this._currentUserCached;
1817
+ }
1818
+ /**
1819
+ * @param {{ forceReadFromStorage?: boolean }} [opts]
1820
+ * @returns {Promise<User | undefined>}
1821
+ */
1822
+ async _getCurrentUser(opts) {
1823
+ if (opts?.forceReadFromStorage) {
1824
+ await this.kv.unloadKey(currentUserKey);
1825
+ }
1826
+ const user = await this.kv.waitForKeyToLoad(currentUserKey);
1827
+ return typeof user === 'string' ? JSON.parse(user) : user;
1828
+ }
1829
+ /**
1830
+ * @param {{ forceReadFromStorage?: boolean }} [opts]
1831
+ * @returns {Promise<AuthState>}
1832
+ */
1833
+ async getCurrentUser(opts) {
1834
+ const oauthResp = await this._waitForOAuthCallbackResponse();
1835
+ if (oauthResp?.error) {
1836
+ const errorV = { error: oauthResp.error, user: undefined };
1837
+ this._currentUserCached = { isLoading: false, ...errorV };
1838
+ return this._currentUserCached;
1839
+ }
1840
+ try {
1841
+ const user = await this._getCurrentUser(opts);
1842
+ const userV = { user: user, error: undefined };
1843
+ this._currentUserCached = {
1844
+ isLoading: false,
1845
+ ...userV,
1846
+ };
1847
+ return this._currentUserCached;
1848
+ }
1849
+ catch (e) {
1850
+ return {
1851
+ user: undefined,
1852
+ isLoading: false,
1853
+ error: { message: e?.message || 'Error loading user' },
1854
+ };
1855
+ }
1856
+ }
1857
+ async _hasCurrentUser() {
1858
+ const user = await this.kv.waitForKeyToLoad(currentUserKey);
1859
+ return typeof user === 'string' ? JSON.parse(user) != null : user != null;
1860
+ }
1861
+ async changeCurrentUser(newUser) {
1862
+ const { user: oldUser } = await this.getCurrentUser();
1863
+ if (areObjectsDeepEqual(oldUser, newUser)) {
1864
+ // We were already logged in as the newUser, don't
1865
+ // bother updating
1866
+ return;
1867
+ }
1868
+ // Clear any data in the framework client
1869
+ this._frameworkClient = null;
1870
+ await this.setCurrentUser(newUser);
1871
+ // We need to remove all `result` from querySubs,
1872
+ // as they are no longer valid for the new user
1873
+ await this.updateUser(newUser);
1874
+ try {
1875
+ // Make sure everything is written to storage before we tell the
1876
+ // other tab to refetch
1877
+ await this.kv.flush();
1878
+ this._broadcastChannel?.postMessage({ type: 'auth' });
1879
+ }
1880
+ catch (error) {
1881
+ console.error('Error posting message to broadcast channel', error);
1882
+ }
1883
+ }
1884
+ async setupUserSyncTimer() {
1885
+ if (!this.config.firstPartyPath)
1886
+ return;
1887
+ try {
1888
+ const lastSynced = await this.kv.waitForKeyToLoad(COOKIE_SYNC_LAST_UPDATED_KEY);
1889
+ const lastSyncTime = lastSynced ? new Date(lastSynced).getTime() : 0;
1890
+ const shouldSync = !Number.isFinite(lastSyncTime) ||
1891
+ Date.now() - lastSyncTime >= ONE_DAY_MS;
1892
+ if (!shouldSync)
1893
+ return;
1894
+ const { user } = await this.getCurrentUser();
1895
+ await this.syncUserToEndpoint(user ?? null);
1896
+ }
1897
+ catch (error) {
1898
+ this._log.error('Error syncing user cookie on startup', error);
1899
+ }
1900
+ }
1901
+ async syncUserToEndpoint(user) {
1902
+ if (!this.config.firstPartyPath)
1903
+ return;
1904
+ try {
1905
+ const response = await fetch(this.config.firstPartyPath + '/', {
1906
+ method: 'POST',
1907
+ body: JSON.stringify({
1908
+ type: 'sync-user',
1909
+ appId: this.config.appId,
1910
+ user: user,
1911
+ }),
1912
+ headers: {
1913
+ 'Content-Type': 'application/json',
1914
+ },
1915
+ });
1916
+ if (!response.ok) {
1917
+ throw new Error(`HTTP error! status: ${response.status}`);
1918
+ }
1919
+ }
1920
+ catch (error) {
1921
+ this._log.error('Error syncing user with external endpoint', error);
1922
+ }
1923
+ this.kv.updateInPlace((prev) => {
1924
+ prev[COOKIE_SYNC_LAST_UPDATED_KEY] = new Date().toISOString();
1925
+ });
1926
+ }
1927
+ async updateUser(newUser) {
1928
+ const newV = { error: undefined, user: newUser };
1929
+ this._currentUserCached = { isLoading: false, ...newV };
1930
+ this._dataForQueryCache = {};
1931
+ this.querySubs.updateInPlace((prev) => {
1932
+ Object.keys(prev).forEach((k) => {
1933
+ delete prev[k].result;
1934
+ });
1935
+ });
1936
+ this.querySubs.clearUnloadedKeys();
1937
+ this._updatePendingMutations((prev) => {
1938
+ // Mark all pending mutations with an error, since we won't be able to
1939
+ // deliver the result
1940
+ for (const [eventId, _v] of prev.entries()) {
1941
+ if (this.mutationDeferredStore.get(eventId)) {
1942
+ this._finishTransaction('error', eventId, {
1943
+ message: 'User changed while transaction was in progress.',
1944
+ type: 'user-changed',
1945
+ });
1946
+ }
1947
+ }
1948
+ prev.clear();
1949
+ });
1950
+ this._reconnectTimeoutMs = 0;
1951
+ this._transport.close();
1952
+ this._oauthCallbackResponse = null;
1953
+ this.notifyAuthSubs(newV);
1954
+ try {
1955
+ await this.syncUserToEndpoint(newUser);
1956
+ }
1957
+ catch (error) {
1958
+ this._log.error('Error syncing user with external endpoint', error);
1959
+ }
1960
+ }
1961
+ sendMagicCode({ email }) {
1962
+ return authAPI.sendMagicCode({
1963
+ apiURI: this.config.apiURI,
1964
+ appId: this.config.appId,
1965
+ email: email,
1966
+ });
1967
+ }
1968
+ async signInWithMagicCode(params) {
1969
+ const currentUser = await this.getCurrentUser();
1970
+ const isGuest = currentUser?.user?.type === 'guest';
1971
+ const res = await authAPI.checkMagicCode({
1972
+ apiURI: this.config.apiURI,
1973
+ appId: this.config.appId,
1974
+ email: params.email,
1975
+ code: params.code,
1976
+ refreshToken: isGuest ? currentUser?.user?.refresh_token : undefined,
1977
+ extraFields: params.extraFields,
1978
+ });
1979
+ await this.changeCurrentUser(res.user);
1980
+ return res;
1981
+ }
1982
+ async signInWithCustomToken(authToken) {
1983
+ const res = await authAPI.verifyRefreshToken({
1984
+ apiURI: this.config.apiURI,
1985
+ appId: this.config.appId,
1986
+ refreshToken: authToken,
1987
+ });
1988
+ await this.changeCurrentUser(res.user);
1989
+ return res;
1990
+ }
1991
+ async signInAsGuest(params) {
1992
+ const res = await authAPI.signInAsGuest({
1993
+ apiURI: this.config.apiURI,
1994
+ appId: this.config.appId,
1995
+ extraFields: params?.extraFields,
1996
+ });
1997
+ await this.changeCurrentUser(res.user);
1998
+ return res;
1999
+ }
2000
+ potentiallyInvalidateToken(currentUser, opts) {
2001
+ const refreshToken = currentUser?.user?.refresh_token;
2002
+ if (!refreshToken) {
2003
+ return;
2004
+ }
2005
+ const wantsToSkip = opts.invalidateToken === false;
2006
+ if (wantsToSkip) {
2007
+ this._log.info('[auth-invalidate] skipped invalidateToken');
2008
+ return;
2009
+ }
2010
+ authAPI
2011
+ .signOut({
2012
+ apiURI: this.config.apiURI,
2013
+ appId: this.config.appId,
2014
+ refreshToken,
2015
+ })
2016
+ .then(() => {
2017
+ this._log.info('[auth-invalidate] completed invalidateToken');
2018
+ })
2019
+ .catch((e) => { });
2020
+ }
2021
+ async signOut(opts) {
2022
+ const currentUser = await this.getCurrentUser();
2023
+ this.potentiallyInvalidateToken(currentUser, opts);
2024
+ await this.changeCurrentUser(null);
2025
+ }
2026
+ /**
2027
+ * Creates an OAuth authorization URL.
2028
+ *
2029
+ * @param {Object} params - The parameters to create the authorization URL.
2030
+ * @param {string} params.clientName - The name of the client requesting authorization.
2031
+ * @param {string} params.redirectURL - The URL to redirect users to after authorization.
2032
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2033
+ * @returns {string} The created authorization URL.
2034
+ */
2035
+ createAuthorizationURL({ clientName, redirectURL, extraFields }) {
2036
+ const { apiURI, appId } = this.config;
2037
+ let finalRedirectURL = redirectURL;
2038
+ if (extraFields) {
2039
+ // Store extraFields under a unique ID so multiple
2040
+ // createAuthorizationURL calls don't overwrite each other.
2041
+ // The ID is passed through the redirect URL and used
2042
+ // by _oauthLoginInit to retrieve the right extraFields.
2043
+ // All entries are cleaned up after login.
2044
+ const extraFieldsId = `${Math.random().toString(36).slice(2)}`;
2045
+ this.kv.updateInPlace((prev) => {
2046
+ const stored = prev[oauthExtraFieldsKey] || {};
2047
+ stored[extraFieldsId] = extraFields;
2048
+ prev[oauthExtraFieldsKey] = stored;
2049
+ });
2050
+ finalRedirectURL = `${redirectURL}${redirectURL.includes('?') ? '&' : '?'}${OAUTH_EXTRA_FIELDS_ID_PARAM}=${extraFieldsId}`;
2051
+ }
2052
+ return `${apiURI}/runtime/oauth/start?app_id=${appId}&client_name=${clientName}&redirect_uri=${encodeURIComponent(finalRedirectURL)}`;
2053
+ }
2054
+ /**
2055
+ * @param {Object} params
2056
+ * @param {string} params.code - The code received from the OAuth service.
2057
+ * @param {string} [params.codeVerifier] - The code verifier used to generate the code challenge.
2058
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2059
+ */
2060
+ async exchangeCodeForToken({ code, codeVerifier, extraFields }) {
2061
+ const currentUser = await this.getCurrentUser();
2062
+ const isGuest = currentUser?.user?.type === 'guest';
2063
+ const res = await authAPI.exchangeCodeForToken({
2064
+ apiURI: this.config.apiURI,
2065
+ appId: this.config.appId,
2066
+ code: code,
2067
+ codeVerifier,
2068
+ refreshToken: isGuest ? currentUser?.user?.refresh_token : undefined,
2069
+ extraFields,
2070
+ });
2071
+ await this.changeCurrentUser(res.user);
2072
+ return res;
2073
+ }
2074
+ issuerURI() {
2075
+ const { apiURI, appId } = this.config;
2076
+ return `${apiURI}/runtime/${appId}`;
2077
+ }
2078
+ /**
2079
+ * @param {Object} params
2080
+ * @param {string} params.clientName - The name of the client requesting authorization.
2081
+ * @param {string} params.idToken - The id_token from the external service
2082
+ * @param {string | null | undefined} [params.nonce] - The nonce used when requesting the id_token from the external service
2083
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2084
+ */
2085
+ async signInWithIdToken(params) {
2086
+ const currentUser = await this.getCurrentUser();
2087
+ const refreshToken = currentUser?.user?.refresh_token;
2088
+ const res = await authAPI.signInWithIdToken({
2089
+ apiURI: this.config.apiURI,
2090
+ appId: this.config.appId,
2091
+ idToken: params.idToken,
2092
+ clientName: params.clientName,
2093
+ nonce: params.nonce,
2094
+ refreshToken,
2095
+ extraFields: params.extraFields,
2096
+ });
2097
+ await this.changeCurrentUser(res.user);
2098
+ return res;
2099
+ }
2100
+ // --------
2101
+ // Rooms
2102
+ /**
2103
+ * @param {string} roomType
2104
+ * @param {string} roomId
2105
+ * @param {any | null | undefined} [initialPresence] -- initial presence data to send when joining the room
2106
+ * @returns () => void
2107
+ */
2108
+ joinRoom(roomType, roomId, initialPresence) {
2109
+ let needsToSendJoin = false;
2110
+ if (!this._rooms[roomId]) {
2111
+ needsToSendJoin = true;
2112
+ this._rooms[roomId] = {
2113
+ roomType,
2114
+ isConnected: false,
2115
+ error: undefined,
2116
+ };
2117
+ }
2118
+ this._presence[roomId] = this._presence[roomId] || {};
2119
+ const previousResult = this._presence[roomId].result;
2120
+ if (initialPresence && !previousResult) {
2121
+ this._presence[roomId].result = this._presence[roomId].result || {};
2122
+ this._presence[roomId].result.user = initialPresence;
2123
+ this._notifyPresenceSubs(roomId);
2124
+ }
2125
+ if (needsToSendJoin) {
2126
+ this._tryJoinRoom(roomType, roomId, initialPresence);
2127
+ }
2128
+ return () => {
2129
+ this._cleanupRoom(roomId);
2130
+ };
2131
+ }
2132
+ _cleanupRoom(roomId) {
2133
+ if (!this._presence[roomId]?.handlers?.length &&
2134
+ !Object.keys(this._broadcastSubs[roomId] ?? {}).length) {
2135
+ const isConnected = this._rooms[roomId]?.isConnected;
2136
+ delete this._rooms[roomId];
2137
+ delete this._presence[roomId];
2138
+ delete this._broadcastSubs[roomId];
2139
+ if (isConnected) {
2140
+ this._tryLeaveRoom(roomId);
2141
+ }
2142
+ else {
2143
+ this._roomsPendingLeave[roomId] = true;
2144
+ }
2145
+ }
2146
+ }
2147
+ // --------
2148
+ // Presence
2149
+ // TODO: look into typing again
2150
+ getPresence(roomType, roomId, opts = {}) {
2151
+ const room = this._rooms[roomId];
2152
+ const presence = this._presence[roomId];
2153
+ if (!room || !presence || !presence.result)
2154
+ return null;
2155
+ return {
2156
+ ...buildPresenceSlice(presence.result, opts, this._sessionId),
2157
+ isLoading: !room.isConnected,
2158
+ error: room.error,
2159
+ };
2160
+ }
2161
+ // TODO: look into typing again
2162
+ publishPresence(roomType, roomId, partialData) {
2163
+ const room = this._rooms[roomId];
2164
+ const presence = this._presence[roomId];
2165
+ if (!room || !presence) {
2166
+ return;
2167
+ }
2168
+ presence.result = presence.result || {};
2169
+ const data = {
2170
+ ...presence.result.user,
2171
+ ...partialData,
2172
+ };
2173
+ presence.result.user = data;
2174
+ if (!room.isConnected) {
2175
+ return;
2176
+ }
2177
+ this._trySetPresence(roomId, data);
2178
+ this._notifyPresenceSubs(roomId);
2179
+ }
2180
+ _trySetPresence(roomId, data) {
2181
+ this._trySendAuthed(uuid(), {
2182
+ op: 'set-presence',
2183
+ 'room-id': roomId,
2184
+ data,
2185
+ });
2186
+ }
2187
+ _tryJoinRoom(roomType, roomId, data) {
2188
+ this._trySendAuthed(uuid(), {
2189
+ op: 'join-room',
2190
+ 'room-type': roomType,
2191
+ 'room-id': roomId,
2192
+ data,
2193
+ });
2194
+ delete this._roomsPendingLeave[roomId];
2195
+ }
2196
+ _tryLeaveRoom(roomId) {
2197
+ this._trySendAuthed(uuid(), { op: 'leave-room', 'room-id': roomId });
2198
+ }
2199
+ _trySetRoomConnected(roomId, isConnected) {
2200
+ const room = this._rooms[roomId];
2201
+ if (room) {
2202
+ room.isConnected = isConnected;
2203
+ }
2204
+ }
2205
+ // TODO: look into typing again
2206
+ subscribePresence(roomType, roomId, opts, cb) {
2207
+ const leaveRoom = this.joinRoom(roomType, roomId,
2208
+ // Oct 28, 2025
2209
+ // Note: initialData is deprecated.
2210
+ // Keeping here for backwards compatibility
2211
+ opts.initialPresence || opts.initialData);
2212
+ const handler = { ...opts, roomId, cb, prev: null };
2213
+ this._presence[roomId] = this._presence[roomId] || {};
2214
+ this._presence[roomId].handlers = this._presence[roomId].handlers || [];
2215
+ this._presence[roomId].handlers.push(handler);
2216
+ this._notifyPresenceSub(roomId, handler);
2217
+ return () => {
2218
+ this._presence[roomId].handlers =
2219
+ this._presence[roomId]?.handlers?.filter((x) => x !== handler) ?? [];
2220
+ leaveRoom();
2221
+ };
2222
+ }
2223
+ _notifyPresenceSubs(roomId) {
2224
+ this._presence[roomId]?.handlers?.forEach((handler) => {
2225
+ this._notifyPresenceSub(roomId, handler);
2226
+ });
2227
+ }
2228
+ _notifyPresenceSub(roomId, handler) {
2229
+ const slice = this.getPresence('', roomId, handler);
2230
+ if (!slice) {
2231
+ return;
2232
+ }
2233
+ if (handler.prev && !hasPresenceResponseChanged(slice, handler.prev)) {
2234
+ return;
2235
+ }
2236
+ handler.prev = slice;
2237
+ handler.cb(slice);
2238
+ }
2239
+ _patchPresencePeers(roomId, edits) {
2240
+ const peers = this._presence[roomId]?.result?.peers || {};
2241
+ let sessions = Object.fromEntries(Object.entries(peers).map(([k, v]) => [k, { data: v }]));
2242
+ const myPresence = this._presence[roomId]?.result;
2243
+ const newSessions = create(sessions, (draft) => {
2244
+ for (let [path, op, value] of edits) {
2245
+ switch (op) {
2246
+ case '+':
2247
+ insertInMutative(draft, path, value);
2248
+ break;
2249
+ case 'r':
2250
+ assocInMutative(draft, path, value);
2251
+ break;
2252
+ case '-':
2253
+ dissocInMutative(draft, path);
2254
+ break;
2255
+ }
2256
+ }
2257
+ // Ignore our own edits
2258
+ delete draft[this._sessionId];
2259
+ });
2260
+ this._setPresencePeers(roomId, newSessions);
2261
+ }
2262
+ _setPresencePeers(roomId, data) {
2263
+ const sessions = { ...data };
2264
+ // no need to keep track of `user`
2265
+ delete sessions[this._sessionId];
2266
+ const peers = Object.fromEntries(Object.entries(sessions).map(([k, v]) => [k, v.data]));
2267
+ this._presence = create(this._presence, (draft) => {
2268
+ assocInMutative(draft, [roomId, 'result', 'peers'], peers);
2269
+ });
2270
+ }
2271
+ // --------
2272
+ // Broadcast
2273
+ publishTopic({ roomType, roomId, topic, data }) {
2274
+ const room = this._rooms[roomId];
2275
+ if (!room) {
2276
+ return;
2277
+ }
2278
+ if (!room.isConnected) {
2279
+ this._broadcastQueue[roomId] = this._broadcastQueue[roomId] ?? [];
2280
+ this._broadcastQueue[roomId].push({ topic, roomType, data });
2281
+ return;
2282
+ }
2283
+ this._tryBroadcast(roomId, roomType, topic, data);
2284
+ }
2285
+ _tryBroadcast(roomId, roomType, topic, data) {
2286
+ this._trySendAuthed(uuid(), {
2287
+ op: 'client-broadcast',
2288
+ 'room-id': roomId,
2289
+ roomType,
2290
+ topic,
2291
+ data,
2292
+ });
2293
+ }
2294
+ subscribeTopic(roomType, roomId, topic, cb) {
2295
+ const leaveRoom = this.joinRoom(roomType, roomId);
2296
+ this._broadcastSubs[roomId] = this._broadcastSubs[roomId] || {};
2297
+ this._broadcastSubs[roomId][topic] =
2298
+ this._broadcastSubs[roomId][topic] || [];
2299
+ this._broadcastSubs[roomId][topic].push(cb);
2300
+ this._presence[roomId] = this._presence[roomId] || {};
2301
+ return () => {
2302
+ this._broadcastSubs[roomId][topic] = this._broadcastSubs[roomId][topic].filter((x) => x !== cb);
2303
+ if (!this._broadcastSubs[roomId][topic].length) {
2304
+ delete this._broadcastSubs[roomId][topic];
2305
+ }
2306
+ leaveRoom();
2307
+ };
2308
+ }
2309
+ _notifyBroadcastSubs(room, topic, msg) {
2310
+ this._broadcastSubs?.[room]?.[topic]?.forEach((cb) => {
2311
+ const data = msg.data?.data;
2312
+ const peer = msg.data['peer-id'] === this._sessionId
2313
+ ? this._presence[room]?.result?.user
2314
+ : this._presence[room]?.result?.peers?.[msg.data['peer-id']];
2315
+ return cb(data, peer);
2316
+ });
2317
+ }
2318
+ // --------
2319
+ // Storage
2320
+ async uploadFile(path, file, opts) {
2321
+ const currentUser = await this.getCurrentUser();
2322
+ const refreshToken = currentUser?.user?.refresh_token;
2323
+ return StorageApi.uploadFile({
2324
+ ...opts,
2325
+ apiURI: this.config.apiURI,
2326
+ appId: this.config.appId,
2327
+ path: path,
2328
+ file,
2329
+ refreshToken: refreshToken,
2330
+ });
2331
+ }
2332
+ async deleteFile(path) {
2333
+ const currentUser = await this.getCurrentUser();
2334
+ const refreshToken = currentUser?.user?.refresh_token;
2335
+ const result = await StorageApi.deleteFile({
2336
+ apiURI: this.config.apiURI,
2337
+ appId: this.config.appId,
2338
+ path,
2339
+ refreshToken: refreshToken,
2340
+ });
2341
+ return result;
2342
+ }
2343
+ // Deprecated Storage API (Jan 2025)
2344
+ // ---------------------------------
2345
+ async upload(path, file) {
2346
+ const currentUser = await this.getCurrentUser();
2347
+ const refreshToken = currentUser?.user?.refresh_token;
2348
+ const fileName = path || file.name;
2349
+ const url = await StorageApi.getSignedUploadUrl({
2350
+ apiURI: this.config.apiURI,
2351
+ appId: this.config.appId,
2352
+ fileName: fileName,
2353
+ refreshToken: refreshToken,
2354
+ });
2355
+ const isSuccess = await StorageApi.upload(url, file);
2356
+ return isSuccess;
2357
+ }
2358
+ async getDownloadUrl(path) {
2359
+ const currentUser = await this.getCurrentUser();
2360
+ const refreshToken = currentUser?.user?.refresh_token;
2361
+ const url = await StorageApi.getDownloadUrl({
2362
+ apiURI: this.config.apiURI,
2363
+ appId: this.config.appId,
2364
+ path: path,
2365
+ refreshToken: refreshToken,
2366
+ });
2367
+ return url;
2368
+ }
2369
+ }