opentasks 0.0.5 → 0.0.6

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 (311) hide show
  1. package/README.md +40 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +153 -8
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/schema.d.ts +54 -0
  6. package/dist/config/schema.d.ts.map +1 -1
  7. package/dist/config/schema.js +52 -0
  8. package/dist/config/schema.js.map +1 -1
  9. package/dist/core/merge-driver.d.ts +6 -2
  10. package/dist/core/merge-driver.d.ts.map +1 -1
  11. package/dist/core/merge-driver.js +11 -7
  12. package/dist/core/merge-driver.js.map +1 -1
  13. package/dist/core/worktree.js +1 -1
  14. package/dist/core/worktree.js.map +1 -1
  15. package/dist/daemon/ipc.d.ts.map +1 -1
  16. package/dist/daemon/ipc.js +4 -1
  17. package/dist/daemon/ipc.js.map +1 -1
  18. package/dist/daemon/lifecycle.d.ts.map +1 -1
  19. package/dist/daemon/lifecycle.js +5 -0
  20. package/dist/daemon/lifecycle.js.map +1 -1
  21. package/dist/graph/git-graph-syncer.js +1 -1
  22. package/dist/graph/git-graph-syncer.js.map +1 -1
  23. package/dist/providers/from-config.d.ts +3 -0
  24. package/dist/providers/from-config.d.ts.map +1 -1
  25. package/dist/providers/from-config.js +29 -0
  26. package/dist/providers/from-config.js.map +1 -1
  27. package/dist/providers/index.d.ts +3 -0
  28. package/dist/providers/index.d.ts.map +1 -1
  29. package/dist/providers/index.js +6 -0
  30. package/dist/providers/index.js.map +1 -1
  31. package/dist/providers/map-client-factory.d.ts +55 -0
  32. package/dist/providers/map-client-factory.d.ts.map +1 -0
  33. package/dist/providers/map-client-factory.js +123 -0
  34. package/dist/providers/map-client-factory.js.map +1 -0
  35. package/dist/providers/map-event-bridge.d.ts +146 -0
  36. package/dist/providers/map-event-bridge.d.ts.map +1 -0
  37. package/dist/providers/map-event-bridge.js +208 -0
  38. package/dist/providers/map-event-bridge.js.map +1 -0
  39. package/dist/providers/map.d.ts +115 -0
  40. package/dist/providers/map.d.ts.map +1 -0
  41. package/dist/providers/map.js +376 -0
  42. package/dist/providers/map.js.map +1 -0
  43. package/package.json +1 -1
  44. package/dist/__tests__/cli-tools.test.d.ts +0 -8
  45. package/dist/__tests__/cli-tools.test.d.ts.map +0 -1
  46. package/dist/__tests__/cli-tools.test.js +0 -546
  47. package/dist/__tests__/cli-tools.test.js.map +0 -1
  48. package/dist/__tests__/cli.test.d.ts +0 -5
  49. package/dist/__tests__/cli.test.d.ts.map +0 -1
  50. package/dist/__tests__/cli.test.js +0 -77
  51. package/dist/__tests__/cli.test.js.map +0 -1
  52. package/dist/__tests__/p1-p3-gaps.test.d.ts +0 -2
  53. package/dist/__tests__/p1-p3-gaps.test.d.ts.map +0 -1
  54. package/dist/__tests__/p1-p3-gaps.test.js +0 -463
  55. package/dist/__tests__/p1-p3-gaps.test.js.map +0 -1
  56. package/dist/client/__tests__/client-crud.test.d.ts +0 -7
  57. package/dist/client/__tests__/client-crud.test.d.ts.map +0 -1
  58. package/dist/client/__tests__/client-crud.test.js +0 -404
  59. package/dist/client/__tests__/client-crud.test.js.map +0 -1
  60. package/dist/client/__tests__/client.test.d.ts +0 -5
  61. package/dist/client/__tests__/client.test.d.ts.map +0 -1
  62. package/dist/client/__tests__/client.test.js +0 -518
  63. package/dist/client/__tests__/client.test.js.map +0 -1
  64. package/dist/config/__tests__/defaults.test.d.ts +0 -2
  65. package/dist/config/__tests__/defaults.test.d.ts.map +0 -1
  66. package/dist/config/__tests__/defaults.test.js +0 -57
  67. package/dist/config/__tests__/defaults.test.js.map +0 -1
  68. package/dist/config/__tests__/env.test.d.ts +0 -2
  69. package/dist/config/__tests__/env.test.d.ts.map +0 -1
  70. package/dist/config/__tests__/env.test.js +0 -136
  71. package/dist/config/__tests__/env.test.js.map +0 -1
  72. package/dist/config/__tests__/index.test.d.ts +0 -2
  73. package/dist/config/__tests__/index.test.d.ts.map +0 -1
  74. package/dist/config/__tests__/index.test.js +0 -113
  75. package/dist/config/__tests__/index.test.js.map +0 -1
  76. package/dist/config/__tests__/loader.test.d.ts +0 -2
  77. package/dist/config/__tests__/loader.test.d.ts.map +0 -1
  78. package/dist/config/__tests__/loader.test.js +0 -128
  79. package/dist/config/__tests__/loader.test.js.map +0 -1
  80. package/dist/config/__tests__/merge.test.d.ts +0 -2
  81. package/dist/config/__tests__/merge.test.d.ts.map +0 -1
  82. package/dist/config/__tests__/merge.test.js +0 -79
  83. package/dist/config/__tests__/merge.test.js.map +0 -1
  84. package/dist/config/__tests__/schema.test.d.ts +0 -2
  85. package/dist/config/__tests__/schema.test.d.ts.map +0 -1
  86. package/dist/config/__tests__/schema.test.js +0 -300
  87. package/dist/config/__tests__/schema.test.js.map +0 -1
  88. package/dist/core/__tests__/conditional-redirects.test.d.ts +0 -2
  89. package/dist/core/__tests__/conditional-redirects.test.d.ts.map +0 -1
  90. package/dist/core/__tests__/conditional-redirects.test.js +0 -83
  91. package/dist/core/__tests__/conditional-redirects.test.js.map +0 -1
  92. package/dist/core/__tests__/connections.test.d.ts +0 -2
  93. package/dist/core/__tests__/connections.test.d.ts.map +0 -1
  94. package/dist/core/__tests__/connections.test.js +0 -158
  95. package/dist/core/__tests__/connections.test.js.map +0 -1
  96. package/dist/core/__tests__/hash.test.d.ts +0 -2
  97. package/dist/core/__tests__/hash.test.d.ts.map +0 -1
  98. package/dist/core/__tests__/hash.test.js +0 -139
  99. package/dist/core/__tests__/hash.test.js.map +0 -1
  100. package/dist/core/__tests__/id.test.d.ts +0 -2
  101. package/dist/core/__tests__/id.test.d.ts.map +0 -1
  102. package/dist/core/__tests__/id.test.js +0 -142
  103. package/dist/core/__tests__/id.test.js.map +0 -1
  104. package/dist/core/__tests__/location.test.d.ts +0 -2
  105. package/dist/core/__tests__/location.test.d.ts.map +0 -1
  106. package/dist/core/__tests__/location.test.js +0 -77
  107. package/dist/core/__tests__/location.test.js.map +0 -1
  108. package/dist/core/__tests__/merge-driver.test.d.ts +0 -2
  109. package/dist/core/__tests__/merge-driver.test.d.ts.map +0 -1
  110. package/dist/core/__tests__/merge-driver.test.js +0 -218
  111. package/dist/core/__tests__/merge-driver.test.js.map +0 -1
  112. package/dist/core/__tests__/redirects.test.d.ts +0 -2
  113. package/dist/core/__tests__/redirects.test.d.ts.map +0 -1
  114. package/dist/core/__tests__/redirects.test.js +0 -123
  115. package/dist/core/__tests__/redirects.test.js.map +0 -1
  116. package/dist/core/__tests__/resolve-location-target.test.d.ts +0 -8
  117. package/dist/core/__tests__/resolve-location-target.test.d.ts.map +0 -1
  118. package/dist/core/__tests__/resolve-location-target.test.js +0 -303
  119. package/dist/core/__tests__/resolve-location-target.test.js.map +0 -1
  120. package/dist/core/__tests__/uri.test.d.ts +0 -2
  121. package/dist/core/__tests__/uri.test.d.ts.map +0 -1
  122. package/dist/core/__tests__/uri.test.js +0 -159
  123. package/dist/core/__tests__/uri.test.js.map +0 -1
  124. package/dist/core/__tests__/worktree.test.d.ts +0 -2
  125. package/dist/core/__tests__/worktree.test.d.ts.map +0 -1
  126. package/dist/core/__tests__/worktree.test.js +0 -120
  127. package/dist/core/__tests__/worktree.test.js.map +0 -1
  128. package/dist/daemon/__tests__/flush.test.d.ts +0 -5
  129. package/dist/daemon/__tests__/flush.test.d.ts.map +0 -1
  130. package/dist/daemon/__tests__/flush.test.js +0 -213
  131. package/dist/daemon/__tests__/flush.test.js.map +0 -1
  132. package/dist/daemon/__tests__/integration.test.d.ts +0 -7
  133. package/dist/daemon/__tests__/integration.test.d.ts.map +0 -1
  134. package/dist/daemon/__tests__/integration.test.js +0 -276
  135. package/dist/daemon/__tests__/integration.test.js.map +0 -1
  136. package/dist/daemon/__tests__/ipc.test.d.ts +0 -5
  137. package/dist/daemon/__tests__/ipc.test.d.ts.map +0 -1
  138. package/dist/daemon/__tests__/ipc.test.js +0 -314
  139. package/dist/daemon/__tests__/ipc.test.js.map +0 -1
  140. package/dist/daemon/__tests__/lifecycle.test.d.ts +0 -5
  141. package/dist/daemon/__tests__/lifecycle.test.d.ts.map +0 -1
  142. package/dist/daemon/__tests__/lifecycle.test.js +0 -301
  143. package/dist/daemon/__tests__/lifecycle.test.js.map +0 -1
  144. package/dist/daemon/__tests__/lock.test.d.ts +0 -5
  145. package/dist/daemon/__tests__/lock.test.d.ts.map +0 -1
  146. package/dist/daemon/__tests__/lock.test.js +0 -192
  147. package/dist/daemon/__tests__/lock.test.js.map +0 -1
  148. package/dist/daemon/__tests__/methods/graph.test.d.ts +0 -5
  149. package/dist/daemon/__tests__/methods/graph.test.d.ts.map +0 -1
  150. package/dist/daemon/__tests__/methods/graph.test.js +0 -309
  151. package/dist/daemon/__tests__/methods/graph.test.js.map +0 -1
  152. package/dist/daemon/__tests__/methods/provider.test.d.ts +0 -7
  153. package/dist/daemon/__tests__/methods/provider.test.d.ts.map +0 -1
  154. package/dist/daemon/__tests__/methods/provider.test.js +0 -181
  155. package/dist/daemon/__tests__/methods/provider.test.js.map +0 -1
  156. package/dist/daemon/__tests__/methods/tools.test.d.ts +0 -5
  157. package/dist/daemon/__tests__/methods/tools.test.d.ts.map +0 -1
  158. package/dist/daemon/__tests__/methods/tools.test.js +0 -587
  159. package/dist/daemon/__tests__/methods/tools.test.js.map +0 -1
  160. package/dist/daemon/__tests__/multi-location.test.d.ts +0 -8
  161. package/dist/daemon/__tests__/multi-location.test.d.ts.map +0 -1
  162. package/dist/daemon/__tests__/multi-location.test.js +0 -669
  163. package/dist/daemon/__tests__/multi-location.test.js.map +0 -1
  164. package/dist/daemon/__tests__/registry.test.d.ts +0 -5
  165. package/dist/daemon/__tests__/registry.test.d.ts.map +0 -1
  166. package/dist/daemon/__tests__/registry.test.js +0 -208
  167. package/dist/daemon/__tests__/registry.test.js.map +0 -1
  168. package/dist/daemon/__tests__/watcher.test.d.ts +0 -5
  169. package/dist/daemon/__tests__/watcher.test.d.ts.map +0 -1
  170. package/dist/daemon/__tests__/watcher.test.js +0 -234
  171. package/dist/daemon/__tests__/watcher.test.js.map +0 -1
  172. package/dist/daemon/methods/__tests__/graph.test.d.ts +0 -5
  173. package/dist/daemon/methods/__tests__/graph.test.d.ts.map +0 -1
  174. package/dist/daemon/methods/__tests__/graph.test.js +0 -274
  175. package/dist/daemon/methods/__tests__/graph.test.js.map +0 -1
  176. package/dist/daemon/methods/__tests__/provider.test.d.ts +0 -5
  177. package/dist/daemon/methods/__tests__/provider.test.d.ts.map +0 -1
  178. package/dist/daemon/methods/__tests__/provider.test.js +0 -184
  179. package/dist/daemon/methods/__tests__/provider.test.js.map +0 -1
  180. package/dist/daemon/methods/__tests__/tools.test.d.ts +0 -5
  181. package/dist/daemon/methods/__tests__/tools.test.d.ts.map +0 -1
  182. package/dist/daemon/methods/__tests__/tools.test.js +0 -295
  183. package/dist/daemon/methods/__tests__/tools.test.js.map +0 -1
  184. package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts +0 -2
  185. package/dist/graph/__tests__/EdgeTypeRegistry.test.d.ts.map +0 -1
  186. package/dist/graph/__tests__/EdgeTypeRegistry.test.js +0 -212
  187. package/dist/graph/__tests__/EdgeTypeRegistry.test.js.map +0 -1
  188. package/dist/graph/__tests__/FederatedGraph.test.d.ts +0 -2
  189. package/dist/graph/__tests__/FederatedGraph.test.d.ts.map +0 -1
  190. package/dist/graph/__tests__/FederatedGraph.test.js +0 -661
  191. package/dist/graph/__tests__/FederatedGraph.test.js.map +0 -1
  192. package/dist/graph/__tests__/GraphologyAdapter.test.d.ts +0 -2
  193. package/dist/graph/__tests__/GraphologyAdapter.test.d.ts.map +0 -1
  194. package/dist/graph/__tests__/GraphologyAdapter.test.js +0 -326
  195. package/dist/graph/__tests__/GraphologyAdapter.test.js.map +0 -1
  196. package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts +0 -2
  197. package/dist/graph/__tests__/HydratingFederatedGraph.test.d.ts.map +0 -1
  198. package/dist/graph/__tests__/HydratingFederatedGraph.test.js +0 -587
  199. package/dist/graph/__tests__/HydratingFederatedGraph.test.js.map +0 -1
  200. package/dist/graph/__tests__/debounce.test.d.ts +0 -5
  201. package/dist/graph/__tests__/debounce.test.d.ts.map +0 -1
  202. package/dist/graph/__tests__/debounce.test.js +0 -195
  203. package/dist/graph/__tests__/debounce.test.js.map +0 -1
  204. package/dist/graph/__tests__/edge-cases.test.d.ts +0 -8
  205. package/dist/graph/__tests__/edge-cases.test.d.ts.map +0 -1
  206. package/dist/graph/__tests__/edge-cases.test.js +0 -472
  207. package/dist/graph/__tests__/edge-cases.test.js.map +0 -1
  208. package/dist/graph/__tests__/expansion.test.d.ts +0 -2
  209. package/dist/graph/__tests__/expansion.test.d.ts.map +0 -1
  210. package/dist/graph/__tests__/expansion.test.js +0 -105
  211. package/dist/graph/__tests__/expansion.test.js.map +0 -1
  212. package/dist/graph/__tests__/provider-store.test.d.ts +0 -5
  213. package/dist/graph/__tests__/provider-store.test.d.ts.map +0 -1
  214. package/dist/graph/__tests__/provider-store.test.js +0 -791
  215. package/dist/graph/__tests__/provider-store.test.js.map +0 -1
  216. package/dist/graph/__tests__/query.test.d.ts +0 -5
  217. package/dist/graph/__tests__/query.test.d.ts.map +0 -1
  218. package/dist/graph/__tests__/query.test.js +0 -774
  219. package/dist/graph/__tests__/query.test.js.map +0 -1
  220. package/dist/graph/__tests__/store.test.d.ts +0 -5
  221. package/dist/graph/__tests__/store.test.d.ts.map +0 -1
  222. package/dist/graph/__tests__/store.test.js +0 -489
  223. package/dist/graph/__tests__/store.test.js.map +0 -1
  224. package/dist/graph/__tests__/sync.test.d.ts +0 -5
  225. package/dist/graph/__tests__/sync.test.d.ts.map +0 -1
  226. package/dist/graph/__tests__/sync.test.js +0 -129
  227. package/dist/graph/__tests__/sync.test.js.map +0 -1
  228. package/dist/graph/__tests__/validation.test.d.ts +0 -2
  229. package/dist/graph/__tests__/validation.test.d.ts.map +0 -1
  230. package/dist/graph/__tests__/validation.test.js +0 -521
  231. package/dist/graph/__tests__/validation.test.js.map +0 -1
  232. package/dist/providers/__tests__/beads.test.d.ts +0 -5
  233. package/dist/providers/__tests__/beads.test.d.ts.map +0 -1
  234. package/dist/providers/__tests__/beads.test.js +0 -591
  235. package/dist/providers/__tests__/beads.test.js.map +0 -1
  236. package/dist/providers/__tests__/claude-tasks.test.d.ts +0 -5
  237. package/dist/providers/__tests__/claude-tasks.test.d.ts.map +0 -1
  238. package/dist/providers/__tests__/claude-tasks.test.js +0 -392
  239. package/dist/providers/__tests__/claude-tasks.test.js.map +0 -1
  240. package/dist/providers/__tests__/from-config.test.d.ts +0 -5
  241. package/dist/providers/__tests__/from-config.test.d.ts.map +0 -1
  242. package/dist/providers/__tests__/from-config.test.js +0 -152
  243. package/dist/providers/__tests__/from-config.test.js.map +0 -1
  244. package/dist/providers/__tests__/materialization.test.d.ts +0 -5
  245. package/dist/providers/__tests__/materialization.test.d.ts.map +0 -1
  246. package/dist/providers/__tests__/materialization.test.js +0 -407
  247. package/dist/providers/__tests__/materialization.test.js.map +0 -1
  248. package/dist/providers/__tests__/native.test.d.ts +0 -5
  249. package/dist/providers/__tests__/native.test.d.ts.map +0 -1
  250. package/dist/providers/__tests__/native.test.js +0 -566
  251. package/dist/providers/__tests__/native.test.js.map +0 -1
  252. package/dist/providers/__tests__/registry.test.d.ts +0 -5
  253. package/dist/providers/__tests__/registry.test.d.ts.map +0 -1
  254. package/dist/providers/__tests__/registry.test.js +0 -183
  255. package/dist/providers/__tests__/registry.test.js.map +0 -1
  256. package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts +0 -2
  257. package/dist/providers/traits/__tests__/RelationshipQueryable.test.d.ts.map +0 -1
  258. package/dist/providers/traits/__tests__/RelationshipQueryable.test.js +0 -169
  259. package/dist/providers/traits/__tests__/RelationshipQueryable.test.js.map +0 -1
  260. package/dist/providers/traits/__tests__/TaskManageable.test.d.ts +0 -2
  261. package/dist/providers/traits/__tests__/TaskManageable.test.d.ts.map +0 -1
  262. package/dist/providers/traits/__tests__/TaskManageable.test.js +0 -172
  263. package/dist/providers/traits/__tests__/TaskManageable.test.js.map +0 -1
  264. package/dist/schema/__tests__/validation.test.d.ts +0 -2
  265. package/dist/schema/__tests__/validation.test.d.ts.map +0 -1
  266. package/dist/schema/__tests__/validation.test.js +0 -241
  267. package/dist/schema/__tests__/validation.test.js.map +0 -1
  268. package/dist/storage/__tests__/atomic-write.test.d.ts +0 -5
  269. package/dist/storage/__tests__/atomic-write.test.d.ts.map +0 -1
  270. package/dist/storage/__tests__/atomic-write.test.js +0 -170
  271. package/dist/storage/__tests__/atomic-write.test.js.map +0 -1
  272. package/dist/storage/__tests__/file-lock.test.d.ts +0 -2
  273. package/dist/storage/__tests__/file-lock.test.d.ts.map +0 -1
  274. package/dist/storage/__tests__/file-lock.test.js +0 -89
  275. package/dist/storage/__tests__/file-lock.test.js.map +0 -1
  276. package/dist/storage/__tests__/jsonl.test.d.ts +0 -2
  277. package/dist/storage/__tests__/jsonl.test.d.ts.map +0 -1
  278. package/dist/storage/__tests__/jsonl.test.js +0 -228
  279. package/dist/storage/__tests__/jsonl.test.js.map +0 -1
  280. package/dist/storage/__tests__/locked-writer.test.d.ts +0 -2
  281. package/dist/storage/__tests__/locked-writer.test.d.ts.map +0 -1
  282. package/dist/storage/__tests__/locked-writer.test.js +0 -109
  283. package/dist/storage/__tests__/locked-writer.test.js.map +0 -1
  284. package/dist/storage/__tests__/sqlite.test.d.ts +0 -2
  285. package/dist/storage/__tests__/sqlite.test.d.ts.map +0 -1
  286. package/dist/storage/__tests__/sqlite.test.js +0 -470
  287. package/dist/storage/__tests__/sqlite.test.js.map +0 -1
  288. package/dist/tools/__tests__/annotate.test.d.ts +0 -5
  289. package/dist/tools/__tests__/annotate.test.d.ts.map +0 -1
  290. package/dist/tools/__tests__/annotate.test.js +0 -314
  291. package/dist/tools/__tests__/annotate.test.js.map +0 -1
  292. package/dist/tools/__tests__/link.test.d.ts +0 -5
  293. package/dist/tools/__tests__/link.test.d.ts.map +0 -1
  294. package/dist/tools/__tests__/link.test.js +0 -245
  295. package/dist/tools/__tests__/link.test.js.map +0 -1
  296. package/dist/tools/__tests__/query.test.d.ts +0 -5
  297. package/dist/tools/__tests__/query.test.d.ts.map +0 -1
  298. package/dist/tools/__tests__/query.test.js +0 -288
  299. package/dist/tools/__tests__/query.test.js.map +0 -1
  300. package/dist/tools/__tests__/task.test.d.ts +0 -5
  301. package/dist/tools/__tests__/task.test.d.ts.map +0 -1
  302. package/dist/tools/__tests__/task.test.js +0 -178
  303. package/dist/tools/__tests__/task.test.js.map +0 -1
  304. package/dist/tracking/claude-task-reconstructor.d.ts +0 -41
  305. package/dist/tracking/claude-task-reconstructor.d.ts.map +0 -1
  306. package/dist/tracking/claude-task-reconstructor.js +0 -91
  307. package/dist/tracking/claude-task-reconstructor.js.map +0 -1
  308. package/dist/tracking/plan-mode-tracker.d.ts +0 -20
  309. package/dist/tracking/plan-mode-tracker.d.ts.map +0 -1
  310. package/dist/tracking/plan-mode-tracker.js +0 -35
  311. package/dist/tracking/plan-mode-tracker.js.map +0 -1
@@ -1,791 +0,0 @@
1
- /**
2
- * Tests for Provider-Aware Graph Store
3
- */
4
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
5
- import { createProviderAwareStore, } from '../provider-store.js';
6
- import { createProviderRegistry } from '../../providers/registry.js';
7
- describe('ProviderAwareStore', () => {
8
- let baseStore;
9
- let providerStore;
10
- let mockBeadsProvider;
11
- // Mock nodes
12
- const mockSpec = {
13
- id: 's-abc1',
14
- uuid: 'uuid-1',
15
- type: 'spec',
16
- title: 'Test Spec',
17
- content: 'Spec content',
18
- created_at: '2024-01-01T00:00:00.000Z',
19
- updated_at: '2024-01-01T00:00:00.000Z',
20
- };
21
- const mockExternalNode = {
22
- id: 'x-ext1',
23
- uuid: 'uuid-ext-1',
24
- type: 'external',
25
- title: 'External Issue',
26
- uri: 'beads://./bd-123',
27
- source: 'beads',
28
- materialized: true,
29
- cached_at: new Date().toISOString(),
30
- created_at: '2024-01-01T00:00:00.000Z',
31
- updated_at: '2024-01-01T00:00:00.000Z',
32
- };
33
- const mockProviderNode = {
34
- id: 'bd-123',
35
- uri: 'beads://./bd-123',
36
- type: 'issue',
37
- title: 'Beads Issue',
38
- content: 'Issue content',
39
- status: 'open',
40
- priority: 2,
41
- rawData: { custom: 'data' },
42
- fetchedAt: '2024-01-01T00:00:00.000Z',
43
- };
44
- beforeEach(() => {
45
- vi.useFakeTimers();
46
- // Create mock base store
47
- baseStore = {
48
- getNode: vi.fn(),
49
- createNode: vi.fn(),
50
- updateNode: vi.fn(),
51
- deleteNode: vi.fn(),
52
- query: {
53
- nodes: vi.fn().mockResolvedValue([]),
54
- edges: vi.fn(),
55
- blockers: vi.fn(),
56
- blocking: vi.fn(),
57
- ready: vi.fn(),
58
- children: vi.fn(),
59
- descendants: vi.fn(),
60
- ancestors: vi.fn(),
61
- feedback: vi.fn(),
62
- },
63
- initialize: vi.fn(),
64
- close: vi.fn(),
65
- flush: vi.fn(),
66
- createEdge: vi.fn(),
67
- getEdge: vi.fn(),
68
- deleteEdge: vi.fn(),
69
- restoreNode: vi.fn(),
70
- addTags: vi.fn(),
71
- removeTags: vi.fn(),
72
- setTags: vi.fn(),
73
- transaction: vi.fn(),
74
- };
75
- // Create mock beads provider
76
- mockBeadsProvider = {
77
- name: 'beads',
78
- schemes: ['beads', 'bd'],
79
- capabilities: { read: true, write: true, search: true, watch: false, mount: true, feedback: false },
80
- parseUri: vi.fn((uri) => {
81
- if (uri.startsWith('beads://') || uri.startsWith('bd://')) {
82
- const id = uri.split('/').pop() || '';
83
- return { scheme: 'beads', id, isRelative: uri.includes('/.') };
84
- }
85
- return null;
86
- }),
87
- buildUri: vi.fn((id) => `beads://./${id}`),
88
- isValidUri: vi.fn((uri) => uri.startsWith('beads://') || uri.startsWith('bd://')),
89
- get: vi.fn().mockResolvedValue(mockProviderNode),
90
- list: vi.fn(),
91
- create: vi.fn(),
92
- update: vi.fn(),
93
- delete: vi.fn(),
94
- };
95
- // Create provider store
96
- providerStore = createProviderAwareStore(baseStore);
97
- });
98
- afterEach(() => {
99
- providerStore.stopBackgroundSync();
100
- vi.useRealTimers();
101
- });
102
- describe('initialization', () => {
103
- it('should have providers registry', () => {
104
- expect(providerStore.providers).toBeDefined();
105
- expect(providerStore.providers.list).toBeDefined();
106
- });
107
- it('should have materialization manager', () => {
108
- expect(providerStore.materialization).toBeDefined();
109
- expect(providerStore.materialization.config).toBeDefined();
110
- });
111
- it('should auto-register native provider', () => {
112
- const nativeProvider = providerStore.providers.get('native');
113
- expect(nativeProvider).toBeDefined();
114
- expect(nativeProvider?.name).toBe('native');
115
- });
116
- it('should not auto-register native provider when disabled', () => {
117
- const storeWithoutNative = createProviderAwareStore(baseStore, {
118
- autoRegisterNative: false,
119
- });
120
- expect(storeWithoutNative.providers.get('native')).toBeUndefined();
121
- });
122
- it('should use provided registry', () => {
123
- const customRegistry = createProviderRegistry();
124
- customRegistry.register(mockBeadsProvider);
125
- const storeWithCustomRegistry = createProviderAwareStore(baseStore, {
126
- registry: customRegistry,
127
- autoRegisterNative: false,
128
- });
129
- expect(storeWithCustomRegistry.providers.get('beads')).toBe(mockBeadsProvider);
130
- });
131
- });
132
- describe('resolveNode', () => {
133
- describe('local IDs', () => {
134
- it('should resolve local IDs via getNode', async () => {
135
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
136
- const result = await providerStore.resolveNode('s-abc1');
137
- expect(baseStore.getNode).toHaveBeenCalledWith('s-abc1');
138
- expect(result).toBe(mockSpec);
139
- });
140
- it('should return null for non-existent local node', async () => {
141
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
142
- const result = await providerStore.resolveNode('s-notfound');
143
- expect(result).toBeNull();
144
- });
145
- it('should handle all local ID prefixes', async () => {
146
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
147
- for (const prefix of ['s-', 'i-', 'f-', 'e-', 'x-']) {
148
- await providerStore.resolveNode(`${prefix}abc1`);
149
- expect(baseStore.getNode).toHaveBeenCalledWith(`${prefix}abc1`);
150
- }
151
- });
152
- });
153
- describe('external URIs', () => {
154
- beforeEach(() => {
155
- providerStore.providers.register(mockBeadsProvider);
156
- });
157
- it('should resolve URIs via provider', async () => {
158
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
159
- const result = await providerStore.resolveNode('beads://./bd-123');
160
- expect(mockBeadsProvider.get).toHaveBeenCalledWith('bd-123', undefined);
161
- expect(result).toBeDefined();
162
- });
163
- it('should return null for unknown schemes', async () => {
164
- const result = await providerStore.resolveNode('unknown://./something');
165
- expect(result).toBeNull();
166
- });
167
- it('should use cached node when not stale', async () => {
168
- const freshNode = {
169
- ...mockExternalNode,
170
- cached_at: new Date().toISOString(),
171
- };
172
- vi.mocked(baseStore.query.nodes).mockResolvedValue([freshNode]);
173
- const result = await providerStore.resolveNode('beads://./bd-123');
174
- expect(mockBeadsProvider.get).not.toHaveBeenCalled();
175
- expect(result).toEqual(freshNode);
176
- });
177
- it('should bypass cache when refresh=true', async () => {
178
- const freshNode = {
179
- ...mockExternalNode,
180
- cached_at: new Date().toISOString(),
181
- };
182
- vi.mocked(baseStore.query.nodes).mockResolvedValue([freshNode]);
183
- await providerStore.resolveNode('beads://./bd-123', { refresh: true });
184
- expect(mockBeadsProvider.get).toHaveBeenCalled();
185
- });
186
- it('should materialize when explicitly requested', async () => {
187
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
188
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
189
- const result = await providerStore.resolveNode('beads://./bd-123', {
190
- materialize: true,
191
- });
192
- expect(baseStore.createNode).toHaveBeenCalledWith(expect.objectContaining({
193
- type: 'external',
194
- uri: 'beads://./bd-123',
195
- }));
196
- });
197
- it('should return provider node directly when not materializing', async () => {
198
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
199
- const result = await providerStore.resolveNode('beads://./bd-123');
200
- // With on-demand strategy (default), should not materialize without explicit flag
201
- expect(baseStore.createNode).not.toHaveBeenCalled();
202
- });
203
- });
204
- });
205
- describe('materializeNode', () => {
206
- beforeEach(() => {
207
- providerStore.providers.register(mockBeadsProvider);
208
- });
209
- it('should create external node from provider data', async () => {
210
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
211
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
212
- const result = await providerStore.materializeNode('beads://./bd-123');
213
- expect(mockBeadsProvider.get).toHaveBeenCalledWith('bd-123');
214
- expect(baseStore.createNode).toHaveBeenCalledWith(expect.objectContaining({
215
- type: 'external',
216
- uri: 'beads://./bd-123',
217
- source: 'beads',
218
- title: 'Beads Issue',
219
- }));
220
- });
221
- it('should throw for unknown URI scheme', async () => {
222
- await expect(providerStore.materializeNode('unknown://./something')).rejects.toThrow('No provider found');
223
- });
224
- it('should throw if node not found in provider', async () => {
225
- vi.mocked(mockBeadsProvider.get).mockResolvedValue(null);
226
- await expect(providerStore.materializeNode('beads://./bd-notfound')).rejects.toThrow('Node not found');
227
- });
228
- it('should update existing node if already materialized', async () => {
229
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockExternalNode]);
230
- vi.mocked(baseStore.updateNode).mockResolvedValue(mockExternalNode);
231
- await providerStore.materializeNode('beads://./bd-123');
232
- expect(baseStore.updateNode).toHaveBeenCalled();
233
- expect(baseStore.createNode).not.toHaveBeenCalled();
234
- });
235
- });
236
- describe('refreshNode', () => {
237
- beforeEach(() => {
238
- providerStore.providers.register(mockBeadsProvider);
239
- });
240
- it('should refresh an external node', async () => {
241
- vi.mocked(baseStore.getNode).mockResolvedValue(mockExternalNode);
242
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockExternalNode]);
243
- vi.mocked(baseStore.updateNode).mockResolvedValue(mockExternalNode);
244
- const result = await providerStore.refreshNode('x-ext1');
245
- expect(mockBeadsProvider.get).toHaveBeenCalledWith('beads://./bd-123', undefined);
246
- expect(baseStore.updateNode).toHaveBeenCalled();
247
- });
248
- it('should throw for non-external node', async () => {
249
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
250
- await expect(providerStore.refreshNode('s-abc1')).rejects.toThrow('not external');
251
- });
252
- it('should throw for non-existent node', async () => {
253
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
254
- await expect(providerStore.refreshNode('x-notfound')).rejects.toThrow('Node not found');
255
- });
256
- });
257
- describe('background sync', () => {
258
- it('should start and stop background sync', () => {
259
- providerStore = createProviderAwareStore(baseStore, {
260
- materialization: {
261
- backgroundSyncInterval: 1000,
262
- },
263
- });
264
- expect(providerStore.isBackgroundSyncRunning()).toBe(false);
265
- providerStore.startBackgroundSync();
266
- expect(providerStore.isBackgroundSyncRunning()).toBe(true);
267
- providerStore.stopBackgroundSync();
268
- expect(providerStore.isBackgroundSyncRunning()).toBe(false);
269
- });
270
- it('should not start if interval is 0', () => {
271
- providerStore = createProviderAwareStore(baseStore, {
272
- materialization: {
273
- backgroundSyncInterval: 0,
274
- },
275
- });
276
- providerStore.startBackgroundSync();
277
- expect(providerStore.isBackgroundSyncRunning()).toBe(false);
278
- });
279
- });
280
- describe('provider registration', () => {
281
- it('should allow registering additional providers', () => {
282
- providerStore.providers.register(mockBeadsProvider);
283
- expect(providerStore.providers.get('beads')).toBe(mockBeadsProvider);
284
- expect(providerStore.providers.canResolve('beads://./bd-123')).toBe(true);
285
- });
286
- it('should allow unregistering providers', () => {
287
- providerStore.providers.register(mockBeadsProvider);
288
- providerStore.providers.unregister('beads');
289
- expect(providerStore.providers.get('beads')).toBeUndefined();
290
- });
291
- });
292
- describe('base store methods', () => {
293
- it('should pass through getNode to base store', async () => {
294
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
295
- const result = await providerStore.getNode('s-abc1');
296
- expect(baseStore.getNode).toHaveBeenCalledWith('s-abc1');
297
- expect(result).toBe(mockSpec);
298
- });
299
- it('should pass through createNode to base store', async () => {
300
- vi.mocked(baseStore.createNode).mockResolvedValue(mockSpec);
301
- const result = await providerStore.createNode({
302
- type: 'spec',
303
- title: 'New Spec',
304
- });
305
- expect(baseStore.createNode).toHaveBeenCalled();
306
- expect(result).toBe(mockSpec);
307
- });
308
- it('should pass through query to base store', async () => {
309
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockSpec]);
310
- const result = await providerStore.query.nodes({ type: 'spec' });
311
- expect(baseStore.query.nodes).toHaveBeenCalledWith({ type: 'spec' });
312
- expect(result).toEqual([mockSpec]);
313
- });
314
- });
315
- describe('materialization strategies', () => {
316
- beforeEach(() => {
317
- providerStore.providers.register(mockBeadsProvider);
318
- });
319
- it('should materialize with lazy strategy on resolve', async () => {
320
- const lazyStore = createProviderAwareStore(baseStore, {
321
- materialization: { default: 'lazy' },
322
- });
323
- lazyStore.providers.register(mockBeadsProvider);
324
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
325
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
326
- await lazyStore.resolveNode('beads://./bd-123');
327
- expect(baseStore.createNode).toHaveBeenCalled();
328
- });
329
- it('should materialize with eager strategy always', async () => {
330
- const eagerStore = createProviderAwareStore(baseStore, {
331
- materialization: { default: 'eager' },
332
- });
333
- eagerStore.providers.register(mockBeadsProvider);
334
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
335
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
336
- await eagerStore.resolveNode('beads://./bd-123');
337
- expect(baseStore.createNode).toHaveBeenCalled();
338
- });
339
- it('should not materialize with none strategy', async () => {
340
- const noneStore = createProviderAwareStore(baseStore, {
341
- materialization: { default: 'none' },
342
- });
343
- noneStore.providers.register(mockBeadsProvider);
344
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
345
- await noneStore.resolveNode('beads://./bd-123', { materialize: true });
346
- // Even with explicit flag, 'none' should not materialize
347
- expect(baseStore.createNode).not.toHaveBeenCalled();
348
- });
349
- });
350
- // ===========================================================================
351
- // Provider-Routed CRUD (Unified Interface)
352
- // ===========================================================================
353
- describe('providerCreate', () => {
354
- it('should create locally when defaultProvider is native', async () => {
355
- const store = createProviderAwareStore(baseStore, {
356
- defaultProvider: 'native',
357
- });
358
- const mockNode = { ...mockSpec };
359
- vi.mocked(baseStore.createNode).mockResolvedValue(mockNode);
360
- const result = await store.providerCreate({
361
- type: 'spec',
362
- title: 'Test Spec',
363
- });
364
- expect(result.provider).toBe('native');
365
- expect(result.uri).toBeUndefined();
366
- expect(baseStore.createNode).toHaveBeenCalledWith({
367
- type: 'spec',
368
- title: 'Test Spec',
369
- });
370
- });
371
- it('should route to external provider via scheme param', async () => {
372
- const store = createProviderAwareStore(baseStore, {
373
- defaultProvider: 'native',
374
- });
375
- store.providers.register(mockBeadsProvider);
376
- const createdProviderNode = {
377
- id: 'bd-new1',
378
- uri: 'beads://./bd-new1',
379
- type: 'issue',
380
- title: 'New Issue',
381
- fetchedAt: new Date().toISOString(),
382
- };
383
- vi.mocked(mockBeadsProvider.create).mockResolvedValue(createdProviderNode);
384
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
385
- vi.mocked(baseStore.createNode).mockResolvedValue({
386
- ...mockExternalNode,
387
- id: 'x-new1',
388
- title: 'New Issue',
389
- uri: 'beads://./bd-new1',
390
- });
391
- const result = await store.providerCreate({ type: 'issue', title: 'New Issue' }, { scheme: 'beads' });
392
- expect(result.provider).toBe('beads');
393
- expect(result.uri).toBe('beads://./bd-new1');
394
- expect(mockBeadsProvider.create).toHaveBeenCalledWith({
395
- type: 'issue',
396
- title: 'New Issue',
397
- content: undefined,
398
- status: undefined,
399
- priority: undefined,
400
- metadata: undefined,
401
- }, undefined);
402
- // Should materialize the node locally
403
- expect(baseStore.createNode).toHaveBeenCalled();
404
- });
405
- it('should use defaultProvider when no scheme specified', async () => {
406
- const store = createProviderAwareStore(baseStore, {
407
- defaultProvider: 'beads',
408
- });
409
- store.providers.register(mockBeadsProvider);
410
- const createdProviderNode = {
411
- id: 'bd-new2',
412
- uri: 'beads://./bd-new2',
413
- type: 'issue',
414
- title: 'Default Provider Issue',
415
- fetchedAt: new Date().toISOString(),
416
- };
417
- vi.mocked(mockBeadsProvider.create).mockResolvedValue(createdProviderNode);
418
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
419
- vi.mocked(baseStore.createNode).mockResolvedValue({
420
- ...mockExternalNode,
421
- id: 'x-new2',
422
- });
423
- const result = await store.providerCreate({
424
- type: 'issue',
425
- title: 'Default Provider Issue',
426
- });
427
- // Should route to beads since it's the defaultProvider
428
- expect(result.provider).toBe('beads');
429
- expect(mockBeadsProvider.create).toHaveBeenCalled();
430
- });
431
- it('should throw for unknown provider', async () => {
432
- const store = createProviderAwareStore(baseStore);
433
- await expect(store.providerCreate({ type: 'issue', title: 'Test' }, { scheme: 'nonexistent' })).rejects.toThrow('Unknown provider: nonexistent');
434
- });
435
- it('should throw for read-only provider', async () => {
436
- const readOnlyProvider = {
437
- ...mockBeadsProvider,
438
- name: 'readonly',
439
- schemes: ['readonly'],
440
- capabilities: { read: true, write: false, search: false, watch: false, mount: false, feedback: false },
441
- };
442
- const store = createProviderAwareStore(baseStore);
443
- store.providers.register(readOnlyProvider);
444
- await expect(store.providerCreate({ type: 'issue', title: 'Test' }, { scheme: 'readonly' })).rejects.toThrow('read-only');
445
- });
446
- it('should throw for non-mountable provider', async () => {
447
- const nonMountable = {
448
- ...mockBeadsProvider,
449
- name: 'nomount',
450
- schemes: ['nomount'],
451
- capabilities: { read: true, write: true, search: false, watch: false, mount: false, feedback: false },
452
- };
453
- const store = createProviderAwareStore(baseStore);
454
- store.providers.register(nonMountable);
455
- await expect(store.providerCreate({ type: 'issue', title: 'Test' }, { scheme: 'nomount' })).rejects.toThrow('does not support mounting');
456
- });
457
- });
458
- describe('providerGet', () => {
459
- it('should get local node directly', async () => {
460
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
461
- const result = await providerStore.providerGet('s-abc1');
462
- expect(baseStore.getNode).toHaveBeenCalledWith('s-abc1');
463
- expect(result).toEqual(mockSpec);
464
- });
465
- it('should refresh stale external node', async () => {
466
- const staleNode = {
467
- ...mockExternalNode,
468
- cached_at: '2020-01-01T00:00:00.000Z', // very old
469
- };
470
- vi.mocked(baseStore.getNode).mockResolvedValue(staleNode);
471
- providerStore.providers.register(mockBeadsProvider);
472
- // Mock the refresh path: query for existing, then update
473
- vi.mocked(baseStore.query.nodes).mockResolvedValue([staleNode]);
474
- vi.mocked(baseStore.updateNode).mockResolvedValue({
475
- ...staleNode,
476
- title: 'Beads Issue',
477
- cached_at: new Date().toISOString(),
478
- });
479
- const result = await providerStore.providerGet('x-ext1');
480
- expect(mockBeadsProvider.get).toHaveBeenCalled();
481
- });
482
- it('should resolve provider URI and materialize', async () => {
483
- providerStore.providers.register(mockBeadsProvider);
484
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
485
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
486
- const result = await providerStore.providerGet('beads://./bd-123');
487
- expect(mockBeadsProvider.get).toHaveBeenCalled();
488
- });
489
- it('should return null for nonexistent local node', async () => {
490
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
491
- const result = await providerStore.providerGet('s-nonexist');
492
- expect(result).toBeNull();
493
- });
494
- });
495
- describe('providerUpdate', () => {
496
- it('should update local node directly', async () => {
497
- const updatedNode = { ...mockSpec, title: 'Updated' };
498
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
499
- vi.mocked(baseStore.updateNode).mockResolvedValue(updatedNode);
500
- const result = await providerStore.providerUpdate('s-abc1', { title: 'Updated' });
501
- expect(baseStore.updateNode).toHaveBeenCalledWith('s-abc1', { title: 'Updated' });
502
- expect(result.title).toBe('Updated');
503
- });
504
- it('should route update to external provider for materialized node', async () => {
505
- providerStore.providers.register(mockBeadsProvider);
506
- vi.mocked(baseStore.getNode).mockResolvedValue(mockExternalNode);
507
- const updatedProviderNode = {
508
- ...mockProviderNode,
509
- title: 'Updated in Beads',
510
- };
511
- vi.mocked(mockBeadsProvider.update).mockResolvedValue(updatedProviderNode);
512
- // Mock materialization (find existing, then update)
513
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockExternalNode]);
514
- vi.mocked(baseStore.updateNode).mockResolvedValue({
515
- ...mockExternalNode,
516
- title: 'Updated in Beads',
517
- });
518
- const result = await providerStore.providerUpdate('x-ext1', { title: 'Updated in Beads' });
519
- expect(mockBeadsProvider.update).toHaveBeenCalledWith('bd-123', {
520
- title: 'Updated in Beads',
521
- content: undefined,
522
- status: undefined,
523
- priority: undefined,
524
- metadata: undefined,
525
- }, undefined);
526
- });
527
- it('should route update via provider URI', async () => {
528
- providerStore.providers.register(mockBeadsProvider);
529
- // No existing materialized copy
530
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
531
- // Mock fetching the node for materialization
532
- vi.mocked(baseStore.createNode).mockResolvedValue(mockExternalNode);
533
- const updatedProviderNode = {
534
- ...mockProviderNode,
535
- title: 'Updated via URI',
536
- };
537
- vi.mocked(mockBeadsProvider.update).mockResolvedValue(updatedProviderNode);
538
- // After materialization, the update path will re-materialize
539
- vi.mocked(baseStore.updateNode).mockResolvedValue({
540
- ...mockExternalNode,
541
- title: 'Updated via URI',
542
- });
543
- const result = await providerStore.providerUpdate('beads://./bd-123', { title: 'Updated via URI' });
544
- expect(mockBeadsProvider.update).toHaveBeenCalled();
545
- });
546
- it('should throw for nonexistent node', async () => {
547
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
548
- await expect(providerStore.providerUpdate('s-nonexist', { title: 'Test' })).rejects.toThrow('not found');
549
- });
550
- it('should throw when external node provider is not registered', async () => {
551
- // External node exists but its provider (beads) is NOT registered
552
- vi.mocked(baseStore.getNode).mockResolvedValue(mockExternalNode);
553
- await expect(providerStore.providerUpdate('x-ext1', { title: 'Update' })).rejects.toThrow('Provider not registered');
554
- });
555
- });
556
- describe('providerDelete', () => {
557
- it('should delete local node directly', async () => {
558
- vi.mocked(baseStore.getNode).mockResolvedValue(mockSpec);
559
- vi.mocked(baseStore.deleteNode).mockResolvedValue(undefined);
560
- await providerStore.providerDelete('s-abc1');
561
- expect(baseStore.deleteNode).toHaveBeenCalledWith('s-abc1', undefined);
562
- });
563
- it('should route delete to external provider and remove local copy', async () => {
564
- providerStore.providers.register(mockBeadsProvider);
565
- vi.mocked(baseStore.getNode).mockResolvedValue(mockExternalNode);
566
- vi.mocked(mockBeadsProvider.delete).mockResolvedValue(undefined);
567
- vi.mocked(baseStore.deleteNode).mockResolvedValue(undefined);
568
- await providerStore.providerDelete('x-ext1');
569
- // Should delete in provider
570
- expect(mockBeadsProvider.delete).toHaveBeenCalledWith('bd-123', undefined);
571
- // Should hard-delete local materialized copy
572
- expect(baseStore.deleteNode).toHaveBeenCalledWith('x-ext1', { hard: true });
573
- });
574
- it('should throw for nonexistent node', async () => {
575
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
576
- await expect(providerStore.providerDelete('s-nonexist')).rejects.toThrow('not found');
577
- });
578
- it('should throw when external node provider is not registered', async () => {
579
- // External node exists but its provider (beads) is NOT registered
580
- vi.mocked(baseStore.getNode).mockResolvedValue(mockExternalNode);
581
- await expect(providerStore.providerDelete('x-ext1')).rejects.toThrow('Provider not registered');
582
- });
583
- });
584
- describe('defaultProvider', () => {
585
- it('should default to native', () => {
586
- const store = createProviderAwareStore(baseStore);
587
- expect(store.defaultProvider).toBe('native');
588
- });
589
- it('should respect config', () => {
590
- const store = createProviderAwareStore(baseStore, {
591
- defaultProvider: 'sudocode',
592
- });
593
- expect(store.defaultProvider).toBe('sudocode');
594
- });
595
- });
596
- // =========================================================================
597
- // Task Lifecycle (TaskManageable)
598
- // =========================================================================
599
- describe('task lifecycle', () => {
600
- let mockTaskProvider;
601
- const mockTaskProviderNode = {
602
- id: 'task-1',
603
- uri: 'taskprov://task-1',
604
- type: 'issue',
605
- title: 'Task One',
606
- status: 'in_progress',
607
- fetchedAt: new Date().toISOString(),
608
- };
609
- const mockTaskExternalNode = {
610
- id: 'x-task1',
611
- uuid: 'uuid-task-1',
612
- type: 'external',
613
- title: 'Task One',
614
- uri: 'taskprov://task-1',
615
- source: 'taskprov',
616
- materialized: true,
617
- cached_at: new Date().toISOString(),
618
- created_at: '2024-01-01T00:00:00.000Z',
619
- updated_at: '2024-01-01T00:00:00.000Z',
620
- };
621
- beforeEach(() => {
622
- mockTaskProvider = {
623
- name: 'taskprov',
624
- schemes: ['taskprov'],
625
- capabilities: { read: true, write: true, search: false, watch: false, mount: true, feedback: false },
626
- parseUri: vi.fn((uri) => {
627
- if (uri.startsWith('taskprov://')) {
628
- const id = uri.slice('taskprov://'.length);
629
- return { scheme: 'taskprov', id, isRelative: false };
630
- }
631
- return null;
632
- }),
633
- buildUri: vi.fn((id) => `taskprov://${id}`),
634
- isValidUri: vi.fn((uri) => uri.startsWith('taskprov://')),
635
- get: vi.fn().mockResolvedValue(mockTaskProviderNode),
636
- list: vi.fn().mockResolvedValue([]),
637
- create: vi.fn(),
638
- update: vi.fn(),
639
- delete: vi.fn(),
640
- taskCapabilities: {
641
- actions: ['start', 'complete', 'block', 'reopen'],
642
- supportsAssignment: true,
643
- supportsReadyQuery: true,
644
- statusModel: ['open', 'in_progress', 'blocked', 'done'],
645
- },
646
- transitionTask: vi.fn().mockResolvedValue(mockTaskProviderNode),
647
- readyTasks: vi.fn().mockResolvedValue([mockTaskProviderNode]),
648
- assignTask: vi.fn().mockResolvedValue(mockTaskProviderNode),
649
- validActions: vi.fn().mockResolvedValue(['start', 'close']),
650
- };
651
- });
652
- describe('taskTransition', () => {
653
- it('should transition a task via provider URI', async () => {
654
- providerStore.providers.register(mockTaskProvider);
655
- // Mock materialization: no existing node, so create one
656
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
657
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
658
- const result = await providerStore.taskTransition('taskprov://task-1', 'start');
659
- expect(mockTaskProvider.transitionTask).toHaveBeenCalledWith('task-1', 'start', undefined);
660
- expect(result.action).toBe('start');
661
- expect(result.provider).toBe('taskprov');
662
- expect(result.node).toBeDefined();
663
- });
664
- it('should transition a task via local external node ID', async () => {
665
- providerStore.providers.register(mockTaskProvider);
666
- vi.mocked(baseStore.getNode).mockResolvedValue(mockTaskExternalNode);
667
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockTaskExternalNode]);
668
- vi.mocked(baseStore.updateNode).mockResolvedValue(mockTaskExternalNode);
669
- const result = await providerStore.taskTransition('x-task1', 'complete');
670
- expect(mockTaskProvider.transitionTask).toHaveBeenCalledWith('task-1', 'complete', undefined);
671
- expect(result.action).toBe('complete');
672
- });
673
- it('should throw for provider that does not support tasks', async () => {
674
- providerStore.providers.register(mockBeadsProvider);
675
- await expect(providerStore.taskTransition('beads://./bd-123', 'start')).rejects.toThrow('does not support task operations');
676
- });
677
- it('should throw for nonexistent node', async () => {
678
- vi.mocked(baseStore.getNode).mockResolvedValue(null);
679
- await expect(providerStore.taskTransition('s-nonexist', 'start')).rejects.toThrow('not found');
680
- });
681
- it('should forward context to provider', async () => {
682
- providerStore.providers.register(mockTaskProvider);
683
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
684
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
685
- const ctx = { cwd: '/other/dir', timeout: 5000 };
686
- await providerStore.taskTransition('taskprov://task-1', 'start', { context: ctx });
687
- expect(mockTaskProvider.transitionTask).toHaveBeenCalledWith('task-1', 'start', ctx);
688
- });
689
- });
690
- describe('taskReady', () => {
691
- it('should aggregate ready tasks across task-capable providers', async () => {
692
- providerStore.providers.register(mockTaskProvider);
693
- // Mock materialization for each ready task
694
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
695
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
696
- const ready = await providerStore.taskReady();
697
- expect(mockTaskProvider.readyTasks).toHaveBeenCalled();
698
- expect(ready.length).toBeGreaterThanOrEqual(1);
699
- });
700
- it('should skip non-task-capable providers', async () => {
701
- providerStore.providers.register(mockBeadsProvider);
702
- providerStore.providers.register(mockTaskProvider);
703
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
704
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
705
- await providerStore.taskReady();
706
- // Only taskprov should be queried
707
- expect(mockTaskProvider.readyTasks).toHaveBeenCalled();
708
- });
709
- it('should filter by provider name', async () => {
710
- providerStore.providers.register(mockTaskProvider);
711
- await providerStore.taskReady({ providers: ['other-provider'] });
712
- // taskprov should NOT be queried because it wasn't in the filter
713
- expect(mockTaskProvider.readyTasks).not.toHaveBeenCalled();
714
- });
715
- it('should return existing materialized nodes when available', async () => {
716
- providerStore.providers.register(mockTaskProvider);
717
- // Mock: existing materialized node found
718
- vi.mocked(baseStore.query.nodes).mockResolvedValue([mockTaskExternalNode]);
719
- const ready = await providerStore.taskReady();
720
- expect(ready[0]).toEqual(mockTaskExternalNode);
721
- });
722
- it('should forward options to provider', async () => {
723
- providerStore.providers.register(mockTaskProvider);
724
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
725
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
726
- await providerStore.taskReady({ limit: 5, tags: ['urgent'], assignee: 'alice' });
727
- expect(mockTaskProvider.readyTasks).toHaveBeenCalledWith({ limit: 5, tags: ['urgent'], priority: undefined, assignee: 'alice' }, undefined);
728
- });
729
- });
730
- describe('taskAssign', () => {
731
- it('should assign a task via provider', async () => {
732
- providerStore.providers.register(mockTaskProvider);
733
- vi.mocked(baseStore.query.nodes).mockResolvedValue([]);
734
- vi.mocked(baseStore.createNode).mockResolvedValue(mockTaskExternalNode);
735
- const result = await providerStore.taskAssign('taskprov://task-1', 'alice');
736
- expect(mockTaskProvider.assignTask).toHaveBeenCalledWith('task-1', 'alice', undefined);
737
- expect(result).toBeDefined();
738
- });
739
- it('should throw for provider that does not support assignment', async () => {
740
- const noAssignProvider = {
741
- ...mockTaskProvider,
742
- name: 'noassign',
743
- schemes: ['noassign'],
744
- parseUri: vi.fn((uri) => {
745
- if (uri.startsWith('noassign://')) {
746
- return { scheme: 'noassign', id: uri.slice('noassign://'.length), isRelative: false };
747
- }
748
- return null;
749
- }),
750
- buildUri: vi.fn((id) => `noassign://${id}`),
751
- isValidUri: vi.fn((uri) => uri.startsWith('noassign://')),
752
- taskCapabilities: {
753
- ...mockTaskProvider.taskCapabilities,
754
- supportsAssignment: false,
755
- },
756
- assignTask: undefined,
757
- };
758
- providerStore.providers.register(noAssignProvider);
759
- await expect(providerStore.taskAssign('noassign://task-1', 'alice')).rejects.toThrow('does not support task assignment');
760
- });
761
- });
762
- describe('taskValidActions', () => {
763
- it('should return valid actions from provider', async () => {
764
- providerStore.providers.register(mockTaskProvider);
765
- const actions = await providerStore.taskValidActions('taskprov://task-1');
766
- expect(mockTaskProvider.validActions).toHaveBeenCalledWith('task-1');
767
- expect(actions).toEqual(['start', 'close']);
768
- });
769
- it('should fall back to taskCapabilities.actions when validActions not implemented', async () => {
770
- const noValidActionsProvider = {
771
- ...mockTaskProvider,
772
- name: 'novalid',
773
- schemes: ['novalid'],
774
- parseUri: vi.fn((uri) => {
775
- if (uri.startsWith('novalid://')) {
776
- return { scheme: 'novalid', id: uri.slice('novalid://'.length), isRelative: false };
777
- }
778
- return null;
779
- }),
780
- buildUri: vi.fn((id) => `novalid://${id}`),
781
- isValidUri: vi.fn((uri) => uri.startsWith('novalid://')),
782
- validActions: undefined,
783
- };
784
- providerStore.providers.register(noValidActionsProvider);
785
- const actions = await providerStore.taskValidActions('novalid://task-1');
786
- expect(actions).toEqual(['start', 'complete', 'block', 'reopen']);
787
- });
788
- });
789
- });
790
- });
791
- //# sourceMappingURL=provider-store.test.js.map