@astrale-os/shell-react 0.2.9 → 0.2.10-beta.3

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 (335) hide show
  1. package/dist/application/apps/app-host.d.ts +4 -15
  2. package/dist/application/apps/app-host.d.ts.map +1 -1
  3. package/dist/application/apps/app-host.js +1 -1
  4. package/dist/application/apps/apps.hook.d.ts +7 -4
  5. package/dist/application/apps/apps.hook.d.ts.map +1 -1
  6. package/dist/application/apps/apps.hook.js +10 -2
  7. package/dist/application/apps/domains.hook.d.ts +1 -1
  8. package/dist/application/apps/domains.hook.d.ts.map +1 -1
  9. package/dist/application/apps/domains.hook.js +1 -1
  10. package/dist/application/apps/open-app.hook.d.ts +2 -3
  11. package/dist/application/apps/open-app.hook.d.ts.map +1 -1
  12. package/dist/application/index.d.ts +2 -4
  13. package/dist/application/index.d.ts.map +1 -1
  14. package/dist/application/index.js +2 -3
  15. package/dist/application/spaces/space.hook.d.ts +2 -2
  16. package/dist/application/spaces/space.hook.d.ts.map +1 -1
  17. package/dist/application/spaces/space.hook.js +2 -2
  18. package/dist/application/spaces/spaces.hook.d.ts +2 -3
  19. package/dist/application/spaces/spaces.hook.d.ts.map +1 -1
  20. package/dist/application/spaces/spaces.hook.js +10 -3
  21. package/dist/auth/act-as.context.d.ts +15 -32
  22. package/dist/auth/act-as.context.d.ts.map +1 -1
  23. package/dist/auth/act-as.context.js +22 -66
  24. package/dist/auth/auth.hook.d.ts +1 -0
  25. package/dist/auth/auth.hook.d.ts.map +1 -1
  26. package/dist/auth/auth.hook.js +2 -22
  27. package/dist/auth/can.hook.d.ts +10 -38
  28. package/dist/auth/can.hook.d.ts.map +1 -1
  29. package/dist/auth/can.hook.js +35 -110
  30. package/dist/auth/index.d.ts +1 -0
  31. package/dist/auth/index.d.ts.map +1 -1
  32. package/dist/auth/self.hook.d.ts +2 -2
  33. package/dist/auth/self.hook.d.ts.map +1 -1
  34. package/dist/auth/self.hook.js +0 -7
  35. package/dist/auth/session-kernel.d.ts +2 -3
  36. package/dist/auth/session-kernel.d.ts.map +1 -1
  37. package/dist/auth/session-kernel.js +4 -13
  38. package/dist/components/astrale.d.ts +14 -0
  39. package/dist/components/astrale.d.ts.map +1 -0
  40. package/dist/components/astrale.js +12 -0
  41. package/dist/components/boundaries.d.ts +47 -0
  42. package/dist/components/boundaries.d.ts.map +1 -0
  43. package/dist/components/boundaries.js +55 -0
  44. package/dist/components/can.d.ts +11 -0
  45. package/dist/components/can.d.ts.map +1 -0
  46. package/dist/components/can.js +5 -0
  47. package/dist/components/index.d.ts +6 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +3 -0
  50. package/dist/graph/call/call.hook.d.ts +8 -18
  51. package/dist/graph/call/call.hook.d.ts.map +1 -1
  52. package/dist/graph/call/call.hook.js +7 -13
  53. package/dist/graph/call/stream.hook.d.ts +12 -20
  54. package/dist/graph/call/stream.hook.d.ts.map +1 -1
  55. package/dist/graph/call/stream.hook.js +55 -111
  56. package/dist/graph/graph.context.d.ts +4 -3
  57. package/dist/graph/graph.context.d.ts.map +1 -1
  58. package/dist/graph/graph.context.js +2 -8
  59. package/dist/graph/guards.error.d.ts +7 -8
  60. package/dist/graph/guards.error.d.ts.map +1 -1
  61. package/dist/graph/guards.error.js +11 -7
  62. package/dist/graph/index.d.ts +8 -16
  63. package/dist/graph/index.d.ts.map +1 -1
  64. package/dist/graph/index.js +3 -7
  65. package/dist/graph/memory/read/envelope.d.ts +22 -54
  66. package/dist/graph/memory/read/envelope.d.ts.map +1 -1
  67. package/dist/graph/memory/read/envelope.js +26 -20
  68. package/dist/graph/memory/read/idle.d.ts +1 -15
  69. package/dist/graph/memory/read/idle.d.ts.map +1 -1
  70. package/dist/graph/memory/read/idle.js +4 -13
  71. package/dist/graph/query-definition.hook.d.ts +11 -0
  72. package/dist/graph/query-definition.hook.d.ts.map +1 -0
  73. package/dist/graph/query-definition.hook.js +205 -0
  74. package/dist/graph/resource.d.ts +34 -0
  75. package/dist/graph/resource.d.ts.map +1 -0
  76. package/dist/graph/resource.js +244 -0
  77. package/dist/graph/store.d.ts +25 -0
  78. package/dist/graph/store.d.ts.map +1 -0
  79. package/dist/graph/store.js +69 -0
  80. package/dist/host/actions.hook.d.ts +34 -0
  81. package/dist/host/actions.hook.d.ts.map +1 -0
  82. package/dist/host/actions.hook.js +125 -0
  83. package/dist/host/components.d.ts +15 -0
  84. package/dist/host/components.d.ts.map +1 -0
  85. package/dist/host/components.js +14 -0
  86. package/dist/host/index.d.ts +5 -0
  87. package/dist/host/index.d.ts.map +1 -0
  88. package/dist/host/index.js +3 -0
  89. package/dist/index.d.ts +26 -37
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +20 -31
  92. package/dist/intents/envelope.d.ts +7 -19
  93. package/dist/intents/envelope.d.ts.map +1 -1
  94. package/dist/intents/envelope.js +2 -3
  95. package/dist/intents/send-intent.hook.d.ts +5 -7
  96. package/dist/intents/send-intent.hook.d.ts.map +1 -1
  97. package/dist/intents/send-intent.hook.js +17 -75
  98. package/dist/navigation/adapters/adapter.d.ts +5 -0
  99. package/dist/navigation/adapters/adapter.d.ts.map +1 -1
  100. package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -1
  101. package/dist/navigation/adapters/memory.adapter.js +4 -0
  102. package/dist/navigation/adapters/url.adapter.d.ts.map +1 -1
  103. package/dist/navigation/adapters/url.adapter.js +57 -10
  104. package/dist/navigation/codec.d.ts +60 -0
  105. package/dist/navigation/codec.d.ts.map +1 -0
  106. package/dist/navigation/codec.js +121 -0
  107. package/dist/navigation/history.d.ts +11 -9
  108. package/dist/navigation/history.d.ts.map +1 -1
  109. package/dist/navigation/history.js +6 -8
  110. package/dist/navigation/hooks/ancestors.hook.d.ts +5 -7
  111. package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -1
  112. package/dist/navigation/hooks/ancestors.hook.js +7 -51
  113. package/dist/navigation/hooks/location.hook.d.ts.map +1 -1
  114. package/dist/navigation/hooks/location.hook.js +4 -6
  115. package/dist/navigation/hooks/scope.hook.d.ts.map +1 -1
  116. package/dist/navigation/hooks/scope.hook.js +4 -6
  117. package/dist/navigation/index.d.ts +4 -2
  118. package/dist/navigation/index.d.ts.map +1 -1
  119. package/dist/navigation/index.js +2 -1
  120. package/dist/navigation/link.d.ts +12 -0
  121. package/dist/navigation/link.d.ts.map +1 -1
  122. package/dist/navigation/link.js +13 -2
  123. package/dist/navigation/location.d.ts +13 -40
  124. package/dist/navigation/location.d.ts.map +1 -1
  125. package/dist/navigation/location.js +14 -76
  126. package/dist/navigation/scope.context.d.ts +17 -26
  127. package/dist/navigation/scope.context.d.ts.map +1 -1
  128. package/dist/navigation/scope.context.js +140 -108
  129. package/dist/navigation/snapshot.d.ts +14 -19
  130. package/dist/navigation/snapshot.d.ts.map +1 -1
  131. package/dist/navigation/snapshot.js +48 -44
  132. package/dist/schema/action.hook.d.ts +44 -0
  133. package/dist/schema/action.hook.d.ts.map +1 -0
  134. package/dist/schema/action.hook.js +72 -0
  135. package/dist/schema/domain.hook.d.ts +12 -7
  136. package/dist/schema/domain.hook.d.ts.map +1 -1
  137. package/dist/schema/domain.hook.js +101 -4
  138. package/dist/schema/index.d.ts +9 -8
  139. package/dist/schema/index.d.ts.map +1 -1
  140. package/dist/schema/index.js +5 -7
  141. package/dist/schema/kernel.hook.d.ts +4 -9
  142. package/dist/schema/kernel.hook.d.ts.map +1 -1
  143. package/dist/schema/kernel.hook.js +4 -6
  144. package/dist/schema/mutation.hook.d.ts +9 -0
  145. package/dist/schema/mutation.hook.d.ts.map +1 -0
  146. package/dist/schema/mutation.hook.js +41 -0
  147. package/dist/schema/object.hook.d.ts +16 -0
  148. package/dist/schema/object.hook.d.ts.map +1 -0
  149. package/dist/schema/object.hook.js +46 -0
  150. package/dist/schema/relation.hook.d.ts +46 -0
  151. package/dist/schema/relation.hook.d.ts.map +1 -0
  152. package/dist/schema/relation.hook.js +136 -0
  153. package/dist/selection/live.d.ts +9 -17
  154. package/dist/selection/live.d.ts.map +1 -1
  155. package/dist/selection/live.js +65 -112
  156. package/dist/selection/scope.context.js +5 -4
  157. package/dist/session/boot/boot.d.ts +1 -13
  158. package/dist/session/boot/boot.d.ts.map +1 -1
  159. package/dist/session/boot/boot.js +9 -27
  160. package/dist/session/index.d.ts +1 -0
  161. package/dist/session/index.d.ts.map +1 -1
  162. package/dist/session/kernel-instance.hook.d.ts +1 -4
  163. package/dist/session/kernel-instance.hook.d.ts.map +1 -1
  164. package/dist/session/kernel-instance.hook.js +1 -5
  165. package/dist/session/provider.d.ts +9 -23
  166. package/dist/session/provider.d.ts.map +1 -1
  167. package/dist/session/provider.js +29 -75
  168. package/dist/session/session.context.d.ts +5 -0
  169. package/dist/session/session.context.d.ts.map +1 -1
  170. package/dist/window/adopt.d.ts +0 -1
  171. package/dist/window/adopt.d.ts.map +1 -1
  172. package/dist/window/hooks/target-node.hook.d.ts +10 -6
  173. package/dist/window/hooks/target-node.hook.d.ts.map +1 -1
  174. package/dist/window/hooks/target-node.hook.js +14 -72
  175. package/dist/window/hooks/views-for.hook.d.ts +4 -3
  176. package/dist/window/hooks/views-for.hook.d.ts.map +1 -1
  177. package/dist/window/hooks/views-for.hook.js +6 -22
  178. package/dist/window/hooks/windows.hook.d.ts.map +1 -1
  179. package/dist/window/hooks/windows.hook.js +3 -4
  180. package/dist/window/host.d.ts +9 -11
  181. package/dist/window/host.d.ts.map +1 -1
  182. package/dist/window/host.js +26 -62
  183. package/dist/window/hub.d.ts +2 -0
  184. package/dist/window/hub.d.ts.map +1 -1
  185. package/dist/window/hub.js +33 -0
  186. package/dist/window/index.d.ts +2 -2
  187. package/dist/window/index.d.ts.map +1 -1
  188. package/dist/window/index.js +2 -2
  189. package/dist/window/lifecycle/mount.d.ts +5 -44
  190. package/dist/window/lifecycle/mount.d.ts.map +1 -1
  191. package/dist/window/lifecycle/mount.js +14 -120
  192. package/dist/window/lifecycle/states.d.ts +0 -5
  193. package/dist/window/lifecycle/states.d.ts.map +1 -1
  194. package/package.json +20 -8
  195. package/src/application/apps/app-host.tsx +11 -15
  196. package/src/application/apps/apps.hook.ts +13 -6
  197. package/src/application/apps/domains.hook.ts +1 -1
  198. package/src/application/apps/open-app.hook.ts +3 -4
  199. package/src/application/index.ts +4 -6
  200. package/src/application/spaces/space.hook.ts +5 -6
  201. package/src/application/spaces/spaces.hook.ts +14 -9
  202. package/src/auth/act-as.context.tsx +37 -91
  203. package/src/auth/auth.hook.ts +2 -30
  204. package/src/auth/can.hook.ts +44 -135
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/self.hook.ts +2 -9
  207. package/src/auth/session-kernel.ts +6 -16
  208. package/src/components/astrale.tsx +40 -0
  209. package/src/components/boundaries.tsx +113 -0
  210. package/src/components/can.tsx +16 -0
  211. package/src/components/index.ts +13 -0
  212. package/src/graph/call/call.hook.ts +22 -27
  213. package/src/graph/call/stream.hook.ts +66 -143
  214. package/src/graph/graph.context.ts +6 -11
  215. package/src/graph/guards.error.ts +17 -55
  216. package/src/graph/index.ts +23 -17
  217. package/src/graph/memory/read/envelope.ts +40 -65
  218. package/src/graph/memory/read/idle.ts +5 -18
  219. package/src/graph/query-definition.hook.ts +270 -0
  220. package/src/graph/resource.ts +358 -0
  221. package/src/graph/store.ts +97 -0
  222. package/src/host/actions.hook.ts +165 -0
  223. package/src/host/components.tsx +29 -0
  224. package/src/host/index.ts +17 -0
  225. package/src/index.ts +131 -124
  226. package/src/intents/envelope.ts +7 -23
  227. package/src/intents/send-intent.hook.ts +24 -79
  228. package/src/navigation/adapters/adapter.ts +3 -0
  229. package/src/navigation/adapters/memory.adapter.ts +4 -0
  230. package/src/navigation/adapters/url.adapter.ts +48 -8
  231. package/src/navigation/codec.ts +190 -0
  232. package/src/navigation/history.ts +20 -11
  233. package/src/navigation/hooks/ancestors.hook.ts +10 -60
  234. package/src/navigation/hooks/location.hook.ts +4 -6
  235. package/src/navigation/hooks/scope.hook.ts +4 -6
  236. package/src/navigation/index.ts +18 -2
  237. package/src/navigation/link.tsx +27 -2
  238. package/src/navigation/location.ts +26 -93
  239. package/src/navigation/scope.context.tsx +151 -136
  240. package/src/navigation/snapshot.ts +69 -50
  241. package/src/schema/action.hook.ts +146 -0
  242. package/src/schema/domain.hook.ts +127 -11
  243. package/src/schema/index.ts +9 -30
  244. package/src/schema/kernel.hook.ts +6 -13
  245. package/src/schema/mutation.hook.ts +63 -0
  246. package/src/schema/object.hook.ts +85 -0
  247. package/src/schema/relation.hook.ts +253 -0
  248. package/src/selection/live.ts +75 -125
  249. package/src/selection/scope.context.tsx +4 -4
  250. package/src/session/boot/boot.ts +8 -33
  251. package/src/session/index.ts +1 -0
  252. package/src/session/kernel-instance.hook.ts +2 -11
  253. package/src/session/provider.tsx +34 -103
  254. package/src/session/session.context.ts +6 -0
  255. package/src/window/adopt.ts +0 -1
  256. package/src/window/hooks/target-node.hook.ts +20 -85
  257. package/src/window/hooks/views-for.hook.ts +12 -25
  258. package/src/window/hooks/windows.hook.ts +3 -4
  259. package/src/window/host.tsx +35 -77
  260. package/src/window/hub.ts +33 -0
  261. package/src/window/index.ts +3 -3
  262. package/src/window/lifecycle/mount.ts +15 -160
  263. package/src/window/lifecycle/states.ts +1 -5
  264. package/dist/application/roles.hook.d.ts +0 -7
  265. package/dist/application/roles.hook.d.ts.map +0 -1
  266. package/dist/application/roles.hook.js +0 -23
  267. package/dist/graph/memory/base.d.ts +0 -21
  268. package/dist/graph/memory/base.d.ts.map +0 -1
  269. package/dist/graph/memory/base.js +0 -33
  270. package/dist/graph/memory/read/read.d.ts +0 -9
  271. package/dist/graph/memory/read/read.d.ts.map +0 -1
  272. package/dist/graph/memory/read/read.js +0 -87
  273. package/dist/graph/memory/scope.d.ts +0 -7
  274. package/dist/graph/memory/scope.d.ts.map +0 -1
  275. package/dist/graph/memory/scope.js +0 -25
  276. package/dist/graph/mutate/move.hook.d.ts +0 -7
  277. package/dist/graph/mutate/move.hook.d.ts.map +0 -1
  278. package/dist/graph/mutate/move.hook.js +0 -49
  279. package/dist/graph/mutate/mutate.hook.d.ts +0 -17
  280. package/dist/graph/mutate/mutate.hook.d.ts.map +0 -1
  281. package/dist/graph/mutate/mutate.hook.js +0 -45
  282. package/dist/graph/query/children.hook.d.ts +0 -29
  283. package/dist/graph/query/children.hook.d.ts.map +0 -1
  284. package/dist/graph/query/children.hook.js +0 -64
  285. package/dist/graph/query/graph.hook.d.ts +0 -9
  286. package/dist/graph/query/graph.hook.d.ts.map +0 -1
  287. package/dist/graph/query/graph.hook.js +0 -5
  288. package/dist/graph/query/neighbors.hook.d.ts +0 -7
  289. package/dist/graph/query/neighbors.hook.d.ts.map +0 -1
  290. package/dist/graph/query/neighbors.hook.js +0 -104
  291. package/dist/graph/query/node.hook.d.ts +0 -14
  292. package/dist/graph/query/node.hook.d.ts.map +0 -1
  293. package/dist/graph/query/node.hook.js +0 -43
  294. package/dist/graph/query/query.hook.d.ts +0 -14
  295. package/dist/graph/query/query.hook.d.ts.map +0 -1
  296. package/dist/graph/query/query.hook.js +0 -23
  297. package/dist/schema/bind-read.d.ts +0 -37
  298. package/dist/schema/bind-read.d.ts.map +0 -1
  299. package/dist/schema/bind-read.js +0 -205
  300. package/dist/schema/binding.d.ts +0 -21
  301. package/dist/schema/binding.d.ts.map +0 -1
  302. package/dist/schema/binding.js +0 -99
  303. package/dist/schema/function.hook.d.ts +0 -9
  304. package/dist/schema/function.hook.d.ts.map +0 -1
  305. package/dist/schema/function.hook.js +0 -40
  306. package/dist/schema/method.hook.d.ts +0 -14
  307. package/dist/schema/method.hook.d.ts.map +0 -1
  308. package/dist/schema/method.hook.js +0 -49
  309. package/dist/schema/tag.d.ts +0 -11
  310. package/dist/schema/tag.d.ts.map +0 -1
  311. package/dist/schema/tag.js +0 -11
  312. package/dist/schema/types.d.ts +0 -73
  313. package/dist/schema/types.d.ts.map +0 -1
  314. package/dist/schema/types.js +0 -1
  315. package/dist/schema/write.d.ts +0 -16
  316. package/dist/schema/write.d.ts.map +0 -1
  317. package/dist/schema/write.js +0 -56
  318. package/src/application/roles.hook.ts +0 -41
  319. package/src/graph/memory/base.ts +0 -50
  320. package/src/graph/memory/read/read.ts +0 -103
  321. package/src/graph/memory/scope.ts +0 -29
  322. package/src/graph/mutate/move.hook.ts +0 -70
  323. package/src/graph/mutate/mutate.hook.ts +0 -69
  324. package/src/graph/query/children.hook.ts +0 -136
  325. package/src/graph/query/graph.hook.ts +0 -13
  326. package/src/graph/query/neighbors.hook.ts +0 -159
  327. package/src/graph/query/node.hook.ts +0 -83
  328. package/src/graph/query/query.hook.ts +0 -42
  329. package/src/schema/bind-read.ts +0 -319
  330. package/src/schema/binding.ts +0 -146
  331. package/src/schema/function.hook.ts +0 -68
  332. package/src/schema/method.hook.ts +0 -103
  333. package/src/schema/tag.ts +0 -19
  334. package/src/schema/types.ts +0 -150
  335. package/src/schema/write.ts +0 -96
@@ -0,0 +1,253 @@
1
+ import type { StoreQueryResponse } from '@astrale-os/kernel-client/store'
2
+ import type {
3
+ BoundInstanceOf,
4
+ Definition,
5
+ EdgeEndpointAcceptedDefinitionsOf,
6
+ } from '@astrale-os/sdk/domain'
7
+ import type { LinkedCountOf } from '@astrale-os/sdk/query'
8
+
9
+ import { NodeId } from '@astrale-os/sdk/graph/node'
10
+ import { Path } from '@astrale-os/sdk/graph/path'
11
+ import { Query } from '@astrale-os/sdk/query'
12
+ import { useCallback, useMemo } from 'react'
13
+
14
+ import type { GraphResource } from '../graph/resource.js'
15
+
16
+ import { useStoreResource } from '../graph/resource.js'
17
+ import { bindingFor } from './domain.hook.js'
18
+
19
+ type DefinitionCoordinates = Pick<
20
+ Definition<'class' | 'interface'>,
21
+ 'kind' | 'origin' | 'name' | 'key' | 'schemaRef' | 'definition'
22
+ >
23
+ type NodeDefinition = DefinitionCoordinates & { readonly family: 'node' }
24
+ type EdgeDefinition = DefinitionCoordinates & { readonly family: 'edge' }
25
+ type EndpointRole = 'source' | 'target'
26
+ type RelationCardinality = '1' | '0..1' | '0..*' | '1..*'
27
+ type RuntimeEndpoint = {
28
+ readonly accepts: readonly {
29
+ readonly origin: string
30
+ readonly kind: 'class' | 'interface'
31
+ readonly name: string
32
+ }[]
33
+ readonly outgoing?: RelationCardinality
34
+ readonly incoming?: RelationCardinality
35
+ }
36
+ type Opposite<Role extends EndpointRole> = Role extends 'source' ? 'target' : 'source'
37
+ type Accepted<
38
+ Definition extends EdgeDefinition,
39
+ Role extends EndpointRole,
40
+ > = EdgeEndpointAcceptedDefinitionsOf<Definition, Opposite<Role>>
41
+ type StartingDefinition<
42
+ Relation extends EdgeDefinition,
43
+ Role extends EndpointRole,
44
+ > = EdgeEndpointAcceptedDefinitionsOf<Relation, Role> & NodeDefinition
45
+ type RelatedDefinition<Relation extends EdgeDefinition, Role extends EndpointRole> = Accepted<
46
+ Relation,
47
+ Role
48
+ > &
49
+ NodeDefinition
50
+ type EffectiveCardinality<
51
+ Relation extends EdgeDefinition,
52
+ Role extends EndpointRole,
53
+ To extends RelatedDefinition<Relation, Role>,
54
+ > = LinkedCountOf<Relation, Role, To>
55
+
56
+ export interface RelationUnavailable {
57
+ readonly kind: 'required-hidden-or-invalid'
58
+ readonly cardinality: '1' | '1..*'
59
+ }
60
+
61
+ export class RelationUnavailableError extends Error {
62
+ readonly code = 'SHELL_REACT_RELATION_UNAVAILABLE' as const
63
+ constructor(readonly unavailable: RelationUnavailable) {
64
+ super(
65
+ `Required relation (${unavailable.cardinality}) is hidden or violates the installed schema.`,
66
+ )
67
+ this.name = 'RelationUnavailableError'
68
+ }
69
+ }
70
+
71
+ export type RelationResource<T, C extends string> = C extends '1' | '0..1'
72
+ ? GraphResource<T | null> & {
73
+ readonly cardinality: C
74
+ readonly unavailable?: RelationUnavailable
75
+ require(): T
76
+ }
77
+ : GraphResource<readonly T[]> & {
78
+ readonly cardinality: C
79
+ readonly unavailable?: RelationUnavailable
80
+ require(): readonly T[]
81
+ }
82
+
83
+ export interface RelationOptions<Role extends EndpointRole, To> {
84
+ readonly from: Role
85
+ /** Exact rich opposite-endpoint definition used to admit and brand returned nodes. */
86
+ readonly to: To
87
+ readonly page?: { readonly size: number }
88
+ }
89
+
90
+ /** Traverse one exact rich Edge definition with endpoint and cardinality inference. */
91
+ export function useRelation<
92
+ Definition extends EdgeDefinition,
93
+ Role extends EndpointRole,
94
+ To extends RelatedDefinition<Definition, Role>,
95
+ >(
96
+ definition: Definition,
97
+ source: BoundInstanceOf<StartingDefinition<Definition, Role>> | null | undefined,
98
+ options: RelationOptions<Role, To>,
99
+ ): RelationResource<BoundInstanceOf<To>, EffectiveCardinality<Definition, Role, To>> {
100
+ const binding = bindingFor(definition)
101
+ if (source !== null && source !== undefined) {
102
+ admitSource(binding, definition, options.from, source)
103
+ }
104
+ admitRelatedDefinition(binding, definition, options.from, options.to)
105
+ const path = source === null || source === undefined ? null : Path.id(NodeId(source.id))
106
+ const query = useMemo(() => {
107
+ if (path === null) return null
108
+ const root = Query.from({ kind: 'node', paths: [path], binding: 'relation_source' })
109
+ const admitted = root.filter({ satisfies: source!.$.definition.schemaRef })
110
+ const expanded = admitted.expand({
111
+ via: [definition.schemaRef],
112
+ direction: options.from === 'source' ? 'outgoing' : 'incoming',
113
+ })
114
+ return expanded
115
+ .filter({ satisfies: options.to.schemaRef })
116
+ .select({ kind: 'graph', binding: expanded.node })
117
+ }, [definition, options.from, options.to, path?.raw, source])
118
+ const cardinality: RelationCardinality = effectiveCardinality(
119
+ binding,
120
+ definition,
121
+ options.from,
122
+ options.to,
123
+ )
124
+ const project = useCallback(
125
+ (response: StoreQueryResponse<NonNullable<typeof query>>) => {
126
+ const result = response.result
127
+ if (result.kind !== 'graph' || result.selection.kind !== 'node') {
128
+ return cardinality === '0..1'
129
+ ? null
130
+ : cardinality === '0..*'
131
+ ? Object.freeze([])
132
+ : undefined
133
+ }
134
+ const byId = new Map(result.graph.nodes.map((node) => [node.id, node]))
135
+ const values = result.selection.ids.flatMap((id) => {
136
+ const node = byId.get(id)
137
+ if (node === undefined) return []
138
+ const bind = binding.$.bind as unknown as (
139
+ expected: To,
140
+ value: typeof node,
141
+ ) => BoundInstanceOf<To>
142
+ return [bind(options.to, node)]
143
+ })
144
+ if (cardinality === '0..1') return values[0] ?? null
145
+ if (cardinality === '1') return values[0]
146
+ if (cardinality === '1..*' && values.length === 0) return undefined
147
+ return Object.freeze(values)
148
+ },
149
+ [binding, cardinality, options.to, query],
150
+ )
151
+ const resource = useStoreResource(query, options.page?.size ?? 50, project)
152
+ const unavailable =
153
+ (cardinality === '1' &&
154
+ resource.data === undefined &&
155
+ resource.state !== 'idle' &&
156
+ resource.state !== 'loading') ||
157
+ (cardinality === '1..*' &&
158
+ resource.data === undefined &&
159
+ resource.state !== 'idle' &&
160
+ resource.state !== 'loading')
161
+ ? Object.freeze({ kind: 'required-hidden-or-invalid' as const, cardinality })
162
+ : undefined
163
+ const require = () => {
164
+ if (unavailable !== undefined) throw new RelationUnavailableError(unavailable)
165
+ if (resource.data === undefined || resource.data === null) {
166
+ throw new Error('Relation resource is not settled with an available value.')
167
+ }
168
+ return resource.data
169
+ }
170
+ return Object.freeze({
171
+ ...resource,
172
+ cardinality,
173
+ ...(unavailable === undefined ? {} : { unavailable }),
174
+ require,
175
+ }) as RelationResource<BoundInstanceOf<To>, EffectiveCardinality<Definition, Role, To>>
176
+ }
177
+
178
+ function admitSource<Relation extends EdgeDefinition, Role extends EndpointRole>(
179
+ binding: ReturnType<typeof bindingFor>,
180
+ relation: Relation,
181
+ from: Role,
182
+ source: BoundInstanceOf<StartingDefinition<Relation, Role>>,
183
+ ): void {
184
+ const accepted = edgeEndpoints(relation)[from].accepts
185
+ const matches = accepted.some((ref) => {
186
+ const candidate = binding.$.inspect(ref) as Definition<'class' | 'interface'>
187
+ return source.$.definition.satisfies(candidate)
188
+ })
189
+ if (!matches) throw new TypeError('Relation source does not satisfy the selected endpoint.')
190
+ }
191
+
192
+ function endpointCardinality(definition: EdgeDefinition, side: EndpointRole): RelationCardinality {
193
+ const endpoint = edgeEndpoints(definition)[side]
194
+ const cardinality = side === 'source' ? endpoint.outgoing : endpoint.incoming
195
+ if (cardinality === undefined) {
196
+ throw new TypeError(`Relation ${side} endpoint does not expose its traversal cardinality.`)
197
+ }
198
+ return cardinality
199
+ }
200
+
201
+ function effectiveCardinality<
202
+ Relation extends EdgeDefinition,
203
+ Role extends EndpointRole,
204
+ To extends RelatedDefinition<Relation, Role>,
205
+ >(
206
+ binding: ReturnType<typeof bindingFor>,
207
+ relation: Relation,
208
+ from: Role,
209
+ to: To,
210
+ ): EffectiveCardinality<Relation, Role, To> {
211
+ const base = endpointCardinality(relation, from)
212
+ if (base !== '1' && base !== '1..*') {
213
+ return base as EffectiveCardinality<Relation, Role, To>
214
+ }
215
+ const opposite: EndpointRole = from === 'source' ? 'target' : 'source'
216
+ const coversEndpoint = edgeEndpoints(relation)[opposite].accepts.every((ref) => {
217
+ const candidate = binding.$.inspect(ref) as Definition<'class' | 'interface'>
218
+ return candidate.satisfies(to)
219
+ })
220
+ return (coversEndpoint ? base : base === '1' ? '0..1' : '0..*') as EffectiveCardinality<
221
+ Relation,
222
+ Role,
223
+ To
224
+ >
225
+ }
226
+
227
+ function admitRelatedDefinition<
228
+ Relation extends EdgeDefinition,
229
+ Role extends EndpointRole,
230
+ To extends RelatedDefinition<Relation, Role>,
231
+ >(binding: ReturnType<typeof bindingFor>, relation: Relation, from: Role, to: To): void {
232
+ const opposite: EndpointRole = from === 'source' ? 'target' : 'source'
233
+ const accepted = edgeEndpoints(relation)[opposite].accepts
234
+ const matches = accepted.some((ref) => {
235
+ const candidate = binding.$.inspect(ref) as Definition<'class' | 'interface'>
236
+ return to.satisfies(candidate)
237
+ })
238
+ if (!matches)
239
+ throw new TypeError('Relation target definition is not admitted by the opposite endpoint.')
240
+ }
241
+
242
+ function edgeEndpoints(relation: EdgeDefinition): Readonly<Record<EndpointRole, RuntimeEndpoint>> {
243
+ const declaration = relation.definition as unknown as {
244
+ readonly family?: unknown
245
+ readonly endpoints?: Partial<Record<EndpointRole, RuntimeEndpoint>>
246
+ }
247
+ const source = declaration.endpoints?.source
248
+ const target = declaration.endpoints?.target
249
+ if (declaration.family !== 'edge' || source === undefined || target === undefined) {
250
+ throw new TypeError('Relation definition does not expose admitted Edge endpoints.')
251
+ }
252
+ return Object.freeze({ source, target })
253
+ }
@@ -1,142 +1,92 @@
1
- import type { GraphMemory, ReadOptions, ShapeInput } from '@astrale-os/kernel-client/store'
1
+ import type { Node, NodeId } from '@astrale-os/sdk/graph/node'
2
2
 
3
- /**
4
- * Selection liveness (API §9) — resolve the scope's ids to LIVE memory nodes, and
5
- * the node types the selection speaks. Each id is subscribed as an independent
6
- * point-read shape; an id drops ONLY once its shape has SETTLED to absence (the
7
- * fixed latch) — a cold shape whose point-read has not landed is kept alive with
8
- * no node, so a selection made from a children row is never self-cleared before
9
- * its fetch resolves. Degrades to no nodes (ids kept) when no memory is in scope.
10
- */
11
- import { useCallback, useContext, useRef, useSyncExternalStore } from 'react'
3
+ import { Path } from '@astrale-os/sdk/graph/path'
4
+ import { useCallback, useEffect, useRef, useState } from 'react'
12
5
 
13
- import { GraphContext } from '../graph/graph.context.js'
6
+ import { useSessionKernel } from '../auth/session-kernel.js'
14
7
 
15
- /** A live memory node — the non-null projection of a point read. */
16
- export type SelectionNode = NonNullable<ReturnType<GraphMemory['getSnapshot']>['node']>
17
- export type NodeId = SelectionNode['id']
8
+ export type SelectionNode = Node
9
+ export type { NodeId }
18
10
 
19
- const LIVE_OPTS: ReadOptions = { live: true }
20
- const EMPTY_NODES: readonly SelectionNode[] = []
21
- const NOOP = (): void => {}
22
-
23
- /** Point-read shape addressing a node by its graph id. */
24
- function nodeShape(id: NodeId): ShapeInput {
25
- return `@${id}`
26
- }
27
-
28
- /** A point-read snapshot — the arms selection reads (widened to tolerate fakes). */
29
- type NodeSnapshot = { readonly node: SelectionNode | null; live?: boolean; pending?: boolean }
30
-
31
- /**
32
- * A null-node id is a CONFIRMED absence (drop it) only when the shape settled
33
- * there: it was seen warm and is now gone (deletion), or a live subscription has
34
- * finished fetching and found nothing (mask/never-existed). A never-warm, not-
35
- * yet-fetched (cold/pending) shape is NOT absence — the id stays alive, no node.
36
- */
37
- function isConfirmedAbsent(snap: NodeSnapshot, id: NodeId, everWarm: ReadonlySet<NodeId>): boolean {
38
- if (everWarm.has(id)) return true
39
- return snap.live === true && snap.pending === false
40
- }
41
-
42
- /**
43
- * The in-scope graph memory, structurally narrowed. Reads the base memory the
44
- * provider seats in `GraphContext`, defensively so selection also works under a
45
- * bare/fake context (tests) or before one is mounted.
46
- */
47
- export function useMemory(): GraphMemory | null {
48
- const ctx = useContext(GraphContext) as unknown as GraphMemory | null
49
- return ctx !== null &&
50
- typeof ctx.subscribe === 'function' &&
51
- typeof ctx.getSnapshot === 'function'
52
- ? ctx
53
- : null
54
- }
55
-
56
- export interface LiveSnapshot {
11
+ export interface ResolvedSelection {
57
12
  readonly aliveIds: readonly NodeId[]
58
13
  readonly nodes: readonly SelectionNode[]
14
+ readonly pending: boolean
15
+ readonly error: unknown
16
+ refetch(): Promise<void>
59
17
  }
60
18
 
61
- function unchanged(
62
- prev: LiveSnapshot,
63
- aliveIds: readonly NodeId[],
64
- nodes: readonly SelectionNode[],
65
- ): boolean {
66
- // `aliveIds` and `nodes` can differ in length (a cold id is alive with no node
67
- // yet), so compare the two lists independently.
68
- if (prev.aliveIds.length !== aliveIds.length || prev.nodes.length !== nodes.length) return false
69
- for (let i = 0; i < aliveIds.length; i += 1) if (prev.aliveIds[i] !== aliveIds[i]) return false
70
- for (let i = 0; i < nodes.length; i += 1) if (prev.nodes[i] !== nodes[i]) return false
71
- return true
19
+ interface State {
20
+ readonly key: string
21
+ readonly aliveIds: readonly NodeId[]
22
+ readonly nodes: readonly SelectionNode[]
23
+ readonly pending: boolean
24
+ readonly error: unknown
72
25
  }
73
26
 
74
- /**
75
- * Subscribe to each id's node shape and project the surviving nodes. The result
76
- * keeps a stable reference across renders when membership and node identities
77
- * are unchanged (useSyncExternalStore contract).
78
- */
79
- export function useLiveNodes(
80
- orderedIds: readonly NodeId[],
81
- memory: GraphMemory | null,
82
- ): LiveSnapshot {
83
- // Stands in for `orderedIds` in the callback deps (a fresh array each render).
84
- const idsKey = orderedIds.join(' ')
85
- const cache = useRef<{ memory: GraphMemory | null; snap: LiveSnapshot } | null>(null)
86
- // Ids whose shape has been seen warm at least once — a later null is a deletion
87
- // (confirmed absence), whereas a never-warm null is merely cold. Monotonic, so
88
- // re-reading getSnapshot within a render is idempotent (useSyncExternalStore-safe).
89
- const everWarm = useRef<Set<NodeId>>(new Set())
27
+ /** Resolve selected identities through the authenticated GraphApi; no fake live subscription. */
28
+ export function useResolvedNodes(orderedIds: readonly NodeId[]): ResolvedSelection {
29
+ const graph = useSessionKernel().graph
30
+ const key = orderedIds.join('\u0000')
31
+ const idsRef = useRef(orderedIds)
32
+ idsRef.current = orderedIds
33
+ const generation = useRef(0)
34
+ const alive = useRef(true)
35
+ const [state, setState] = useState<State>({
36
+ key,
37
+ aliveIds: orderedIds,
38
+ nodes: [],
39
+ pending: orderedIds.length > 0,
40
+ error: undefined,
41
+ })
42
+ useEffect(() => {
43
+ alive.current = true
44
+ return () => {
45
+ alive.current = false
46
+ }
47
+ }, [])
90
48
 
91
- const getSnapshot = useCallback((): LiveSnapshot => {
92
- let aliveIds: readonly NodeId[]
93
- let nodes: readonly SelectionNode[]
94
- if (memory === null) {
95
- // No liveness source: keep the ids, resolve no nodes.
96
- aliveIds = orderedIds
97
- nodes = EMPTY_NODES
98
- } else {
99
- const seen = everWarm.current
100
- const a: NodeId[] = []
101
- const n: SelectionNode[] = []
102
- for (const id of orderedIds) {
103
- const snap = memory.getSnapshot(nodeShape(id), LIVE_OPTS) as NodeSnapshot
104
- const node = snap.node
105
- if (node !== null) {
106
- seen.add(id)
107
- a.push(id)
108
- n.push(node)
109
- } else if (!isConfirmedAbsent(snap, id, seen)) {
110
- // Cold / not-yet-fetched: keep the id alive, no node yet — never a
111
- // premature drop (the critical self-clear this guards against).
112
- a.push(id)
113
- }
114
- // else: settled absence (deleted/masked) → drops from ids and nodes.
49
+ const load = useCallback(async (): Promise<void> => {
50
+ const ids = idsRef.current
51
+ const currentKey = ids.join('\u0000')
52
+ const id = ++generation.current
53
+ if (ids.length === 0) {
54
+ if (alive.current) {
55
+ setState({ key: currentKey, aliveIds: [], nodes: [], pending: false, error: undefined })
115
56
  }
116
- aliveIds = a
117
- nodes = n
57
+ return
118
58
  }
119
- const prev = cache.current
120
- if (prev !== null && prev.memory === memory && unchanged(prev.snap, aliveIds, nodes)) {
121
- return prev.snap
59
+ setState({ key: currentKey, aliveIds: ids, nodes: [], pending: true, error: undefined })
60
+ try {
61
+ const values = await Promise.all(ids.map((nodeId) => graph.get(Path.id(nodeId))))
62
+ if (!alive.current || id !== generation.current) return
63
+ const aliveIds: NodeId[] = []
64
+ const nodes: Node[] = []
65
+ values.forEach((node, index) => {
66
+ if (node === null) return
67
+ aliveIds.push(ids[index]!)
68
+ nodes.push(node)
69
+ })
70
+ setState({
71
+ key: currentKey,
72
+ aliveIds: Object.freeze(aliveIds),
73
+ nodes: Object.freeze(nodes),
74
+ pending: false,
75
+ error: undefined,
76
+ })
77
+ } catch (error) {
78
+ if (alive.current && id === generation.current) {
79
+ setState({ key: currentKey, aliveIds: ids, nodes: [], pending: false, error })
80
+ }
122
81
  }
123
- const snap: LiveSnapshot = { aliveIds, nodes }
124
- cache.current = { memory, snap }
125
- return snap
126
- }, [memory, idsKey])
82
+ }, [graph])
127
83
 
128
- const subscribe = useCallback(
129
- (onStoreChange: () => void): (() => void) => {
130
- if (memory === null || orderedIds.length === 0) return NOOP
131
- const unsubs = orderedIds.map((id) =>
132
- memory.subscribe(nodeShape(id), onStoreChange, LIVE_OPTS),
133
- )
134
- return () => {
135
- for (const unsub of unsubs) unsub()
136
- }
137
- },
138
- [memory, idsKey],
139
- )
84
+ useEffect(() => {
85
+ void load()
86
+ }, [key, load])
140
87
 
141
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
88
+ if (state.key !== key) {
89
+ return { aliveIds: orderedIds, nodes: [], pending: true, error: undefined, refetch: load }
90
+ }
91
+ return { ...state, refetch: load }
142
92
  }
@@ -10,7 +10,7 @@ import { createContext, useCallback, useEffect, useMemo, useReducer, useRef } fr
10
10
 
11
11
  import type { NodeId, SelectionNode } from './live.js'
12
12
 
13
- import { useLiveNodes, useMemory } from './live.js'
13
+ import { useResolvedNodes } from './live.js'
14
14
 
15
15
  /** The value `useSelection()` returns (API §9). */
16
16
  export interface Selection {
@@ -109,14 +109,14 @@ export function SelectionScope(props: SelectionScopeProps): ReactElement {
109
109
  const { initial, onChange, children } = props
110
110
  const [state, dispatch] = useReducer(reducer, initial, initState)
111
111
 
112
- const memory = useMemory()
113
- const { aliveIds, nodes } = useLiveNodes(state.ids, memory)
112
+ const { aliveIds, nodes, pending, error } = useResolvedNodes(state.ids)
114
113
 
115
114
  // Re-canonicalize stored state when liveness drops a node (or its anchor).
116
115
  useEffect(() => {
117
116
  const anchorDead = state.anchor !== null && !aliveIds.includes(state.anchor)
117
+ if (pending || error !== undefined) return
118
118
  if (!sameIds(state.ids, aliveIds) || anchorDead) dispatch({ t: 'prune', alive: aliveIds })
119
- }, [aliveIds, state.ids, state.anchor])
119
+ }, [aliveIds, pending, error, state.ids, state.anchor])
120
120
 
121
121
  // Report changes (explicit ops AND liveness drops), skipping the initial mount.
122
122
  const onChangeRef = useRef(onChange)
@@ -1,8 +1,3 @@
1
- /**
2
- * The boot machine inputs (API §2): config identity (what triggers a reboot) and
3
- * whoami resolution (`createShell → init → whoami → OS-user preresolve`). Kept
4
- * pure of React so the provider's boot effect composes them.
5
- */
6
1
  import type { Shell, ShellConfig } from '@astrale-os/shell'
7
2
 
8
3
  import type { SelfNode } from '../session.context.js'
@@ -13,45 +8,25 @@ export function isShellConfig(config: ShellConfig | Shell): config is ShellConfi
13
8
 
14
9
  let prebuiltCounter = 0
15
10
  const prebuiltIds = new WeakMap<object, string>()
16
- /** Stable id per prebuilt-Shell object reference (a new object reboots). */
11
+
17
12
  function prebuiltId(shell: object): string {
18
13
  let id = prebuiltIds.get(shell)
19
14
  if (id === undefined) {
20
- prebuiltCounter += 1
21
- id = `prebuilt#${prebuiltCounter}`
15
+ id = `prebuilt#${++prebuiltCounter}`
22
16
  prebuiltIds.set(shell, id)
23
17
  }
24
18
  return id
25
19
  }
26
20
 
27
- /**
28
- * Identity of a config — primitive fields only, so re-passing an inline `fetch`,
29
- * `adapter`, `getCredential`, or `parentWindow` each render does not reboot;
30
- * changing the kernel/credential does. A principal switch driven by a NEW
31
- * `getCredential` closure (rather than a new `credential` string) is therefore
32
- * NOT observed here — remount the provider (React `key`) to rebind it.
33
- */
21
+ /** Stable ownership key. Capability objects intentionally use reference identity. */
34
22
  export function configIdentity(config: ShellConfig | Shell): string {
35
23
  if (!isShellConfig(config)) return prebuiltId(config)
36
- if (config.mode === 'standalone') {
37
- return [
38
- 'standalone',
39
- config.kernelUrl,
40
- config.credential ?? '',
41
- config.mintIdentity ?? '',
42
- config.windowId ?? '',
43
- ].join('|')
44
- }
45
- return `sandboxed|${config.initTimeoutMs ?? ''}`
24
+ if (config.mode === 'sandboxed') return prebuiltId(config)
25
+ if (config.client !== undefined) return `standalone-client|${prebuiltId(config.client)}`
26
+ return `standalone-session|${prebuiltId(config.session)}`
46
27
  }
47
28
 
48
- /** Resolve whoami when the bound kernel exposes an auth surface; else null. */
49
29
  export async function resolveSelf(shell: Shell): Promise<SelfNode | null> {
50
- const kernel = shell.kernel as unknown as {
51
- auth?: { whoami?: () => Promise<SelfNode> }
52
- }
53
- const auth = kernel.auth
54
- const whoami = auth?.whoami
55
- if (typeof whoami !== 'function') return null
56
- return whoami.call(auth)
30
+ const auth = shell.kernel.auth
31
+ return typeof auth.whoami === 'function' ? auth.whoami() : null
57
32
  }
@@ -6,3 +6,4 @@ export { useKernelClient } from './kernel-client.hook.js'
6
6
  export { useKernelInstance } from './kernel-instance.hook.js'
7
7
  export type { KernelInstance } from './kernel-instance.hook.js'
8
8
  export { useConnection } from './connection/connection.hook.js'
9
+ export type { ConnectionStatus, ConnectionValue } from './connection/connection.store.js'
@@ -2,21 +2,12 @@ import { useMemo } from 'react'
2
2
 
3
3
  import { useKernelClient } from './kernel-client.hook.js'
4
4
 
5
+ /** Public, non-secret identity of the connected source Kernel. */
5
6
  export interface KernelInstance {
6
- /** Kernel envelope endpoint this shell is bound to. */
7
7
  readonly url: string
8
- /** Current kernel credential. Never cache; read per request. */
9
- token(): string
10
8
  }
11
9
 
12
- /** Low-level access for direct kernel-envelope calls and token exchange code. */
13
10
  export function useKernelInstance(): KernelInstance {
14
11
  const kernel = useKernelClient()
15
- return useMemo<KernelInstance>(
16
- () => ({
17
- url: kernel.url,
18
- token: () => kernel.authToken(),
19
- }),
20
- [kernel],
21
- )
12
+ return useMemo(() => Object.freeze({ url: kernel.url }), [kernel])
22
13
  }