@vibesdotdev/storage 0.0.1

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 (423) hide show
  1. package/README.md +75 -0
  2. package/SPEC.md +60 -0
  3. package/dist/adapters/index.d.ts +14 -0
  4. package/dist/adapters/index.d.ts.map +1 -0
  5. package/dist/adapters/index.js +15 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/bridge/index.d.ts +3 -0
  8. package/dist/bridge/index.d.ts.map +1 -0
  9. package/dist/bridge/index.js +2 -0
  10. package/dist/bridge/index.js.map +1 -0
  11. package/dist/bridge/storage-kind-bridge.d.ts +27 -0
  12. package/dist/bridge/storage-kind-bridge.d.ts.map +1 -0
  13. package/dist/bridge/storage-kind-bridge.js +110 -0
  14. package/dist/bridge/storage-kind-bridge.js.map +1 -0
  15. package/dist/bridge/types.d.ts +45 -0
  16. package/dist/bridge/types.d.ts.map +1 -0
  17. package/dist/bridge/types.js +8 -0
  18. package/dist/bridge/types.js.map +1 -0
  19. package/dist/cli/commands/list/storage.list.cli-command.descriptor.d.ts +4 -0
  20. package/dist/cli/commands/list/storage.list.cli-command.descriptor.d.ts.map +1 -0
  21. package/dist/cli/commands/list/storage.list.cli-command.descriptor.js +18 -0
  22. package/dist/cli/commands/list/storage.list.cli-command.descriptor.js.map +1 -0
  23. package/dist/cli/commands/list/storage.list.cli-command.impl.d.ts +11 -0
  24. package/dist/cli/commands/list/storage.list.cli-command.impl.d.ts.map +1 -0
  25. package/dist/cli/commands/list/storage.list.cli-command.impl.js +48 -0
  26. package/dist/cli/commands/list/storage.list.cli-command.impl.js.map +1 -0
  27. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.d.ts +4 -0
  28. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.d.ts.map +1 -0
  29. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.js +26 -0
  30. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.js.map +1 -0
  31. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.d.ts +13 -0
  32. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.d.ts.map +1 -0
  33. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.js +136 -0
  34. package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.js.map +1 -0
  35. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.d.ts +4 -0
  36. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.d.ts.map +1 -0
  37. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.js +26 -0
  38. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.js.map +1 -0
  39. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.d.ts +13 -0
  40. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.d.ts.map +1 -0
  41. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.js +165 -0
  42. package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.js.map +1 -0
  43. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.d.ts +4 -0
  44. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.d.ts.map +1 -0
  45. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.js +20 -0
  46. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.js.map +1 -0
  47. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.d.ts +12 -0
  48. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.d.ts.map +1 -0
  49. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.js +95 -0
  50. package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.js.map +1 -0
  51. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.d.ts +4 -0
  52. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.d.ts.map +1 -0
  53. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.js +15 -0
  54. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.js.map +1 -0
  55. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.d.ts +13 -0
  56. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.d.ts.map +1 -0
  57. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.js +150 -0
  58. package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.js.map +1 -0
  59. package/dist/cli/context.consumer.d.ts +26 -0
  60. package/dist/cli/context.consumer.d.ts.map +1 -0
  61. package/dist/cli/context.consumer.js +164 -0
  62. package/dist/cli/context.consumer.js.map +1 -0
  63. package/dist/cli/storage.cli-group.descriptor.d.ts +4 -0
  64. package/dist/cli/storage.cli-group.descriptor.d.ts.map +1 -0
  65. package/dist/cli/storage.cli-group.descriptor.js +11 -0
  66. package/dist/cli/storage.cli-group.descriptor.js.map +1 -0
  67. package/dist/cli/storage.sync.cli-group.descriptor.d.ts +4 -0
  68. package/dist/cli/storage.sync.cli-group.descriptor.d.ts.map +1 -0
  69. package/dist/cli/storage.sync.cli-group.descriptor.js +12 -0
  70. package/dist/cli/storage.sync.cli-group.descriptor.js.map +1 -0
  71. package/dist/cli/types.d.ts +26 -0
  72. package/dist/cli/types.d.ts.map +1 -0
  73. package/dist/cli/types.js +11 -0
  74. package/dist/cli/types.js.map +1 -0
  75. package/dist/client/index.d.ts +10 -0
  76. package/dist/client/index.d.ts.map +1 -0
  77. package/dist/client/index.js +10 -0
  78. package/dist/client/index.js.map +1 -0
  79. package/dist/client/list-browsable-adapters.d.ts +31 -0
  80. package/dist/client/list-browsable-adapters.d.ts.map +1 -0
  81. package/dist/client/list-browsable-adapters.js +40 -0
  82. package/dist/client/list-browsable-adapters.js.map +1 -0
  83. package/dist/daemon/commands/sync.d.ts +23 -0
  84. package/dist/daemon/commands/sync.d.ts.map +1 -0
  85. package/dist/daemon/commands/sync.js +169 -0
  86. package/dist/daemon/commands/sync.js.map +1 -0
  87. package/dist/docs/adapters.docs.descriptor.d.ts +4 -0
  88. package/dist/docs/adapters.docs.descriptor.d.ts.map +1 -0
  89. package/dist/docs/adapters.docs.descriptor.js +223 -0
  90. package/dist/docs/adapters.docs.descriptor.js.map +1 -0
  91. package/dist/docs/manifests.docs.descriptor.d.ts +4 -0
  92. package/dist/docs/manifests.docs.descriptor.d.ts.map +1 -0
  93. package/dist/docs/manifests.docs.descriptor.js +316 -0
  94. package/dist/docs/manifests.docs.descriptor.js.map +1 -0
  95. package/dist/docs/records.docs.descriptor.d.ts +4 -0
  96. package/dist/docs/records.docs.descriptor.d.ts.map +1 -0
  97. package/dist/docs/records.docs.descriptor.js +269 -0
  98. package/dist/docs/records.docs.descriptor.js.map +1 -0
  99. package/dist/docs/sync.docs.descriptor.d.ts +4 -0
  100. package/dist/docs/sync.docs.descriptor.d.ts.map +1 -0
  101. package/dist/docs/sync.docs.descriptor.js +279 -0
  102. package/dist/docs/sync.docs.descriptor.js.map +1 -0
  103. package/dist/index.d.ts +27 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +29 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/interfaces/adapter.d.ts +382 -0
  108. package/dist/interfaces/adapter.d.ts.map +1 -0
  109. package/dist/interfaces/adapter.js +11 -0
  110. package/dist/interfaces/adapter.js.map +1 -0
  111. package/dist/interfaces/document-store.d.ts +62 -0
  112. package/dist/interfaces/document-store.d.ts.map +1 -0
  113. package/dist/interfaces/document-store.js +8 -0
  114. package/dist/interfaces/document-store.js.map +1 -0
  115. package/dist/interfaces/index.d.ts +17 -0
  116. package/dist/interfaces/index.d.ts.map +1 -0
  117. package/dist/interfaces/index.js +24 -0
  118. package/dist/interfaces/index.js.map +1 -0
  119. package/dist/interfaces/key-value.d.ts +95 -0
  120. package/dist/interfaces/key-value.d.ts.map +1 -0
  121. package/dist/interfaces/key-value.js +8 -0
  122. package/dist/interfaces/key-value.js.map +1 -0
  123. package/dist/interfaces/query-builder.d.ts +31 -0
  124. package/dist/interfaces/query-builder.d.ts.map +1 -0
  125. package/dist/interfaces/query-builder.js +11 -0
  126. package/dist/interfaces/query-builder.js.map +1 -0
  127. package/dist/interfaces/record/index.d.ts +7 -0
  128. package/dist/interfaces/record/index.d.ts.map +1 -0
  129. package/dist/interfaces/record/index.js +7 -0
  130. package/dist/interfaces/record/index.js.map +1 -0
  131. package/dist/interfaces/record/interface.d.ts +593 -0
  132. package/dist/interfaces/record/interface.d.ts.map +1 -0
  133. package/dist/interfaces/record/interface.js +49 -0
  134. package/dist/interfaces/record/interface.js.map +1 -0
  135. package/dist/interfaces/repository.d.ts +262 -0
  136. package/dist/interfaces/repository.d.ts.map +1 -0
  137. package/dist/interfaces/repository.js +8 -0
  138. package/dist/interfaces/repository.js.map +1 -0
  139. package/dist/interfaces/schema/index.d.ts +8 -0
  140. package/dist/interfaces/schema/index.d.ts.map +1 -0
  141. package/dist/interfaces/schema/index.js +10 -0
  142. package/dist/interfaces/schema/index.js.map +1 -0
  143. package/dist/interfaces/schema/schema-interface.d.ts +516 -0
  144. package/dist/interfaces/schema/schema-interface.d.ts.map +1 -0
  145. package/dist/interfaces/schema/schema-interface.js +31 -0
  146. package/dist/interfaces/schema/schema-interface.js.map +1 -0
  147. package/dist/interfaces/schema/table-interface.d.ts +250 -0
  148. package/dist/interfaces/schema/table-interface.d.ts.map +1 -0
  149. package/dist/interfaces/schema/table-interface.js +31 -0
  150. package/dist/interfaces/schema/table-interface.js.map +1 -0
  151. package/dist/interfaces/storage-consumer-fs.d.ts +40 -0
  152. package/dist/interfaces/storage-consumer-fs.d.ts.map +1 -0
  153. package/dist/interfaces/storage-consumer-fs.js +42 -0
  154. package/dist/interfaces/storage-consumer-fs.js.map +1 -0
  155. package/dist/manifest/base.d.ts +187 -0
  156. package/dist/manifest/base.d.ts.map +1 -0
  157. package/dist/manifest/base.js +516 -0
  158. package/dist/manifest/base.js.map +1 -0
  159. package/dist/manifest/cloud-sync.consumer.d.ts +7 -0
  160. package/dist/manifest/cloud-sync.consumer.d.ts.map +1 -0
  161. package/dist/manifest/cloud-sync.consumer.js +82 -0
  162. package/dist/manifest/cloud-sync.consumer.js.map +1 -0
  163. package/dist/manifest/index.d.ts +7 -0
  164. package/dist/manifest/index.d.ts.map +1 -0
  165. package/dist/manifest/index.js +7 -0
  166. package/dist/manifest/index.js.map +1 -0
  167. package/dist/manifest/source-policy.d.ts +8 -0
  168. package/dist/manifest/source-policy.d.ts.map +1 -0
  169. package/dist/manifest/source-policy.js +62 -0
  170. package/dist/manifest/source-policy.js.map +1 -0
  171. package/dist/paths.d.ts +6 -0
  172. package/dist/paths.d.ts.map +1 -0
  173. package/dist/paths.js +152 -0
  174. package/dist/paths.js.map +1 -0
  175. package/dist/policy/context.d.ts +9 -0
  176. package/dist/policy/context.d.ts.map +1 -0
  177. package/dist/policy/context.js +29 -0
  178. package/dist/policy/context.js.map +1 -0
  179. package/dist/policy/index.d.ts +4 -0
  180. package/dist/policy/index.d.ts.map +1 -0
  181. package/dist/policy/index.js +4 -0
  182. package/dist/policy/index.js.map +1 -0
  183. package/dist/policy/overrides.d.ts +40 -0
  184. package/dist/policy/overrides.d.ts.map +1 -0
  185. package/dist/policy/overrides.js +37 -0
  186. package/dist/policy/overrides.js.map +1 -0
  187. package/dist/policy/persistence.d.ts +38 -0
  188. package/dist/policy/persistence.d.ts.map +1 -0
  189. package/dist/policy/persistence.js +65 -0
  190. package/dist/policy/persistence.js.map +1 -0
  191. package/dist/policy/types.d.ts +21 -0
  192. package/dist/policy/types.d.ts.map +1 -0
  193. package/dist/policy/types.js +7 -0
  194. package/dist/policy/types.js.map +1 -0
  195. package/dist/record/base.d.ts +186 -0
  196. package/dist/record/base.d.ts.map +1 -0
  197. package/dist/record/base.js +1437 -0
  198. package/dist/record/base.js.map +1 -0
  199. package/dist/record/events.d.ts +71 -0
  200. package/dist/record/events.d.ts.map +1 -0
  201. package/dist/record/events.js +52 -0
  202. package/dist/record/events.js.map +1 -0
  203. package/dist/record/extensions.d.ts +51 -0
  204. package/dist/record/extensions.d.ts.map +1 -0
  205. package/dist/record/extensions.js +222 -0
  206. package/dist/record/extensions.js.map +1 -0
  207. package/dist/record/index.d.ts +7 -0
  208. package/dist/record/index.d.ts.map +1 -0
  209. package/dist/record/index.js +7 -0
  210. package/dist/record/index.js.map +1 -0
  211. package/dist/resolver/storage-backed-resolver.d.ts +23 -0
  212. package/dist/resolver/storage-backed-resolver.d.ts.map +1 -0
  213. package/dist/resolver/storage-backed-resolver.js +187 -0
  214. package/dist/resolver/storage-backed-resolver.js.map +1 -0
  215. package/dist/schemas/adapter-descriptor.d.ts +108 -0
  216. package/dist/schemas/adapter-descriptor.d.ts.map +1 -0
  217. package/dist/schemas/adapter-descriptor.js +101 -0
  218. package/dist/schemas/adapter-descriptor.js.map +1 -0
  219. package/dist/schemas/index.d.ts +14 -0
  220. package/dist/schemas/index.d.ts.map +1 -0
  221. package/dist/schemas/index.js +12 -0
  222. package/dist/schemas/index.js.map +1 -0
  223. package/dist/schemas/manifest-descriptor.d.ts +110 -0
  224. package/dist/schemas/manifest-descriptor.d.ts.map +1 -0
  225. package/dist/schemas/manifest-descriptor.js +114 -0
  226. package/dist/schemas/manifest-descriptor.js.map +1 -0
  227. package/dist/schemas/record/descriptor.d.ts +570 -0
  228. package/dist/schemas/record/descriptor.d.ts.map +1 -0
  229. package/dist/schemas/record/descriptor.js +389 -0
  230. package/dist/schemas/record/descriptor.js.map +1 -0
  231. package/dist/schemas/record/index.d.ts +8 -0
  232. package/dist/schemas/record/index.d.ts.map +1 -0
  233. package/dist/schemas/record/index.js +7 -0
  234. package/dist/schemas/record/index.js.map +1 -0
  235. package/dist/schemas/record/relation.d.ts +36 -0
  236. package/dist/schemas/record/relation.d.ts.map +1 -0
  237. package/dist/schemas/record/relation.js +37 -0
  238. package/dist/schemas/record/relation.js.map +1 -0
  239. package/dist/schemas/schema/descriptor.d.ts +231 -0
  240. package/dist/schemas/schema/descriptor.d.ts.map +1 -0
  241. package/dist/schemas/schema/descriptor.js +205 -0
  242. package/dist/schemas/schema/descriptor.js.map +1 -0
  243. package/dist/schemas/schema/index.d.ts +7 -0
  244. package/dist/schemas/schema/index.d.ts.map +1 -0
  245. package/dist/schemas/schema/index.js +7 -0
  246. package/dist/schemas/schema/index.js.map +1 -0
  247. package/dist/schemas/sync-bridge-descriptor.d.ts +124 -0
  248. package/dist/schemas/sync-bridge-descriptor.d.ts.map +1 -0
  249. package/dist/schemas/sync-bridge-descriptor.js +67 -0
  250. package/dist/schemas/sync-bridge-descriptor.js.map +1 -0
  251. package/dist/server/manifest-route.d.ts +92 -0
  252. package/dist/server/manifest-route.d.ts.map +1 -0
  253. package/dist/server/manifest-route.js +226 -0
  254. package/dist/server/manifest-route.js.map +1 -0
  255. package/dist/storage-policy.context.descriptor.d.ts +16 -0
  256. package/dist/storage-policy.context.descriptor.d.ts.map +1 -0
  257. package/dist/storage-policy.context.descriptor.js +16 -0
  258. package/dist/storage-policy.context.descriptor.js.map +1 -0
  259. package/dist/storage-policy.context.server.d.ts +14 -0
  260. package/dist/storage-policy.context.server.d.ts.map +1 -0
  261. package/dist/storage-policy.context.server.js +27 -0
  262. package/dist/storage-policy.context.server.js.map +1 -0
  263. package/dist/storage.plugin.d.ts +38 -0
  264. package/dist/storage.plugin.d.ts.map +1 -0
  265. package/dist/storage.plugin.js +204 -0
  266. package/dist/storage.plugin.js.map +1 -0
  267. package/dist/sync/cloud-pull.d.ts +15 -0
  268. package/dist/sync/cloud-pull.d.ts.map +1 -0
  269. package/dist/sync/cloud-pull.js +130 -0
  270. package/dist/sync/cloud-pull.js.map +1 -0
  271. package/dist/sync/cloud-record-crud-config.d.ts +19 -0
  272. package/dist/sync/cloud-record-crud-config.d.ts.map +1 -0
  273. package/dist/sync/cloud-record-crud-config.js +22 -0
  274. package/dist/sync/cloud-record-crud-config.js.map +1 -0
  275. package/dist/sync/record-sync-bridge.d.ts +5 -0
  276. package/dist/sync/record-sync-bridge.d.ts.map +1 -0
  277. package/dist/sync/record-sync-bridge.js +121 -0
  278. package/dist/sync/record-sync-bridge.js.map +1 -0
  279. package/dist/sync/resolve-sync-target.d.ts +23 -0
  280. package/dist/sync/resolve-sync-target.d.ts.map +1 -0
  281. package/dist/sync/resolve-sync-target.js +45 -0
  282. package/dist/sync/resolve-sync-target.js.map +1 -0
  283. package/dist/sync/schemas/cloud-pull.d.ts +25 -0
  284. package/dist/sync/schemas/cloud-pull.d.ts.map +1 -0
  285. package/dist/sync/schemas/cloud-pull.js +7 -0
  286. package/dist/sync/schemas/cloud-pull.js.map +1 -0
  287. package/dist/sync/schemas/index.d.ts +9 -0
  288. package/dist/sync/schemas/index.d.ts.map +1 -0
  289. package/dist/sync/schemas/index.js +9 -0
  290. package/dist/sync/schemas/index.js.map +1 -0
  291. package/dist/sync/schemas/sync-helpers.d.ts +17 -0
  292. package/dist/sync/schemas/sync-helpers.d.ts.map +1 -0
  293. package/dist/sync/schemas/sync-helpers.js +7 -0
  294. package/dist/sync/schemas/sync-helpers.js.map +1 -0
  295. package/dist/sync/schemas/sync-state.d.ts +65 -0
  296. package/dist/sync/schemas/sync-state.d.ts.map +1 -0
  297. package/dist/sync/schemas/sync-state.js +32 -0
  298. package/dist/sync/schemas/sync-state.js.map +1 -0
  299. package/dist/sync/sync-bridge.d.ts +80 -0
  300. package/dist/sync/sync-bridge.d.ts.map +1 -0
  301. package/dist/sync/sync-bridge.js +617 -0
  302. package/dist/sync/sync-bridge.js.map +1 -0
  303. package/dist/sync/sync-manifest.d.ts +20 -0
  304. package/dist/sync/sync-manifest.d.ts.map +1 -0
  305. package/dist/sync/sync-manifest.js +140 -0
  306. package/dist/sync/sync-manifest.js.map +1 -0
  307. package/dist/sync/sync-state.consumer.d.ts +23 -0
  308. package/dist/sync/sync-state.consumer.d.ts.map +1 -0
  309. package/dist/sync/sync-state.consumer.js +111 -0
  310. package/dist/sync/sync-state.consumer.js.map +1 -0
  311. package/dist/transform/index.d.ts +75 -0
  312. package/dist/transform/index.d.ts.map +1 -0
  313. package/dist/transform/index.js +81 -0
  314. package/dist/transform/index.js.map +1 -0
  315. package/dist/transform/transform-descriptor.d.ts +51 -0
  316. package/dist/transform/transform-descriptor.d.ts.map +1 -0
  317. package/dist/transform/transform-descriptor.js +30 -0
  318. package/dist/transform/transform-descriptor.js.map +1 -0
  319. package/dist/tui/storage-explorer.descriptor.d.ts +14 -0
  320. package/dist/tui/storage-explorer.descriptor.d.ts.map +1 -0
  321. package/dist/tui/storage-explorer.descriptor.js +29 -0
  322. package/dist/tui/storage-explorer.descriptor.js.map +1 -0
  323. package/dist/tui/storage-explorer.impl.consumer.d.ts +11 -0
  324. package/dist/tui/storage-explorer.impl.consumer.d.ts.map +1 -0
  325. package/dist/tui/storage-explorer.impl.consumer.js +13 -0
  326. package/dist/tui/storage-explorer.impl.consumer.js.map +1 -0
  327. package/dist/tui/storage-open.descriptor.d.ts +14 -0
  328. package/dist/tui/storage-open.descriptor.d.ts.map +1 -0
  329. package/dist/tui/storage-open.descriptor.js +28 -0
  330. package/dist/tui/storage-open.descriptor.js.map +1 -0
  331. package/dist/tui/storage-open.impl.consumer.d.ts +11 -0
  332. package/dist/tui/storage-open.impl.consumer.d.ts.map +1 -0
  333. package/dist/tui/storage-open.impl.consumer.js +13 -0
  334. package/dist/tui/storage-open.impl.consumer.js.map +1 -0
  335. package/dist/utils/error-context.d.ts +2 -0
  336. package/dist/utils/error-context.d.ts.map +1 -0
  337. package/dist/utils/error-context.js +17 -0
  338. package/dist/utils/error-context.js.map +1 -0
  339. package/package.json +241 -0
  340. package/src/adapters/index.ts +13 -0
  341. package/src/bridge/index.ts +2 -0
  342. package/src/bridge/storage-kind-bridge.ts +131 -0
  343. package/src/bridge/types.ts +46 -0
  344. package/src/cli/commands/list/storage.list.cli-command.descriptor.ts +20 -0
  345. package/src/cli/commands/list/storage.list.cli-command.impl.ts +72 -0
  346. package/src/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.ts +28 -0
  347. package/src/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.ts +171 -0
  348. package/src/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.ts +28 -0
  349. package/src/cli/commands/sync/push/storage.sync.push.cli-command.impl.ts +213 -0
  350. package/src/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.ts +22 -0
  351. package/src/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.ts +129 -0
  352. package/src/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.ts +17 -0
  353. package/src/cli/commands/sync/status/storage.sync.status.cli-command.impl.ts +192 -0
  354. package/src/cli/context.consumer.ts +195 -0
  355. package/src/cli/storage.cli-group.descriptor.ts +13 -0
  356. package/src/cli/storage.sync.cli-group.descriptor.ts +14 -0
  357. package/src/cli/types.ts +25 -0
  358. package/src/client/index.ts +10 -0
  359. package/src/client/list-browsable-adapters.ts +42 -0
  360. package/src/daemon/commands/sync.ts +225 -0
  361. package/src/docs/adapters.docs.descriptor.ts +225 -0
  362. package/src/docs/manifests.docs.descriptor.ts +318 -0
  363. package/src/docs/records.docs.descriptor.ts +271 -0
  364. package/src/docs/sync.docs.descriptor.ts +281 -0
  365. package/src/index.ts +109 -0
  366. package/src/interfaces/adapter.ts +451 -0
  367. package/src/interfaces/document-store.ts +74 -0
  368. package/src/interfaces/index.ts +117 -0
  369. package/src/interfaces/key-value.ts +112 -0
  370. package/src/interfaces/query-builder.ts +31 -0
  371. package/src/interfaces/record/index.ts +58 -0
  372. package/src/interfaces/record/interface.ts +723 -0
  373. package/src/interfaces/repository.ts +300 -0
  374. package/src/interfaces/schema/index.ts +37 -0
  375. package/src/interfaces/schema/schema-interface.ts +586 -0
  376. package/src/interfaces/schema/table-interface.ts +314 -0
  377. package/src/interfaces/storage-consumer-fs.ts +66 -0
  378. package/src/manifest/base.ts +594 -0
  379. package/src/manifest/cloud-sync.consumer.ts +120 -0
  380. package/src/manifest/index.ts +7 -0
  381. package/src/manifest/source-policy.ts +74 -0
  382. package/src/paths.ts +171 -0
  383. package/src/policy/context.ts +30 -0
  384. package/src/policy/index.ts +20 -0
  385. package/src/policy/overrides.ts +46 -0
  386. package/src/policy/persistence.ts +117 -0
  387. package/src/policy/types.ts +28 -0
  388. package/src/record/base.ts +1872 -0
  389. package/src/record/events.ts +56 -0
  390. package/src/record/extensions.ts +308 -0
  391. package/src/record/index.ts +7 -0
  392. package/src/resolver/storage-backed-resolver.ts +255 -0
  393. package/src/schemas/adapter-descriptor.ts +128 -0
  394. package/src/schemas/index.ts +104 -0
  395. package/src/schemas/manifest-descriptor.ts +143 -0
  396. package/src/schemas/record/descriptor.ts +621 -0
  397. package/src/schemas/record/index.ts +60 -0
  398. package/src/schemas/record/relation.ts +49 -0
  399. package/src/schemas/schema/descriptor.ts +278 -0
  400. package/src/schemas/schema/index.ts +14 -0
  401. package/src/schemas/sync-bridge-descriptor.ts +143 -0
  402. package/src/server/manifest-route.ts +319 -0
  403. package/src/storage-policy.context.descriptor.ts +17 -0
  404. package/src/storage-policy.context.server.ts +34 -0
  405. package/src/storage.plugin.ts +264 -0
  406. package/src/sync/cloud-pull.ts +202 -0
  407. package/src/sync/cloud-record-crud-config.ts +41 -0
  408. package/src/sync/record-sync-bridge.ts +183 -0
  409. package/src/sync/resolve-sync-target.ts +57 -0
  410. package/src/sync/schemas/cloud-pull.ts +26 -0
  411. package/src/sync/schemas/index.ts +18 -0
  412. package/src/sync/schemas/sync-helpers.ts +18 -0
  413. package/src/sync/schemas/sync-state.ts +41 -0
  414. package/src/sync/sync-bridge.ts +763 -0
  415. package/src/sync/sync-manifest.ts +212 -0
  416. package/src/sync/sync-state.consumer.ts +144 -0
  417. package/src/transform/index.ts +130 -0
  418. package/src/transform/transform-descriptor.ts +53 -0
  419. package/src/tui/storage-explorer.descriptor.ts +32 -0
  420. package/src/tui/storage-explorer.impl.consumer.ts +14 -0
  421. package/src/tui/storage-open.descriptor.ts +31 -0
  422. package/src/tui/storage-open.impl.consumer.ts +14 -0
  423. package/src/utils/error-context.ts +19 -0
@@ -0,0 +1,763 @@
1
+ /**
2
+ * Sync Bridge — Default Implementation
3
+ *
4
+ * Executes bidirectional sync between local runtime descriptors and a cloud CRUD API.
5
+ * Each SyncBridge instance corresponds to a storage/sync-bridge descriptor.
6
+ *
7
+ * Flow:
8
+ * 1. Collect local descriptors via runtime.query(source.runtimeKind)
9
+ * 2. Fetch remote entities from target.apiPath
10
+ * 3. Diff local ↔ remote by idField
11
+ * 4. Push new/updated local items, pull new/updated remote items
12
+ * 5. Track state in sync-state.json alongside storage manifests
13
+ */
14
+
15
+ import { getRuntimeEnv } from '@vibesdotdev/runtime';
16
+ import type { SyncBridgeDescriptor } from '../schemas/sync-bridge-descriptor';
17
+
18
+ import type { KindContext } from '@vibesdotdev/runtime/schemas/kind';
19
+ import {
20
+ loadSyncState,
21
+ saveSyncState,
22
+ getManifestSyncState,
23
+ markSynced,
24
+ markLocalAhead,
25
+ markConflict,
26
+ markError,
27
+ markRemoteAhead,
28
+ computeHash
29
+ } from './sync-state.consumer';
30
+
31
+ export interface SyncBridgeResult {
32
+ bridgeId: string;
33
+ pushed: number;
34
+ pulled: number;
35
+ skipped: number;
36
+ conflicts: number;
37
+ errors: number;
38
+ details: Array<{
39
+ entityId: string;
40
+ action: 'pushed' | 'pulled' | 'skipped' | 'conflict' | 'error';
41
+ message?: string;
42
+ }>;
43
+ }
44
+
45
+ export interface SyncBridgeOptions {
46
+ dryRun?: boolean;
47
+ force?: boolean;
48
+ direction?: 'push' | 'pull' | 'both';
49
+ }
50
+
51
+ /**
52
+ * Default SyncBridge implementation.
53
+ * Constructed by the runtime with (descriptor, context).
54
+ */
55
+ export class SyncBridge {
56
+ readonly descriptor: SyncBridgeDescriptor;
57
+
58
+ constructor(descriptor: SyncBridgeDescriptor, _context?: KindContext) {
59
+ this.descriptor = descriptor;
60
+ }
61
+
62
+ get id(): string {
63
+ return this.descriptor.id;
64
+ }
65
+
66
+ /** Cached h2 fetch for HTTPS local endpoints */
67
+ private _h2Fetch: ((input: string, init?: RequestInit) => Promise<Response>) | null = null;
68
+
69
+ /**
70
+ * Get an HTTP/2-aware fetch function.
71
+ * Local HTTPS endpoints (vite dev) require h2 since Bun's fetch is h1 only.
72
+ */
73
+ private async getFetch(
74
+ apiEndpoint: string
75
+ ): Promise<(input: string, init?: RequestInit) => Promise<Response>> {
76
+ if (this._h2Fetch) return this._h2Fetch;
77
+
78
+ const isHttps = apiEndpoint.startsWith('https://');
79
+ const isLocal =
80
+ apiEndpoint.includes('.local') ||
81
+ apiEndpoint.includes('localhost') ||
82
+ apiEndpoint.includes('127.0.0.1');
83
+
84
+ if (isHttps && isLocal) {
85
+ try {
86
+ // Set REJECT_UNAUTHORIZED=false for local self-signed certs
87
+ if (!getRuntimeEnv('REJECT_UNAUTHORIZED')) {
88
+ globalThis.process?.['env'] && (globalThis.process.env.REJECT_UNAUTHORIZED = 'false');
89
+ }
90
+ // h2 fetch lives in the app layer; local HTTP/2 dev servers fall through to regular fetch.
91
+ } catch {
92
+ // Fall through to regular fetch
93
+ }
94
+ }
95
+
96
+ this._h2Fetch = (input: string, init?: RequestInit) => fetch(input, init);
97
+ return this._h2Fetch;
98
+ }
99
+
100
+ /**
101
+ * Collect local descriptors from the runtime.
102
+ */
103
+ async collectLocal(): Promise<Record<string, unknown>[]> {
104
+ if (this.descriptor.collect) {
105
+ return this.descriptor.collect();
106
+ }
107
+
108
+ const { getVibesRuntime } = await import('@vibesdotdev/runtime');
109
+ const runtime = getVibesRuntime();
110
+ const descriptors = runtime
111
+ .query(this.descriptor.source.runtimeKind)
112
+ .descriptors() as unknown as Record<string, unknown>[];
113
+ return descriptors;
114
+ }
115
+
116
+ private stripSensitiveFields(entity: Record<string, unknown>): Record<string, unknown> {
117
+ const sanitized = { ...entity };
118
+ for (const field of this.descriptor.sync.sensitiveFields) {
119
+ delete sanitized[field];
120
+ }
121
+ return sanitized;
122
+ }
123
+
124
+ private buildPushShape(entity: Record<string, unknown>): Record<string, unknown> {
125
+ const transformed = this.descriptor.toRemote ? this.descriptor.toRemote(entity) : entity;
126
+ return this.stripSensitiveFields(transformed);
127
+ }
128
+
129
+ private buildComparableRemote(entity: Record<string, unknown>): Record<string, unknown> {
130
+ const localShape = this.descriptor.fromRemote ? this.descriptor.fromRemote(entity) : entity;
131
+ return this.buildPushShape(localShape);
132
+ }
133
+
134
+ private getEntityTimestamp(entity: Record<string, unknown> | undefined): number | null {
135
+ if (!entity) return null;
136
+ const value =
137
+ typeof entity.deletedAt === 'string'
138
+ ? entity.deletedAt
139
+ : typeof entity.updatedAt === 'string'
140
+ ? entity.updatedAt
141
+ : typeof entity.createdAt === 'string'
142
+ ? entity.createdAt
143
+ : null;
144
+ if (!value) return null;
145
+ const timestamp = new Date(value).getTime();
146
+ return Number.isNaN(timestamp) ? null : timestamp;
147
+ }
148
+
149
+ private isDeleted(entity: Record<string, unknown> | undefined): boolean {
150
+ return Boolean(entity?.deletedAt);
151
+ }
152
+
153
+ private resolveConflictAction(
154
+ localItem: Record<string, unknown>,
155
+ remoteItem: Record<string, unknown>,
156
+ direction: 'push' | 'pull' | 'both'
157
+ ): 'push' | 'pull' | 'conflict' {
158
+ const strategy = this.descriptor.sync.conflictStrategy;
159
+
160
+ if (strategy === 'manual') {
161
+ return 'conflict';
162
+ }
163
+ if (strategy === 'local-wins') {
164
+ return direction === 'pull' ? 'conflict' : 'push';
165
+ }
166
+ if (strategy === 'remote-wins') {
167
+ return direction === 'push' ? 'conflict' : 'pull';
168
+ }
169
+
170
+ const localTimestamp = this.getEntityTimestamp(localItem);
171
+ const remoteTimestamp = this.getEntityTimestamp(remoteItem);
172
+ if (localTimestamp === null || remoteTimestamp === null || localTimestamp === remoteTimestamp) {
173
+ return 'conflict';
174
+ }
175
+
176
+ if (localTimestamp > remoteTimestamp) {
177
+ return direction === 'pull' ? 'conflict' : 'push';
178
+ }
179
+
180
+ return direction === 'push' ? 'conflict' : 'pull';
181
+ }
182
+
183
+ private extractList(data: unknown): Record<string, unknown>[] {
184
+ if (!data || typeof data !== 'object') {
185
+ return [];
186
+ }
187
+
188
+ const record = data as Record<string, unknown>;
189
+ const listField = this.descriptor.target.listField;
190
+ if (listField && Array.isArray(record[listField])) {
191
+ return record[listField] as Record<string, unknown>[];
192
+ }
193
+ if (Array.isArray(data)) {
194
+ return data as Record<string, unknown>[];
195
+ }
196
+ for (const key of ['items', 'data', 'results', 'benchmarks', 'scenarios']) {
197
+ if (Array.isArray(record[key])) {
198
+ return record[key] as Record<string, unknown>[];
199
+ }
200
+ }
201
+ return [];
202
+ }
203
+
204
+ /**
205
+ * Fetch remote entities from the cloud CRUD API.
206
+ */
207
+ async fetchRemote(apiEndpoint: string, apiToken?: string): Promise<Record<string, unknown>[]> {
208
+ const { target } = this.descriptor;
209
+ const url = `${apiEndpoint}${target.apiPath}`;
210
+
211
+ try {
212
+ const headers: Record<string, string> = {
213
+ 'Content-Type': 'application/json'
214
+ };
215
+ if (apiToken) {
216
+ headers['Authorization'] = `Bearer ${apiToken}`;
217
+ }
218
+
219
+ const doFetch = await this.getFetch(apiEndpoint);
220
+ const fetchPage = async (
221
+ page: number
222
+ ): Promise<{
223
+ items: Record<string, unknown>[];
224
+ totalPages: number;
225
+ }> => {
226
+ const pageUrl = new URL(url);
227
+ pageUrl.searchParams.set('page', String(page));
228
+ if (!pageUrl.searchParams.has('perPage')) {
229
+ pageUrl.searchParams.set('perPage', '100');
230
+ }
231
+ if (target.includeDeleted && !pageUrl.searchParams.has('includeDeleted')) {
232
+ pageUrl.searchParams.set('includeDeleted', 'true');
233
+ }
234
+
235
+ const response = await doFetch(pageUrl.toString(), { headers });
236
+ if (!response.ok) {
237
+ if (response.status === 404) {
238
+ return { items: [], totalPages: 0 };
239
+ }
240
+ throw new Error(`API ${response.status}: ${response.statusText}`);
241
+ }
242
+
243
+ const data = await response.json();
244
+ const items = this.extractList(data);
245
+ const pagination =
246
+ data && typeof data === 'object' && 'pagination' in (data as Record<string, unknown>)
247
+ ? ((data as Record<string, unknown>).pagination as Record<string, unknown>)
248
+ : null;
249
+ const totalPages =
250
+ typeof pagination?.totalPages === 'number'
251
+ ? pagination.totalPages
252
+ : typeof pagination?.totalPages === 'string'
253
+ ? Number.parseInt(pagination.totalPages, 10)
254
+ : 1;
255
+
256
+ return { items, totalPages: Number.isNaN(totalPages) ? 1 : totalPages };
257
+ };
258
+
259
+ const firstPage = await fetchPage(1);
260
+ if (firstPage.totalPages <= 1) {
261
+ return firstPage.items;
262
+ }
263
+
264
+ const items = [...firstPage.items];
265
+ for (let page = 2; page <= firstPage.totalPages; page++) {
266
+ const nextPage = await fetchPage(page);
267
+ items.push(...nextPage.items);
268
+ }
269
+
270
+ return items;
271
+ } catch (error) {
272
+ throw new Error(`Failed to fetch remote from ${url}: ${(error as Error).message}`);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Push a single entity to the cloud.
278
+ */
279
+ async pushEntity(
280
+ entity: Record<string, unknown>,
281
+ apiEndpoint: string,
282
+ apiToken?: string
283
+ ): Promise<void> {
284
+ const { target, sync } = this.descriptor;
285
+ const entityId = entity[sync.idField] as string;
286
+ const entityPath = target.entityPath ?? `${target.apiPath}/:id`;
287
+ const url = `${apiEndpoint}${entityPath.replace(':id', encodeURIComponent(entityId))}`;
288
+
289
+ const headers: Record<string, string> = {
290
+ 'Content-Type': 'application/json'
291
+ };
292
+ if (apiToken) {
293
+ headers['Authorization'] = `Bearer ${apiToken}`;
294
+ }
295
+
296
+ const doFetch = await this.getFetch(apiEndpoint);
297
+
298
+ // Try PUT first (upsert), fall back to POST
299
+ let response = await doFetch(url, {
300
+ method: 'PUT',
301
+ headers,
302
+ body: JSON.stringify(entity)
303
+ });
304
+
305
+ if (response.status === 404 || response.status === 405) {
306
+ // PUT not supported for this entity — try POST to collection
307
+ response = await doFetch(`${apiEndpoint}${target.apiPath}`, {
308
+ method: 'POST',
309
+ headers,
310
+ body: JSON.stringify(entity)
311
+ });
312
+ }
313
+
314
+ if (!response.ok) {
315
+ const body = await response.text().catch(() => '');
316
+ throw new Error(`Push failed (${response.status}): ${body}`);
317
+ }
318
+ }
319
+
320
+ async deleteEntity(
321
+ entity: Record<string, unknown>,
322
+ apiEndpoint: string,
323
+ apiToken?: string
324
+ ): Promise<void> {
325
+ const { target, sync } = this.descriptor;
326
+ const entityId = entity[sync.idField] as string;
327
+ const entityPath = target.entityPath ?? `${target.apiPath}/:id`;
328
+ const url = `${apiEndpoint}${entityPath.replace(':id', encodeURIComponent(entityId))}`;
329
+
330
+ const headers: Record<string, string> = {};
331
+ if (apiToken) {
332
+ headers['Authorization'] = `Bearer ${apiToken}`;
333
+ }
334
+
335
+ const doFetch = await this.getFetch(apiEndpoint);
336
+ if (this.descriptor.deleteRemote) {
337
+ await this.descriptor.deleteRemote(entity, {
338
+ apiEndpoint,
339
+ apiToken,
340
+ fetch: doFetch
341
+ });
342
+ return;
343
+ }
344
+
345
+ const response = await doFetch(url, {
346
+ method: 'DELETE',
347
+ headers
348
+ });
349
+
350
+ if (response.status === 404) {
351
+ return;
352
+ }
353
+
354
+ if (!response.ok) {
355
+ const body = await response.text().catch(() => '');
356
+ throw new Error(`Delete failed (${response.status}): ${body}`);
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Persist pulled items locally.
362
+ * Storage-backed bridges: merge into the storage manifest (localdb/json).
363
+ * Descriptor-only bridges: register with runtime.
364
+ */
365
+ private async persistPulledItems(
366
+ newItems: Record<string, unknown>[],
367
+ existingLocalItems: Record<string, unknown>[],
368
+ idField: string
369
+ ): Promise<void> {
370
+ const { writePulled, storageManifestId } = this.descriptor;
371
+
372
+ if (writePulled) {
373
+ await writePulled(newItems);
374
+ return;
375
+ }
376
+
377
+ if (storageManifestId) {
378
+ const { StorageManifest } = await import('../manifest/base');
379
+ const manifest = StorageManifest.fromId<Record<string, unknown>[]>(storageManifestId);
380
+ const existing = (await manifest.load()) ?? [];
381
+ if (newItems.length > 0) {
382
+ const merged = new Map(existing.map((item) => [String(item[idField]), item]));
383
+ for (const item of newItems) {
384
+ merged.set(String(item[idField]), item);
385
+ }
386
+ await manifest.save([...merged.values()] as never);
387
+ }
388
+ return;
389
+ }
390
+
391
+ // Descriptor-only: register with runtime
392
+ const { getVibesRuntime } = await import('@vibesdotdev/runtime');
393
+ const runtime = getVibesRuntime();
394
+ const kindId = this.descriptor.source.runtimeKind;
395
+ if (!runtime.hasKind(kindId)) return;
396
+
397
+ for (const item of newItems) {
398
+ try {
399
+ runtime.registerDescriptor(
400
+ kindId,
401
+ item as unknown as Parameters<typeof runtime.registerDescriptor>[1]
402
+ );
403
+ } catch {
404
+ // Descriptor already registered or invalid — skip
405
+ }
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Execute a full sync cycle.
411
+ */
412
+ async execute(
413
+ apiEndpoint: string,
414
+ options: SyncBridgeOptions = {},
415
+ apiToken?: string
416
+ ): Promise<SyncBridgeResult> {
417
+ const { sync } = this.descriptor;
418
+ const { dryRun = false, force = false, direction = 'both' } = options;
419
+ const idField = sync.idField;
420
+
421
+ const result: SyncBridgeResult = {
422
+ bridgeId: this.id,
423
+ pushed: 0,
424
+ pulled: 0,
425
+ skipped: 0,
426
+ conflicts: 0,
427
+ errors: 0,
428
+ details: []
429
+ };
430
+
431
+ const syncState = await loadSyncState();
432
+
433
+ try {
434
+ let localItems = await this.collectLocal();
435
+
436
+ if (sync.ownershipFilter !== 'all' && this.descriptor.source.sourceTypeField) {
437
+ const field = this.descriptor.source.sourceTypeField;
438
+ localItems = localItems.filter((item) => {
439
+ const sourceType = item[field] as string | undefined;
440
+ if (sync.ownershipFilter === 'user-only') return sourceType !== 'system';
441
+ if (sync.ownershipFilter === 'system-only') return sourceType === 'system';
442
+ return true;
443
+ });
444
+ }
445
+
446
+ let remoteItems: Record<string, unknown>[] = [];
447
+ try {
448
+ remoteItems = await this.fetchRemote(apiEndpoint, apiToken);
449
+ } catch (error) {
450
+ if (direction === 'pull') {
451
+ result.errors++;
452
+ result.details.push({
453
+ entityId: '*',
454
+ action: 'error',
455
+ message: `Remote unreachable: ${(error as Error).message}`
456
+ });
457
+ return result;
458
+ }
459
+ }
460
+
461
+ const pushAllowed = direction === 'push' || direction === 'both';
462
+ const pullAllowed = direction === 'pull' || direction === 'both';
463
+ const doFetch = await this.getFetch(apiEndpoint);
464
+
465
+ const localMap = new Map(localItems.map((item) => [String(item[idField]), item]));
466
+ const remoteMap = new Map(
467
+ remoteItems.map((item) => [String(item[idField] ?? item.id), item])
468
+ );
469
+ const allEntityIds = new Set([...localMap.keys(), ...remoteMap.keys()]);
470
+ const pulledItems: Record<string, unknown>[] = [];
471
+
472
+ const pushLocal = async (
473
+ entityId: string,
474
+ localItem: Record<string, unknown>,
475
+ localHash: string
476
+ ): Promise<void> => {
477
+ const stateKey = `${this.id}:${entityId}`;
478
+ const pushItem = this.buildPushShape(localItem);
479
+
480
+ if (dryRun) {
481
+ result.pushed++;
482
+ result.details.push({
483
+ entityId,
484
+ action: 'pushed',
485
+ message: '[dry-run] Would push to cloud'
486
+ });
487
+ return;
488
+ }
489
+
490
+ try {
491
+ if (this.descriptor.beforePush) {
492
+ await this.descriptor.beforePush(localItem, {
493
+ apiEndpoint,
494
+ apiToken,
495
+ fetch: doFetch
496
+ });
497
+ }
498
+ await this.pushEntity(pushItem, apiEndpoint, apiToken);
499
+ result.pushed++;
500
+ result.details.push({ entityId, action: 'pushed' });
501
+ markSynced(syncState, stateKey, localHash, 'push');
502
+ } catch (error) {
503
+ result.errors++;
504
+ result.details.push({
505
+ entityId,
506
+ action: 'error',
507
+ message: `Push failed: ${(error as Error).message}`
508
+ });
509
+ markError(syncState, stateKey, (error as Error).message);
510
+ }
511
+ };
512
+
513
+ const deleteRemote = async (
514
+ entityId: string,
515
+ localItem: Record<string, unknown>,
516
+ localHash: string
517
+ ): Promise<void> => {
518
+ const stateKey = `${this.id}:${entityId}`;
519
+
520
+ if (dryRun) {
521
+ result.pushed++;
522
+ result.details.push({
523
+ entityId,
524
+ action: 'pushed',
525
+ message: '[dry-run] Would delete remote tombstone'
526
+ });
527
+ return;
528
+ }
529
+
530
+ try {
531
+ await this.deleteEntity(localItem, apiEndpoint, apiToken);
532
+ result.pushed++;
533
+ result.details.push({
534
+ entityId,
535
+ action: 'pushed',
536
+ message: 'Deleted remote via tombstone'
537
+ });
538
+ markSynced(syncState, stateKey, localHash, 'push');
539
+ } catch (error) {
540
+ result.errors++;
541
+ result.details.push({
542
+ entityId,
543
+ action: 'error',
544
+ message: `Delete failed: ${(error as Error).message}`
545
+ });
546
+ markError(syncState, stateKey, (error as Error).message);
547
+ }
548
+ };
549
+
550
+ const pullRemote = (
551
+ entityId: string,
552
+ remoteItem: Record<string, unknown>,
553
+ remoteHash: string
554
+ ): void => {
555
+ const stateKey = `${this.id}:${entityId}`;
556
+ const localItem = this.descriptor.fromRemote
557
+ ? this.descriptor.fromRemote(remoteItem)
558
+ : remoteItem;
559
+
560
+ if (dryRun) {
561
+ result.pulled++;
562
+ result.details.push({
563
+ entityId,
564
+ action: 'pulled',
565
+ message: '[dry-run] Would pull from cloud'
566
+ });
567
+ return;
568
+ }
569
+
570
+ pulledItems.push(localItem);
571
+ result.pulled++;
572
+ result.details.push({ entityId, action: 'pulled' });
573
+ markSynced(syncState, stateKey, remoteHash, 'pull');
574
+ };
575
+
576
+ for (const entityId of allEntityIds) {
577
+ const stateKey = `${this.id}:${entityId}`;
578
+ const localItem = localMap.get(entityId);
579
+ const remoteItem = remoteMap.get(entityId);
580
+ const comparableRemote = remoteItem
581
+ ? this.descriptor.fromRemote
582
+ ? this.descriptor.fromRemote(remoteItem)
583
+ : remoteItem
584
+ : null;
585
+ const localHash = localItem ? computeHash(this.buildPushShape(localItem)) : null;
586
+ const remoteHash = comparableRemote
587
+ ? computeHash(this.buildPushShape(comparableRemote))
588
+ : null;
589
+ const prev = getManifestSyncState(syncState, stateKey);
590
+ const localDeleted = this.isDeleted(localItem);
591
+ const remoteDeleted = this.isDeleted(comparableRemote ?? undefined);
592
+
593
+ if (localItem && remoteItem && localHash === remoteHash) {
594
+ result.skipped++;
595
+ result.details.push({ entityId, action: 'skipped', message: 'In sync' });
596
+ markSynced(
597
+ syncState,
598
+ stateKey,
599
+ localHash ?? remoteHash ?? '',
600
+ direction === 'pull' ? 'pull' : 'push'
601
+ );
602
+ continue;
603
+ }
604
+
605
+ if (localItem && !remoteItem) {
606
+ if (localDeleted) {
607
+ result.skipped++;
608
+ result.details.push({
609
+ entityId,
610
+ action: 'skipped',
611
+ message: 'Local tombstone already applied remotely'
612
+ });
613
+ if (localHash) {
614
+ markSynced(syncState, stateKey, localHash, 'push');
615
+ }
616
+ continue;
617
+ }
618
+ if (pushAllowed && localHash) {
619
+ await pushLocal(entityId, localItem, localHash);
620
+ } else {
621
+ result.skipped++;
622
+ result.details.push({
623
+ entityId,
624
+ action: 'skipped',
625
+ message: 'Local-only item'
626
+ });
627
+ if (localHash) {
628
+ markLocalAhead(syncState, stateKey, localHash);
629
+ }
630
+ }
631
+ continue;
632
+ }
633
+
634
+ if (!localItem && remoteItem) {
635
+ if (pullAllowed && remoteHash) {
636
+ pullRemote(entityId, remoteItem, remoteHash);
637
+ } else {
638
+ result.skipped++;
639
+ result.details.push({
640
+ entityId,
641
+ action: 'skipped',
642
+ message: 'Remote-only item'
643
+ });
644
+ if (remoteHash) {
645
+ markRemoteAhead(syncState, stateKey, remoteHash);
646
+ }
647
+ }
648
+ continue;
649
+ }
650
+
651
+ if (!localItem || !remoteItem || !localHash || !remoteHash) {
652
+ continue;
653
+ }
654
+
655
+ if (localDeleted !== remoteDeleted) {
656
+ if (localDeleted) {
657
+ if (pushAllowed) {
658
+ await deleteRemote(entityId, localItem, localHash);
659
+ } else {
660
+ result.skipped++;
661
+ result.details.push({
662
+ entityId,
663
+ action: 'skipped',
664
+ message: 'Local tombstone pending remote delete'
665
+ });
666
+ markLocalAhead(syncState, stateKey, localHash);
667
+ }
668
+ } else if (pullAllowed) {
669
+ pullRemote(entityId, remoteItem, remoteHash);
670
+ } else {
671
+ result.skipped++;
672
+ result.details.push({
673
+ entityId,
674
+ action: 'skipped',
675
+ message: 'Remote tombstone pending local pull'
676
+ });
677
+ markRemoteAhead(syncState, stateKey, remoteHash);
678
+ }
679
+ continue;
680
+ }
681
+
682
+ const localChanged = force || localHash !== prev.localHash;
683
+ const remoteChanged = force || remoteHash !== prev.remoteHash;
684
+
685
+ if (pushAllowed && pullAllowed) {
686
+ if (localChanged && !remoteChanged) {
687
+ await pushLocal(entityId, localItem, localHash);
688
+ continue;
689
+ }
690
+ if (remoteChanged && !localChanged) {
691
+ pullRemote(entityId, remoteItem, remoteHash);
692
+ continue;
693
+ }
694
+ }
695
+
696
+ if (pushAllowed && !pullAllowed) {
697
+ if (localChanged) {
698
+ await pushLocal(entityId, localItem, localHash);
699
+ } else {
700
+ result.skipped++;
701
+ result.details.push({
702
+ entityId,
703
+ action: 'skipped',
704
+ message: 'Remote changed; pull required'
705
+ });
706
+ markRemoteAhead(syncState, stateKey, remoteHash);
707
+ }
708
+ continue;
709
+ }
710
+
711
+ if (pullAllowed && !pushAllowed) {
712
+ if (remoteChanged) {
713
+ pullRemote(entityId, remoteItem, remoteHash);
714
+ } else {
715
+ result.skipped++;
716
+ result.details.push({
717
+ entityId,
718
+ action: 'skipped',
719
+ message: 'Local changed; push required'
720
+ });
721
+ markLocalAhead(syncState, stateKey, localHash);
722
+ }
723
+ continue;
724
+ }
725
+
726
+ const conflictAction = this.resolveConflictAction(localItem, remoteItem, direction);
727
+ if (conflictAction === 'push') {
728
+ await pushLocal(entityId, localItem, localHash);
729
+ continue;
730
+ }
731
+ if (conflictAction === 'pull') {
732
+ pullRemote(entityId, remoteItem, remoteHash);
733
+ continue;
734
+ }
735
+
736
+ result.conflicts++;
737
+ result.details.push({
738
+ entityId,
739
+ action: 'conflict',
740
+ message: 'Both local and remote changed'
741
+ });
742
+ markConflict(syncState, stateKey, localHash, remoteHash);
743
+ }
744
+
745
+ if (!dryRun && pulledItems.length > 0) {
746
+ await this.persistPulledItems(pulledItems, localItems, idField);
747
+ }
748
+
749
+ if (!dryRun) {
750
+ await saveSyncState(syncState);
751
+ }
752
+ } catch (error) {
753
+ result.errors++;
754
+ result.details.push({
755
+ entityId: '*',
756
+ action: 'error',
757
+ message: `Bridge sync failed: ${(error as Error).message}`
758
+ });
759
+ }
760
+
761
+ return result;
762
+ }
763
+ }