@abloatai/ablo 0.36.0 → 0.37.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 (597) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +71 -2013
  3. package/NOTICE +2 -2
  4. package/README.md +25 -71
  5. package/assets/banner.png +0 -0
  6. package/dist/auth.d.ts +2 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +2 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/client.d.ts +3 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +3 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/coordination.d.ts +2 -0
  15. package/dist/coordination.d.ts.map +1 -0
  16. package/dist/coordination.js +2 -0
  17. package/dist/coordination.js.map +1 -0
  18. package/dist/index.d.ts +3 -114
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3 -163
  21. package/dist/index.js.map +1 -0
  22. package/dist/react.d.ts +4 -0
  23. package/dist/react.d.ts.map +1 -0
  24. package/dist/react.js +3 -0
  25. package/dist/react.js.map +1 -0
  26. package/dist/schema.d.ts +2 -0
  27. package/dist/schema.d.ts.map +1 -0
  28. package/dist/schema.js +2 -0
  29. package/dist/schema.js.map +1 -0
  30. package/dist/server.d.ts +2 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +2 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/source-conformance.d.ts +2 -0
  35. package/dist/source-conformance.d.ts.map +1 -0
  36. package/dist/source-conformance.js +2 -0
  37. package/dist/source-conformance.js.map +1 -0
  38. package/dist/source-drizzle.d.ts +2 -0
  39. package/dist/source-drizzle.d.ts.map +1 -0
  40. package/dist/source-drizzle.js +2 -0
  41. package/dist/source-drizzle.js.map +1 -0
  42. package/dist/source-kysely.d.ts +2 -0
  43. package/dist/source-kysely.d.ts.map +1 -0
  44. package/dist/source-kysely.js +2 -0
  45. package/dist/source-kysely.js.map +1 -0
  46. package/dist/source-next.d.ts +2 -0
  47. package/dist/source-next.d.ts.map +1 -0
  48. package/dist/source-next.js +2 -0
  49. package/dist/source-next.js.map +1 -0
  50. package/dist/source.d.ts +2 -0
  51. package/dist/source.d.ts.map +1 -0
  52. package/dist/source.js +2 -0
  53. package/dist/source.js.map +1 -0
  54. package/dist/wire.d.ts +2 -0
  55. package/dist/wire.d.ts.map +1 -0
  56. package/dist/wire.js +2 -0
  57. package/dist/wire.js.map +1 -0
  58. package/docs/agents.md +1 -1
  59. package/docs/api-keys.md +7 -6
  60. package/docs/api.md +10 -10
  61. package/docs/client-behavior.md +5 -5
  62. package/docs/coordination.md +52 -62
  63. package/docs/data-sources.md +1 -1
  64. package/docs/examples/agent-human.md +4 -4
  65. package/docs/examples/ai-sdk-tool.md +1 -1
  66. package/docs/examples/existing-python-backend.md +15 -4
  67. package/docs/examples/nextjs.md +27 -6
  68. package/docs/examples/server-agent.md +2 -2
  69. package/docs/how-it-works.md +4 -4
  70. package/docs/identity.md +2 -1
  71. package/docs/integration-guide.md +24 -13
  72. package/docs/internal/README.md +18 -0
  73. package/docs/internal/agent-fleet-coordination-design.md +171 -0
  74. package/docs/internal/agent-orchestration.md +58 -0
  75. package/docs/internal/commit-identifiers.md +91 -0
  76. package/docs/internal/concurrency-open-decisions.md +37 -0
  77. package/docs/internal/data-source-reverse-channel.md +150 -0
  78. package/docs/internal/per-field-conflict-detection.md +165 -0
  79. package/docs/internal/postgres-replication.md +64 -0
  80. package/docs/internal/serializable-schema.md +119 -0
  81. package/docs/internal/structure.md +32 -0
  82. package/docs/mcp.md +2 -2
  83. package/docs/migration.md +3 -3
  84. package/docs/quickstart.md +2 -2
  85. package/docs/react.md +5 -5
  86. package/docs/schema-contract.md +3 -3
  87. package/docs/sessions.md +91 -37
  88. package/examples/README.md +2 -2
  89. package/examples/data-source/README.md +1 -1
  90. package/examples/data-source/ablo-driver.ts +1 -1
  91. package/examples/data-source/customer-server.ts +1 -1
  92. package/examples/data-source/run.ts +1 -1
  93. package/examples/data-source/schema.ts +1 -1
  94. package/examples/quickstart.ts +2 -2
  95. package/llms.txt +8 -8
  96. package/package.json +63 -166
  97. package/bin/ablo.cjs +0 -39
  98. package/dist/BaseSyncedStore.d.ts +0 -843
  99. package/dist/BaseSyncedStore.js +0 -1971
  100. package/dist/Database.d.ts +0 -323
  101. package/dist/Database.js +0 -1502
  102. package/dist/InstanceCache.d.ts +0 -237
  103. package/dist/InstanceCache.js +0 -1166
  104. package/dist/LazyReferenceCollection.d.ts +0 -177
  105. package/dist/LazyReferenceCollection.js +0 -461
  106. package/dist/Model.d.ts +0 -454
  107. package/dist/Model.js +0 -919
  108. package/dist/ModelRegistry.d.ts +0 -225
  109. package/dist/ModelRegistry.js +0 -539
  110. package/dist/NetworkMonitor.d.ts +0 -28
  111. package/dist/NetworkMonitor.js +0 -79
  112. package/dist/RuntimeContext.d.ts +0 -52
  113. package/dist/RuntimeContext.js +0 -80
  114. package/dist/SyncClient.d.ts +0 -541
  115. package/dist/SyncClient.js +0 -2202
  116. package/dist/adapters/alwaysOnline.d.ts +0 -14
  117. package/dist/adapters/alwaysOnline.js +0 -17
  118. package/dist/adapters/inMemoryStorage.d.ts +0 -31
  119. package/dist/adapters/inMemoryStorage.js +0 -110
  120. package/dist/ai-sdk/coordinatedTool.d.ts +0 -120
  121. package/dist/ai-sdk/coordinatedTool.js +0 -134
  122. package/dist/ai-sdk/coordinationContext.d.ts +0 -46
  123. package/dist/ai-sdk/coordinationContext.js +0 -106
  124. package/dist/ai-sdk/index.d.ts +0 -121
  125. package/dist/ai-sdk/index.js +0 -121
  126. package/dist/ai-sdk/wrap.d.ts +0 -65
  127. package/dist/ai-sdk/wrap.js +0 -39
  128. package/dist/auth/index.d.ts +0 -1
  129. package/dist/auth/index.js +0 -8
  130. package/dist/batching/index.d.ts +0 -55
  131. package/dist/batching/index.js +0 -147
  132. package/dist/client/Ablo.d.ts +0 -231
  133. package/dist/client/Ablo.js +0 -160
  134. package/dist/client/abloClient.d.ts +0 -309
  135. package/dist/client/abloClient.js +0 -13
  136. package/dist/client/clientPrelude.d.ts +0 -52
  137. package/dist/client/clientPrelude.js +0 -60
  138. package/dist/client/consoleLogger.d.ts +0 -35
  139. package/dist/client/consoleLogger.js +0 -44
  140. package/dist/client/coreClient.d.ts +0 -60
  141. package/dist/client/coreClient.js +0 -118
  142. package/dist/client/createInternalComponents.d.ts +0 -50
  143. package/dist/client/createInternalComponents.js +0 -98
  144. package/dist/client/createModelProxy.d.ts +0 -248
  145. package/dist/client/createModelProxy.js +0 -884
  146. package/dist/client/humans.d.ts +0 -69
  147. package/dist/client/humans.js +0 -78
  148. package/dist/client/modelRegistration.d.ts +0 -10
  149. package/dist/client/modelRegistration.js +0 -312
  150. package/dist/client/options.d.ts +0 -461
  151. package/dist/client/options.js +0 -7
  152. package/dist/client/reactiveEngine.d.ts +0 -53
  153. package/dist/client/reactiveEngine.js +0 -688
  154. package/dist/client/resourceTypes.d.ts +0 -12
  155. package/dist/client/resourceTypes.js +0 -10
  156. package/dist/client/schemaConfig.d.ts +0 -44
  157. package/dist/client/schemaConfig.js +0 -185
  158. package/dist/client/storeCluster.d.ts +0 -47
  159. package/dist/client/storeCluster.js +0 -118
  160. package/dist/client/storeLifecycle.d.ts +0 -61
  161. package/dist/client/storeLifecycle.js +0 -231
  162. package/dist/client/validateAbloOptions.d.ts +0 -42
  163. package/dist/client/validateAbloOptions.js +0 -43
  164. package/dist/client/wsMutationExecutor.d.ts +0 -27
  165. package/dist/client/wsMutationExecutor.js +0 -72
  166. package/dist/context.d.ts +0 -42
  167. package/dist/context.js +0 -81
  168. package/dist/coordination/ClaimLog.d.ts +0 -26
  169. package/dist/coordination/ClaimLog.js +0 -32
  170. package/dist/coordination/index.d.ts +0 -1
  171. package/dist/coordination/index.js +0 -8
  172. package/dist/core/index.d.ts +0 -33
  173. package/dist/core/index.js +0 -48
  174. package/dist/docs/catalog.d.ts +0 -72
  175. package/dist/docs/catalog.js +0 -230
  176. package/dist/docs/index.d.ts +0 -10
  177. package/dist/docs/index.js +0 -10
  178. package/dist/environment.d.ts +0 -1
  179. package/dist/environment.js +0 -8
  180. package/dist/interfaces/index.d.ts +0 -311
  181. package/dist/interfaces/index.js +0 -9
  182. package/dist/keys/index.d.ts +0 -1
  183. package/dist/keys/index.js +0 -8
  184. package/dist/mutators/RecordingMutation.d.ts +0 -36
  185. package/dist/mutators/RecordingMutation.js +0 -182
  186. package/dist/mutators/Transaction.d.ts +0 -40
  187. package/dist/mutators/Transaction.js +0 -58
  188. package/dist/mutators/UndoManager.d.ts +0 -258
  189. package/dist/mutators/UndoManager.js +0 -658
  190. package/dist/mutators/defineMutators.d.ts +0 -60
  191. package/dist/mutators/defineMutators.js +0 -18
  192. package/dist/mutators/inverseOp.d.ts +0 -126
  193. package/dist/mutators/inverseOp.js +0 -71
  194. package/dist/mutators/mutateActions.d.ts +0 -45
  195. package/dist/mutators/mutateActions.js +0 -105
  196. package/dist/mutators/readerActions.d.ts +0 -33
  197. package/dist/mutators/readerActions.js +0 -57
  198. package/dist/mutators/undoApply.d.ts +0 -51
  199. package/dist/mutators/undoApply.js +0 -117
  200. package/dist/policy/index.d.ts +0 -21
  201. package/dist/policy/index.js +0 -20
  202. package/dist/query/QueryProcessor.d.ts +0 -75
  203. package/dist/query/QueryProcessor.js +0 -255
  204. package/dist/query/client.d.ts +0 -64
  205. package/dist/query/client.js +0 -138
  206. package/dist/query/types.d.ts +0 -85
  207. package/dist/query/types.js +0 -16
  208. package/dist/react/AbloProvider.d.ts +0 -242
  209. package/dist/react/AbloProvider.js +0 -458
  210. package/dist/react/ClientSideSuspense.d.ts +0 -36
  211. package/dist/react/ClientSideSuspense.js +0 -17
  212. package/dist/react/DefaultFallback.d.ts +0 -24
  213. package/dist/react/DefaultFallback.js +0 -43
  214. package/dist/react/context.d.ts +0 -55
  215. package/dist/react/context.js +0 -29
  216. package/dist/react/createAbloReact.d.ts +0 -56
  217. package/dist/react/createAbloReact.js +0 -51
  218. package/dist/react/index.d.ts +0 -62
  219. package/dist/react/index.js +0 -69
  220. package/dist/react/internalContext.d.ts +0 -33
  221. package/dist/react/internalContext.js +0 -3
  222. package/dist/react/useAblo.d.ts +0 -82
  223. package/dist/react/useAblo.js +0 -120
  224. package/dist/react/useCurrentUserId.d.ts +0 -22
  225. package/dist/react/useCurrentUserId.js +0 -34
  226. package/dist/react/useErrorListener.d.ts +0 -20
  227. package/dist/react/useErrorListener.js +0 -38
  228. package/dist/react/useMutationFailureListener.d.ts +0 -26
  229. package/dist/react/useMutationFailureListener.js +0 -38
  230. package/dist/react/useMutators.d.ts +0 -56
  231. package/dist/react/useMutators.js +0 -84
  232. package/dist/react/useReactive.d.ts +0 -35
  233. package/dist/react/useReactive.js +0 -123
  234. package/dist/react/useSyncStatus.d.ts +0 -59
  235. package/dist/react/useSyncStatus.js +0 -76
  236. package/dist/react/useUndoScope.d.ts +0 -34
  237. package/dist/react/useUndoScope.js +0 -81
  238. package/dist/schema/coordination.d.ts +0 -112
  239. package/dist/schema/coordination.js +0 -133
  240. package/dist/schema/ddl.d.ts +0 -97
  241. package/dist/schema/ddl.js +0 -491
  242. package/dist/schema/ddlLock.d.ts +0 -35
  243. package/dist/schema/ddlLock.js +0 -46
  244. package/dist/schema/diff.d.ts +0 -225
  245. package/dist/schema/diff.js +0 -289
  246. package/dist/schema/generate.d.ts +0 -19
  247. package/dist/schema/generate.js +0 -86
  248. package/dist/schema/index.d.ts +0 -42
  249. package/dist/schema/index.js +0 -80
  250. package/dist/schema/queries.d.ts +0 -201
  251. package/dist/schema/queries.js +0 -144
  252. package/dist/schema/select.d.ts +0 -40
  253. package/dist/schema/select.js +0 -90
  254. package/dist/schema/serialize.d.ts +0 -115
  255. package/dist/schema/serialize.js +0 -265
  256. package/dist/schema/sugar.d.ts +0 -109
  257. package/dist/schema/sugar.js +0 -83
  258. package/dist/schema/syncDeltaRow.d.ts +0 -6
  259. package/dist/schema/syncDeltaRow.js +0 -6
  260. package/dist/server/adapter.d.ts +0 -173
  261. package/dist/server/adapter.js +0 -18
  262. package/dist/server/commit.d.ts +0 -107
  263. package/dist/server/commit.js +0 -1
  264. package/dist/server/index.d.ts +0 -14
  265. package/dist/server/index.js +0 -2
  266. package/dist/server/readConfig.d.ts +0 -80
  267. package/dist/server/readConfig.js +0 -8
  268. package/dist/server/storageMode.d.ts +0 -23
  269. package/dist/server/storageMode.js +0 -17
  270. package/dist/source/adapter.d.ts +0 -83
  271. package/dist/source/adapter.js +0 -24
  272. package/dist/source/adapters/drizzle.d.ts +0 -48
  273. package/dist/source/adapters/drizzle.js +0 -219
  274. package/dist/source/adapters/kysely.d.ts +0 -42
  275. package/dist/source/adapters/kysely.js +0 -205
  276. package/dist/source/adapters/kyselyMutationCore.d.ts +0 -76
  277. package/dist/source/adapters/kyselyMutationCore.js +0 -125
  278. package/dist/source/adapters/memory.d.ts +0 -13
  279. package/dist/source/adapters/memory.js +0 -130
  280. package/dist/source/adapters/prisma.d.ts +0 -63
  281. package/dist/source/adapters/prisma.js +0 -202
  282. package/dist/source/conformance.d.ts +0 -37
  283. package/dist/source/conformance.js +0 -215
  284. package/dist/source/connector.d.ts +0 -95
  285. package/dist/source/connector.js +0 -266
  286. package/dist/source/connectorProtocol.d.ts +0 -154
  287. package/dist/source/connectorProtocol.js +0 -163
  288. package/dist/source/contract.d.ts +0 -195
  289. package/dist/source/contract.js +0 -164
  290. package/dist/source/factory.d.ts +0 -92
  291. package/dist/source/factory.js +0 -286
  292. package/dist/source/idempotency.d.ts +0 -61
  293. package/dist/source/idempotency.js +0 -144
  294. package/dist/source/index.d.ts +0 -23
  295. package/dist/source/index.js +0 -30
  296. package/dist/source/migrations.d.ts +0 -21
  297. package/dist/source/migrations.js +0 -103
  298. package/dist/source/next.d.ts +0 -32
  299. package/dist/source/next.js +0 -25
  300. package/dist/source/pushQueue.d.ts +0 -134
  301. package/dist/source/pushQueue.js +0 -256
  302. package/dist/source/signing.d.ts +0 -92
  303. package/dist/source/signing.js +0 -162
  304. package/dist/source/types.d.ts +0 -401
  305. package/dist/source/types.js +0 -59
  306. package/dist/storeContract.d.ts +0 -145
  307. package/dist/storeContract.js +0 -12
  308. package/dist/stores/DatabaseManager.d.ts +0 -107
  309. package/dist/stores/DatabaseManager.js +0 -388
  310. package/dist/stores/ObjectStore.d.ts +0 -115
  311. package/dist/stores/ObjectStore.js +0 -393
  312. package/dist/stores/ObjectStoreContract.d.ts +0 -38
  313. package/dist/stores/ObjectStoreContract.js +0 -1
  314. package/dist/stores/StoreManager.d.ts +0 -114
  315. package/dist/stores/StoreManager.js +0 -304
  316. package/dist/stores/SyncActionStore.d.ts +0 -99
  317. package/dist/stores/SyncActionStore.js +0 -506
  318. package/dist/stores/openIDBWithTimeout.d.ts +0 -65
  319. package/dist/stores/openIDBWithTimeout.js +0 -153
  320. package/dist/stores/syncAction.d.ts +0 -26
  321. package/dist/stores/syncAction.js +0 -16
  322. package/dist/surface.d.ts +0 -36
  323. package/dist/surface.js +0 -75
  324. package/dist/sync/BootstrapFetcher.d.ts +0 -284
  325. package/dist/sync/BootstrapFetcher.js +0 -964
  326. package/dist/sync/ConnectionManager.d.ts +0 -8
  327. package/dist/sync/ConnectionManager.js +0 -8
  328. package/dist/sync/OnDemandLoader.d.ts +0 -231
  329. package/dist/sync/OnDemandLoader.js +0 -743
  330. package/dist/sync/SubscriptionManager.d.ts +0 -159
  331. package/dist/sync/SubscriptionManager.js +0 -243
  332. package/dist/sync/SyncWebSocket.d.ts +0 -173
  333. package/dist/sync/SyncWebSocket.js +0 -438
  334. package/dist/sync/awaitClaimGrant.d.ts +0 -6
  335. package/dist/sync/awaitClaimGrant.js +0 -6
  336. package/dist/sync/bootstrapApply.d.ts +0 -73
  337. package/dist/sync/bootstrapApply.js +0 -73
  338. package/dist/sync/commitFrames.d.ts +0 -8
  339. package/dist/sync/commitFrames.js +0 -8
  340. package/dist/sync/contextPorts.d.ts +0 -18
  341. package/dist/sync/contextPorts.js +0 -31
  342. package/dist/sync/createClaimStream.d.ts +0 -7
  343. package/dist/sync/createClaimStream.js +0 -7
  344. package/dist/sync/createPresenceStream.d.ts +0 -69
  345. package/dist/sync/createPresenceStream.js +0 -200
  346. package/dist/sync/createSnapshot.d.ts +0 -29
  347. package/dist/sync/createSnapshot.js +0 -118
  348. package/dist/sync/credentialLifecycle.d.ts +0 -7
  349. package/dist/sync/credentialLifecycle.js +0 -7
  350. package/dist/sync/deltaPipeline.d.ts +0 -114
  351. package/dist/sync/deltaPipeline.js +0 -278
  352. package/dist/sync/groupChange.d.ts +0 -116
  353. package/dist/sync/groupChange.js +0 -244
  354. package/dist/sync/participants.d.ts +0 -132
  355. package/dist/sync/participants.js +0 -346
  356. package/dist/sync/persistedPrefix.d.ts +0 -12
  357. package/dist/sync/persistedPrefix.js +0 -22
  358. package/dist/sync/schemaDrift.d.ts +0 -55
  359. package/dist/sync/schemaDrift.js +0 -53
  360. package/dist/sync/schemas.d.ts +0 -71
  361. package/dist/sync/schemas.js +0 -94
  362. package/dist/sync/syncCursor.d.ts +0 -40
  363. package/dist/sync/syncCursor.js +0 -55
  364. package/dist/sync/syncPlan.d.ts +0 -54
  365. package/dist/sync/syncPlan.js +0 -50
  366. package/dist/sync/wsFrameHandlers.d.ts +0 -8
  367. package/dist/sync/wsFrameHandlers.js +0 -8
  368. package/dist/syncLog/contract.d.ts +0 -20
  369. package/dist/syncLog/contract.js +0 -19
  370. package/dist/syncLog/index.d.ts +0 -1
  371. package/dist/syncLog/index.js +0 -1
  372. package/dist/transaction/ablo.d.ts +0 -88
  373. package/dist/transaction/ablo.js +0 -33
  374. package/dist/transaction/auth/apiKey.d.ts +0 -152
  375. package/dist/transaction/auth/apiKey.js +0 -419
  376. package/dist/transaction/auth/bootstrapScope.d.ts +0 -15
  377. package/dist/transaction/auth/bootstrapScope.js +0 -1
  378. package/dist/transaction/auth/capability.d.ts +0 -212
  379. package/dist/transaction/auth/capability.js +0 -224
  380. package/dist/transaction/auth/credentialEndpoint.d.ts +0 -61
  381. package/dist/transaction/auth/credentialEndpoint.js +0 -86
  382. package/dist/transaction/auth/credentialPolicy.d.ts +0 -148
  383. package/dist/transaction/auth/credentialPolicy.js +0 -125
  384. package/dist/transaction/auth/credentialSource.d.ts +0 -30
  385. package/dist/transaction/auth/credentialSource.js +0 -55
  386. package/dist/transaction/auth/hostedEndpoints.d.ts +0 -21
  387. package/dist/transaction/auth/hostedEndpoints.js +0 -21
  388. package/dist/transaction/auth/identity.d.ts +0 -55
  389. package/dist/transaction/auth/identity.js +0 -210
  390. package/dist/transaction/auth/index.d.ts +0 -162
  391. package/dist/transaction/auth/index.js +0 -304
  392. package/dist/transaction/auth/schemas.d.ts +0 -59
  393. package/dist/transaction/auth/schemas.js +0 -85
  394. package/dist/transaction/auth/sessionMint.d.ts +0 -28
  395. package/dist/transaction/auth/sessionMint.js +0 -85
  396. package/dist/transaction/coordination/awaitClaimGrant.d.ts +0 -56
  397. package/dist/transaction/coordination/awaitClaimGrant.js +0 -124
  398. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +0 -84
  399. package/dist/transaction/coordination/claimHeartbeatLoop.js +0 -108
  400. package/dist/transaction/coordination/claimMeta.d.ts +0 -49
  401. package/dist/transaction/coordination/claimMeta.js +0 -52
  402. package/dist/transaction/coordination/createClaimStream.d.ts +0 -64
  403. package/dist/transaction/coordination/createClaimStream.js +0 -475
  404. package/dist/transaction/coordination/events.d.ts +0 -74
  405. package/dist/transaction/coordination/events.js +0 -7
  406. package/dist/transaction/coordination/index.d.ts +0 -19
  407. package/dist/transaction/coordination/index.js +0 -45
  408. package/dist/transaction/coordination/locator.d.ts +0 -104
  409. package/dist/transaction/coordination/locator.js +0 -102
  410. package/dist/transaction/coordination/schema.d.ts +0 -1536
  411. package/dist/transaction/coordination/schema.js +0 -1177
  412. package/dist/transaction/coordination/targetConflict.d.ts +0 -2
  413. package/dist/transaction/coordination/targetConflict.js +0 -107
  414. package/dist/transaction/coordination/trace.d.ts +0 -78
  415. package/dist/transaction/coordination/trace.js +0 -138
  416. package/dist/transaction/durableWrites.d.ts +0 -62
  417. package/dist/transaction/durableWrites.js +0 -71
  418. package/dist/transaction/environment.d.ts +0 -105
  419. package/dist/transaction/environment.js +0 -108
  420. package/dist/transaction/errorCodes.d.ts +0 -403
  421. package/dist/transaction/errorCodes.js +0 -484
  422. package/dist/transaction/errors.d.ts +0 -428
  423. package/dist/transaction/errors.js +0 -686
  424. package/dist/transaction/footprint.d.ts +0 -111
  425. package/dist/transaction/footprint.js +0 -0
  426. package/dist/transaction/index.d.ts +0 -20
  427. package/dist/transaction/index.js +0 -20
  428. package/dist/transaction/keys/index.d.ts +0 -87
  429. package/dist/transaction/keys/index.js +0 -207
  430. package/dist/transaction/log/syncDeltaRow.d.ts +0 -158
  431. package/dist/transaction/log/syncDeltaRow.js +0 -95
  432. package/dist/transaction/logPosition.d.ts +0 -97
  433. package/dist/transaction/logPosition.js +0 -125
  434. package/dist/transaction/logger.d.ts +0 -16
  435. package/dist/transaction/logger.js +0 -7
  436. package/dist/transaction/observability.d.ts +0 -53
  437. package/dist/transaction/observability.js +0 -19
  438. package/dist/transaction/persistence.d.ts +0 -12
  439. package/dist/transaction/persistence.js +0 -11
  440. package/dist/transaction/plugin.d.ts +0 -285
  441. package/dist/transaction/plugin.js +0 -106
  442. package/dist/transaction/policy/types.d.ts +0 -217
  443. package/dist/transaction/policy/types.js +0 -126
  444. package/dist/transaction/resources/functionalUpdate.d.ts +0 -79
  445. package/dist/transaction/resources/functionalUpdate.js +0 -87
  446. package/dist/transaction/resources/httpResources.d.ts +0 -321
  447. package/dist/transaction/resources/httpResources.js +0 -7
  448. package/dist/transaction/resources/modelOperations.d.ts +0 -427
  449. package/dist/transaction/resources/modelOperations.js +0 -12
  450. package/dist/transaction/resources/mutationOptions.d.ts +0 -66
  451. package/dist/transaction/resources/mutationOptions.js +0 -9
  452. package/dist/transaction/resources/where.d.ts +0 -101
  453. package/dist/transaction/resources/where.js +0 -115
  454. package/dist/transaction/resources/writeOptionsSchema.d.ts +0 -47
  455. package/dist/transaction/resources/writeOptionsSchema.js +0 -73
  456. package/dist/transaction/schema/field.d.ts +0 -120
  457. package/dist/transaction/schema/field.js +0 -265
  458. package/dist/transaction/schema/fieldRef.d.ts +0 -38
  459. package/dist/transaction/schema/fieldRef.js +0 -11
  460. package/dist/transaction/schema/loadStrategy.d.ts +0 -45
  461. package/dist/transaction/schema/loadStrategy.js +0 -46
  462. package/dist/transaction/schema/model.d.ts +0 -379
  463. package/dist/transaction/schema/model.js +0 -123
  464. package/dist/transaction/schema/openapi.d.ts +0 -58
  465. package/dist/transaction/schema/openapi.js +0 -501
  466. package/dist/transaction/schema/relation.d.ts +0 -204
  467. package/dist/transaction/schema/relation.js +0 -104
  468. package/dist/transaction/schema/residency.d.ts +0 -29
  469. package/dist/transaction/schema/residency.js +0 -25
  470. package/dist/transaction/schema/roles.d.ts +0 -249
  471. package/dist/transaction/schema/roles.js +0 -230
  472. package/dist/transaction/schema/schema.d.ts +0 -351
  473. package/dist/transaction/schema/schema.js +0 -325
  474. package/dist/transaction/schema/tenancy.d.ts +0 -139
  475. package/dist/transaction/schema/tenancy.js +0 -190
  476. package/dist/transaction/transactionLayer.d.ts +0 -82
  477. package/dist/transaction/transactionLayer.js +0 -24
  478. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +0 -143
  479. package/dist/transaction/transactions/settlement/commitEnvelope.js +0 -161
  480. package/dist/transaction/transactions/settlement/httpCommitEnvelope.d.ts +0 -53
  481. package/dist/transaction/transactions/settlement/httpCommitEnvelope.js +0 -207
  482. package/dist/transaction/transactions/settlement/idempotencyKey.d.ts +0 -10
  483. package/dist/transaction/transactions/settlement/idempotencyKey.js +0 -9
  484. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +0 -112
  485. package/dist/transaction/transactions/settlement/pendingWrite.js +0 -20
  486. package/dist/transaction/transport/commitFrames.d.ts +0 -90
  487. package/dist/transaction/transport/commitFrames.js +0 -134
  488. package/dist/transaction/transport/connectionManager.d.ts +0 -215
  489. package/dist/transaction/transport/connectionManager.js +0 -673
  490. package/dist/transaction/transport/credentialLifecycle.d.ts +0 -177
  491. package/dist/transaction/transport/credentialLifecycle.js +0 -324
  492. package/dist/transaction/transport/heartbeat.d.ts +0 -65
  493. package/dist/transaction/transport/heartbeat.js +0 -93
  494. package/dist/transaction/transport/httpClient.d.ts +0 -131
  495. package/dist/transaction/transport/httpClient.js +0 -146
  496. package/dist/transaction/transport/httpOptions.d.ts +0 -33
  497. package/dist/transaction/transport/httpOptions.js +0 -12
  498. package/dist/transaction/transport/httpTransport.d.ts +0 -8
  499. package/dist/transaction/transport/httpTransport.js +0 -1388
  500. package/dist/transaction/transport/networkProbe.d.ts +0 -84
  501. package/dist/transaction/transport/networkProbe.js +0 -207
  502. package/dist/transaction/transport/wsFrameHandlers.d.ts +0 -128
  503. package/dist/transaction/transport/wsFrameHandlers.js +0 -429
  504. package/dist/transaction/transport/wsTransport.d.ts +0 -574
  505. package/dist/transaction/transport/wsTransport.js +0 -1023
  506. package/dist/transaction/types/assertExact.d.ts +0 -17
  507. package/dist/transaction/types/assertExact.js +0 -1
  508. package/dist/transaction/types/global.d.ts +0 -107
  509. package/dist/transaction/types/global.js +0 -40
  510. package/dist/transaction/types/index.d.ts +0 -205
  511. package/dist/transaction/types/index.js +0 -56
  512. package/dist/transaction/types/modelData.d.ts +0 -10
  513. package/dist/transaction/types/modelData.js +0 -9
  514. package/dist/transaction/types/participant.d.ts +0 -20
  515. package/dist/transaction/types/participant.js +0 -10
  516. package/dist/transaction/types/streams.d.ts +0 -550
  517. package/dist/transaction/types/streams.js +0 -11
  518. package/dist/transaction/utils/asyncIterator.d.ts +0 -34
  519. package/dist/transaction/utils/asyncIterator.js +0 -135
  520. package/dist/transaction/utils/duration.d.ts +0 -50
  521. package/dist/transaction/utils/duration.js +0 -77
  522. package/dist/transaction/utils/json.d.ts +0 -57
  523. package/dist/transaction/utils/json.js +0 -276
  524. package/dist/transaction/wire/accountResponses.d.ts +0 -420
  525. package/dist/transaction/wire/accountResponses.js +0 -290
  526. package/dist/transaction/wire/auth.d.ts +0 -56
  527. package/dist/transaction/wire/auth.js +0 -63
  528. package/dist/transaction/wire/bootstrapReason.d.ts +0 -9
  529. package/dist/transaction/wire/bootstrapReason.js +0 -8
  530. package/dist/transaction/wire/claimEvent.d.ts +0 -76
  531. package/dist/transaction/wire/claimEvent.js +0 -73
  532. package/dist/transaction/wire/claims.d.ts +0 -530
  533. package/dist/transaction/wire/claims.js +0 -327
  534. package/dist/transaction/wire/commit.d.ts +0 -603
  535. package/dist/transaction/wire/commit.js +0 -321
  536. package/dist/transaction/wire/delta.d.ts +0 -250
  537. package/dist/transaction/wire/delta.js +0 -147
  538. package/dist/transaction/wire/errorEnvelope.d.ts +0 -72
  539. package/dist/transaction/wire/errorEnvelope.js +0 -123
  540. package/dist/transaction/wire/feedCursor.d.ts +0 -60
  541. package/dist/transaction/wire/feedCursor.js +0 -82
  542. package/dist/transaction/wire/feedEvent.d.ts +0 -204
  543. package/dist/transaction/wire/feedEvent.js +0 -65
  544. package/dist/transaction/wire/frames.d.ts +0 -194
  545. package/dist/transaction/wire/frames.js +0 -50
  546. package/dist/transaction/wire/inboundFrames.d.ts +0 -562
  547. package/dist/transaction/wire/inboundFrames.js +0 -116
  548. package/dist/transaction/wire/index.d.ts +0 -54
  549. package/dist/transaction/wire/index.js +0 -83
  550. package/dist/transaction/wire/listEnvelope.d.ts +0 -37
  551. package/dist/transaction/wire/listEnvelope.js +0 -42
  552. package/dist/transaction/wire/modelMutations.d.ts +0 -31
  553. package/dist/transaction/wire/modelMutations.js +0 -52
  554. package/dist/transaction/wire/modelResponses.d.ts +0 -85
  555. package/dist/transaction/wire/modelResponses.js +0 -43
  556. package/dist/transaction/wire/modelShape.d.ts +0 -78
  557. package/dist/transaction/wire/modelShape.js +0 -74
  558. package/dist/transaction/wire/protocol.d.ts +0 -38
  559. package/dist/transaction/wire/protocol.js +0 -38
  560. package/dist/transaction/wire/protocolVersion.d.ts +0 -73
  561. package/dist/transaction/wire/protocolVersion.js +0 -83
  562. package/dist/transactions/mutations/MutationQueue.d.ts +0 -661
  563. package/dist/transactions/mutations/MutationQueue.js +0 -2807
  564. package/dist/transactions/mutations/MutationStore.d.ts +0 -20
  565. package/dist/transactions/mutations/MutationStore.js +0 -53
  566. package/dist/transactions/mutations/UnconfirmedWrites.d.ts +0 -82
  567. package/dist/transactions/mutations/UnconfirmedWrites.js +0 -104
  568. package/dist/transactions/mutations/coalesceRules.d.ts +0 -58
  569. package/dist/transactions/mutations/coalesceRules.js +0 -140
  570. package/dist/transactions/mutations/commitLatency.d.ts +0 -52
  571. package/dist/transactions/mutations/commitLatency.js +0 -130
  572. package/dist/transactions/mutations/commitOutboxStore.d.ts +0 -28
  573. package/dist/transactions/mutations/commitOutboxStore.js +0 -26
  574. package/dist/transactions/mutations/commitPayload.d.ts +0 -165
  575. package/dist/transactions/mutations/commitPayload.js +0 -152
  576. package/dist/transactions/mutations/deltaConfirmation.d.ts +0 -63
  577. package/dist/transactions/mutations/deltaConfirmation.js +0 -235
  578. package/dist/transactions/mutations/durableWriteStore.d.ts +0 -14
  579. package/dist/transactions/mutations/durableWriteStore.js +0 -12
  580. package/dist/transactions/mutations/optimisticApply.d.ts +0 -49
  581. package/dist/transactions/mutations/optimisticApply.js +0 -65
  582. package/dist/transactions/mutations/replayValidation.d.ts +0 -187
  583. package/dist/transactions/mutations/replayValidation.js +0 -164
  584. package/dist/utils/mobxSetup.d.ts +0 -53
  585. package/dist/utils/mobxSetup.js +0 -330
  586. package/dist/views/QueryView.d.ts +0 -79
  587. package/dist/views/QueryView.js +0 -218
  588. package/dist/views/ViewRegistry.d.ts +0 -20
  589. package/dist/views/ViewRegistry.js +0 -55
  590. package/dist/views/incrementalView.d.ts +0 -45
  591. package/dist/views/incrementalView.js +0 -69
  592. package/dist/webhooks/events.d.ts +0 -43
  593. package/dist/webhooks/events.js +0 -42
  594. package/dist/webhooks/index.d.ts +0 -8
  595. package/dist/webhooks/index.js +0 -8
  596. package/dist/wire/index.d.ts +0 -1
  597. package/dist/wire/index.js +0 -8
@@ -1,1166 +0,0 @@
1
- /**
2
- * InstanceCache is the in-memory cache of live model instances, keyed by id and
3
- * deduplicated so each entity has a single instance. It maintains type and
4
- * foreign-key indexes for fast lookups, evicts entries under a size cap, and
5
- * notifies subscribers and query views as models are added, updated, or
6
- * removed. It holds a {@link ModelRegistry} to map between model names and
7
- * constructor classes, but performs no persistence of its own.
8
- */
9
- import { makeObservable, observable, action, computed, runInAction } from 'mobx';
10
- import { Model } from './Model.js';
11
- import { ModelRegistry } from './ModelRegistry.js';
12
- import { globalRuntime } from './context.js';
13
- import { AbloValidationError } from './transaction/errors.js';
14
- import { ModelScope } from './transaction/types/index.js';
15
- import { ViewRegistry } from './views/ViewRegistry.js';
16
- import { QueryView } from './views/QueryView.js';
17
- // Re-exported so `import { ModelScope } from './InstanceCache.js'` resolves
18
- export { ModelScope };
19
- /**
20
- * The in-memory cache of model instances, keyed by id and deduplicated so each
21
- * entity resolves to a single instance.
22
- */
23
- export class InstanceCache {
24
- // Single source of truth for all models (observable for reactivity)
25
- entries = observable.map();
26
- typeIndex = observable.map();
27
- // Non-observable access time tracking — kept outside observable.map so that
28
- // updating timestamps in get() during React render does NOT trigger MobX
29
- // reactions (which would cause infinite re-render loops).
30
- accessTimes = new Map();
31
- // Deduplication tracking
32
- recentAdditions = new Map(); // "modelType:modelId" -> timestamp
33
- deltaHistory = new Map();
34
- // No intermediate cache layer — getByType() reads typeIndex and entries
35
- // directly. Both are observable, so the data structures are themselves the
36
- // reactivity source; there are no computed getters with conditional cache
37
- // invalidation to get wrong.
38
- // Foreign key indexes: Map<"ModelType:fieldName", Map<fieldValue, ObservableSet<modelId>>>
39
- // Enables O(1) lookups like "all Block models where sectionId = X"
40
- // instead of scanning all models of a type and filtering.
41
- foreignKeyIndexes = new Map();
42
- // Registry of which fields to index: Map<modelName, fieldName[]>
43
- foreignKeyConfig = new Map();
44
- // Performance tracking
45
- metrics = {
46
- hits: 0,
47
- misses: 0,
48
- evictions: 0,
49
- additions: 0,
50
- duplicatesSkipped: 0,
51
- };
52
- // Configuration
53
- config;
54
- runtime;
55
- gcTimer;
56
- // ModelRegistry instance — single source of truth for model metadata
57
- registry;
58
- // ViewRegistry — tracks active QueryViews for incremental view maintenance
59
- viewRegistry = new ViewRegistry();
60
- // Subscription registry
61
- subscriptions = new Map();
62
- /**
63
- * Subscribe to updates for a specific model type.
64
- */
65
- subscribe(modelClass, callback) {
66
- const modelName = this.registry.getModelNameFromConstructor(modelClass);
67
- if (!modelName) {
68
- throw new AbloValidationError(`Model class not registered: ${modelClass.name}`, {
69
- code: 'pool_subscribe_unregistered',
70
- });
71
- }
72
- let subs = this.subscriptions.get(modelName);
73
- if (!subs) {
74
- subs = new Set();
75
- this.subscriptions.set(modelName, subs);
76
- }
77
- const erased = callback;
78
- subs.add(erased);
79
- return () => subs.delete(erased);
80
- }
81
- notifySubscribers(model) {
82
- const modelName = model.getModelName();
83
- const subs = this.subscriptions.get(modelName);
84
- if (subs) {
85
- for (const callback of subs) {
86
- callback(model);
87
- }
88
- }
89
- }
90
- constructor(config = {}, modelRegistry) {
91
- this.config = {
92
- maxSize: config.maxSize ?? 10000,
93
- // Idle-eviction disabled by default. The 5-minute default used to
94
- // live here, but with schema-driven dynamic classes not
95
- // registering `LazyReferenceCollection`s, the
96
- // `hasObservedCollections()` guard in gc() didn't fire for most
97
- // actively-rendered models — and they'd evict out from under a
98
- // user whose tab sat for 10 minutes. Memory pressure relief is
99
- // handled by the `maxSize` LRU cap (see `evictOldest`), which is
100
- // the bound that actually matches usage: "keep the most recent N
101
- // entities, not the entities touched in the last N minutes."
102
- //
103
- // Callers who genuinely want time-based eviction can pass an
104
- // explicit `maxAge`. Leaving the default at Infinity keeps
105
- // correctness as the default and makes aggressive GC an opt-in.
106
- maxAge: config.maxAge ?? Number.POSITIVE_INFINITY,
107
- gcInterval: config.gcInterval ?? 60000, // 1 minute
108
- useWeakRefs: config.useWeakRefs ?? true,
109
- };
110
- this.runtime = config.runtime ?? globalRuntime;
111
- // Store the model registry reference
112
- if (!modelRegistry) {
113
- throw new AbloValidationError('InstanceCache requires ModelRegistry for production-safe model name lookup', { code: 'pool_registry_missing' });
114
- }
115
- this.registry = modelRegistry;
116
- // Type indexes are initialized on first use, so models can be registered
117
- // after the InstanceCache is created; the first getByType call builds them.
118
- // No computed cache layer: entries and typeIndex are both observable, and
119
- // getByType() reads them directly, so MobX always tracks the dependency.
120
- makeObservable(this, {
121
- add: action,
122
- addBatch: action,
123
- upsertBatch: action,
124
- removeBatch: action,
125
- addToArchive: action,
126
- remove: action,
127
- removeFromArchive: action,
128
- clear: action,
129
- updateScope: action,
130
- size: computed,
131
- });
132
- this.startGC();
133
- }
134
- // No computed getters — getByType() reads typeIndex and entries directly.
135
- // Both are observable, so MobX always tracks the dependency; there is no
136
- // conditional cache path that could silently drop a dependency.
137
- // There is no cache layer to manage: typeIndex and entries are observable
138
- // and read directly by getByType().
139
- resolveModel(entry, id) {
140
- if (entry.model)
141
- return entry.model;
142
- if (entry.weakRef) {
143
- const model = entry.weakRef.deref();
144
- if (model) {
145
- entry.model = model;
146
- if (id)
147
- this.accessTimes.set(id, Date.now());
148
- return model;
149
- }
150
- }
151
- return undefined;
152
- }
153
- // The type parameter appears only in the return position on purpose: this is
154
- // an ergonomic typed accessor (like Map<K, V>.get) that centralizes what would
155
- // otherwise be an `as T` cast at every call site.
156
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
157
- get(id) {
158
- const entry = this.entries.get(id);
159
- if (!entry) {
160
- runInAction(() => {
161
- this.metrics.misses++;
162
- });
163
- return undefined;
164
- }
165
- let model = entry.model;
166
- if (!model && entry.weakRef) {
167
- const restoredModel = entry.weakRef.deref();
168
- if (!restoredModel) {
169
- runInAction(() => {
170
- this.entries.delete(id);
171
- this.removeFromTypeIndex(id, entry.model?.getModelName());
172
- this.metrics.misses++;
173
- });
174
- return undefined;
175
- }
176
- model = restoredModel;
177
- runInAction(() => {
178
- entry.model = restoredModel;
179
- });
180
- }
181
- // Never return disposed models — they are logically removed and may have
182
- // torn-down internal state. Callers (e.g. flushPendingDeltas) must not
183
- // receive a disposed reference that will throw on updateFromData().
184
- if (model?.disposed) {
185
- return undefined;
186
- }
187
- // Update access time in non-observable map — prevents MobX reactions during render
188
- this.accessTimes.set(id, Date.now());
189
- this.metrics.hits++;
190
- return model ?? undefined;
191
- }
192
- /**
193
- * Look a row up **within one model**.
194
- *
195
- * The pool is a single id space: `get(id)` returns whatever row carries that
196
- * id, whatever model it belongs to. That is the correct storage shape — ids
197
- * are globally unique, the same premise as Relay's Global Object
198
- * Identification — but it means an *untyped* lookup cannot stand in for a
199
- * typed one. Apollo and EmberData avoid the question by keying their identity
200
- * maps on `Type:id`; with unique ids the equivalent guarantee comes from
201
- * stating the expected model at the lookup instead.
202
- *
203
- * Returns `undefined` for a row belonging to another model: from the asking
204
- * model's perspective that id is simply absent. Callers that must tell "not
205
- * here" apart from "here, but another model's" should compare against
206
- * {@link get}.
207
- *
208
- * Prefer this over `get()` anywhere the caller knows which model it wants —
209
- * `get()` returning another model's row has caused three product bugs, most
210
- * recently a resize gesture that reverted after every commit.
211
- */
212
- // `T` appears only in the return position, which is normally a caller-chosen
213
- // cast in disguise. It is sound here precisely because `modelName` is checked
214
- // at runtime below before the row is handed back, so the caller's expected
215
- // type and the row's registered identity cannot disagree.
216
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
217
- getOfType(id, modelName) {
218
- const model = this.get(id);
219
- if (!model)
220
- return undefined;
221
- // Checked, so the assertion below is sound: `typeIndex` and
222
- // `getModelName()` are the same registered-name identity.
223
- return model.getModelName() === modelName ? model : undefined;
224
- }
225
- /**
226
- * Add model with deduplication support
227
- */
228
- add(model, scope = ModelScope.live, deltaInfo) {
229
- const id = model.id;
230
- const modelType = model.getModelName();
231
- const addKey = `${modelType}:${id}`;
232
- // Ensure type index exists for this model type
233
- if (!this.typeIndex.has(modelType)) {
234
- this.typeIndex.set(modelType, observable.set());
235
- }
236
- // Check if model already exists to prevent duplicates
237
- const existingEntry = this.entries.get(id);
238
- if (existingEntry?.model && !existingEntry.model.disposed) {
239
- // Model already exists and is valid, update its scope if needed
240
- if (existingEntry.scope !== scope) {
241
- runInAction(() => {
242
- this.entries.set(id, { ...existingEntry, scope });
243
- });
244
- this.accessTimes.set(id, Date.now());
245
- }
246
- this.metrics.duplicatesSkipped++;
247
- return;
248
- }
249
- // Check rapid additions (within 50ms) for better deduplication
250
- const lastAdded = this.recentAdditions.get(addKey);
251
- if (lastAdded && Date.now() - lastAdded < 50) {
252
- this.metrics.duplicatesSkipped++;
253
- return;
254
- }
255
- // Check delta history for duplicate processing
256
- if (deltaInfo?.syncId) {
257
- const history = this.deltaHistory.get(addKey);
258
- if (history) {
259
- // Skip if we've already processed a newer or equal sync ID
260
- if (history.lastSyncId >= deltaInfo.syncId) {
261
- this.metrics.duplicatesSkipped++;
262
- return;
263
- }
264
- // Warn about suspicious patterns
265
- if (deltaInfo.action === 'I' &&
266
- (history.lastAction === 'U' || history.lastAction === 'D')) {
267
- // Internal delta-ordering anomaly that reconciles on the next
268
- // catch-up — forensic, not consumer-actionable → debug.
269
- this.runtime.logger.debug(`InstanceCache.add() SUSPICIOUS: INSERT after ${history.lastAction}`, { modelType, id, syncId: deltaInfo.syncId });
270
- }
271
- }
272
- // Update delta history
273
- this.deltaHistory.set(addKey, {
274
- lastAction: deltaInfo.action ?? 'U',
275
- lastSyncId: deltaInfo.syncId,
276
- timestamp: Date.now(),
277
- });
278
- }
279
- // Track this addition
280
- this.recentAdditions.set(addKey, Date.now());
281
- // Clean old tracking entries periodically
282
- if (this.recentAdditions.size > 100) {
283
- this.cleanupTracking();
284
- }
285
- // Note: existingEntry check is now done earlier for better deduplication
286
- if (this.entries.size >= this.config.maxSize) {
287
- this.evictOldest();
288
- }
289
- const entry = {
290
- model,
291
- scope,
292
- };
293
- if (this.config.useWeakRefs && this.isLargeModel(model)) {
294
- entry.weakRef = new WeakRef(model);
295
- }
296
- this.accessTimes.set(id, Date.now());
297
- runInAction(() => {
298
- this.entries.set(id, entry);
299
- this.addToTypeIndex(id, model.getModelName());
300
- this.metrics.additions++;
301
- });
302
- // No cache to invalidate — typeIndex + entries are directly observable
303
- // Notify views of the addition
304
- this.notifySubscribers(model);
305
- this.viewRegistry.notifyAdded(modelType, model);
306
- }
307
- /**
308
- * Upsert a model - INSERT if new, UPDATE if exists
309
- */
310
- upsert(model, scope = ModelScope.live) {
311
- const id = model.id;
312
- const existingEntry = this.entries.get(id);
313
- if (existingEntry?.model && !existingEntry.model.disposed) {
314
- // Model exists - update it in-place
315
- const existingModel = existingEntry.model;
316
- // Skip updateFromData if same instance - preserves _local changes for client mutations
317
- if (model !== existingModel) {
318
- existingModel.updateFromData(model.toJSON());
319
- }
320
- // Update scope if different
321
- if (existingEntry.scope !== scope) {
322
- runInAction(() => {
323
- this.entries.set(id, { ...existingEntry, scope });
324
- });
325
- this.accessTimes.set(id, Date.now());
326
- }
327
- this.notifySubscribers(existingModel);
328
- // Notify views of the update
329
- this.viewRegistry.notifyUpdated(existingModel.getModelName(), existingModel);
330
- }
331
- else {
332
- // Model doesn't exist - add it (add() already notifies views)
333
- this.add(model, scope);
334
- }
335
- }
336
- /**
337
- * Batch add models - optimized for hydration
338
- * All models are added in a single MobX action to minimize reactivity overhead
339
- */
340
- addBatch(models, scope = ModelScope.live) {
341
- if (models.length === 0)
342
- return 0;
343
- let addedCount = 0;
344
- const now = Date.now();
345
- // Process all models in a single action to avoid per-item reaction cycles
346
- for (const model of models) {
347
- const id = model.id;
348
- const modelType = model.getModelName();
349
- // Ensure type index exists
350
- if (!this.typeIndex.has(modelType)) {
351
- this.typeIndex.set(modelType, observable.set());
352
- }
353
- // Skip if model already exists and is valid
354
- const existingEntry = this.entries.get(id);
355
- if (existingEntry?.model && !existingEntry.model.disposed) {
356
- if (existingEntry.scope !== scope) {
357
- this.entries.set(id, { ...existingEntry, scope });
358
- this.accessTimes.set(id, now);
359
- }
360
- this.metrics.duplicatesSkipped++;
361
- continue;
362
- }
363
- // Evict if at capacity
364
- if (this.entries.size >= this.config.maxSize) {
365
- this.evictOldest();
366
- }
367
- const entry = {
368
- model,
369
- scope,
370
- };
371
- this.accessTimes.set(id, now);
372
- if (this.config.useWeakRefs && this.isLargeModel(model)) {
373
- entry.weakRef = new WeakRef(model);
374
- }
375
- this.entries.set(id, entry);
376
- this.addToTypeIndex(id, modelType);
377
- // Populate the foreign-key indexes. The single-item `add()` path
378
- // does this; `addBatch()` used to skip it, which meant every
379
- // block / ledger cell / message that came in through a bulk
380
- // loader (`ensureReportBlocks`, `prefetchSectionBlocks`, bootstrap
381
- // hydration) was in the pool but invisible to `hasMany` lookups
382
- // — `section.blocks` returned `[]` until the user clicked a block
383
- // and something else ran a non-batch `add` that happened to
384
- // populate the FK index as a side effect. The UX symptom was
385
- // "sections show empty until you click on one." Adding this one
386
- // line closes the gap.
387
- this.addToForeignKeyIndex(id, model, modelType);
388
- this.metrics.additions++;
389
- addedCount++;
390
- this.notifySubscribers(model);
391
- // Notify views of the addition
392
- this.viewRegistry.notifyAdded(modelType, model);
393
- }
394
- // No cache to invalidate — typeIndex + entries are directly observable
395
- return addedCount;
396
- }
397
- /**
398
- * Batch upsert models - optimized for delta processing.
399
- * All upserts happen in a single MobX action to minimize reactivity overhead.
400
- */
401
- upsertBatch(models, scope = ModelScope.live) {
402
- if (models.length === 0)
403
- return;
404
- for (const model of models) {
405
- const id = model.id;
406
- const existingEntry = this.entries.get(id);
407
- if (existingEntry?.model && !existingEntry.model.disposed) {
408
- if (model !== existingEntry.model) {
409
- existingEntry.model.updateFromData(model.toJSON());
410
- }
411
- if (existingEntry.scope !== scope) {
412
- this.entries.set(id, { ...existingEntry, scope });
413
- this.accessTimes.set(id, Date.now());
414
- }
415
- this.notifySubscribers(existingEntry.model);
416
- // Notify views of the update
417
- this.viewRegistry.notifyUpdated(existingEntry.model.getModelName(), existingEntry.model);
418
- }
419
- else {
420
- // Delegate to inline add logic (same as addBatch internals)
421
- const modelType = model.getModelName();
422
- if (!this.typeIndex.has(modelType)) {
423
- this.typeIndex.set(modelType, observable.set());
424
- }
425
- if (this.entries.size >= this.config.maxSize) {
426
- this.evictOldest();
427
- }
428
- const entry = { model, scope };
429
- this.accessTimes.set(id, Date.now());
430
- if (this.config.useWeakRefs && this.isLargeModel(model)) {
431
- entry.weakRef = new WeakRef(model);
432
- }
433
- this.entries.set(id, entry);
434
- this.addToTypeIndex(id, modelType);
435
- this.metrics.additions++;
436
- this.notifySubscribers(model);
437
- // Notify views of the addition
438
- this.viewRegistry.notifyAdded(modelType, model);
439
- }
440
- }
441
- // No cache to invalidate — typeIndex + entries are directly observable
442
- }
443
- /**
444
- * Batch remove models by ID - optimized for delta processing.
445
- * All removals happen in a single MobX action to minimize reactivity overhead.
446
- * Returns the number of models actually removed.
447
- */
448
- removeBatch(ids) {
449
- if (ids.length === 0)
450
- return 0;
451
- let removedCount = 0;
452
- for (const id of ids) {
453
- const entry = this.entries.get(id);
454
- if (!entry)
455
- continue;
456
- const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
457
- // FK/type cleanup must run before entries.delete — see `remove()`
458
- // for the full explanation. Same bug, same fix.
459
- this.removeFromTypeIndex(id, modelName);
460
- this.entries.delete(id);
461
- // Notify views of the removal before disposing
462
- if (modelName) {
463
- this.viewRegistry.notifyRemoved(modelName, id);
464
- }
465
- const model = entry.model ?? entry.weakRef?.deref();
466
- // A non-Model object can reach the pool (see `clear`); only dispose a real one.
467
- if (typeof model?.dispose === 'function')
468
- model.dispose();
469
- const addKey = modelName ? `${modelName}:${id}` : id;
470
- this.recentAdditions.delete(addKey);
471
- this.deltaHistory.delete(addKey);
472
- this.accessTimes.delete(id);
473
- removedCount++;
474
- }
475
- // No cache to invalidate — typeIndex + entries are directly observable
476
- return removedCount;
477
- }
478
- /**
479
- * Read-only accessor for entity IDs by model type.
480
- * Used by applyBootstrapToPool() and rehydrateFromDatabase() for ghost detection.
481
- */
482
- getIdsByModelType(modelType) {
483
- return this.typeIndex.get(modelType);
484
- }
485
- addToArchive(model) {
486
- this.add(model, ModelScope.archived);
487
- }
488
- remove(id) {
489
- const entry = this.entries.get(id);
490
- if (!entry)
491
- return false;
492
- const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
493
- // Order matters here: `removeFromTypeIndex` → `removeFromForeignKeyIndex`
494
- // reads the FK field values off the model via `this.entries.get(id)`.
495
- // If we `this.entries.delete(id)` first, the model is gone and the
496
- // FK cleanup silently no-ops — leaving ghost ids in the FK index.
497
- // That causes `getByForeignKey(..., parentId)` to report
498
- // `matched > returned` (dropped-no-entry) and, on the UI, keeps the
499
- // stale block visible until the next reload rebuilds the index
500
- // from fresh data. Do the FK/type cleanup first, then delete the
501
- // entry.
502
- runInAction(() => {
503
- this.removeFromTypeIndex(id, modelName);
504
- this.entries.delete(id);
505
- });
506
- // No cache to invalidate — typeIndex + entries are directly observable
507
- // Notify views of the removal before disposing
508
- if (modelName) {
509
- this.viewRegistry.notifyRemoved(modelName, id);
510
- }
511
- const model = entry.model ?? entry.weakRef?.deref();
512
- // A non-Model object can reach the pool (see `clear`); only dispose a real one.
513
- if (typeof model?.dispose === 'function')
514
- model.dispose();
515
- // Clean tracking
516
- const addKey = modelName ? `${modelName}:${id}` : id;
517
- this.recentAdditions.delete(addKey);
518
- this.deltaHistory.delete(addKey);
519
- this.accessTimes.delete(id);
520
- return true;
521
- }
522
- removeFromArchive(id) {
523
- const entry = this.entries.get(id);
524
- if (entry?.scope !== ModelScope.archived) {
525
- return false;
526
- }
527
- return this.remove(id);
528
- }
529
- getByType(modelClass, scope = ModelScope.all) {
530
- // Read typeIndex and entries directly. Both are observable maps, so MobX
531
- // always tracks the dependency — there is no conditional cache path.
532
- let actualModelName = this.registry.getModelNameFromConstructor(modelClass);
533
- if (!actualModelName) {
534
- actualModelName = this.registry.getModelNameFromConstructor(modelClass);
535
- if (!actualModelName) {
536
- try {
537
- const ConcreteClass = modelClass;
538
- const tempInstance = new ConcreteClass({});
539
- actualModelName = tempInstance.getModelName();
540
- // Fallback resolved — hand-coded class not in registry but name matches.
541
- // This is expected during migration from hand-coded → dynamic models.
542
- }
543
- catch (e) {
544
- this.runtime.observability.breadcrumb(`Failed to create fallback instance for ${modelClass.name}`, 'sync.database', 'error', {
545
- error: e instanceof Error ? e.message : String(e),
546
- });
547
- return [];
548
- }
549
- }
550
- }
551
- // Read from typeIndex (observable) to get IDs for this model type
552
- const ids = this.typeIndex.get(actualModelName || '');
553
- if (!ids || ids.size === 0) {
554
- return [];
555
- }
556
- // Resolve each ID from entries (observable) with scope filtering.
557
- // Note: we do not check `instanceof modelClass`, because schema-generated
558
- // dynamic classes and hand-coded classes are different constructors that
559
- // both represent the same model type. The typeIndex lookup by name is
560
- // authoritative — if the name matched, the model belongs to this type.
561
- const result = [];
562
- for (const id of ids) {
563
- const entry = this.entries.get(id);
564
- if (!entry)
565
- continue;
566
- if (!this.matchesScope(entry.scope, scope))
567
- continue;
568
- const model = this.resolveModel(entry, id);
569
- if (model && !model.disposed) {
570
- result.push(model);
571
- }
572
- }
573
- return result;
574
- }
575
- /**
576
- * Get all models of a given type by string name.
577
- * Used for custom entity types where multiple entity type names share
578
- * the same CustomEntityModel constructor (getByType can't disambiguate).
579
- * Reads from the same typeIndex as getByType — MobX tracks the dependency.
580
- */
581
- getByTypeName(modelName, scope = ModelScope.all) {
582
- const ids = this.typeIndex.get(modelName);
583
- if (!ids || ids.size === 0) {
584
- return [];
585
- }
586
- const result = [];
587
- for (const id of ids) {
588
- const entry = this.entries.get(id);
589
- if (!entry)
590
- continue;
591
- if (!this.matchesScope(entry.scope, scope))
592
- continue;
593
- const model = this.resolveModel(entry, id);
594
- if (model && !model.disposed) {
595
- result.push(model);
596
- }
597
- }
598
- return result;
599
- }
600
- *iterateByType(modelClass, scope = ModelScope.all) {
601
- const actualModelName = this.registry.getModelNameFromConstructor(modelClass);
602
- if (!actualModelName) {
603
- throw new AbloValidationError(`Model class ${modelClass.name} not registered in ModelRegistry`, { code: 'pool_model_class_not_registered' });
604
- }
605
- const ids = this.typeIndex.get(actualModelName);
606
- if (!ids)
607
- return;
608
- for (const id of ids) {
609
- const entry = this.entries.get(id);
610
- if (!entry)
611
- continue;
612
- if (!this.matchesScope(entry.scope, scope))
613
- continue;
614
- const model = this.get(id);
615
- if (model && model instanceof modelClass) {
616
- yield model;
617
- }
618
- }
619
- }
620
- updateScope(id, scope) {
621
- const entry = this.entries.get(id);
622
- if (entry && entry.scope !== scope) {
623
- // Re-set the entry so ObservableMap notifies observers of the change.
624
- // Mutating entry.scope in-place wouldn't trigger MobX (plain object property).
625
- runInAction(() => {
626
- this.entries.set(id, { ...entry, scope });
627
- });
628
- this.accessTimes.set(id, Date.now());
629
- }
630
- }
631
- /**
632
- * Create (or update) a model instance locally, given a typename and raw
633
- * data. Cleaner than `createFromData({ __typename, ...data })` — the
634
- * typename lives in the arg list, not hidden inside the data object.
635
- *
636
- * Used for optimistic local writes: `pool.create('Section', { id, reportId, ... })`.
637
- * For hydration from server deltas (where `__typename` already rides on
638
- * the payload), use `createFromData(data)` directly — that path is kept
639
- * because the wire format attaches the discriminator to the data itself.
640
- */
641
- create(typename, data) {
642
- return this.createFromData({ ...data, __typename: typename });
643
- }
644
- createFromData(data, ModelClass, opts) {
645
- // Support multiple model identifier fields for backwards compatibility
646
- const modelName = data.__typename ?? data.__class ?? data.modelName ?? 'Unknown';
647
- const Constructor = ModelClass ?? this.registry.getModelByName(modelName);
648
- if (!Constructor) {
649
- if (modelName === 'Unknown') {
650
- // Malformed row with no type marker — dropped, but nothing the consumer
651
- // can act on (the actionable schema-drift case is handled below) → debug.
652
- this.runtime.logger.debug('InstanceCache.createFromData: No model identifier found', { data });
653
- this.runtime.modelDebugLogger?.logError('Unknown', 'CREATE', 'No model identifier found', data);
654
- return null;
655
- }
656
- if (opts?.strict) {
657
- const known = this.registry.getRegisteredModelNames();
658
- throw new AbloValidationError(`Model "${modelName}" is not registered on this client` +
659
- (known.length ? ` (known: ${known.join(', ')})` : '') +
660
- `. The schema pushed to this org may differ from your local ` +
661
- `schema — run \`ablo status\` to compare.`, { code: 'model_not_registered' });
662
- }
663
- // Genuinely actionable and NOT self-healing: a model the server is sending
664
- // isn't in your schema, so these rows are silently skipped. Keep at warn,
665
- // consumer register (their model name + the `ablo status` fix); forensics ride debug.
666
- this.runtime.logger.warn(`Received data for "${modelName}", which isn't in your schema — these rows will be skipped. Run \`ablo status\` to compare your local schema with the server.`);
667
- this.runtime.logger.debug(`InstanceCache.createFromData: No constructor found for model "${modelName}"`, { data });
668
- this.runtime.modelDebugLogger?.logError(modelName, 'CREATE', `No constructor found for model "${modelName}"`, data);
669
- return null;
670
- }
671
- // If the model already exists, update it in place instead of creating a
672
- // duplicate. Keeping the existing instance alive preserves React's
673
- // references and MobX's observation tracking.
674
- if (data.id && this.entries.has(data.id)) {
675
- const existing = this.get(data.id);
676
- if (existing?.getModelName() === modelName) {
677
- // Same ID and same type - update existing model with new data and return it
678
- existing.updateFromData(data);
679
- return existing;
680
- }
681
- // Different type with same ID - this is a shared PK scenario (e.g., two models sharing one row id)
682
- // Don't return existing, create new model (will use composite key for storage)
683
- }
684
- // Log model creation attempt
685
- this.runtime.modelDebugLogger?.logCreation(modelName, data, Constructor);
686
- try {
687
- // Pass data directly to constructor for Prisma-first models
688
- const model = new Constructor(data);
689
- return model;
690
- }
691
- catch (error) {
692
- const errorMessage = error instanceof Error ? error.message : String(error);
693
- // Internal construction failure — captured via observability below and
694
- // re-fetched on resync; the stack is forensic → debug.
695
- this.runtime.logger.debug(`[InstanceCache.createFromData] FAILED ${modelName}`, { errorMessage, stack: error instanceof Error ? error.stack : undefined });
696
- this.runtime.observability.captureMutationFailure({
697
- context: 'createFromData',
698
- modelName,
699
- modelId: data.id,
700
- error: errorMessage,
701
- });
702
- this.runtime.modelDebugLogger?.logError(modelName, 'CREATE', errorMessage, {
703
- data,
704
- constructor: Constructor.name,
705
- });
706
- return null;
707
- }
708
- }
709
- /**
710
- * Clear the object pool
711
- * @param options.preserveObserved - If true, keep models that are being observed by React
712
- * This prevents React components from holding stale references
713
- * after bootstrap/rehydration
714
- */
715
- clear(options = {}) {
716
- const preserveObserved = options.preserveObserved ?? false;
717
- const preservedIds = [];
718
- const preservedEntries = [];
719
- for (const [id, entry] of this.entries) {
720
- const model = entry.model ?? entry.weakRef?.deref();
721
- // Check if this model should be preserved (has active React observers)
722
- if (preserveObserved &&
723
- model &&
724
- typeof model.hasObservedCollections === 'function' &&
725
- model.hasObservedCollections()) {
726
- // Keep this model alive - React is still using it
727
- preservedIds.push(id);
728
- preservedEntries.push([id, entry]);
729
- continue;
730
- }
731
- // `rowAsModel` only casts, so a non-Model object can reach the pool (see
732
- // the SyncClient no-op UPDATE guard). Guard that `dispose` is actually
733
- // callable — mirroring the `hasObservedCollections` typeof-check above —
734
- // rather than assume every pooled entry is a real Model.
735
- if (typeof model?.dispose === 'function')
736
- model.dispose();
737
- }
738
- // Save access times for preserved entries before clearing
739
- const preservedAccessTimes = new Map();
740
- for (const [id] of preservedEntries) {
741
- const time = this.accessTimes.get(id);
742
- if (time)
743
- preservedAccessTimes.set(id, time);
744
- }
745
- runInAction(() => {
746
- this.entries.clear();
747
- this.typeIndex.clear();
748
- // Clear foreign key index data (preserves config/structure, just empties the value maps)
749
- for (const index of this.foreignKeyIndexes.values()) {
750
- index.clear();
751
- }
752
- this.recentAdditions.clear();
753
- this.deltaHistory.clear();
754
- this.metrics = {
755
- hits: 0,
756
- misses: 0,
757
- evictions: 0,
758
- additions: 0,
759
- duplicatesSkipped: 0,
760
- };
761
- // Re-add preserved entries (also rebuilds foreign key indexes via addToTypeIndex)
762
- for (const [id, entry] of preservedEntries) {
763
- this.entries.set(id, entry);
764
- const model = entry.model ?? entry.weakRef?.deref();
765
- if (model) {
766
- this.addToTypeIndex(id, model.getModelName());
767
- }
768
- }
769
- });
770
- // Restore access times: clear then re-add preserved
771
- this.accessTimes.clear();
772
- for (const [id, time] of preservedAccessTimes) {
773
- this.accessTimes.set(id, time);
774
- }
775
- // No cache to invalidate — typeIndex + entries are directly observable
776
- }
777
- has(id) {
778
- return this.entries.has(id);
779
- }
780
- /**
781
- * Touch a model to update its access time (prevents premature GC)
782
- * Used by LazyReferenceCollection to keep parent models alive during active usage
783
- */
784
- touch(id) {
785
- const entry = this.entries.get(id);
786
- if (!entry) {
787
- return false;
788
- }
789
- this.accessTimes.set(id, Date.now());
790
- return true;
791
- }
792
- getAllIds() {
793
- return Array.from(this.entries.keys());
794
- }
795
- getAllModels() {
796
- const results = [];
797
- for (const [id] of this.entries) {
798
- const model = this.get(id);
799
- if (model) {
800
- results.push(model);
801
- }
802
- }
803
- return results;
804
- }
805
- get size() {
806
- return this.entries.size;
807
- }
808
- get hitRate() {
809
- const total = this.metrics.hits + this.metrics.misses;
810
- return total > 0 ? (this.metrics.hits / total) * 100 : 0;
811
- }
812
- getStats() {
813
- const scopeCounts = { live: 0, archived: 0 };
814
- const typeCounts = new Map();
815
- for (const [, entry] of this.entries) {
816
- if (entry.scope === ModelScope.live)
817
- scopeCounts.live++;
818
- else if (entry.scope === ModelScope.archived)
819
- scopeCounts.archived++;
820
- const modelName = entry.model?.getModelName() ?? entry.weakRef?.deref()?.getModelName();
821
- if (modelName) {
822
- typeCounts.set(modelName, (typeCounts.get(modelName) ?? 0) + 1);
823
- }
824
- }
825
- return {
826
- size: this.size,
827
- hitRate: this.hitRate,
828
- metrics: { ...this.metrics },
829
- scopeCounts,
830
- typeCounts: Object.fromEntries(typeCounts),
831
- deltaHistorySize: this.deltaHistory.size,
832
- recentAdditionsSize: this.recentAdditions.size,
833
- config: { ...this.config },
834
- };
835
- }
836
- clearDeltaHistory(olderThanMs = 3600000) {
837
- const now = Date.now();
838
- const toDelete = [];
839
- for (const [key, history] of this.deltaHistory) {
840
- if (now - history.timestamp > olderThanMs) {
841
- toDelete.push(key);
842
- }
843
- }
844
- toDelete.forEach((key) => this.deltaHistory.delete(key));
845
- // Delta history entries cleared silently
846
- }
847
- cleanupTracking() {
848
- const now = Date.now();
849
- for (const [key, time] of this.recentAdditions) {
850
- if (now - time > 1000) {
851
- this.recentAdditions.delete(key);
852
- }
853
- }
854
- }
855
- gc() {
856
- return runInAction(() => {
857
- const now = Date.now();
858
- const toRemove = [];
859
- let evicted = 0;
860
- let skippedObserved = 0;
861
- for (const [id, entry] of this.entries) {
862
- // Check if model has expired based on last access time
863
- const lastAccessed = this.accessTimes.get(id) ?? 0;
864
- if (now - lastAccessed > this.config.maxAge) {
865
- // Do not GC a model that has observed collections — disposing one
866
- // React is still observing would break it (per MobX guidance).
867
- // See: https://mobx.js.org/lazy-observables.html
868
- const model = entry.model ?? entry.weakRef?.deref();
869
- if (model &&
870
- typeof model.hasObservedCollections === 'function' &&
871
- model.hasObservedCollections()) {
872
- // Model has active React observers - refresh access time and skip GC
873
- this.accessTimes.set(id, now);
874
- skippedObserved++;
875
- continue;
876
- }
877
- toRemove.push(id);
878
- continue;
879
- }
880
- // Strong-to-weak-ref demotion at `maxAge / 2` used to live here,
881
- // in service of memory-pressure relief: idle entries would lose
882
- // their strong reference, V8 would collect them, and the next
883
- // access would re-hydrate from IDB/network. In practice it
884
- // caused silent data loss — any model actively being rendered
885
- // through a schema-driven dynamic class (i.e., most of them)
886
- // would be demoted, collected, and the next render's
887
- // `weakRef.deref()` returned undefined, so blocks / cells /
888
- // messages "disappeared" after ~10 min of idle.
889
- //
890
- // The `hasObservedCollections()` guard used by the eviction
891
- // branch above only protects models that explicitly register a
892
- // LazyReferenceCollection; plain observer() components reading
893
- // properties don't register, so for typical UI usage the guard
894
- // didn't apply. Rather than try to make React-observation
895
- // globally visible to the pool, we drop the demotion phase
896
- // entirely — hard eviction at `maxAge` (with its own guard) is
897
- // the only automated removal now. If memory-pressure relief is
898
- // needed later, gate it on an explicit policy (e.g.,
899
- // `documenthidden` + `performance.memory.usedJSHeapSize`) rather
900
- // than a time-based tick.
901
- }
902
- for (const id of toRemove) {
903
- if (this.remove(id)) {
904
- evicted++;
905
- this.metrics.evictions++;
906
- }
907
- }
908
- if (skippedObserved > 0) {
909
- this.runtime.logger.debug(`[InstanceCache GC] Skipped ${skippedObserved} models with active React observers`);
910
- }
911
- // Also clean up old tracking data
912
- this.clearDeltaHistory();
913
- this.cleanupTracking();
914
- return evicted;
915
- });
916
- }
917
- startGC() {
918
- if (this.gcTimer)
919
- return;
920
- this.gcTimer = setInterval(() => this.gc(), this.config.gcInterval);
921
- // Don't hold a headless Node process open just for pool GC — without
922
- // this, an agent that never calls disconnect() can never exit. No-op in
923
- // browsers (where setInterval returns a number without `unref`), which is
924
- // why the call is optional even though the Node timer type always has it.
925
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
926
- this.gcTimer.unref?.();
927
- }
928
- stopGC() {
929
- if (this.gcTimer) {
930
- clearInterval(this.gcTimer);
931
- this.gcTimer = undefined;
932
- }
933
- }
934
- evictOldest() {
935
- runInAction(() => {
936
- let oldest;
937
- let oldestTime = Date.now();
938
- for (const [id, entry] of this.entries) {
939
- // Skip models that are being observed by React - they must stay alive
940
- const model = entry.model ?? entry.weakRef?.deref();
941
- if (model &&
942
- typeof model.hasObservedCollections === 'function' &&
943
- model.hasObservedCollections()) {
944
- continue;
945
- }
946
- const entryAccessTime = this.accessTimes.get(id) ?? 0;
947
- if (entryAccessTime < oldestTime) {
948
- oldest = [id, entry];
949
- oldestTime = entryAccessTime;
950
- }
951
- }
952
- if (oldest) {
953
- this.remove(oldest[0]);
954
- this.metrics.evictions++;
955
- }
956
- });
957
- }
958
- isLargeModel(model) {
959
- try {
960
- const size = JSON.stringify(model).length;
961
- return size > 10240;
962
- }
963
- catch {
964
- return false;
965
- }
966
- }
967
- // ========== FOREIGN KEY INDEX ==========
968
- /**
969
- * Register a foreign key field for indexing on a model type.
970
- * Call once during app initialization (e.g., after model registration).
971
- *
972
- * Example: registerForeignKey('Block', 'sectionId')
973
- * This enables getByForeignKey('Block', 'sectionId', someSectionId) → O(1) lookup
974
- */
975
- registerForeignKey(modelName, fieldName) {
976
- const fields = this.foreignKeyConfig.get(modelName) ?? [];
977
- if (!fields.includes(fieldName)) {
978
- fields.push(fieldName);
979
- this.foreignKeyConfig.set(modelName, fields);
980
- }
981
- // Initialize the index map
982
- const indexKey = `${modelName}:${fieldName}`;
983
- if (!this.foreignKeyIndexes.has(indexKey)) {
984
- this.foreignKeyIndexes.set(indexKey, observable.map());
985
- }
986
- }
987
- /**
988
- * Check whether a foreign key index exists for a given typename + field.
989
- * Used by QueryView to decide whether to use FK-index for initial scan.
990
- */
991
- hasForeignKeyIndex(typename, fieldName) {
992
- const indexKey = `${typename}:${fieldName}`;
993
- return this.foreignKeyIndexes.has(indexKey);
994
- }
995
- /**
996
- * Create a QueryView — an incrementally maintained materialized view.
997
- * The view registers itself with the ViewRegistry and receives
998
- * incremental updates when models of the given typename change.
999
- */
1000
- createView(typename, options) {
1001
- return new QueryView(typename, this, this.viewRegistry, options);
1002
- }
1003
- /**
1004
- * O(1) lookup of models by foreign key value.
1005
- * Returns model instances, filtered to live scope by default.
1006
- */
1007
- getByForeignKey(modelName, fieldName, fieldValue) {
1008
- const indexKey = `${modelName}:${fieldName}`;
1009
- const index = this.foreignKeyIndexes.get(indexKey);
1010
- // Both empty-path early-returns below are normal states, not errors:
1011
- // a model with no FK index yet (not populated), or an index with no
1012
- // entry for this specific parent id (entity genuinely has no
1013
- // children). These used to `console.warn` diagnostic dumps on every
1014
- // call, which turned into hundreds of log lines per second during
1015
- // cursor hover / rapid re-renders on a busy page. If a caller
1016
- // needs visibility into "why is this empty," wire an opt-in
1017
- // `logger.debug` at the specific call site rather than re-adding
1018
- // a blanket warn here.
1019
- if (!index)
1020
- return [];
1021
- const ids = index.get(fieldValue);
1022
- if (!ids || ids.size === 0)
1023
- return [];
1024
- const result = [];
1025
- let droppedNoEntry = 0;
1026
- let droppedScope = 0;
1027
- let droppedDisposed = 0;
1028
- for (const id of ids) {
1029
- const entry = this.entries.get(id);
1030
- if (!entry) {
1031
- droppedNoEntry++;
1032
- continue;
1033
- }
1034
- if (!this.matchesScope(entry.scope, ModelScope.live)) {
1035
- droppedScope++;
1036
- continue;
1037
- }
1038
- const model = this.resolveModel(entry, id);
1039
- if (model && !model.disposed) {
1040
- result.push(model);
1041
- }
1042
- else if (model?.disposed) {
1043
- droppedDisposed++;
1044
- }
1045
- }
1046
- if (droppedNoEntry || droppedScope || droppedDisposed) {
1047
- // Debug-level: happens on every render when a foreign-key index
1048
- // has dangling refs (legacy orphan deltas, pending CREATE
1049
- // transactions, etc.). Noisy at warn level, useful during
1050
- // investigation.
1051
- this.runtime.logger.debug('[InstanceCache.getByForeignKey] ROWS DROPPED', {
1052
- modelName,
1053
- fieldName,
1054
- fieldValue,
1055
- matched: ids.size,
1056
- returned: result.length,
1057
- droppedNoEntry,
1058
- droppedScope,
1059
- droppedDisposed,
1060
- });
1061
- }
1062
- return result;
1063
- }
1064
- /**
1065
- * Add a model to foreign key indexes (called from addToTypeIndex path)
1066
- */
1067
- addToForeignKeyIndex(id, model, modelName) {
1068
- // Silent no-ops for "no config / non-string value / missing index"
1069
- // — all three are legitimate states (non-indexed model, optional
1070
- // nullable FK, index not yet registered because the batch ran
1071
- // before schema registration completed). Diagnostic warns that
1072
- // used to live here spammed the console on every hot-path load.
1073
- const fields = this.foreignKeyConfig.get(modelName);
1074
- if (!fields)
1075
- return;
1076
- for (const fieldName of fields) {
1077
- const fieldValue = model.getField(fieldName);
1078
- if (typeof fieldValue !== 'string')
1079
- continue;
1080
- const indexKey = `${modelName}:${fieldName}`;
1081
- const index = this.foreignKeyIndexes.get(indexKey);
1082
- if (!index)
1083
- continue;
1084
- let ids = index.get(fieldValue);
1085
- if (!ids) {
1086
- ids = observable.set();
1087
- index.set(fieldValue, ids);
1088
- }
1089
- ids.add(id);
1090
- }
1091
- }
1092
- /**
1093
- * Remove a model from foreign key indexes (called from removeFromTypeIndex path)
1094
- */
1095
- removeFromForeignKeyIndex(id, modelName) {
1096
- if (!modelName)
1097
- return;
1098
- const fields = this.foreignKeyConfig.get(modelName);
1099
- if (!fields)
1100
- return;
1101
- // We need the model to read the foreign key value
1102
- const entry = this.entries.get(id);
1103
- const model = entry?.model ?? entry?.weakRef?.deref();
1104
- if (!model)
1105
- return;
1106
- for (const fieldName of fields) {
1107
- const fieldValue = model.getField(fieldName);
1108
- if (typeof fieldValue !== 'string')
1109
- continue;
1110
- const indexKey = `${modelName}:${fieldName}`;
1111
- const index = this.foreignKeyIndexes.get(indexKey);
1112
- if (!index)
1113
- continue;
1114
- const ids = index.get(fieldValue);
1115
- if (ids) {
1116
- ids.delete(id);
1117
- if (ids.size === 0) {
1118
- index.delete(fieldValue);
1119
- }
1120
- }
1121
- }
1122
- }
1123
- addToTypeIndex(id, modelName) {
1124
- if (!modelName)
1125
- return;
1126
- let ids = this.typeIndex.get(modelName);
1127
- if (!ids) {
1128
- ids = observable.set();
1129
- this.typeIndex.set(modelName, ids);
1130
- }
1131
- ids.add(id);
1132
- // Update foreign key indexes. If we can't reach the model object,
1133
- // the FK index will be (re)populated on the first lookup that
1134
- // resolves the entry — no need to warn here.
1135
- const entry = this.entries.get(id);
1136
- const model = entry?.model ?? entry?.weakRef?.deref();
1137
- if (model) {
1138
- this.addToForeignKeyIndex(id, model, modelName);
1139
- }
1140
- }
1141
- removeFromTypeIndex(id, modelName) {
1142
- if (!modelName)
1143
- return;
1144
- // Remove from foreign key indexes BEFORE removing from entries
1145
- this.removeFromForeignKeyIndex(id, modelName);
1146
- const ids = this.typeIndex.get(modelName);
1147
- if (ids) {
1148
- ids.delete(id);
1149
- if (ids.size === 0) {
1150
- this.typeIndex.delete(modelName);
1151
- }
1152
- }
1153
- }
1154
- matchesScope(entryScope, queryScope) {
1155
- switch (queryScope) {
1156
- case ModelScope.all:
1157
- return true;
1158
- case ModelScope.live:
1159
- return entryScope === ModelScope.live;
1160
- case ModelScope.archived:
1161
- return entryScope === ModelScope.archived;
1162
- default:
1163
- return entryScope === queryScope;
1164
- }
1165
- }
1166
- }