on-zero 0.16.20 → 0.16.21

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 (329) hide show
  1. package/dist/cjs/cli.cjs +6 -0
  2. package/dist/cjs/cli.native.js +6 -0
  3. package/dist/cjs/cli.native.js.map +1 -1
  4. package/dist/cjs/combineZeroClients.native.js.map +1 -1
  5. package/dist/cjs/config.native.js.map +1 -1
  6. package/dist/cjs/createPermissions.native.js.map +1 -1
  7. package/dist/cjs/createSchemaFromDrizzle.native.js.map +1 -1
  8. package/dist/cjs/createUseQuery.cjs +14 -4
  9. package/dist/cjs/createUseQuery.native.js +18 -5
  10. package/dist/cjs/createUseQuery.native.js.map +1 -1
  11. package/dist/cjs/createUseQueryDirect.cjs +13 -2
  12. package/dist/cjs/createUseQueryDirect.native.js +14 -2
  13. package/dist/cjs/createUseQueryDirect.native.js.map +1 -1
  14. package/dist/cjs/createZeroClient.authData.test.native.js.map +1 -1
  15. package/dist/cjs/createZeroClient.cjs +21 -3
  16. package/dist/cjs/createZeroClient.connection.test.native.js.map +1 -1
  17. package/dist/cjs/createZeroClient.exportProbe.test.native.js.map +1 -1
  18. package/dist/cjs/createZeroClient.headless.test.native.js.map +1 -1
  19. package/dist/cjs/createZeroClient.httpAuth.test.native.js.map +1 -1
  20. package/dist/cjs/createZeroClient.native.js +28 -3
  21. package/dist/cjs/createZeroClient.native.js.map +1 -1
  22. package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
  23. package/dist/cjs/generate-helpers.native.js.map +1 -1
  24. package/dist/cjs/generate-helpers.test.native.js.map +1 -1
  25. package/dist/cjs/generate-layout.native.js.map +1 -1
  26. package/dist/cjs/generate-lite.native.js.map +1 -1
  27. package/dist/cjs/generate-lite.test.native.js.map +1 -1
  28. package/dist/cjs/generate.cjs +57 -30
  29. package/dist/cjs/generate.native.js +92 -31
  30. package/dist/cjs/generate.native.js.map +1 -1
  31. package/dist/cjs/generate.test.cjs +147 -0
  32. package/dist/cjs/generate.test.native.js +147 -0
  33. package/dist/cjs/generate.test.native.js.map +1 -1
  34. package/dist/cjs/helpers/NullToOptional.native.js.map +1 -1
  35. package/dist/cjs/helpers/asyncContext.hostStorage.test.native.js.map +1 -1
  36. package/dist/cjs/helpers/batchQuery.native.js.map +1 -1
  37. package/dist/cjs/helpers/clearZeroClientData.native.js.map +1 -1
  38. package/dist/cjs/helpers/createMutators.native.js.map +1 -1
  39. package/dist/cjs/helpers/createMutators.test.native.js.map +1 -1
  40. package/dist/cjs/helpers/ellipsis.native.js.map +1 -1
  41. package/dist/cjs/helpers/emitter.native.js.map +1 -1
  42. package/dist/cjs/helpers/ensure.native.js.map +1 -1
  43. package/dist/cjs/helpers/ensureLoggedIn.native.js.map +1 -1
  44. package/dist/cjs/helpers/errors.native.js.map +1 -1
  45. package/dist/cjs/helpers/getAuth.native.js.map +1 -1
  46. package/dist/cjs/helpers/getCurrentComponentStack.native.js.map +1 -1
  47. package/dist/cjs/helpers/globalValue.native.js.map +1 -1
  48. package/dist/cjs/helpers/hostStorageScope.native.js.map +1 -1
  49. package/dist/cjs/helpers/mapObject.native.js.map +1 -1
  50. package/dist/cjs/helpers/mutationLifecycle.native.js.map +1 -1
  51. package/dist/cjs/helpers/mutationLifecycle.test.native.js.map +1 -1
  52. package/dist/cjs/helpers/mutatorContext.native.js.map +1 -1
  53. package/dist/cjs/helpers/mutatorContext.test.native.js.map +1 -1
  54. package/dist/cjs/helpers/prettyFormatZeroQuery.native.js.map +1 -1
  55. package/dist/cjs/helpers/queryContext.native.js.map +1 -1
  56. package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
  57. package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
  58. package/dist/cjs/helpers/showZeroClientError.native.js.map +1 -1
  59. package/dist/cjs/helpers/sleep.native.js.map +1 -1
  60. package/dist/cjs/helpers/time.native.js.map +1 -1
  61. package/dist/cjs/helpers/tuple.native.js.map +1 -1
  62. package/dist/cjs/helpers/useMutation.native.js.map +1 -1
  63. package/dist/cjs/helpers/useMutation.test.native.js.map +1 -1
  64. package/dist/cjs/helpers/useZeroDebug.native.js.map +1 -1
  65. package/dist/cjs/index.native.js.map +1 -1
  66. package/dist/cjs/instanceRegistry.native.js.map +1 -1
  67. package/dist/cjs/modelRegistry.native.js.map +1 -1
  68. package/dist/cjs/multi.cjs +10 -5
  69. package/dist/cjs/multi.native.js +10 -5
  70. package/dist/cjs/multi.native.js.map +1 -1
  71. package/dist/cjs/multiInstance.test.native.js.map +1 -1
  72. package/dist/cjs/multiInstanceMutations.test.native.js.map +1 -1
  73. package/dist/cjs/multiInstanceNested.test.native.js.map +1 -1
  74. package/dist/cjs/multiPartition.test.cjs +46 -2
  75. package/dist/cjs/multiPartition.test.native.js +50 -2
  76. package/dist/cjs/multiPartition.test.native.js.map +1 -1
  77. package/dist/cjs/mutations.native.js.map +1 -1
  78. package/dist/cjs/mutations.test.native.js.map +1 -1
  79. package/dist/cjs/native-typescript.cjs +50 -11
  80. package/dist/cjs/native-typescript.native.js +158 -30
  81. package/dist/cjs/native-typescript.native.js.map +1 -1
  82. package/dist/cjs/provideZero.eagerInstance.test.cjs +22 -0
  83. package/dist/cjs/provideZero.eagerInstance.test.native.js +30 -0
  84. package/dist/cjs/provideZero.eagerInstance.test.native.js.map +1 -1
  85. package/dist/cjs/provideZero.hydration.test.native.js.map +1 -1
  86. package/dist/cjs/provideZero.ssr.test.native.js.map +1 -1
  87. package/dist/cjs/queryErrorRecovery.cjs +93 -0
  88. package/dist/cjs/queryErrorRecovery.native.js +205 -0
  89. package/dist/cjs/queryErrorRecovery.native.js.map +1 -0
  90. package/dist/cjs/queryJson.native.js.map +1 -1
  91. package/dist/cjs/queryRegistry.native.js.map +1 -1
  92. package/dist/cjs/resolveQuery.native.js.map +1 -1
  93. package/dist/cjs/run.native.js.map +1 -1
  94. package/dist/cjs/server.native.js.map +1 -1
  95. package/dist/cjs/server.test.native.js.map +1 -1
  96. package/dist/cjs/serverWhere.native.js.map +1 -1
  97. package/dist/cjs/serverWhere.test.native.js.map +1 -1
  98. package/dist/cjs/state.native.js.map +1 -1
  99. package/dist/cjs/streaming.native.js.map +1 -1
  100. package/dist/cjs/testSetup.native.js.map +1 -1
  101. package/dist/cjs/types.native.js.map +1 -1
  102. package/dist/cjs/usePermission.test.native.js.map +1 -1
  103. package/dist/cjs/useQuery.aggregate.zero.test.cjs +199 -4
  104. package/dist/cjs/useQuery.aggregate.zero.test.native.js +212 -4
  105. package/dist/cjs/useQuery.aggregate.zero.test.native.js.map +1 -1
  106. package/dist/cjs/useQuery.empty.test.native.js.map +1 -1
  107. package/dist/cjs/useQuery.ssr.test.native.js.map +1 -1
  108. package/dist/cjs/useStreamingField.loop.test.native.js.map +1 -1
  109. package/dist/cjs/useStreamingField.native.js.map +1 -1
  110. package/dist/cjs/vite-plugin.cjs +10 -0
  111. package/dist/cjs/vite-plugin.native.js +10 -0
  112. package/dist/cjs/vite-plugin.native.js.map +1 -1
  113. package/dist/cjs/where.native.js.map +1 -1
  114. package/dist/cjs/zeroRunner.native.js.map +1 -1
  115. package/dist/cjs/zql.native.js.map +1 -1
  116. package/dist/esm/cli.mjs +6 -0
  117. package/dist/esm/cli.mjs.map +1 -1
  118. package/dist/esm/cli.native.js +7 -1
  119. package/dist/esm/cli.native.js.map +1 -1
  120. package/dist/esm/combineZeroClients.native.js +4 -4
  121. package/dist/esm/combineZeroClients.native.js.map +1 -1
  122. package/dist/esm/config.native.js.map +1 -1
  123. package/dist/esm/createPermissions.native.js +4 -4
  124. package/dist/esm/createPermissions.native.js.map +1 -1
  125. package/dist/esm/createSchemaFromDrizzle.native.js +1 -1
  126. package/dist/esm/createSchemaFromDrizzle.native.js.map +1 -1
  127. package/dist/esm/createUseQuery.mjs +16 -6
  128. package/dist/esm/createUseQuery.mjs.map +1 -1
  129. package/dist/esm/createUseQuery.native.js +24 -11
  130. package/dist/esm/createUseQuery.native.js.map +1 -1
  131. package/dist/esm/createUseQueryDirect.mjs +13 -2
  132. package/dist/esm/createUseQueryDirect.mjs.map +1 -1
  133. package/dist/esm/createUseQueryDirect.native.js +19 -7
  134. package/dist/esm/createUseQueryDirect.native.js.map +1 -1
  135. package/dist/esm/createZeroClient.authData.test.native.js +2 -2
  136. package/dist/esm/createZeroClient.authData.test.native.js.map +1 -1
  137. package/dist/esm/createZeroClient.connection.test.native.js +1 -1
  138. package/dist/esm/createZeroClient.connection.test.native.js.map +1 -1
  139. package/dist/esm/createZeroClient.exportProbe.test.native.js +1 -1
  140. package/dist/esm/createZeroClient.exportProbe.test.native.js.map +1 -1
  141. package/dist/esm/createZeroClient.headless.test.native.js +2 -2
  142. package/dist/esm/createZeroClient.headless.test.native.js.map +1 -1
  143. package/dist/esm/createZeroClient.httpAuth.test.native.js +1 -1
  144. package/dist/esm/createZeroClient.httpAuth.test.native.js.map +1 -1
  145. package/dist/esm/createZeroClient.mjs +21 -3
  146. package/dist/esm/createZeroClient.mjs.map +1 -1
  147. package/dist/esm/createZeroClient.native.js +49 -24
  148. package/dist/esm/createZeroClient.native.js.map +1 -1
  149. package/dist/esm/createZeroClient.recovery.test.native.js +2 -2
  150. package/dist/esm/createZeroClient.recovery.test.native.js.map +1 -1
  151. package/dist/esm/generate-helpers.native.js.map +1 -1
  152. package/dist/esm/generate-helpers.test.native.js +1 -1
  153. package/dist/esm/generate-helpers.test.native.js.map +1 -1
  154. package/dist/esm/generate-layout.native.js.map +1 -1
  155. package/dist/esm/generate-lite.native.js +1 -1
  156. package/dist/esm/generate-lite.native.js.map +1 -1
  157. package/dist/esm/generate-lite.test.native.js +1 -1
  158. package/dist/esm/generate-lite.test.native.js.map +1 -1
  159. package/dist/esm/generate.mjs +57 -30
  160. package/dist/esm/generate.mjs.map +1 -1
  161. package/dist/esm/generate.native.js +95 -34
  162. package/dist/esm/generate.native.js.map +1 -1
  163. package/dist/esm/generate.test.mjs +147 -0
  164. package/dist/esm/generate.test.mjs.map +1 -1
  165. package/dist/esm/generate.test.native.js +148 -1
  166. package/dist/esm/generate.test.native.js.map +1 -1
  167. package/dist/esm/helpers/NullToOptional.native.js.map +1 -1
  168. package/dist/esm/helpers/asyncContext.hostStorage.test.native.js +7 -7
  169. package/dist/esm/helpers/asyncContext.hostStorage.test.native.js.map +1 -1
  170. package/dist/esm/helpers/batchQuery.native.js +1 -1
  171. package/dist/esm/helpers/batchQuery.native.js.map +1 -1
  172. package/dist/esm/helpers/clearZeroClientData.native.js.map +1 -1
  173. package/dist/esm/helpers/createMutators.native.js +4 -4
  174. package/dist/esm/helpers/createMutators.native.js.map +1 -1
  175. package/dist/esm/helpers/createMutators.test.native.js +2 -2
  176. package/dist/esm/helpers/createMutators.test.native.js.map +1 -1
  177. package/dist/esm/helpers/ellipsis.native.js.map +1 -1
  178. package/dist/esm/helpers/emitter.native.js.map +1 -1
  179. package/dist/esm/helpers/ensure.native.js +1 -1
  180. package/dist/esm/helpers/ensure.native.js.map +1 -1
  181. package/dist/esm/helpers/ensureLoggedIn.native.js +2 -2
  182. package/dist/esm/helpers/ensureLoggedIn.native.js.map +1 -1
  183. package/dist/esm/helpers/errors.native.js.map +1 -1
  184. package/dist/esm/helpers/getAuth.native.js +3 -3
  185. package/dist/esm/helpers/getAuth.native.js.map +1 -1
  186. package/dist/esm/helpers/getCurrentComponentStack.native.js.map +1 -1
  187. package/dist/esm/helpers/globalValue.native.js.map +1 -1
  188. package/dist/esm/helpers/hostStorageScope.native.js.map +1 -1
  189. package/dist/esm/helpers/mapObject.native.js.map +1 -1
  190. package/dist/esm/helpers/mutationLifecycle.native.js +1 -1
  191. package/dist/esm/helpers/mutationLifecycle.native.js.map +1 -1
  192. package/dist/esm/helpers/mutationLifecycle.test.native.js +1 -1
  193. package/dist/esm/helpers/mutationLifecycle.test.native.js.map +1 -1
  194. package/dist/esm/helpers/mutatorContext.native.js +1 -1
  195. package/dist/esm/helpers/mutatorContext.native.js.map +1 -1
  196. package/dist/esm/helpers/mutatorContext.test.native.js +6 -6
  197. package/dist/esm/helpers/mutatorContext.test.native.js.map +1 -1
  198. package/dist/esm/helpers/prettyFormatZeroQuery.native.js +1 -1
  199. package/dist/esm/helpers/prettyFormatZeroQuery.native.js.map +1 -1
  200. package/dist/esm/helpers/queryContext.native.js +1 -1
  201. package/dist/esm/helpers/queryContext.native.js.map +1 -1
  202. package/dist/esm/helpers/recoverZeroClient.native.js +1 -1
  203. package/dist/esm/helpers/recoverZeroClient.native.js.map +1 -1
  204. package/dist/esm/helpers/recoverZeroClient.test.native.js +2 -2
  205. package/dist/esm/helpers/recoverZeroClient.test.native.js.map +1 -1
  206. package/dist/esm/helpers/showZeroClientError.native.js +1 -1
  207. package/dist/esm/helpers/showZeroClientError.native.js.map +1 -1
  208. package/dist/esm/helpers/sleep.native.js +1 -1
  209. package/dist/esm/helpers/sleep.native.js.map +1 -1
  210. package/dist/esm/helpers/time.native.js.map +1 -1
  211. package/dist/esm/helpers/tuple.native.js.map +1 -1
  212. package/dist/esm/helpers/useMutation.native.js.map +1 -1
  213. package/dist/esm/helpers/useMutation.test.native.js +1 -1
  214. package/dist/esm/helpers/useMutation.test.native.js.map +1 -1
  215. package/dist/esm/helpers/useZeroDebug.native.js +2 -2
  216. package/dist/esm/helpers/useZeroDebug.native.js.map +1 -1
  217. package/dist/esm/index.native.js +23 -23
  218. package/dist/esm/index.native.js.map +1 -1
  219. package/dist/esm/instanceRegistry.native.js +2 -2
  220. package/dist/esm/instanceRegistry.native.js.map +1 -1
  221. package/dist/esm/modelRegistry.native.js.map +1 -1
  222. package/dist/esm/multi.mjs +10 -5
  223. package/dist/esm/multi.mjs.map +1 -1
  224. package/dist/esm/multi.native.js +15 -10
  225. package/dist/esm/multi.native.js.map +1 -1
  226. package/dist/esm/multiInstance.test.native.js +10 -10
  227. package/dist/esm/multiInstance.test.native.js.map +1 -1
  228. package/dist/esm/multiInstanceMutations.test.native.js +4 -4
  229. package/dist/esm/multiInstanceMutations.test.native.js.map +1 -1
  230. package/dist/esm/multiInstanceNested.test.native.js +3 -3
  231. package/dist/esm/multiInstanceNested.test.native.js.map +1 -1
  232. package/dist/esm/multiPartition.test.mjs +47 -3
  233. package/dist/esm/multiPartition.test.mjs.map +1 -1
  234. package/dist/esm/multiPartition.test.native.js +52 -4
  235. package/dist/esm/multiPartition.test.native.js.map +1 -1
  236. package/dist/esm/mutations.native.js +2 -2
  237. package/dist/esm/mutations.native.js.map +1 -1
  238. package/dist/esm/mutations.test.native.js +4 -4
  239. package/dist/esm/mutations.test.native.js.map +1 -1
  240. package/dist/esm/native-typescript.mjs +50 -11
  241. package/dist/esm/native-typescript.mjs.map +1 -1
  242. package/dist/esm/native-typescript.native.js +158 -30
  243. package/dist/esm/native-typescript.native.js.map +1 -1
  244. package/dist/esm/provideZero.eagerInstance.test.mjs +22 -0
  245. package/dist/esm/provideZero.eagerInstance.test.mjs.map +1 -1
  246. package/dist/esm/provideZero.eagerInstance.test.native.js +31 -1
  247. package/dist/esm/provideZero.eagerInstance.test.native.js.map +1 -1
  248. package/dist/esm/provideZero.hydration.test.native.js +2 -2
  249. package/dist/esm/provideZero.hydration.test.native.js.map +1 -1
  250. package/dist/esm/provideZero.ssr.test.native.js +2 -2
  251. package/dist/esm/provideZero.ssr.test.native.js.map +1 -1
  252. package/dist/esm/queryErrorRecovery.mjs +66 -0
  253. package/dist/esm/queryErrorRecovery.mjs.map +1 -0
  254. package/dist/esm/queryErrorRecovery.native.js +175 -0
  255. package/dist/esm/queryErrorRecovery.native.js.map +1 -0
  256. package/dist/esm/queryJson.native.js +1 -1
  257. package/dist/esm/queryJson.native.js.map +1 -1
  258. package/dist/esm/queryRegistry.native.js.map +1 -1
  259. package/dist/esm/resolveQuery.native.js +2 -2
  260. package/dist/esm/resolveQuery.native.js.map +1 -1
  261. package/dist/esm/run.native.js +4 -4
  262. package/dist/esm/run.native.js.map +1 -1
  263. package/dist/esm/server.native.js +10 -10
  264. package/dist/esm/server.native.js.map +1 -1
  265. package/dist/esm/server.test.native.js +2 -2
  266. package/dist/esm/server.test.native.js.map +1 -1
  267. package/dist/esm/serverWhere.native.js +1 -1
  268. package/dist/esm/serverWhere.native.js.map +1 -1
  269. package/dist/esm/serverWhere.test.native.js +3 -3
  270. package/dist/esm/serverWhere.test.native.js.map +1 -1
  271. package/dist/esm/state.native.js +1 -1
  272. package/dist/esm/state.native.js.map +1 -1
  273. package/dist/esm/streaming.native.js +1 -1
  274. package/dist/esm/streaming.native.js.map +1 -1
  275. package/dist/esm/testSetup.native.js.map +1 -1
  276. package/dist/esm/types.native.js.map +1 -1
  277. package/dist/esm/usePermission.test.native.js +3 -3
  278. package/dist/esm/usePermission.test.native.js.map +1 -1
  279. package/dist/esm/useQuery.aggregate.zero.test.mjs +197 -2
  280. package/dist/esm/useQuery.aggregate.zero.test.mjs.map +1 -1
  281. package/dist/esm/useQuery.aggregate.zero.test.native.js +212 -4
  282. package/dist/esm/useQuery.aggregate.zero.test.native.js.map +1 -1
  283. package/dist/esm/useQuery.empty.test.native.js +3 -3
  284. package/dist/esm/useQuery.empty.test.native.js.map +1 -1
  285. package/dist/esm/useQuery.ssr.test.native.js +2 -2
  286. package/dist/esm/useQuery.ssr.test.native.js.map +1 -1
  287. package/dist/esm/useStreamingField.loop.test.native.js +1 -1
  288. package/dist/esm/useStreamingField.loop.test.native.js.map +1 -1
  289. package/dist/esm/useStreamingField.native.js.map +1 -1
  290. package/dist/esm/vite-plugin.mjs +10 -0
  291. package/dist/esm/vite-plugin.mjs.map +1 -1
  292. package/dist/esm/vite-plugin.native.js +11 -1
  293. package/dist/esm/vite-plugin.native.js.map +1 -1
  294. package/dist/esm/where.native.js +3 -3
  295. package/dist/esm/where.native.js.map +1 -1
  296. package/dist/esm/zeroRunner.native.js +1 -1
  297. package/dist/esm/zeroRunner.native.js.map +1 -1
  298. package/dist/esm/zql.native.js +1 -1
  299. package/dist/esm/zql.native.js.map +1 -1
  300. package/package.json +4 -4
  301. package/src/cli.ts +6 -0
  302. package/src/createUseQuery.tsx +20 -5
  303. package/src/createUseQueryDirect.tsx +17 -1
  304. package/src/createZeroClient.tsx +37 -4
  305. package/src/generate.test.ts +116 -0
  306. package/src/generate.ts +109 -52
  307. package/src/multi.ts +11 -3
  308. package/src/multiPartition.test.ts +55 -11
  309. package/src/native-typescript.ts +64 -11
  310. package/src/provideZero.eagerInstance.test.tsx +27 -0
  311. package/src/queryErrorRecovery.ts +95 -0
  312. package/src/useQuery.aggregate.zero.test.tsx +187 -2
  313. package/src/vite-plugin.ts +10 -0
  314. package/types/createUseQuery.d.ts +3 -1
  315. package/types/createUseQuery.d.ts.map +1 -1
  316. package/types/createUseQueryDirect.d.ts +3 -1
  317. package/types/createUseQueryDirect.d.ts.map +1 -1
  318. package/types/createZeroClient.d.ts +3 -1
  319. package/types/createZeroClient.d.ts.map +1 -1
  320. package/types/generate.d.ts +4 -0
  321. package/types/generate.d.ts.map +1 -1
  322. package/types/multi.d.ts +3 -1
  323. package/types/multi.d.ts.map +1 -1
  324. package/types/native-typescript.d.ts +7 -1
  325. package/types/native-typescript.d.ts.map +1 -1
  326. package/types/queryErrorRecovery.d.ts +33 -0
  327. package/types/queryErrorRecovery.d.ts.map +1 -0
  328. package/types/useQuery.aggregate.zero.test.d.ts.map +1 -1
  329. package/types/vite-plugin.d.ts.map +1 -1
package/src/generate.ts CHANGED
@@ -40,7 +40,7 @@ import type { ExtractedMutation, ModelMutations, SchemaColumn } from './generate
40
40
  import type { DataLayout } from './generate-layout'
41
41
 
42
42
  const hash = (s: string) => createHash('sha256').update(s).digest('hex')
43
- const GENERATOR_CACHE_VERSION = '8'
43
+ const GENERATOR_CACHE_VERSION = '10'
44
44
 
45
45
  const isGeneratorSourceFile = (name: string) =>
46
46
  name.endsWith('.ts') &&
@@ -48,13 +48,14 @@ const isGeneratorSourceFile = (name: string) =>
48
48
  !name.endsWith('.test.ts') &&
49
49
  !name.endsWith('.spec.ts')
50
50
 
51
- // hash every .ts input the generator reads (all of baseDir except the generated
52
- // output dir + node_modules) so a dev-boot regen can be skipped when nothing
53
- // changed. the expensive part of generate() — loading the typescript compiler
54
- // and building a TS program per query/model for type resolution — runs every
55
- // boot otherwise, even when the output is already current (the configureServer
56
- // watcher re-runs it on real edits, so the boot-time pass is pure redundancy).
57
- function hashInputTree(sourceRoots: string[], generatedDir: string): string {
51
+ // hash generator inputs plus freshly resolved compiler dependencies. a verified
52
+ // cache hit can skip source AST decoding and parsing, while edits still resolve
53
+ // imported types and validate the generated bytes before reusing them.
54
+ function hashInputTree(
55
+ sourceRoots: string[],
56
+ generatedDir: string,
57
+ dependencyPaths: string[]
58
+ ): string {
58
59
  const parts: string[] = []
59
60
  const seen = new Set<string>()
60
61
  const walk = (dir: string) => {
@@ -80,6 +81,14 @@ function hashInputTree(sourceRoots: string[], generatedDir: string): string {
80
81
  }
81
82
  }
82
83
  for (const root of sourceRoots) walk(root)
84
+ // imported types can live outside the data roots, including transitive
85
+ // re-exports and package declarations. use the compiler's resolved inputs.
86
+ for (const path of dependencyPaths) {
87
+ if (seen.has(path) || path.startsWith(`${generatedDir}/`) || !existsSync(path))
88
+ continue
89
+ seen.add(path)
90
+ parts.push(`${path}\0${readFileSync(path, 'utf-8')}`)
91
+ }
83
92
  return hash(parts.join('\0'))
84
93
  }
85
94
 
@@ -447,15 +456,17 @@ async function tsTypeToValibot(
447
456
  seen = new Set<number>()
448
457
  ): Promise<string> {
449
458
  const checker = project.checker
450
- // prevent infinite recursion on circular types
451
- // only track structured types (objects, intersections) — not primitives/unions
459
+ // prevent infinite recursion on circular types. `seen` holds only the
460
+ // structured types (objects, intersections) on the current path, so a type
461
+ // reused by siblings, such as `string[]`, still converts every time.
452
462
  const flags = type.flags
463
+ let path = seen
453
464
  if (flags & (TypeFlags.Object | TypeFlags.Intersection)) {
454
465
  if (seen.has(type.id)) return 'v.unknown()'
455
- seen.add(type.id)
466
+ path = new Set(seen).add(type.id)
456
467
  }
457
468
 
458
- const recurse = (value: Type) => tsTypeToValibot(project, value, seen)
469
+ const recurse = (value: Type) => tsTypeToValibot(project, value, path)
459
470
 
460
471
  // primitives
461
472
  if (flags & TypeFlags.String) return 'v.string()'
@@ -741,6 +752,8 @@ export interface GenerateOptions {
741
752
  dir: string
742
753
  /** explicit on-zero.config.ts path; auto-discovered in `dir` when omitted */
743
754
  config?: string
755
+ /** generation-only TypeScript project; defaults to each source file's project */
756
+ tsconfig?: string
744
757
  /** run after generation */
745
758
  after?: string
746
759
  /** suppress output */
@@ -799,20 +812,28 @@ function dataMembershipFromLayout(layout: DataLayout): DataMembership {
799
812
  }
800
813
  }
801
814
 
802
- async function loadGeneratorProject(baseDir: string): Promise<NativeTypeScriptProject> {
815
+ async function loadGeneratorProject(
816
+ baseDir: string,
817
+ deferSources = false,
818
+ tsconfig?: string
819
+ ): Promise<NativeTypeScriptProject> {
803
820
  const sourcePaths = collectTypeScriptSourcePaths([
804
821
  baseDir,
805
822
  resolve(dirname(baseDir), 'database'),
806
823
  ])
807
- return createNativeTypeScriptProject(baseDir, sourcePaths)
824
+ return createNativeTypeScriptProject(baseDir, sourcePaths, {
825
+ deferSources,
826
+ tsconfig: tsconfig ? resolve(tsconfig) : undefined,
827
+ })
808
828
  }
809
829
 
810
830
  export async function deriveDataMembership(options: {
811
831
  dir: string
812
832
  config?: string
833
+ tsconfig?: string
813
834
  }): Promise<DataMembership> {
814
835
  const baseDir = resolve(options.dir)
815
- const project = await loadGeneratorProject(baseDir)
836
+ const project = await loadGeneratorProject(baseDir, false, options.tsconfig)
816
837
  try {
817
838
  const layout = discoverDataLayout(
818
839
  project,
@@ -829,9 +850,10 @@ export async function generateDrizzleSchemaInputFile(options: {
829
850
  dir: string
830
851
  schemaImportPath: string
831
852
  config?: string
853
+ tsconfig?: string
832
854
  }): Promise<string> {
833
855
  const baseDir = resolve(options.dir)
834
- const project = await loadGeneratorProject(baseDir)
856
+ const project = await loadGeneratorProject(baseDir, false, options.tsconfig)
835
857
  try {
836
858
  return generateDrizzleSchemaInputFileWithProject(options, project)
837
859
  } finally {
@@ -925,7 +947,7 @@ async function generateDrizzleSchemaInputFileWithProject(
925
947
  }
926
948
 
927
949
  export async function generate(options: GenerateOptions): Promise<GenerateResult> {
928
- const project = await loadGeneratorProject(resolve(options.dir))
950
+ const project = await loadGeneratorProject(resolve(options.dir), true, options.tsconfig)
929
951
  try {
930
952
  return await generateWithProject(options, project)
931
953
  } finally {
@@ -947,23 +969,43 @@ async function generateWithProject(
947
969
 
948
970
  loadCache()
949
971
 
950
- // the layout pass is intentionally first: config, filenames, and related()
951
- // calls determine schema membership before any type program exists.
952
- const layout = discoverDataLayout(
953
- project,
954
- baseDir,
955
- config ? resolve(config) : undefined
956
- )
957
- const metadataHash = hash(
958
- layout.metadataPaths
959
- .map((path) => `${path}\0${readFileSync(path, 'utf8')}`)
960
- .join('\0')
961
- )
972
+ const configPath = config ? resolve(config) : resolve(baseDir, 'on-zero.config.ts')
973
+ let cachedInputs: Pick<DataLayout, 'sourceRoots' | 'metadataPaths'> | null = null
974
+ if (generateCache.__configPath === configPath) {
975
+ try {
976
+ const value: unknown = JSON.parse(generateCache.__layoutInputs || 'null')
977
+ if (
978
+ value &&
979
+ typeof value === 'object' &&
980
+ 'sourceRoots' in value &&
981
+ Array.isArray(value.sourceRoots) &&
982
+ value.sourceRoots.every((path) => typeof path === 'string') &&
983
+ 'metadataPaths' in value &&
984
+ Array.isArray(value.metadataPaths) &&
985
+ value.metadataPaths.every((path) => typeof path === 'string')
986
+ ) {
987
+ cachedInputs = {
988
+ sourceRoots: value.sourceRoots,
989
+ metadataPaths: value.metadataPaths,
990
+ }
991
+ }
992
+ } catch {}
993
+ }
994
+ const dependencyPaths = await project.sourceFilePaths()
995
+ const hashInputs = (layout: Pick<DataLayout, 'sourceRoots' | 'metadataPaths'>) => {
996
+ const metadataHash = hash(
997
+ layout.metadataPaths
998
+ .map((path) => `${path}\0${readFileSync(path, 'utf8')}`)
999
+ .join('\0')
1000
+ )
1001
+ return hash(
1002
+ `${hashInputTree(layout.sourceRoots, generatedDir, dependencyPaths)}\0${metadataHash}\0${externalZeroConfig}\0${project.configurationKey}`
1003
+ )
1004
+ }
962
1005
 
963
1006
  // input-freshness gate: if nothing under baseDir changed since the last
964
- // COMPLETED generate (the input hash is only stored at the end, after
965
- // saveCache), the outputs are already current — skip the typescript-program
966
- // build entirely and return the cached counts. the configureServer watcher
1007
+ // completed generate, the outputs are already current. resolve compiler inputs
1008
+ // again, but skip source AST loading and return the cached counts. the watcher
967
1009
  // still re-runs generate on real model/query edits.
968
1010
  const databaseSchemaPath = resolve(dirname(baseDir), 'database/schema.ts')
969
1011
  const needsDrizzleSchema = existsSync(databaseSchemaPath)
@@ -975,34 +1017,24 @@ async function generateWithProject(
975
1017
  ? readFileSync(externalZeroConfigPath, 'utf8')
976
1018
  : null
977
1019
  const needsSqliteZeroSchema = needsDrizzleSchema && externalZeroConfig === null
978
- const inputHash = hash(
979
- `${hashInputTree(layout.sourceRoots, generatedDir)}\0${metadataHash}\0${externalZeroConfig}`
980
- )
1020
+ let cachedInputHash: string | null = null
1021
+ if (cachedInputs) {
1022
+ try {
1023
+ cachedInputHash = hashInputs(cachedInputs)
1024
+ } catch {}
1025
+ }
981
1026
  if (
982
1027
  !force &&
983
1028
  generateCache.__generatorVersion === GENERATOR_CACHE_VERSION &&
984
- generateCache.__inputHash === inputHash &&
1029
+ generateCache.__inputHash === cachedInputHash &&
985
1030
  // the input hash deliberately skips the generated directory, so it cannot
986
1031
  // notice that the outputs themselves moved. existence is not correctness
987
1032
  // either: a checkout, merge or revert leaves every file present and stale.
988
1033
  // confirm the bytes on disk still match what the cache says it wrote there
989
1034
  // before skipping the build, or the fast path serves someone else's output.
990
1035
  //
991
- // a consumer that formats generated output after this runs never hits the
992
- // fast path again, because the cache holds the bytes written here and disk
993
- // holds the formatted ones. soot does exactly that, `on-zero generate &&
994
- // generate-instance-tables && oxfmt src/data/generated/`, and the number is
995
- // already taken: 2.50s median before, 3.69s after, four warm runs each in
996
- // one checkout with only the pin swapped, so about 1.2 seconds or 47
997
- // percent on this step. the full three-command script went 2.65s to 3.14s
998
- // with a spread that swallows most of it.
999
- //
1000
- // the shape matters more than the number. this is not a cache that hits
1001
- // less often, it is a cache that never hits, so the cost does not degrade
1002
- // gracefully and grows with the generated output rather than staying at
1003
- // 1.2s. correct and slower beats fast and stale, which is why it is written
1004
- // this way, but if generate time ever becomes the complaint, record the
1005
- // hash after the consumer's formatter runs rather than weakening this.
1036
+ // successful after commands record their final output hashes. formatting
1037
+ // therefore keeps the fast path without trusting stale generated bytes.
1006
1038
  Object.entries(generateCache).every(([cachedPath, cachedHash]) => {
1007
1039
  if (!cachedPath.startsWith(generatedDir)) return true
1008
1040
  try {
@@ -1028,6 +1060,16 @@ async function generateWithProject(
1028
1060
  }
1029
1061
  }
1030
1062
 
1063
+ // the compiler still resolves current project inputs before the cache check.
1064
+ // only decoding and parsing source ASTs waits until generation is necessary.
1065
+ await project.loadSources()
1066
+ const layout = discoverDataLayout(
1067
+ project,
1068
+ baseDir,
1069
+ config ? resolve(config) : undefined
1070
+ )
1071
+ const inputHash = hashInputs(layout)
1072
+
1031
1073
  const modelNamespaces = layout.namespaces.filter(
1032
1074
  (namespace): namespace is typeof namespace & { modelPath: string } =>
1033
1075
  namespace.modelPath !== null
@@ -1336,6 +1378,15 @@ async function generateWithProject(
1336
1378
  stdio: 'inherit',
1337
1379
  env: { ...process.env, ON_ZERO_GENERATED_DIR: generatedDir },
1338
1380
  })
1381
+ // successful postprocessing owns the final generated bytes. collect all
1382
+ // hashes before updating the cache so a missing output cannot certify a
1383
+ // partially completed postprocessor.
1384
+ const outputHashes = Object.fromEntries(
1385
+ Object.keys(generateCache)
1386
+ .filter((filePath) => filePath.startsWith(generatedDir))
1387
+ .map((filePath) => [filePath, hash(readFileSync(filePath, 'utf-8'))])
1388
+ )
1389
+ Object.assign(generateCache, outputHashes)
1339
1390
  } catch (err) {
1340
1391
  if (!silent) console.error(`Error running after command: ${err}`)
1341
1392
  }
@@ -1343,6 +1394,11 @@ async function generateWithProject(
1343
1394
 
1344
1395
  // record the input hash + counts so the next boot can skip a no-op regen.
1345
1396
  generateCache.__generatorVersion = GENERATOR_CACHE_VERSION
1397
+ generateCache.__configPath = configPath
1398
+ generateCache.__layoutInputs = JSON.stringify({
1399
+ sourceRoots: layout.sourceRoots,
1400
+ metadataPaths: layout.metadataPaths,
1401
+ })
1346
1402
  generateCache.__inputHash = inputHash
1347
1403
  generateCache.__counts = JSON.stringify({
1348
1404
  modelCount: modelNamespaces.length,
@@ -1383,7 +1439,7 @@ export async function watch(options: WatchOptions) {
1383
1439
  }
1384
1440
 
1385
1441
  const databaseDir = resolve(dirname(baseDir), 'database')
1386
- const project = await loadGeneratorProject(baseDir)
1442
+ const project = await loadGeneratorProject(baseDir, false, options.tsconfig)
1387
1443
  let layout: DataLayout
1388
1444
  try {
1389
1445
  layout = discoverDataLayout(
@@ -1398,6 +1454,7 @@ export async function watch(options: WatchOptions) {
1398
1454
  [
1399
1455
  ...new Set([
1400
1456
  ...layout.sourceRoots,
1457
+ ...(options.tsconfig ? [resolve(options.tsconfig)] : []),
1401
1458
  ...(existsSync(databaseDir) ? [databaseDir] : []),
1402
1459
  ]),
1403
1460
  ],
package/src/multi.ts CHANGED
@@ -55,7 +55,7 @@ type ClientUnion<Instances extends Record<string, ZeroInstanceManifestEntry>> =
55
55
 
56
56
  export function createZeroClients<
57
57
  const Instances extends Record<string, ZeroInstanceManifestEntry>,
58
- >(instances: Instances) {
58
+ >(instances: Instances, options: { inner: keyof Instances & string }) {
59
59
  const names = Object.keys(instances)
60
60
  if (names.length === 0) {
61
61
  throw new Error('[on-zero] createZeroClients requires at least one instance')
@@ -65,7 +65,12 @@ export function createZeroClients<
65
65
  names.unshift('default')
66
66
  }
67
67
 
68
- const innerName = names[names.length - 1]!
68
+ const innerName = options.inner
69
+ if (!names.includes(innerName)) {
70
+ throw new Error(
71
+ `[on-zero] createZeroClients inner instance '${innerName}' is not in the manifest`
72
+ )
73
+ }
69
74
  const clients: Record<string, ReturnType<typeof createZeroClientInternal>> = {}
70
75
 
71
76
  // release the complete old partition atomically before any new instance
@@ -99,6 +104,9 @@ export function createZeroClients<
99
104
  return {
100
105
  clients: typedClients,
101
106
  providers,
102
- combined: combineZeroClients(...orderedClients),
107
+ combined: combineZeroClients<[ClientUnion<Instances>, ...ClientUnion<Instances>[]]>(
108
+ ...orderedClients,
109
+ { inner: innerName }
110
+ ),
103
111
  }
104
112
  }
@@ -1,6 +1,7 @@
1
1
  import { createSchema, string, table } from '@rocicorp/zero'
2
- import { describe, expect, test } from 'vitest'
2
+ import { describe, expect, test, vi } from 'vitest'
3
3
 
4
+ import { getInstanceForQueryFn } from './instanceRegistry'
4
5
  import { createZeroClients } from './multi'
5
6
 
6
7
  const schema = createSchema({
@@ -32,7 +33,7 @@ describe('createZeroClients', () => {
32
33
  },
33
34
  } as const
34
35
 
35
- const result = createZeroClients(instances)
36
+ const result = createZeroClients(instances, { inner: 'default' })
36
37
 
37
38
  expect(Object.keys(result.clients)).toEqual(['project', 'default'])
38
39
  expect(result.clients.default.instanceName).toBe('default')
@@ -40,10 +41,47 @@ describe('createZeroClients', () => {
40
41
  expect(result.providers.default).toBe(result.clients.default.ProvideZero)
41
42
  expect(result.providers.project).toBe(result.clients.project.ProvideZero)
42
43
  expect(result.combined.run).toBeTypeOf('function')
44
+
45
+ const innerQuery = vi
46
+ .spyOn(result.clients.default, 'useQuery')
47
+ .mockReturnValue('inner' as never)
48
+ const outerQuery = vi
49
+ .spyOn(result.clients.project, 'useQueryDirect')
50
+ .mockReturnValue('outer' as never)
51
+ expect(result.combined.useQuery(instances.default.queries.account.current, {})).toBe(
52
+ 'inner'
53
+ )
54
+ expect(result.combined.useQuery(instances.project.queries.message.all, {})).toBe(
55
+ 'outer'
56
+ )
57
+ expect(innerQuery).toHaveBeenCalledOnce()
58
+ expect(outerQuery).toHaveBeenCalledOnce()
43
59
  })
44
60
 
45
61
  test('requires at least one generated instance', () => {
46
- expect(() => createZeroClients({})).toThrow(/at least one instance/)
62
+ expect(() => createZeroClients({}, { inner: 'missing' as never })).toThrow(
63
+ /at least one instance/
64
+ )
65
+ })
66
+
67
+ test('rejects an unknown inner instance before replacing registered clients', () => {
68
+ const entry = {
69
+ schema,
70
+ queries: { preserved: { current: () => ({}) as never } },
71
+ models: {},
72
+ tables: [],
73
+ syncTables: [],
74
+ supportTables: [],
75
+ scope: null,
76
+ defaultVisibility: null,
77
+ }
78
+ createZeroClients({ default: entry }, { inner: 'default' })
79
+ const owner = getInstanceForQueryFn(entry.queries.preserved.current)
80
+ expect(owner?.name).toBe('default')
81
+ expect(() =>
82
+ createZeroClients({ default: entry }, { inner: 'missing' as never })
83
+ ).toThrow(/inner instance 'missing'/)
84
+ expect(getInstanceForQueryFn(entry.queries.preserved.current)).toBe(owner)
47
85
  })
48
86
 
49
87
  test('replaces an existing partition atomically when a namespace moves', () => {
@@ -58,16 +96,22 @@ describe('createZeroClients', () => {
58
96
  defaultVisibility: null,
59
97
  })
60
98
 
61
- createZeroClients({
62
- default: entry({ movedNamespace: { before: () => ({}) as never } }),
63
- project: entry({ stableProject: { before: () => ({}) as never } }),
64
- })
99
+ createZeroClients(
100
+ {
101
+ default: entry({ movedNamespace: { before: () => ({}) as never } }),
102
+ project: entry({ stableProject: { before: () => ({}) as never } }),
103
+ },
104
+ { inner: 'project' }
105
+ )
65
106
 
66
107
  expect(() =>
67
- createZeroClients({
68
- default: entry({ stableDefault: { after: () => ({}) as never } }),
69
- project: entry({ movedNamespace: { after: () => ({}) as never } }),
70
- })
108
+ createZeroClients(
109
+ {
110
+ default: entry({ stableDefault: { after: () => ({}) as never } }),
111
+ project: entry({ movedNamespace: { after: () => ({}) as never } }),
112
+ },
113
+ { inner: 'project' }
114
+ )
71
115
  ).not.toThrow()
72
116
  })
73
117
  })
@@ -9,7 +9,10 @@ import type { SourceFile } from 'typescript/unstable/ast'
9
9
  process.env.GOMAXPROCS ??= '2'
10
10
 
11
11
  export type NativeTypeScriptProject = {
12
+ configurationKey: string
12
13
  projectForFile(path: string): Project
14
+ sourceFilePaths(): Promise<string[]>
15
+ loadSources(): Promise<void>
13
16
  sourceFile(path: string): SourceFile
14
17
  hasParseErrors(path: string): boolean
15
18
  close(): Promise<void>
@@ -19,6 +22,7 @@ type CompilerSession = {
19
22
  api: API
20
23
  paths: Set<string>
21
24
  root: string
25
+ tsconfig?: string
22
26
  }
23
27
 
24
28
  let compiler: CompilerSession | null = null
@@ -30,7 +34,9 @@ async function closeCompiler(session: CompilerSession) {
30
34
  try {
31
35
  if (session.paths.size > 0) {
32
36
  const snapshot = await session.api.updateSnapshot({
33
- closeFiles: [...session.paths],
37
+ ...(session.tsconfig
38
+ ? { closeProjects: [session.tsconfig] }
39
+ : { closeFiles: [...session.paths] }),
34
40
  })
35
41
  await snapshot.dispose()
36
42
  }
@@ -39,7 +45,7 @@ async function closeCompiler(session: CompilerSession) {
39
45
  }
40
46
  }
41
47
 
42
- async function acquireCompiler(root: string, sourcePaths: string[]) {
48
+ async function acquireCompiler(root: string, sourcePaths: string[], tsconfig?: string) {
43
49
  const previousTurn = compilerTurn
44
50
  let releaseTurn = () => {}
45
51
  compilerTurn = new Promise<void>((resolveTurn) => {
@@ -51,26 +57,31 @@ async function acquireCompiler(root: string, sourcePaths: string[]) {
51
57
  compilerCloseTimer = null
52
58
  }
53
59
  if (compilerClose) await compilerClose
54
- if (compiler && compiler.root !== root) {
60
+ // configured projects cache their root membership. reopen after generation
61
+ // adds files so a following run includes those outputs in its new program.
62
+ if (
63
+ compiler &&
64
+ (tsconfig || compiler.root !== root || compiler.tsconfig !== tsconfig)
65
+ ) {
55
66
  const previous = compiler
56
67
  compiler = null
57
68
  await closeCompiler(previous)
58
69
  }
59
- compiler ??= { api: new API({ cwd: root }), paths: new Set(), root }
70
+ compiler ??= { api: new API({ cwd: root }), paths: new Set(), root, tsconfig }
60
71
  compiler.api.clearSourceFileCache()
61
72
  const previousPaths = compiler.paths
62
73
  const nextPaths = new Set(sourcePaths)
63
74
  compiler.paths = nextPaths
64
75
  if (previousPaths.size > 0) {
65
76
  const closedSnapshot = await compiler.api.updateSnapshot({
66
- closeFiles: [...previousPaths],
77
+ ...(tsconfig ? { closeProjects: [tsconfig] } : { closeFiles: [...previousPaths] }),
67
78
  fileChanges: { invalidateAll: true },
68
79
  })
69
80
  await closedSnapshot.dispose()
70
81
  compiler.api.clearSourceFileCache()
71
82
  }
72
83
  const snapshot = await compiler.api.updateSnapshot({
73
- openFiles: sourcePaths,
84
+ ...(tsconfig ? { openProjects: [tsconfig] } : { openFiles: sourcePaths }),
74
85
  fileChanges: { invalidateAll: true },
75
86
  })
76
87
  compiler.api.clearSourceFileCache()
@@ -115,25 +126,30 @@ export function collectTypeScriptSourcePaths(roots: string[]): string[] {
115
126
 
116
127
  export async function createNativeTypeScriptProject(
117
128
  root: string,
118
- sourcePaths: string[]
129
+ sourcePaths: string[],
130
+ options: { deferSources?: boolean; tsconfig?: string } = {}
119
131
  ): Promise<NativeTypeScriptProject> {
120
132
  if (sourcePaths.length === 0) {
121
133
  throw new Error(`[on-zero] no TypeScript source files found under ${root}`)
122
134
  }
123
135
 
124
- const { session, snapshot, releaseTurn } = await acquireCompiler(root, sourcePaths)
136
+ const { session, snapshot, releaseTurn } = await acquireCompiler(
137
+ root,
138
+ sourcePaths,
139
+ options.tsconfig
140
+ )
125
141
  const sources = new Map<string, SourceFile>()
126
142
  const projectsByFile = new Map<string, Project>()
127
143
  const parseErrors = new Set<string>()
128
144
 
129
- try {
145
+ const loadSources = async () => {
130
146
  for (const path of sourcePaths) {
131
- const project = await snapshot.getDefaultProjectForFile(path)
147
+ if (sources.has(path)) continue
148
+ const project = projectsByFile.get(path)
132
149
  if (!project) throw new Error(`[on-zero] TypeScript did not open ${path}`)
133
150
  const source = await project.program.getSourceFile(path)
134
151
  if (!source) throw new Error(`[on-zero] TypeScript did not parse ${path}`)
135
152
  sources.set(path, source)
136
- projectsByFile.set(path, project)
137
153
  try {
138
154
  parse(readFileSync(path, 'utf8'), {
139
155
  plugins: ['jsx', 'typescript'],
@@ -143,6 +159,29 @@ export async function createNativeTypeScriptProject(
143
159
  parseErrors.add(path)
144
160
  }
145
161
  }
162
+ }
163
+
164
+ try {
165
+ const scopedProject = options.tsconfig
166
+ ? snapshot.getProject(options.tsconfig)
167
+ : undefined
168
+ if (options.tsconfig) {
169
+ if (!scopedProject)
170
+ throw new Error(`[on-zero] TypeScript did not open ${options.tsconfig}`)
171
+ const included = new Set(await scopedProject.program.getSourceFileNames())
172
+ for (const path of sourcePaths) {
173
+ if (!included.has(path))
174
+ throw new Error(`[on-zero] generation project does not include ${path}`)
175
+ }
176
+ }
177
+ for (const path of sourcePaths) {
178
+ const project = options.tsconfig
179
+ ? scopedProject
180
+ : await snapshot.getDefaultProjectForFile(path)
181
+ if (!project) throw new Error(`[on-zero] TypeScript did not open ${path}`)
182
+ projectsByFile.set(path, project)
183
+ }
184
+ if (!options.deferSources) await loadSources()
146
185
  } catch (error) {
147
186
  await snapshot.dispose()
148
187
  releaseCompiler(session, releaseTurn)
@@ -151,6 +190,20 @@ export async function createNativeTypeScriptProject(
151
190
 
152
191
  let closed = false
153
192
  return {
193
+ configurationKey: JSON.stringify(
194
+ [...new Map([...projectsByFile.values()].map((p) => [p.id, p])).values()]
195
+ .map((project) => [project.configFileName, project.compilerOptions])
196
+ .sort(([left], [right]) => String(left).localeCompare(String(right)))
197
+ ),
198
+ loadSources,
199
+ async sourceFilePaths() {
200
+ const paths = new Set<string>()
201
+ const projects = new Map([...projectsByFile.values()].map((p) => [p.id, p]))
202
+ for (const project of projects.values()) {
203
+ for (const path of await project.program.getSourceFileNames()) paths.add(path)
204
+ }
205
+ return [...paths].sort()
206
+ },
154
207
  projectForFile(path) {
155
208
  const project = projectsByFile.get(resolve(path))
156
209
  if (!project)
@@ -233,3 +233,30 @@ test('disable=true constructs nothing', () => {
233
233
 
234
234
  expect(fakeZero.instances.length).toBe(0)
235
235
  })
236
+
237
+ test('unmounting one of two overlapping providers keeps the shared instance published', () => {
238
+ const client = makeClient()
239
+ const first = createRoot(document.createElement('div'))
240
+ root = createRoot(container)
241
+ const mount = (target: Root | null) =>
242
+ act(() => {
243
+ target?.render(
244
+ <client.ProvideZero cacheURL="http://127.0.0.1:7788/zero" userID="shared">
245
+ <span>ok</span>
246
+ </client.ProvideZero>
247
+ )
248
+ })
249
+
250
+ mount(first)
251
+ mount(root)
252
+ expect(fakeZero.instances.length).toBe(1)
253
+
254
+ act(() => first.unmount())
255
+ expect((client.zero as unknown as { clientID: string }).clientID).toBe(
256
+ fakeZero.instances[0].clientID
257
+ )
258
+
259
+ act(() => root?.unmount())
260
+ root = null
261
+ expect(() => client.zero.clientID).toThrow(/Zero instance not initialized/)
262
+ })