@alneas/eaos-server 0.0.0-stage → 0.2.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 (360) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.ja.md +49 -0
  4. package/README.md +46 -2
  5. package/dist/agentic/agent-jobs.d.ts +333 -0
  6. package/dist/agentic/agent-jobs.js +15 -0
  7. package/dist/agentic/agent-jobs.js.map +1 -0
  8. package/dist/agentic/agent-node.d.ts +40 -0
  9. package/dist/agentic/agent-node.js +7 -0
  10. package/dist/agentic/agent-node.js.map +1 -0
  11. package/dist/agentic/agent-runner.d.ts +33 -0
  12. package/dist/agentic/agent-runner.js +7 -0
  13. package/dist/agentic/agent-runner.js.map +1 -0
  14. package/dist/agentic/ai-broker.d.ts +169 -0
  15. package/dist/agentic/ai-broker.js +8 -0
  16. package/dist/agentic/ai-broker.js.map +1 -0
  17. package/dist/agentic/atomic-checkpointer.d.ts +137 -0
  18. package/dist/agentic/atomic-checkpointer.js +9 -0
  19. package/dist/agentic/atomic-checkpointer.js.map +1 -0
  20. package/dist/agentic/autonomous-worker.d.ts +122 -0
  21. package/dist/agentic/autonomous-worker.js +10 -0
  22. package/dist/agentic/autonomous-worker.js.map +1 -0
  23. package/dist/agentic/event-bridge.d.ts +350 -0
  24. package/dist/agentic/event-bridge.js +9 -0
  25. package/dist/agentic/event-bridge.js.map +1 -0
  26. package/dist/agentic/executor.d.ts +91 -0
  27. package/dist/agentic/executor.js +12 -0
  28. package/dist/agentic/executor.js.map +1 -0
  29. package/dist/agentic/graph-projection.d.ts +119 -0
  30. package/dist/agentic/graph-projection.js +7 -0
  31. package/dist/agentic/graph-projection.js.map +1 -0
  32. package/dist/agentic/graph.d.ts +524 -0
  33. package/dist/agentic/graph.js +7 -0
  34. package/dist/agentic/graph.js.map +1 -0
  35. package/dist/agentic/index.d.ts +28 -0
  36. package/dist/agentic/index.js +87 -0
  37. package/dist/agentic/index.js.map +1 -0
  38. package/dist/agentic/local-runner.d.ts +106 -0
  39. package/dist/agentic/local-runner.js +7 -0
  40. package/dist/agentic/local-runner.js.map +1 -0
  41. package/dist/agentic/remote-executor.d.ts +67 -0
  42. package/dist/agentic/remote-executor.js +8 -0
  43. package/dist/agentic/remote-executor.js.map +1 -0
  44. package/dist/agentic/types.d.ts +157 -0
  45. package/dist/agentic/types.js +7 -0
  46. package/dist/agentic/types.js.map +1 -0
  47. package/dist/agentic/workflow-catalog.d.ts +152 -0
  48. package/dist/agentic/workflow-catalog.js +7 -0
  49. package/dist/agentic/workflow-catalog.js.map +1 -0
  50. package/dist/agentic/workflow-compiler.d.ts +44 -0
  51. package/dist/agentic/workflow-compiler.js +8 -0
  52. package/dist/agentic/workflow-compiler.js.map +1 -0
  53. package/dist/agentic/workflow-coordinator.d.ts +170 -0
  54. package/dist/agentic/workflow-coordinator.js +8 -0
  55. package/dist/agentic/workflow-coordinator.js.map +1 -0
  56. package/dist/agentic/workflow-manager.d.ts +91 -0
  57. package/dist/agentic/workflow-manager.js +7 -0
  58. package/dist/agentic/workflow-manager.js.map +1 -0
  59. package/dist/agentic/workflow-registry.d.ts +334 -0
  60. package/dist/agentic/workflow-registry.js +7 -0
  61. package/dist/agentic/workflow-registry.js.map +1 -0
  62. package/dist/artifacts-CteHmCMI.d.ts +614 -0
  63. package/dist/auth-hooks/api-key.d.ts +75 -0
  64. package/dist/auth-hooks/api-key.js +7 -0
  65. package/dist/auth-hooks/api-key.js.map +1 -0
  66. package/dist/auth-hooks/index.d.ts +80 -0
  67. package/dist/auth-hooks/index.js +28 -0
  68. package/dist/auth-hooks/index.js.map +1 -0
  69. package/dist/browser-auth/account-memberships.d.ts +213 -0
  70. package/dist/browser-auth/account-memberships.js +13 -0
  71. package/dist/browser-auth/account-memberships.js.map +1 -0
  72. package/dist/browser-auth/index.d.ts +8 -0
  73. package/dist/browser-auth/index.js +59 -0
  74. package/dist/browser-auth/index.js.map +1 -0
  75. package/dist/browser-auth/kratos-session.d.ts +37 -0
  76. package/dist/browser-auth/kratos-session.js +7 -0
  77. package/dist/browser-auth/kratos-session.js.map +1 -0
  78. package/dist/browser-auth/platform-session.d.ts +120 -0
  79. package/dist/browser-auth/platform-session.js +7 -0
  80. package/dist/browser-auth/platform-session.js.map +1 -0
  81. package/dist/browser-auth/public-memberships.d.ts +22 -0
  82. package/dist/browser-auth/public-memberships.js +7 -0
  83. package/dist/browser-auth/public-memberships.js.map +1 -0
  84. package/dist/cache/index.d.ts +181 -0
  85. package/dist/cache/index.js +197 -0
  86. package/dist/cache/index.js.map +1 -0
  87. package/dist/chunk-32746UQZ.js +131 -0
  88. package/dist/chunk-32746UQZ.js.map +1 -0
  89. package/dist/chunk-3NLSWP2Y.js +223 -0
  90. package/dist/chunk-3NLSWP2Y.js.map +1 -0
  91. package/dist/chunk-3P4XTTQH.js +346 -0
  92. package/dist/chunk-3P4XTTQH.js.map +1 -0
  93. package/dist/chunk-3VITSHN3.js +15 -0
  94. package/dist/chunk-3VITSHN3.js.map +1 -0
  95. package/dist/chunk-46UWPEW2.js +226 -0
  96. package/dist/chunk-46UWPEW2.js.map +1 -0
  97. package/dist/chunk-4BARRLJR.js +180 -0
  98. package/dist/chunk-4BARRLJR.js.map +1 -0
  99. package/dist/chunk-4VORUFKR.js +45 -0
  100. package/dist/chunk-4VORUFKR.js.map +1 -0
  101. package/dist/chunk-55D4ERWO.js +1 -0
  102. package/dist/chunk-55D4ERWO.js.map +1 -0
  103. package/dist/chunk-5TBBZIND.js +18 -0
  104. package/dist/chunk-5TBBZIND.js.map +1 -0
  105. package/dist/chunk-5WOZWPZA.js +211 -0
  106. package/dist/chunk-5WOZWPZA.js.map +1 -0
  107. package/dist/chunk-6CBVZ6CT.js +168 -0
  108. package/dist/chunk-6CBVZ6CT.js.map +1 -0
  109. package/dist/chunk-6XZXYNJR.js +74 -0
  110. package/dist/chunk-6XZXYNJR.js.map +1 -0
  111. package/dist/chunk-753HWDSC.js +222 -0
  112. package/dist/chunk-753HWDSC.js.map +1 -0
  113. package/dist/chunk-7AHSS7SO.js +81 -0
  114. package/dist/chunk-7AHSS7SO.js.map +1 -0
  115. package/dist/chunk-7FTJN4SA.js +1 -0
  116. package/dist/chunk-7FTJN4SA.js.map +1 -0
  117. package/dist/chunk-AVDMLTPF.js +24 -0
  118. package/dist/chunk-AVDMLTPF.js.map +1 -0
  119. package/dist/chunk-BKAHUEIT.js +95 -0
  120. package/dist/chunk-BKAHUEIT.js.map +1 -0
  121. package/dist/chunk-CJ7OFEF7.js +134 -0
  122. package/dist/chunk-CJ7OFEF7.js.map +1 -0
  123. package/dist/chunk-EJKWDZIU.js +111 -0
  124. package/dist/chunk-EJKWDZIU.js.map +1 -0
  125. package/dist/chunk-F2J6IQ6I.js +247 -0
  126. package/dist/chunk-F2J6IQ6I.js.map +1 -0
  127. package/dist/chunk-F3O7CELI.js +23 -0
  128. package/dist/chunk-F3O7CELI.js.map +1 -0
  129. package/dist/chunk-F4IITPGX.js +221 -0
  130. package/dist/chunk-F4IITPGX.js.map +1 -0
  131. package/dist/chunk-FD6VC2NA.js +66 -0
  132. package/dist/chunk-FD6VC2NA.js.map +1 -0
  133. package/dist/chunk-FRRRWAHO.js +14 -0
  134. package/dist/chunk-FRRRWAHO.js.map +1 -0
  135. package/dist/chunk-HCKAGLLB.js +17 -0
  136. package/dist/chunk-HCKAGLLB.js.map +1 -0
  137. package/dist/chunk-HVC7FG6B.js +191 -0
  138. package/dist/chunk-HVC7FG6B.js.map +1 -0
  139. package/dist/chunk-HXFVOR7Z.js +496 -0
  140. package/dist/chunk-HXFVOR7Z.js.map +1 -0
  141. package/dist/chunk-IJWYS2PF.js +122 -0
  142. package/dist/chunk-IJWYS2PF.js.map +1 -0
  143. package/dist/chunk-INCHXAFK.js +1395 -0
  144. package/dist/chunk-INCHXAFK.js.map +1 -0
  145. package/dist/chunk-JT6BRGQM.js +1 -0
  146. package/dist/chunk-JT6BRGQM.js.map +1 -0
  147. package/dist/chunk-L6PMAEMB.js +115 -0
  148. package/dist/chunk-L6PMAEMB.js.map +1 -0
  149. package/dist/chunk-LOUGW6MU.js +126 -0
  150. package/dist/chunk-LOUGW6MU.js.map +1 -0
  151. package/dist/chunk-LYPZPE53.js +129 -0
  152. package/dist/chunk-LYPZPE53.js.map +1 -0
  153. package/dist/chunk-NNCCN7JP.js +186 -0
  154. package/dist/chunk-NNCCN7JP.js.map +1 -0
  155. package/dist/chunk-PBC3MZUL.js +212 -0
  156. package/dist/chunk-PBC3MZUL.js.map +1 -0
  157. package/dist/chunk-R4ADP2DC.js +20 -0
  158. package/dist/chunk-R4ADP2DC.js.map +1 -0
  159. package/dist/chunk-RHN4Y6VS.js +44 -0
  160. package/dist/chunk-RHN4Y6VS.js.map +1 -0
  161. package/dist/chunk-SDJNYPYK.js +770 -0
  162. package/dist/chunk-SDJNYPYK.js.map +1 -0
  163. package/dist/chunk-UGNTCOVR.js +28 -0
  164. package/dist/chunk-UGNTCOVR.js.map +1 -0
  165. package/dist/chunk-VC2PCK2S.js +31 -0
  166. package/dist/chunk-VC2PCK2S.js.map +1 -0
  167. package/dist/chunk-VF5YDZJC.js +305 -0
  168. package/dist/chunk-VF5YDZJC.js.map +1 -0
  169. package/dist/chunk-VISRDVTK.js +675 -0
  170. package/dist/chunk-VISRDVTK.js.map +1 -0
  171. package/dist/chunk-W6O3LVBY.js +102 -0
  172. package/dist/chunk-W6O3LVBY.js.map +1 -0
  173. package/dist/chunk-WFEAKJLR.js +47 -0
  174. package/dist/chunk-WFEAKJLR.js.map +1 -0
  175. package/dist/chunk-XIEY2OF4.js +67 -0
  176. package/dist/chunk-XIEY2OF4.js.map +1 -0
  177. package/dist/chunk-XITLHDG3.js +37 -0
  178. package/dist/chunk-XITLHDG3.js.map +1 -0
  179. package/dist/chunk-XMWPYZWZ.js +61 -0
  180. package/dist/chunk-XMWPYZWZ.js.map +1 -0
  181. package/dist/chunk-XUP6CNOM.js +34 -0
  182. package/dist/chunk-XUP6CNOM.js.map +1 -0
  183. package/dist/chunk-YABTQZDJ.js +25 -0
  184. package/dist/chunk-YABTQZDJ.js.map +1 -0
  185. package/dist/chunk-YMHVRBUU.js +199 -0
  186. package/dist/chunk-YMHVRBUU.js.map +1 -0
  187. package/dist/chunk-YMJ7BRKU.js +510 -0
  188. package/dist/chunk-YMJ7BRKU.js.map +1 -0
  189. package/dist/chunk-ZC3XWU7B.js +28 -0
  190. package/dist/chunk-ZC3XWU7B.js.map +1 -0
  191. package/dist/chunk-ZHMTD5KV.js +90 -0
  192. package/dist/chunk-ZHMTD5KV.js.map +1 -0
  193. package/dist/chunk-ZWZDLOE7.js +160 -0
  194. package/dist/chunk-ZWZDLOE7.js.map +1 -0
  195. package/dist/db-package/db/local-first-sqlite-adapter.d.ts +432 -0
  196. package/dist/db-package/db/local-first-sqlite-adapter.js +7 -0
  197. package/dist/db-package/db/local-first-sqlite-adapter.js.map +1 -0
  198. package/dist/db-package/index.d.ts +7 -0
  199. package/dist/db-package/index.js +16 -0
  200. package/dist/db-package/index.js.map +1 -0
  201. package/dist/db-package/migration/aggregator.d.ts +86 -0
  202. package/dist/db-package/migration/aggregator.js +7 -0
  203. package/dist/db-package/migration/aggregator.js.map +1 -0
  204. package/dist/db-package/migration/index.d.ts +3 -0
  205. package/dist/db-package/migration/index.js +8 -0
  206. package/dist/db-package/migration/index.js.map +1 -0
  207. package/dist/engine.d.ts +205 -0
  208. package/dist/engine.js +20 -0
  209. package/dist/engine.js.map +1 -0
  210. package/dist/entitlements/index.d.ts +313 -0
  211. package/dist/entitlements/index.js +23 -0
  212. package/dist/entitlements/index.js.map +1 -0
  213. package/dist/execution/chat.d.ts +7 -0
  214. package/dist/execution/chat.js +7 -0
  215. package/dist/execution/chat.js.map +1 -0
  216. package/dist/execution/index.d.ts +342 -0
  217. package/dist/execution/index.js +66 -0
  218. package/dist/execution/index.js.map +1 -0
  219. package/dist/execution/tool-agent.d.ts +7 -0
  220. package/dist/execution/tool-agent.js +8 -0
  221. package/dist/execution/tool-agent.js.map +1 -0
  222. package/dist/extensions/index.d.ts +172 -0
  223. package/dist/extensions/index.js +95 -0
  224. package/dist/extensions/index.js.map +1 -0
  225. package/dist/health/index.d.ts +35 -0
  226. package/dist/health/index.js +7 -0
  227. package/dist/health/index.js.map +1 -0
  228. package/dist/index.d.ts +43 -0
  229. package/dist/index.js +161 -0
  230. package/dist/index.js.map +1 -0
  231. package/dist/lifecycle/index.d.ts +133 -0
  232. package/dist/lifecycle/index.js +13 -0
  233. package/dist/lifecycle/index.js.map +1 -0
  234. package/dist/lifecycle-engine/backend-migration.d.ts +2 -0
  235. package/dist/lifecycle-engine/backend-migration.js +11 -0
  236. package/dist/lifecycle-engine/backend-migration.js.map +1 -0
  237. package/dist/lifecycle-engine/baseline.d.ts +2 -0
  238. package/dist/lifecycle-engine/baseline.js +9 -0
  239. package/dist/lifecycle-engine/baseline.js.map +1 -0
  240. package/dist/lifecycle-engine/index.d.ts +1497 -0
  241. package/dist/lifecycle-engine/index.js +41 -0
  242. package/dist/lifecycle-engine/index.js.map +1 -0
  243. package/dist/lifecycle-engine/platform/artifacts.d.ts +4 -0
  244. package/dist/lifecycle-engine/platform/artifacts.js +7 -0
  245. package/dist/lifecycle-engine/platform/artifacts.js.map +1 -0
  246. package/dist/lifecycle-engine/platform/control-plane.d.ts +12 -0
  247. package/dist/lifecycle-engine/platform/control-plane.js +2 -0
  248. package/dist/lifecycle-engine/platform/control-plane.js.map +1 -0
  249. package/dist/lifecycle-engine/platform/database-router.d.ts +118 -0
  250. package/dist/lifecycle-engine/platform/database-router.js +7 -0
  251. package/dist/lifecycle-engine/platform/database-router.js.map +1 -0
  252. package/dist/lifecycle-engine/platform/deployment-placements.d.ts +464 -0
  253. package/dist/lifecycle-engine/platform/deployment-placements.js +16 -0
  254. package/dist/lifecycle-engine/platform/deployment-placements.js.map +1 -0
  255. package/dist/lifecycle-engine/platform/deployment.d.ts +4 -0
  256. package/dist/lifecycle-engine/platform/deployment.js +17 -0
  257. package/dist/lifecycle-engine/platform/deployment.js.map +1 -0
  258. package/dist/lifecycle-engine/platform/index.d.ts +737 -0
  259. package/dist/lifecycle-engine/platform/index.js +60 -0
  260. package/dist/lifecycle-engine/platform/index.js.map +1 -0
  261. package/dist/lifecycle-engine/platform/runtime.d.ts +80 -0
  262. package/dist/lifecycle-engine/platform/runtime.js +11 -0
  263. package/dist/lifecycle-engine/platform/runtime.js.map +1 -0
  264. package/dist/lifecycle-engine/platform/site-publication.d.ts +102 -0
  265. package/dist/lifecycle-engine/platform/site-publication.js +7 -0
  266. package/dist/lifecycle-engine/platform/site-publication.js.map +1 -0
  267. package/dist/lifecycle-engine/platform/user-directory.d.ts +139 -0
  268. package/dist/lifecycle-engine/platform/user-directory.js +13 -0
  269. package/dist/lifecycle-engine/platform/user-directory.js.map +1 -0
  270. package/dist/mcp.d.ts +92 -0
  271. package/dist/mcp.js +10 -0
  272. package/dist/mcp.js.map +1 -0
  273. package/dist/middleware/auth.d.ts +118 -0
  274. package/dist/middleware/auth.js +7 -0
  275. package/dist/middleware/auth.js.map +1 -0
  276. package/dist/middleware/error-handler.d.ts +35 -0
  277. package/dist/middleware/error-handler.js +7 -0
  278. package/dist/middleware/error-handler.js.map +1 -0
  279. package/dist/middleware/module-guard.d.ts +33 -0
  280. package/dist/middleware/module-guard.js +7 -0
  281. package/dist/middleware/module-guard.js.map +1 -0
  282. package/dist/platform-admin/index.d.ts +37 -0
  283. package/dist/platform-admin/index.js +11 -0
  284. package/dist/platform-admin/index.js.map +1 -0
  285. package/dist/platform-admin/store.d.ts +346 -0
  286. package/dist/platform-admin/store.js +7 -0
  287. package/dist/platform-admin/store.js.map +1 -0
  288. package/dist/plugin-data/annotations.d.ts +6 -0
  289. package/dist/plugin-data/annotations.js +12 -0
  290. package/dist/plugin-data/annotations.js.map +1 -0
  291. package/dist/plugin-data/atomic-context.d.ts +41 -0
  292. package/dist/plugin-data/atomic-context.js +9 -0
  293. package/dist/plugin-data/atomic-context.js.map +1 -0
  294. package/dist/plugin-data/atomic.d.ts +6 -0
  295. package/dist/plugin-data/atomic.js +26 -0
  296. package/dist/plugin-data/atomic.js.map +1 -0
  297. package/dist/plugin-data/audit.d.ts +6 -0
  298. package/dist/plugin-data/audit.js +12 -0
  299. package/dist/plugin-data/audit.js.map +1 -0
  300. package/dist/plugin-data/commands.d.ts +6 -0
  301. package/dist/plugin-data/commands.js +8 -0
  302. package/dist/plugin-data/commands.js.map +1 -0
  303. package/dist/plugin-data/composite.d.ts +6 -0
  304. package/dist/plugin-data/composite.js +8 -0
  305. package/dist/plugin-data/composite.js.map +1 -0
  306. package/dist/plugin-data/delivery.d.ts +6 -0
  307. package/dist/plugin-data/delivery.js +16 -0
  308. package/dist/plugin-data/delivery.js.map +1 -0
  309. package/dist/plugin-data/index.d.ts +2798 -0
  310. package/dist/plugin-data/index.js +102 -0
  311. package/dist/plugin-data/index.js.map +1 -0
  312. package/dist/plugin-data/json-documents.d.ts +6 -0
  313. package/dist/plugin-data/json-documents.js +16 -0
  314. package/dist/plugin-data/json-documents.js.map +1 -0
  315. package/dist/plugin-data/outbox.d.ts +6 -0
  316. package/dist/plugin-data/outbox.js +16 -0
  317. package/dist/plugin-data/outbox.js.map +1 -0
  318. package/dist/plugin-data/scoped-records.d.ts +6 -0
  319. package/dist/plugin-data/scoped-records.js +10 -0
  320. package/dist/plugin-data/scoped-records.js.map +1 -0
  321. package/dist/plugin-sdk.d.ts +41 -0
  322. package/dist/plugin-sdk.js +10 -0
  323. package/dist/plugin-sdk.js.map +1 -0
  324. package/dist/sql-lifecycle/index.d.ts +138 -0
  325. package/dist/sql-lifecycle/index.js +167 -0
  326. package/dist/sql-lifecycle/index.js.map +1 -0
  327. package/dist/storage/index.d.ts +2 -0
  328. package/dist/storage/index.js +13 -0
  329. package/dist/storage/index.js.map +1 -0
  330. package/dist/storage/s3-compatible.d.ts +139 -0
  331. package/dist/storage/s3-compatible.js +11 -0
  332. package/dist/storage/s3-compatible.js.map +1 -0
  333. package/dist/tenant.d.ts +108 -0
  334. package/dist/tenant.js +9 -0
  335. package/dist/tenant.js.map +1 -0
  336. package/dist/workspace-context.d.ts +119 -0
  337. package/dist/workspace-context.js +9 -0
  338. package/dist/workspace-context.js.map +1 -0
  339. package/docs/api.md +30560 -0
  340. package/docs/db/README.md +49 -0
  341. package/docs/db/docs/api.md +1613 -0
  342. package/docs/db/docs/guide.md +47 -0
  343. package/docs/entitlements.md +60 -0
  344. package/docs/guide.md +92 -0
  345. package/docs/index.md +57 -0
  346. package/docs/ja/db/README.md +46 -0
  347. package/docs/ja/db/docs/guide.md +53 -0
  348. package/docs/ja/guide.md +46 -0
  349. package/docs/ja/lifecycle/README.md +46 -0
  350. package/docs/ja/lifecycle/docs/guide.md +45 -0
  351. package/docs/ja/storage/README.md +46 -0
  352. package/docs/ja/storage/docs/guide.md +43 -0
  353. package/docs/lifecycle/README.md +49 -0
  354. package/docs/lifecycle/docs/api.md +3770 -0
  355. package/docs/lifecycle/docs/baseline.md +50 -0
  356. package/docs/lifecycle/docs/guide.md +47 -0
  357. package/docs/storage/README.md +49 -0
  358. package/docs/storage/docs/api.md +470 -0
  359. package/docs/storage/docs/guide.md +47 -0
  360. package/package.json +354 -4
@@ -0,0 +1,675 @@
1
+ // src/db-package/db/local-first-sqlite-adapter.ts
2
+ var LocalFirstSqliteAdapter = class {
3
+ /** Optional create-time tenant_id fallback captured from options; not a read or mutation authorization filter. */
4
+ tenantId;
5
+ sqlite;
6
+ upstream;
7
+ onSyncError;
8
+ autoSyncOnMutation;
9
+ essentialTables;
10
+ syncTimer = null;
11
+ isSyncing = false;
12
+ cleanupListeners;
13
+ /**
14
+ * Captures drivers and starts optional outbox polling and browser lifecycle listeners.
15
+ * @param options - Host drivers and synchronization settings; see {@link LocalFirstSqliteAdapterOptions}.
16
+ * @remarks The constructor performs no schema initialization. Timers/listeners start immediately unless disabled; call destroy when the instance is no longer needed.
17
+ */
18
+ constructor(options) {
19
+ this.tenantId = options.tenantId;
20
+ this.sqlite = options.sqlite;
21
+ this.upstream = options.upstream;
22
+ this.onSyncError = options.onSyncError;
23
+ this.autoSyncOnMutation = options.autoSyncOnMutation !== false;
24
+ this.essentialTables = new Set(options.essentialTables ?? []);
25
+ if (options.syncIntervalMs !== 0) {
26
+ const interval = options.syncIntervalMs || 5e3;
27
+ if (typeof setInterval !== "undefined") {
28
+ this.syncTimer = setInterval(() => this.processOutbox(), interval);
29
+ }
30
+ }
31
+ if (options.enableLifecycleSync !== false && typeof window !== "undefined") {
32
+ this.attachLifecycleListeners();
33
+ }
34
+ }
35
+ /**
36
+ * Initializes SQLite synchronization metadata tables and attempts WAL, NORMAL, and 5000 ms busy-timeout pragmas.
37
+ * @returns No value; resolves after metadata table creation. Pragma failures are deliberately ignored.
38
+ * @throws Rejects metadata table creation failures; earlier tables may already exist.
39
+ * @example
40
+ * ```typescript
41
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
42
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
43
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
44
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
45
+ * try {
46
+ * await adapter.initSchema();
47
+ * await adapter.initSchema();
48
+ * } finally { adapter.destroy(); }
49
+ * }
50
+ * ```
51
+ */
52
+ async initSchema() {
53
+ try {
54
+ await this.sqlite.execute(`PRAGMA journal_mode = WAL;`);
55
+ await this.sqlite.execute(`PRAGMA synchronous = NORMAL;`);
56
+ await this.sqlite.execute(`PRAGMA busy_timeout = 5000;`);
57
+ } catch {
58
+ }
59
+ await this.sqlite.execute(`
60
+ CREATE TABLE IF NOT EXISTS _sync_meta (
61
+ table_name TEXT PRIMARY KEY,
62
+ last_sync_scn INTEGER NOT NULL DEFAULT 0,
63
+ last_sync_at TEXT NOT NULL
64
+ );
65
+ `);
66
+ await this.sqlite.execute(`
67
+ CREATE TABLE IF NOT EXISTS _outbox_queue (
68
+ id TEXT PRIMARY KEY,
69
+ table_name TEXT NOT NULL,
70
+ record_id TEXT NOT NULL,
71
+ operation TEXT NOT NULL,
72
+ payload TEXT NOT NULL,
73
+ previous_state TEXT,
74
+ status TEXT NOT NULL DEFAULT 'pending',
75
+ error_message TEXT,
76
+ retry_count INTEGER NOT NULL DEFAULT 0,
77
+ created_at TEXT NOT NULL
78
+ );
79
+ `);
80
+ await this.sqlite.execute(`
81
+ CREATE TABLE IF NOT EXISTS _access_frequency (
82
+ table_name TEXT PRIMARY KEY,
83
+ read_count INTEGER NOT NULL DEFAULT 0,
84
+ write_count INTEGER NOT NULL DEFAULT 0,
85
+ last_accessed_at TEXT NOT NULL
86
+ );
87
+ `);
88
+ }
89
+ /**
90
+ * Attaches window focus & network online hooks to trigger synchronization at user activity moments.
91
+ */
92
+ attachLifecycleListeners() {
93
+ const handleFocusOrOnline = () => {
94
+ this.processOutbox().catch(() => {
95
+ });
96
+ this.syncActiveTables().catch(() => {
97
+ });
98
+ };
99
+ window.addEventListener("focus", handleFocusOrOnline);
100
+ window.addEventListener("online", handleFocusOrOnline);
101
+ this.cleanupListeners = () => {
102
+ window.removeEventListener("focus", handleFocusOrOnline);
103
+ window.removeEventListener("online", handleFocusOrOnline);
104
+ };
105
+ }
106
+ /**
107
+ * Increments a table read or write counter and records the current ISO timestamp.
108
+ * @param collection - Table name recorded as a metadata value.
109
+ * @param type - Counter to increment; read contributes weight 2 and write weight 5 in adaptive ranking.
110
+ * @returns No value; resolves after the counter upsert.
111
+ * @throws Rejects SQLite failures.
112
+ * @example
113
+ * ```typescript
114
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
115
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
116
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
117
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
118
+ * try {
119
+ * await adapter.initSchema();
120
+ * await adapter.recordAccess("records", "read");
121
+ * } finally { adapter.destroy(); }
122
+ * }
123
+ * ```
124
+ */
125
+ async recordAccess(collection, type) {
126
+ const now = (/* @__PURE__ */ new Date()).toISOString();
127
+ const readIncrement = type === "read" ? 1 : 0;
128
+ const writeIncrement = type === "write" ? 1 : 0;
129
+ await this.sqlite.execute(
130
+ `INSERT INTO _access_frequency (table_name, read_count, write_count, last_accessed_at)
131
+ VALUES (?, ?, ?, ?)
132
+ ON CONFLICT(table_name) DO UPDATE SET
133
+ read_count = _access_frequency.read_count + excluded.read_count,
134
+ write_count = _access_frequency.write_count + excluded.write_count,
135
+ last_accessed_at = excluded.last_accessed_at`,
136
+ [collection, readIncrement, writeIncrement, now]
137
+ );
138
+ }
139
+ /**
140
+ * Pulls every essential table plus the highest weighted access-frequency tables sequentially.
141
+ * @param limit - Maximum frequency-ranked tables selected in addition to essential tables; defaults to 8 and is passed to SQLite without additional validation.
142
+ * @returns Names whose pullChanges calls completed, including tables with no new rows; failed tables are warned and omitted.
143
+ * @throws Rejects the initial frequency query failure; individual pull failures are caught.
144
+ * @example
145
+ * ```typescript
146
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
147
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
148
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
149
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
150
+ * try {
151
+ * await adapter.initSchema();
152
+ * return await adapter.syncActiveTables(8);
153
+ * } finally { adapter.destroy(); }
154
+ * }
155
+ * ```
156
+ */
157
+ async syncActiveTables(limit = 8) {
158
+ const frequent = await this.sqlite.select(
159
+ `SELECT table_name FROM _access_frequency ORDER BY (read_count * 2 + write_count * 5) DESC LIMIT ?`,
160
+ [limit]
161
+ );
162
+ const targetTables = new Set(this.essentialTables);
163
+ frequent.forEach((row) => targetTables.add(row.table_name));
164
+ const synced = [];
165
+ for (const table of targetTables) {
166
+ try {
167
+ await this.pullChanges(table);
168
+ synced.push(table);
169
+ } catch (err) {
170
+ console.warn(`[LocalFirst] Failed to sync active table '${table}':`, err);
171
+ }
172
+ }
173
+ return synced;
174
+ }
175
+ /**
176
+ * Queries local SQLite rows and schedules a best-effort read-frequency update.
177
+ * @typeParam T - Expected local row shape; no runtime schema validation is performed.
178
+ * @param collection - Local table identifier, quoted by the adapter.
179
+ * @param filter - AND-combined equality/null or $eq/$gt/$gte/$lt/$lte column conditions; other operator objects are rejected.
180
+ * @param options - Supports sort, limit, and skip; limit/skip must be nonnegative safe integers. Projection and other options are ignored.
181
+ * @returns Local rows, or an empty array; remote freshness is not implied.
182
+ * @throws Rejects unsupported filters, invalid pagination, or SQLite query failures. Access-stat failures are ignored.
183
+ * @example
184
+ * ```typescript
185
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
186
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
187
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
188
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
189
+ * try {
190
+ * await adapter.initSchema();
191
+ * return await adapter.find("records", { active: true }, { limit: 10 });
192
+ * } finally { adapter.destroy(); }
193
+ * }
194
+ * ```
195
+ */
196
+ async find(collection, filter, options) {
197
+ this.recordAccess(collection, "read").catch(() => {
198
+ });
199
+ const { sql, params } = this.buildSelectQuery(collection, filter, options);
200
+ return await this.sqlite.select(sql, params);
201
+ }
202
+ /**
203
+ * Counts local rows matching a filter without pagination.
204
+ * @param collection - Local table identifier, quoted by the adapter.
205
+ * @param filter - Same supported AND-combined conditions as find.
206
+ * @param options - Sort is retained; limit and skip are discarded. Other options are ignored.
207
+ * @returns Numeric count, or zero if the driver returns no count row.
208
+ * @throws Rejects unsupported filters or SQLite failures; access-stat failures are ignored.
209
+ * @example
210
+ * ```typescript
211
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
212
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
213
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
214
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
215
+ * try {
216
+ * await adapter.initSchema();
217
+ * return await adapter.count("records");
218
+ * } finally { adapter.destroy(); }
219
+ * }
220
+ * ```
221
+ */
222
+ async count(collection, filter, options) {
223
+ this.recordAccess(collection, "read").catch(() => {
224
+ });
225
+ const { sql, params } = this.buildSelectQuery(collection, filter, { ...options, limit: void 0, skip: void 0 });
226
+ const rows = await this.sqlite.select(`SELECT COUNT(*) AS total FROM (${sql})`, params);
227
+ return Number(rows[0]?.total ?? 0);
228
+ }
229
+ /**
230
+ * Queries a local row by its id column.
231
+ * @typeParam T - Expected local row shape; no runtime validation is performed.
232
+ * @param collection - Local table identifier.
233
+ * @param id - ID value bound to an equality filter.
234
+ * @param options - find options with limit overridden to 1; skip, if supplied, still applies.
235
+ * @returns First matching row, or null.
236
+ * @throws Rejects invalid pagination or SQLite query failures.
237
+ * @example
238
+ * ```typescript
239
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
240
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
241
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
242
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
243
+ * try {
244
+ * await adapter.initSchema();
245
+ * return await adapter.findOne("records", "record-1");
246
+ * } finally { adapter.destroy(); }
247
+ * }
248
+ * ```
249
+ */
250
+ async findOne(collection, id, options) {
251
+ const rows = await this.find(collection, { id: { $eq: id } }, { ...options, limit: 1 });
252
+ return rows.length > 0 ? rows[0] : null;
253
+ }
254
+ /**
255
+ * Inserts a local record before separately enqueueing its upstream INSERT.
256
+ * @typeParam T - Expected returned record shape; no runtime validation is performed.
257
+ * @param collection - Provisioned local table identifier.
258
+ * @param data - Column values; a truthy id is retained, otherwise an ID is generated. Missing tenant columns may receive the configured tenantId. Object values are JSON-encoded for local SQL.
259
+ * @param _options - Accepted for DataAccessAdapter compatibility and ignored.
260
+ * @returns The input record with its chosen ID and optional tenant default, before upstream acknowledgement.
261
+ * @throws Rejects local SQL, serialization, or enqueue failure; a local insert may already have committed. Background outbox errors are ignored by this call.
262
+ * @example
263
+ * ```typescript
264
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
265
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
266
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
267
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
268
+ * try {
269
+ * await adapter.initSchema();
270
+ * return await adapter.create("records", { title: "Example" });
271
+ * } finally { adapter.destroy(); }
272
+ * }
273
+ * ```
274
+ */
275
+ async create(collection, data, _options) {
276
+ this.recordAccess(collection, "write").catch(() => {
277
+ });
278
+ const recordId = data.id || (typeof crypto !== "undefined" ? crypto.randomUUID() : `rec_${Date.now()}`);
279
+ const recordWithId = { ...data, id: recordId };
280
+ if (this.tenantId && !recordWithId.tenant_id && !recordWithId.tenantId) {
281
+ recordWithId.tenant_id = this.tenantId;
282
+ }
283
+ await this.insertLocal(collection, recordWithId);
284
+ await this.enqueueOutbox({
285
+ id: typeof crypto !== "undefined" ? crypto.randomUUID() : `out_${Date.now()}`,
286
+ tableName: collection,
287
+ recordId,
288
+ operation: "INSERT",
289
+ payload: recordWithId,
290
+ previousState: null,
291
+ status: "pending",
292
+ retryCount: 0,
293
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
294
+ });
295
+ if (this.autoSyncOnMutation) {
296
+ this.processOutbox().catch(() => {
297
+ });
298
+ }
299
+ return recordWithId;
300
+ }
301
+ /**
302
+ * Snapshots a local row, updates its columns, and separately enqueues an upstream UPDATE.
303
+ * @typeParam T - Expected returned merged record shape.
304
+ * @param collection - Provisioned local table identifier.
305
+ * @param id - Record ID bound in local SQL and used for upstream update.
306
+ * @param data - Updated columns; local SQL excludes id but the payload is forwarded unchanged upstream. An empty update can cause a SQLite syntax failure.
307
+ * @param _options - Accepted for DataAccessAdapter compatibility and ignored.
308
+ * @returns Snapshot merged with data and the supplied id, even if no local row existed.
309
+ * @throws Rejects snapshot, local SQL, serialization, or enqueue failures; local changes may already have committed.
310
+ * @example
311
+ * ```typescript
312
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
313
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
314
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
315
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
316
+ * try {
317
+ * await adapter.initSchema();
318
+ * return await adapter.update("records", "record-1", { title: "Changed" });
319
+ * } finally { adapter.destroy(); }
320
+ * }
321
+ * ```
322
+ */
323
+ async update(collection, id, data, _options) {
324
+ this.recordAccess(collection, "write").catch(() => {
325
+ });
326
+ const existing = await this.findOne(collection, id);
327
+ await this.updateLocal(collection, id, data);
328
+ const updatedRecord = { ...existing || {}, ...data, id };
329
+ await this.enqueueOutbox({
330
+ id: typeof crypto !== "undefined" ? crypto.randomUUID() : `out_${Date.now()}`,
331
+ tableName: collection,
332
+ recordId: id,
333
+ operation: "UPDATE",
334
+ payload: data,
335
+ previousState: existing || null,
336
+ status: "pending",
337
+ retryCount: 0,
338
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
339
+ });
340
+ if (this.autoSyncOnMutation) {
341
+ this.processOutbox().catch(() => {
342
+ });
343
+ }
344
+ return updatedRecord;
345
+ }
346
+ /**
347
+ * Deletes a local row and separately enqueues upstream deletion with a rollback snapshot.
348
+ * @typeParam T - Expected deleted record shape.
349
+ * @param collection - Provisioned local table identifier.
350
+ * @param id - Record ID used for local and upstream deletion.
351
+ * @param _options - Accepted for DataAccessAdapter compatibility and ignored.
352
+ * @returns The previous local record, or an object containing only id when absent.
353
+ * @throws Rejects snapshot, local delete, or enqueue failures; local deletion may already have committed.
354
+ * @example
355
+ * ```typescript
356
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
357
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
358
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
359
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
360
+ * try {
361
+ * await adapter.initSchema();
362
+ * return await adapter.delete("records", "record-1");
363
+ * } finally { adapter.destroy(); }
364
+ * }
365
+ * ```
366
+ */
367
+ async delete(collection, id, _options) {
368
+ this.recordAccess(collection, "write").catch(() => {
369
+ });
370
+ const existing = await this.findOne(collection, id);
371
+ await this.sqlite.execute(`DELETE FROM ${this.escapeId(collection)} WHERE id = ?`, [id]);
372
+ await this.enqueueOutbox({
373
+ id: typeof crypto !== "undefined" ? crypto.randomUUID() : `out_${Date.now()}`,
374
+ tableName: collection,
375
+ recordId: id,
376
+ operation: "DELETE",
377
+ payload: { id },
378
+ previousState: existing || null,
379
+ status: "pending",
380
+ retryCount: 0,
381
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
382
+ });
383
+ if (this.autoSyncOnMutation) {
384
+ this.processOutbox().catch(() => {
385
+ });
386
+ }
387
+ return existing || { id };
388
+ }
389
+ /**
390
+ * Pulls incremental remote changes from Oracle ADB using ORA_ROWSCN cursor within a batch transaction.
391
+ *
392
+ * @param collection - Provisioned local table whose upstream supports ORA_ROWSCN filtering, ordering, and projection.
393
+ * @returns Number of remote rows upserted, or zero when no changes were returned.
394
+ * @throws Rejects upstream or local SQL failures; an opened local transaction is rolled back best-effort on upsert/cursor failure.
395
+ * @remarks Remote deletes are not discovered by this pull. No per-table synchronization lock prevents overlapping pulls or concurrent host transactions.
396
+ * @example
397
+ * ```typescript
398
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
399
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
400
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
401
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
402
+ * try {
403
+ * await adapter.initSchema();
404
+ * return await adapter.pullChanges("records");
405
+ * } finally { adapter.destroy(); }
406
+ * }
407
+ * ```
408
+ */
409
+ async pullChanges(collection) {
410
+ const meta = await this.sqlite.select(
411
+ `SELECT last_sync_scn FROM _sync_meta WHERE table_name = ?`,
412
+ [collection]
413
+ );
414
+ const lastScn = meta.length > 0 ? meta[0].last_sync_scn : 0;
415
+ const filter = lastScn > 0 ? { ORA_ROWSCN: { $gt: lastScn } } : {};
416
+ const remoteRecords = await this.upstream.find(collection, filter, {
417
+ sort: { ORA_ROWSCN: 1 },
418
+ projection: ["*", "ORA_ROWSCN"]
419
+ });
420
+ if (remoteRecords.length === 0) {
421
+ return 0;
422
+ }
423
+ let maxScn = lastScn;
424
+ await this.sqlite.execute(`BEGIN TRANSACTION;`);
425
+ try {
426
+ for (const record of remoteRecords) {
427
+ const recordScn = Number(record.ORA_ROWSCN) || 0;
428
+ if (recordScn > maxScn) {
429
+ maxScn = recordScn;
430
+ }
431
+ await this.upsertLocal(collection, record);
432
+ }
433
+ await this.sqlite.execute(
434
+ `INSERT INTO _sync_meta (table_name, last_sync_scn, last_sync_at)
435
+ VALUES (?, ?, ?)
436
+ ON CONFLICT(table_name) DO UPDATE SET
437
+ last_sync_scn = excluded.last_sync_scn,
438
+ last_sync_at = excluded.last_sync_at`,
439
+ [collection, maxScn, (/* @__PURE__ */ new Date()).toISOString()]
440
+ );
441
+ await this.sqlite.execute(`COMMIT;`);
442
+ } catch (txErr) {
443
+ await this.sqlite.execute(`ROLLBACK;`).catch(() => {
444
+ });
445
+ throw txErr;
446
+ }
447
+ return remoteRecords.length;
448
+ }
449
+ /**
450
+ * Background Outbox Worker:
451
+ * Processes at most 20 pending mutations in creation order, pushes upstream, and attempts rollback for non-network failures.
452
+ * @returns No value; resolves after one batch, or immediately if this instance already has a batch running.
453
+ * @throws Rejects metadata reads, malformed persisted JSON, or error-handling SQL failures. Ordinary upstream failures are recorded or rolled back rather than rejected.
454
+ * @remarks Recognized network failures return rows to pending and increment retry_count; no delay/backoff is applied here. Other failures attempt local snapshot rollback and mark failed; rollback/callback errors are logged. Remote success followed by local queue-delete failure may leave an uncertain delivery outcome. There is no cross-instance lease or deduplication.
455
+ * @example
456
+ * ```typescript
457
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
458
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
459
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
460
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
461
+ * try {
462
+ * await adapter.initSchema();
463
+ * await adapter.processOutbox();
464
+ * } finally { adapter.destroy(); }
465
+ * }
466
+ * ```
467
+ */
468
+ async processOutbox() {
469
+ if (this.isSyncing) return;
470
+ this.isSyncing = true;
471
+ try {
472
+ const pendingItems = await this.sqlite.select(
473
+ `SELECT * FROM _outbox_queue WHERE status = 'pending' ORDER BY created_at ASC LIMIT 20`
474
+ );
475
+ for (const rawItem of pendingItems) {
476
+ const item = {
477
+ id: rawItem.id,
478
+ tableName: rawItem.table_name || rawItem.tableName,
479
+ recordId: rawItem.record_id || rawItem.recordId,
480
+ operation: rawItem.operation,
481
+ payload: typeof rawItem.payload === "string" ? JSON.parse(rawItem.payload) : rawItem.payload,
482
+ previousState: rawItem.previous_state ? typeof rawItem.previous_state === "string" ? JSON.parse(rawItem.previous_state) : rawItem.previous_state : null,
483
+ status: rawItem.status,
484
+ retryCount: rawItem.retry_count ?? rawItem.retryCount ?? 0,
485
+ createdAt: rawItem.created_at || rawItem.createdAt
486
+ };
487
+ try {
488
+ await this.sqlite.execute(`UPDATE _outbox_queue SET status = 'syncing' WHERE id = ?`, [item.id]);
489
+ if (item.operation === "INSERT") {
490
+ await this.upstream.create(item.tableName, item.payload);
491
+ } else if (item.operation === "UPDATE") {
492
+ await this.upstream.update(item.tableName, item.recordId, item.payload);
493
+ } else if (item.operation === "DELETE") {
494
+ await this.upstream.delete(item.tableName, item.recordId);
495
+ }
496
+ await this.sqlite.execute(`DELETE FROM _outbox_queue WHERE id = ?`, [item.id]);
497
+ } catch (err) {
498
+ const isNetworkError = err.name === "AbortError" || err.code === "ECONNREFUSED" || err.message?.includes("fetch failed") || err.message?.includes("NetworkError");
499
+ if (isNetworkError) {
500
+ await this.sqlite.execute(
501
+ `UPDATE _outbox_queue SET status = 'pending', retry_count = retry_count + 1, error_message = ? WHERE id = ?`,
502
+ [err.message, item.id]
503
+ );
504
+ } else {
505
+ await this.rollbackItem(item, err);
506
+ }
507
+ }
508
+ }
509
+ } finally {
510
+ this.isSyncing = false;
511
+ }
512
+ }
513
+ /**
514
+ * Rolls back a failed mutation in local SQLite using its previousState snapshot.
515
+ */
516
+ async rollbackItem(item, error) {
517
+ console.error(`[LocalFirst] Rolling back ${item.tableName}:${item.recordId} due to error:`, error);
518
+ try {
519
+ if (item.operation === "INSERT") {
520
+ await this.sqlite.execute(`DELETE FROM ${this.escapeId(item.tableName)} WHERE id = ?`, [item.recordId]);
521
+ } else if (item.operation === "UPDATE" || item.operation === "DELETE") {
522
+ if (item.previousState) {
523
+ await this.upsertLocal(item.tableName, item.previousState);
524
+ }
525
+ }
526
+ await this.sqlite.execute(`UPDATE _outbox_queue SET status = 'failed', error_message = ? WHERE id = ?`, [
527
+ error.message,
528
+ item.id
529
+ ]);
530
+ if (this.onSyncError) {
531
+ this.onSyncError({
532
+ item,
533
+ error,
534
+ rolledBack: true
535
+ });
536
+ }
537
+ } catch (rbErr) {
538
+ console.error("[LocalFirst] Critical: Failed to rollback record:", rbErr);
539
+ }
540
+ }
541
+ async enqueueOutbox(item) {
542
+ await this.sqlite.execute(
543
+ `INSERT INTO _outbox_queue (id, table_name, record_id, operation, payload, previous_state, status, retry_count, created_at)
544
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
545
+ [
546
+ item.id,
547
+ item.tableName,
548
+ item.recordId,
549
+ item.operation,
550
+ JSON.stringify(item.payload),
551
+ item.previousState ? JSON.stringify(item.previousState) : null,
552
+ item.status,
553
+ item.retryCount,
554
+ item.createdAt
555
+ ]
556
+ );
557
+ }
558
+ async insertLocal(table, record) {
559
+ const keys = Object.keys(record);
560
+ const placeholders = keys.map(() => "?").join(", ");
561
+ const values = keys.map((k) => typeof record[k] === "object" ? JSON.stringify(record[k]) : record[k]);
562
+ const escapedKeys = keys.map((k) => this.escapeId(k)).join(", ");
563
+ await this.sqlite.execute(
564
+ `INSERT INTO ${this.escapeId(table)} (${escapedKeys}) VALUES (${placeholders})`,
565
+ values
566
+ );
567
+ }
568
+ async updateLocal(table, id, data) {
569
+ const keys = Object.keys(data).filter((k) => k !== "id");
570
+ const setClause = keys.map((k) => `${this.escapeId(k)} = ?`).join(", ");
571
+ const values = keys.map((k) => typeof data[k] === "object" ? JSON.stringify(data[k]) : data[k]);
572
+ await this.sqlite.execute(
573
+ `UPDATE ${this.escapeId(table)} SET ${setClause} WHERE id = ?`,
574
+ [...values, id]
575
+ );
576
+ }
577
+ async upsertLocal(table, record) {
578
+ const keys = Object.keys(record);
579
+ const placeholders = keys.map(() => "?").join(", ");
580
+ const values = keys.map((k) => typeof record[k] === "object" ? JSON.stringify(record[k]) : record[k]);
581
+ const escapedKeys = keys.map((k) => this.escapeId(k)).join(", ");
582
+ const updateClauses = keys.filter((k) => k !== "id").map((k) => `${this.escapeId(k)} = excluded.${this.escapeId(k)}`).join(", ");
583
+ await this.sqlite.execute(
584
+ `INSERT INTO ${this.escapeId(table)} (${escapedKeys}) VALUES (${placeholders})
585
+ ON CONFLICT(id) DO UPDATE SET ${updateClauses}`,
586
+ values
587
+ );
588
+ }
589
+ buildSelectQuery(table, filter, options) {
590
+ let sql = `SELECT * FROM ${this.escapeId(table)}`;
591
+ const params = [];
592
+ const whereParts = [];
593
+ for (const value of [options?.limit, options?.skip]) if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error("Invalid SQLite pagination");
594
+ if (filter) {
595
+ for (const [key, val] of Object.entries(filter)) {
596
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
597
+ const opObj = val;
598
+ if ("$eq" in opObj) {
599
+ whereParts.push(`${this.escapeId(key)} = ?`);
600
+ params.push(opObj.$eq);
601
+ } else if ("$gt" in opObj) {
602
+ whereParts.push(`${this.escapeId(key)} > ?`);
603
+ params.push(opObj.$gt);
604
+ } else if ("$gte" in opObj) {
605
+ whereParts.push(`${this.escapeId(key)} >= ?`);
606
+ params.push(opObj.$gte);
607
+ } else if ("$lt" in opObj) {
608
+ whereParts.push(`${this.escapeId(key)} < ?`);
609
+ params.push(opObj.$lt);
610
+ } else if ("$lte" in opObj) {
611
+ whereParts.push(`${this.escapeId(key)} <= ?`);
612
+ params.push(opObj.$lte);
613
+ } else {
614
+ throw new Error("Unsupported SQLite filter");
615
+ }
616
+ } else if (val === null) {
617
+ whereParts.push(`${this.escapeId(key)} IS NULL`);
618
+ } else {
619
+ whereParts.push(`${this.escapeId(key)} = ?`);
620
+ params.push(val);
621
+ }
622
+ }
623
+ }
624
+ if (whereParts.length > 0) {
625
+ sql += ` WHERE ${whereParts.join(" AND ")}`;
626
+ }
627
+ if (options?.sort) {
628
+ const orderParts = Object.entries(options.sort).map(
629
+ ([col, dir]) => `${this.escapeId(col)} ${dir === -1 ? "DESC" : "ASC"}`
630
+ );
631
+ sql += ` ORDER BY ${orderParts.join(", ")}`;
632
+ }
633
+ if (options?.limit !== void 0 || options?.skip !== void 0) {
634
+ sql += ` LIMIT ${options.limit ?? -1}`;
635
+ if (options?.skip) {
636
+ sql += ` OFFSET ${options.skip}`;
637
+ }
638
+ }
639
+ return { sql, params };
640
+ }
641
+ escapeId(id) {
642
+ return `"${id.replace(/"/g, '""')}"`;
643
+ }
644
+ /**
645
+ * Stops this instance's polling timer and removes its browser focus/online listeners.
646
+ * @returns No value; repeated calls are safe. In-flight synchronization continues and drivers remain open.
647
+ * @example
648
+ * ```typescript
649
+ * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from "@alneas/eaos-server/db/local-first-sqlite-adapter";
650
+ * export async function example(options: LocalFirstSqliteAdapterOptions) {
651
+ * // The host supplies an initialized domain schema and authorized upstream adapter.
652
+ * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });
653
+ * try {
654
+ * await adapter.initSchema();
655
+ * adapter.destroy();
656
+ * } finally { adapter.destroy(); }
657
+ * }
658
+ * ```
659
+ */
660
+ destroy() {
661
+ if (this.syncTimer) {
662
+ clearInterval(this.syncTimer);
663
+ this.syncTimer = null;
664
+ }
665
+ if (this.cleanupListeners) {
666
+ this.cleanupListeners();
667
+ this.cleanupListeners = void 0;
668
+ }
669
+ }
670
+ };
671
+
672
+ export {
673
+ LocalFirstSqliteAdapter
674
+ };
675
+ //# sourceMappingURL=chunk-VISRDVTK.js.map