@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,190 @@
1
+ import type { Key } from '@astrale-os/sdk/schema'
2
+ import type { ReactElement, ReactNode } from 'react'
3
+
4
+ import { Path } from '@astrale-os/sdk/graph/path'
5
+ import { Key as admitKey } from '@astrale-os/sdk/schema'
6
+ import { createElement } from 'react'
7
+
8
+ import type { NavAdapter } from './adapters/adapter.js'
9
+ import type { NavSnapshot, SerializedLocation } from './history.js'
10
+
11
+ import { NavScope } from './scope.context.js'
12
+ import { useNavCtx } from './scope.context.js'
13
+ import { useHistoryVersion } from './scope.context.js'
14
+
15
+ export interface NavigationParam<Value> {
16
+ encode(value: Value): string
17
+ decode(value: string | undefined): Value
18
+ }
19
+
20
+ export function pathParam(_name: string): NavigationParam<Path> {
21
+ return Object.freeze({
22
+ encode: (value: Path) => Path.from(value).raw,
23
+ decode(value: string | undefined) {
24
+ if (value === undefined) throw new TypeError('Navigation target is missing.')
25
+ return Path.parse(value)
26
+ },
27
+ })
28
+ }
29
+
30
+ export function enumParam<const Values extends readonly [string, ...string[]]>(
31
+ values: Values,
32
+ fallback: Values[number],
33
+ ): NavigationParam<Values[number]> {
34
+ if (!values.includes(fallback)) throw new TypeError('Navigation enum fallback is not admitted.')
35
+ return Object.freeze({
36
+ encode(value: Values[number]) {
37
+ if (!values.includes(value)) throw new TypeError(`Invalid navigation enum value '${value}'.`)
38
+ return value
39
+ },
40
+ decode(value: string | undefined) {
41
+ if (value === undefined) return fallback
42
+ if (!values.includes(value)) throw new TypeError(`Invalid navigation enum value '${value}'.`)
43
+ return value as Values[number]
44
+ },
45
+ })
46
+ }
47
+
48
+ export type NavigationParams = Readonly<Record<string, NavigationParam<unknown>>>
49
+
50
+ export interface LocationDefinition<Target, Params extends NavigationParams> {
51
+ readonly view: Key.View
52
+ readonly target: NavigationParam<Target>
53
+ readonly params: Params
54
+ }
55
+
56
+ export function location<
57
+ Target,
58
+ const Params extends NavigationParams = Readonly<Record<never, never>>,
59
+ >(input: {
60
+ readonly view: Key.View | { readonly key: Key.View }
61
+ readonly target: NavigationParam<Target>
62
+ readonly params?: Params
63
+ }): LocationDefinition<Target, Params> {
64
+ return Object.freeze({
65
+ view: viewKey(typeof input.view === 'string' ? input.view : input.view.key),
66
+ target: input.target,
67
+ params: Object.freeze({ ...input.params }) as Params,
68
+ })
69
+ }
70
+
71
+ type ValueOf<Codec> = Codec extends NavigationParam<infer Value> ? Value : never
72
+ type RouteTarget<
73
+ Routes extends Readonly<Record<string, LocationDefinition<unknown, NavigationParams>>>,
74
+ > = {
75
+ [Name in keyof Routes]: Readonly<{
76
+ name: Name
77
+ target: ValueOf<Routes[Name]['target']>
78
+ params: Readonly<{
79
+ [Param in keyof Routes[Name]['params']]: ValueOf<Routes[Name]['params'][Param]>
80
+ }>
81
+ }>
82
+ }[keyof Routes]
83
+
84
+ export type NavigationRoutes = Readonly<
85
+ Record<string, LocationDefinition<unknown, NavigationParams>>
86
+ >
87
+
88
+ export interface NavigationDefinition<Routes extends NavigationRoutes = NavigationRoutes> {
89
+ readonly routes: Routes
90
+ encode(target: RouteTarget<Routes>): SerializedLocation
91
+ decode(location: SerializedLocation): RouteTarget<Routes> | null
92
+ admit(location: SerializedLocation): SerializedLocation | null
93
+ }
94
+
95
+ export function defineNavigation<const Routes extends NavigationRoutes>(
96
+ routes: Routes,
97
+ ): NavigationDefinition<Routes> {
98
+ const names = Object.keys(routes).sort() as (keyof Routes)[]
99
+ const byView = new Map(names.map((name) => [routes[name]!.view, name] as const))
100
+ if (byView.size !== names.length) throw new TypeError('Navigation Views must be unique.')
101
+ const definition: NavigationDefinition<Routes> = {
102
+ routes: Object.freeze({ ...routes }),
103
+ encode(target) {
104
+ const route = routes[target.name]
105
+ if (route === undefined)
106
+ throw new TypeError(`Unknown navigation location '${String(target.name)}'.`)
107
+ const params = Object.fromEntries(
108
+ Object.entries(route.params).map(([name, codec]) => [
109
+ name,
110
+ codec.encode((target.params as Record<string, unknown>)[name]),
111
+ ]),
112
+ )
113
+ return Object.freeze({
114
+ node: Path.parse(route.target.encode(target.target)).raw,
115
+ view: route.view,
116
+ ...(Object.keys(params).length === 0 ? {} : { params: Object.freeze(params) }),
117
+ })
118
+ },
119
+ decode(serialized) {
120
+ if (serialized.view === undefined) return null
121
+ const name = byView.get(serialized.view)
122
+ if (name === undefined) return null
123
+ const route = routes[name]!
124
+ try {
125
+ const params = Object.fromEntries(
126
+ Object.entries(route.params).map(([param, codec]) => [
127
+ param,
128
+ codec.decode(serialized.params?.[param]),
129
+ ]),
130
+ )
131
+ if (Object.keys(serialized.params ?? {}).some((param) => !(param in route.params)))
132
+ return null
133
+ return Object.freeze({
134
+ name,
135
+ target: route.target.decode(serialized.node),
136
+ params: Object.freeze(params),
137
+ }) as RouteTarget<Routes>
138
+ } catch {
139
+ return null
140
+ }
141
+ },
142
+ admit(serialized) {
143
+ const decoded = definition.decode(serialized)
144
+ return decoded === null ? null : definition.encode(decoded)
145
+ },
146
+ }
147
+ return Object.freeze(definition)
148
+ }
149
+
150
+ export function NavigationScope<Routes extends NavigationRoutes>(props: {
151
+ readonly navigation: NavigationDefinition<Routes>
152
+ readonly adapter?: NavAdapter
153
+ readonly restore?: NavSnapshot
154
+ readonly initial?: RouteTarget<Routes>
155
+ readonly children: ReactNode
156
+ }): ReactElement {
157
+ return createElement(NavScope, {
158
+ ...(props.adapter === undefined ? {} : { adapter: props.adapter }),
159
+ ...(props.restore === undefined ? {} : { restore: props.restore }),
160
+ ...(props.initial === undefined ? {} : { initial: props.navigation.encode(props.initial) }),
161
+ admit: props.navigation.admit,
162
+ children: props.children,
163
+ })
164
+ }
165
+
166
+ export function useNavigation<Routes extends NavigationRoutes>(
167
+ navigation: NavigationDefinition<Routes>,
168
+ ) {
169
+ const context = useNavCtx()
170
+ useHistoryVersion(context.history)
171
+ const current = context.history.current
172
+ return Object.freeze({
173
+ location: current === null ? null : navigation.decode(current),
174
+ push: (target: RouteTarget<Routes>) => context.navigate(navigation.encode(target)),
175
+ replace: (target: RouteTarget<Routes>) =>
176
+ context.navigate(navigation.encode(target), { replace: true }),
177
+ back: context.navigate.back,
178
+ forward: context.navigate.forward,
179
+ snapshot: () => context.history.snapshot(),
180
+ })
181
+ }
182
+
183
+ export type NavigationTarget<Definition> =
184
+ Definition extends NavigationDefinition<infer Routes> ? RouteTarget<Routes> : never
185
+
186
+ function viewKey(input: string): Key.View {
187
+ const key = admitKey(input)
188
+ if (!admitKey.ref(key).startsWith('view.')) throw new TypeError(`Invalid View key '${input}'.`)
189
+ return key as Key.View
190
+ }
@@ -4,19 +4,21 @@
4
4
  * (`SerializedLocation`/`NavSnapshot` + equality/clone), and the seed precedence.
5
5
  * Source of truth for back/forward; the adapter is a projection sink around it.
6
6
  */
7
+ import type { NodePath } from '@astrale-os/sdk/graph/path'
8
+ import type { Key } from '@astrale-os/sdk/schema'
7
9
 
8
10
  /** The serializable projection persisted in history / URLs / snapshots. */
9
11
  export interface SerializedLocation {
10
- node: string
11
- view?: string
12
- params?: Record<string, string>
12
+ readonly node: NodePath
13
+ readonly view?: Key.View
14
+ readonly params?: Readonly<Record<string, string>>
13
15
  }
14
16
 
15
17
  /** A versioned history snapshot — round-trips through `NavScope.restore`. */
16
18
  export interface NavSnapshot {
17
- v: 1
18
- entries: SerializedLocation[]
19
- cursor: number
19
+ readonly v: 1
20
+ readonly entries: readonly SerializedLocation[]
21
+ readonly cursor: number
20
22
  }
21
23
 
22
24
  /** Structural equality of two serialized locations (node + view + params). */
@@ -24,7 +26,10 @@ export function sameLocation(a: SerializedLocation, b: SerializedLocation): bool
24
26
  return a.node === b.node && a.view === b.view && sameParams(a.params, b.params)
25
27
  }
26
28
 
27
- function sameParams(a?: Record<string, string>, b?: Record<string, string>): boolean {
29
+ function sameParams(
30
+ a?: Readonly<Record<string, string>>,
31
+ b?: Readonly<Record<string, string>>,
32
+ ): boolean {
28
33
  if (a === b) return true
29
34
  const ak = a ? Object.keys(a) : []
30
35
  const bk = b ? Object.keys(b) : []
@@ -74,7 +79,11 @@ export class NavHistory {
74
79
  }
75
80
 
76
81
  snapshot(): NavSnapshot {
77
- return { v: 1, entries: this.entries.map(cloneLocation), cursor: this.cursor }
82
+ return Object.freeze({
83
+ v: 1 as const,
84
+ entries: Object.freeze(this.entries.map(cloneLocation)),
85
+ cursor: this.cursor,
86
+ })
78
87
  }
79
88
 
80
89
  /** Push (or replace the current entry with) a location. No-op on same location. */
@@ -129,8 +138,8 @@ export class NavHistory {
129
138
  }
130
139
 
131
140
  /** Replace the whole stack (restore revalidation prune). */
132
- reset(entries: SerializedLocation[], cursor: number): void {
133
- this.entries = entries
141
+ reset(entries: readonly SerializedLocation[], cursor: number): void {
142
+ this.entries = [...entries]
134
143
  this.cursor = cursor
135
144
  this.emit()
136
145
  }
@@ -151,7 +160,7 @@ export class NavHistory {
151
160
  */
152
161
  export function seedEntries(
153
162
  fromAdapter: SerializedLocation | null,
154
- restore: { entries: SerializedLocation[]; cursor: number } | undefined,
163
+ restore: { readonly entries: readonly SerializedLocation[]; readonly cursor: number } | undefined,
155
164
  initialSerialized: SerializedLocation | null,
156
165
  ): { entries: SerializedLocation[]; cursor: number } {
157
166
  if (restore !== undefined && restore.entries.length > 0) {
@@ -1,71 +1,21 @@
1
- import type { PathLike } from '@astrale-os/kernel-client/graph'
2
- import type { LiveResult, ShapeInput } from '@astrale-os/kernel-client/store'
3
-
4
- /**
5
- * useAncestors (API §8.2) — the live, inclusive root-first ancestor chain of a
6
- * node (a move re-renders it). Defaults to the nearest scope's location node;
7
- * passing a node/path makes it usable outside a scope. Drops any masked ancestor.
8
- */
9
- import { useMemo } from 'react'
1
+ import type { Node } from '@astrale-os/sdk/graph/node'
2
+ import type { PathLike } from '@astrale-os/sdk/graph/path'
10
3
 
11
4
  import type { ReadState } from '../../graph/memory/read/envelope.js'
12
- import type { Node } from '../location.js'
13
5
 
14
- import { idleReadState } from '../../graph/memory/read/idle.js'
15
- import { useMemory } from '../../graph/memory/scope.js'
16
- import { chainPaths, pathHint, pathOf } from '../location.js'
17
- import { useHistoryVersion, useOptionalNavCtx, useMemoryShape } from '../scope.context.js'
18
- import { serializeTarget } from '../snapshot.js'
6
+ import { unsupportedReadState } from '../../graph/memory/read/envelope.js'
7
+
8
+ const EMPTY_CHAIN: readonly Node[] = Object.freeze([])
19
9
 
20
10
  /**
21
- * The root-first ancestor chain (inclusive of the node itself), live: a move
22
- * re-renders it. `node` defaults to the nearest scope's location node; passing a
23
- * node/path makes it usable outside a scope. The chain drops any ancestor whose
24
- * record is masked/absent.
11
+ * Retained public concern with an explicit failed state: V2 has no implicit
12
+ * slash-tree ancestry. Callers must query a declared Edge Class instead.
25
13
  */
26
14
  export function useAncestors(
27
- node?: Node | PathLike,
15
+ _node?: Node | PathLike,
28
16
  ): ReadState & { readonly chain: readonly Node[] } {
29
- const memory = useMemory()
30
- const ctx = useOptionalNavCtx()
31
- useHistoryVersion(ctx?.history ?? null)
32
-
33
- const baseRef =
34
- node !== undefined ? serializeTarget(node).node : (ctx?.history.current?.node ?? null)
35
- const baseResult = useMemoryShape(memory, baseRef)
36
- const basePath = baseResult?.node ? pathOf(baseResult.node) : pathHint(baseRef ?? '')
37
-
38
- const paths = useMemo(() => (basePath !== '' ? chainPaths(basePath) : EMPTY_PATHS), [basePath])
39
- const shape = useMemo<ShapeInput | null>(
40
- () => (basePath !== '' ? (q) => q.from(...chainPaths(basePath)) : null),
41
- [basePath],
42
- )
43
- const chainResult = useMemoryShape(memory, shape)
44
- const chain = useMemo<readonly Node[]>(() => {
45
- if (chainResult === null) return EMPTY_CHAIN
46
- const byPath = new Map<string, Node>()
47
- for (const n of chainResult.roots) byPath.set(pathOf(n), n)
48
- const out: Node[] = []
49
- for (const p of paths) {
50
- const hit = byPath.get(p)
51
- if (hit !== undefined) out.push(hit)
52
- }
53
- return out
54
- }, [paths, chainResult])
55
-
56
- return { ...readStateOf(chainResult), chain }
57
- }
58
-
59
- const EMPTY_PATHS: readonly string[] = Object.freeze([])
60
- const EMPTY_CHAIN: readonly Node[] = Object.freeze([])
61
-
62
- function readStateOf(result: LiveResult | null): ReadState {
63
- if (result === null) return idleReadState()
64
17
  return {
65
- pending: result.pending,
66
- error: result.error,
67
- live: result.live,
68
- seq: result.seq,
69
- refetch: result.refetch,
18
+ ...unsupportedReadState('navigation.ancestors requires an explicit parent Edge Class'),
19
+ chain: EMPTY_CHAIN,
70
20
  }
71
21
  }
@@ -6,16 +6,14 @@ import { useMemo } from 'react'
6
6
 
7
7
  import type { NavLocation } from '../location.js'
8
8
 
9
- import { buildLocation, useSettled } from '../location.js'
10
- import { useHistoryVersion, useNavCtx, useMemoryShape } from '../scope.context.js'
9
+ import { buildLocation } from '../location.js'
10
+ import { useGraphPoint, useHistoryVersion, useNavCtx } from '../scope.context.js'
11
11
 
12
12
  /** The nearest scope's live location. Throws outside any {@link NavScope}. */
13
13
  export function useLocation(): NavLocation {
14
14
  const ctx = useNavCtx()
15
15
  useHistoryVersion(ctx.history)
16
16
  const entry = ctx.history.current
17
- const shape = entry?.node ?? null
18
- const result = useMemoryShape(ctx.memory, shape)
19
- const settled = useSettled(result, shape)
20
- return useMemo(() => buildLocation(entry, result, settled), [entry, result, settled])
17
+ const result = useGraphPoint(entry?.node ?? null)
18
+ return useMemo(() => buildLocation(entry, result), [entry, result])
21
19
  }
@@ -9,8 +9,8 @@ import type { NavSnapshot } from '../history.js'
9
9
  import type { NavLocation } from '../location.js'
10
10
  import type { Navigate } from '../scope.context.js'
11
11
 
12
- import { buildLocation, useSettled } from '../location.js'
13
- import { useNavCtx, useMemoryShape } from '../scope.context.js'
12
+ import { buildLocation } from '../location.js'
13
+ import { useGraphPoint, useNavCtx } from '../scope.context.js'
14
14
  import { useNavigateHandle } from './navigate.hook.js'
15
15
 
16
16
  /** The nearest scope, for chrome/persistence (API §8.2). */
@@ -25,10 +25,8 @@ export function useNavScope(): NavScopeHandle {
25
25
  const ctx = useNavCtx()
26
26
  const navigate = useNavigateHandle(ctx)
27
27
  const entry = ctx.history.current
28
- const shape = entry?.node ?? null
29
- const result = useMemoryShape(ctx.memory, shape)
30
- const settled = useSettled(result, shape)
31
- const location = useMemo(() => buildLocation(entry, result, settled), [entry, result, settled])
28
+ const result = useGraphPoint(entry?.node ?? null)
29
+ const location = useMemo(() => buildLocation(entry, result), [entry, result])
32
30
  const snapshot = useCallback(() => ctx.history.snapshot(), [ctx.history])
33
31
  return { location, snapshot, navigate }
34
32
  }
@@ -7,8 +7,24 @@ export { useNavigate } from './hooks/navigate.hook.js'
7
7
  export { useAncestors } from './hooks/ancestors.hook.js'
8
8
  export { useNavScope } from './hooks/scope.hook.js'
9
9
  export type { NavScopeHandle } from './hooks/scope.hook.js'
10
- export { Link } from './link.js'
11
- export type { LinkProps } from './link.js'
10
+ export { Link, NodeLink, ViewLink } from './link.js'
11
+ export type { LinkProps, NodeLinkProps, ViewLinkProps } from './link.js'
12
+ export {
13
+ NavigationScope,
14
+ defineNavigation,
15
+ enumParam,
16
+ location,
17
+ pathParam,
18
+ useNavigation,
19
+ } from './codec.js'
20
+ export type {
21
+ LocationDefinition,
22
+ NavigationDefinition,
23
+ NavigationParam,
24
+ NavigationParams,
25
+ NavigationRoutes,
26
+ NavigationTarget,
27
+ } from './codec.js'
12
28
  export { memoryAdapter } from './adapters/memory.adapter.js'
13
29
  export { urlAdapter } from './adapters/url.adapter.js'
14
30
  export type { NavAdapter } from './adapters/adapter.js'
@@ -8,6 +8,7 @@ import type {
8
8
  ReactNode,
9
9
  } from 'react'
10
10
 
11
+ import { Query } from '@astrale-os/sdk/query'
11
12
  /**
12
13
  * Link (API §8.2) — navigates the nearest scope. Renders `<a href>` when the
13
14
  * scope's adapter exposes a URL form (cmd/ctrl/middle-click then falls through
@@ -16,6 +17,7 @@ import type {
16
17
  */
17
18
  import { useCallback, useEffect } from 'react'
18
19
 
20
+ import type { NavigationDefinition, NavigationTarget } from './codec.js'
19
21
  import type { NavTarget } from './snapshot.js'
20
22
 
21
23
  import { useNavCtx } from './scope.context.js'
@@ -48,8 +50,9 @@ export function Link(props: LinkProps): ReactElement {
48
50
  const nodeRef = target.node
49
51
 
50
52
  const preload = useCallback(() => {
51
- void ctx.memory?.preload(nodeRef)
52
- }, [ctx.memory, nodeRef])
53
+ const query = Query.from({ kind: 'node', paths: [nodeRef] }).select({ kind: 'graph' })
54
+ void ctx.store.preload(query, { page: { size: 1 } }).catch(() => {})
55
+ }, [ctx.store, nodeRef])
53
56
 
54
57
  useEffect(() => {
55
58
  if (prefetch === 'render') preload()
@@ -108,3 +111,25 @@ export function Link(props: LinkProps): ReactElement {
108
111
  </a>
109
112
  )
110
113
  }
114
+
115
+ export type ViewLinkProps<Definition extends NavigationDefinition> = Omit<LinkProps, 'to'> & {
116
+ readonly navigation: Definition
117
+ readonly to: NavigationTarget<Definition>
118
+ }
119
+
120
+ /** Codec-admitted link for one declared View location. */
121
+ export function ViewLink<Definition extends NavigationDefinition>(
122
+ props: ViewLinkProps<Definition>,
123
+ ): ReactElement {
124
+ const { navigation, to, ...link } = props
125
+ return <Link {...link} to={navigation.encode(to)} />
126
+ }
127
+
128
+ export type NodeLinkProps = Omit<LinkProps, 'to'> & {
129
+ readonly to: Extract<NavTarget, string | object>
130
+ }
131
+
132
+ /** Canonical node-only link; use ViewLink when a View/params contract exists. */
133
+ export function NodeLink(props: NodeLinkProps): ReactElement {
134
+ return <Link {...props} />
135
+ }
@@ -1,122 +1,55 @@
1
- import type { LiveResult } from '@astrale-os/kernel-client/store'
1
+ import type { Node, NodeId } from '@astrale-os/sdk/graph/node'
2
+ import type { NodePath } from '@astrale-os/sdk/graph/path'
3
+ import type { Key } from '@astrale-os/sdk/schema'
2
4
 
3
- import { rawOf } from '@astrale-os/kernel-client/graph'
4
- /**
5
- * NavLocation projection (API §8.1) — the live, id-anchored location a scope
6
- * points at (live path, missing flag), its `buildLocation` assembly, the
7
- * `useSettled` guard against a one-render false-empty, and the framework-free
8
- * path math (parent/chain) the scope and hooks share.
9
- */
10
- import { useEffect, useRef } from 'react'
5
+ import { Path } from '@astrale-os/sdk/graph/path'
11
6
 
7
+ import type { ReadState } from '../graph/memory/read/envelope.js'
12
8
  import type { SerializedLocation } from './history.js'
13
9
 
14
- /**
15
- * The rich graph `Node`, derived from the memory's read envelope — kernel-client
16
- * does not re-export `Node` at its root yet (API §14 G8), so shell-react reads it
17
- * off `LiveResult` rather than taking a direct kernel-core dependency.
18
- */
19
- export type Node = NonNullable<LiveResult['node']>
20
- export type NodeId = Node['id']
10
+ export interface PointResult extends ReadState {
11
+ readonly node: Node | null
12
+ }
21
13
 
22
- /** The live, id-anchored location a scope currently points at (API §8.1). */
14
+ /** One canonical location and its current caller-visible point projection. */
23
15
  export interface NavLocation {
24
- /** The anchor — survives rename/move. */
25
- readonly id: NodeId
26
- /** LIVE projection of the node's current path (empty until first resolve). */
27
- readonly path: string
28
- /** Live memory node — null while cold, or after the anchor stops resolving. */
16
+ readonly target: NodePath | null
17
+ readonly id: NodeId | null
29
18
  readonly node: Node | null
30
- /** View function ref rendering it, when one is open. */
31
- readonly view?: string
19
+ readonly view?: Key.View
32
20
  readonly params: Readonly<Record<string, string>>
33
- /** The anchor no longer resolves (deleted / masked). */
34
21
  readonly missing: boolean
35
22
  }
36
23
 
37
24
  const EMPTY_PARAMS: Readonly<Record<string, string>> = Object.freeze({})
38
25
 
39
- /** The idle location a scope with no current entry projects (inert / boot). */
40
- export const IDLE_LOCATION: NavLocation = {
41
- id: '' as NodeId,
42
- path: '',
26
+ export const IDLE_LOCATION: NavLocation = Object.freeze({
27
+ target: null,
28
+ id: null,
43
29
  node: null,
44
30
  params: EMPTY_PARAMS,
45
31
  missing: false,
46
- }
32
+ })
47
33
 
48
- /** Project a serialized entry + its live read into the public {@link NavLocation}. */
49
34
  export function buildLocation(
50
35
  entry: SerializedLocation | null,
51
- result: LiveResult | null,
52
- settled: boolean,
36
+ result: PointResult | null,
53
37
  ): NavLocation {
54
38
  if (entry === null) return IDLE_LOCATION
55
39
  const node = result?.node ?? null
56
40
  return {
57
- id: (node?.id ?? idHint(entry.node)) as NodeId,
58
- path: node ? pathOf(node) : pathHint(entry.node),
41
+ target: entry.node,
42
+ id: node?.id ?? idAnchor(entry.node),
59
43
  node,
60
- ...(entry.view !== undefined ? { view: entry.view } : {}),
61
- params: entry.params ?? IDLE_LOCATION.params,
62
- missing: settled && node === null,
44
+ ...(entry.view === undefined ? {} : { view: entry.view }),
45
+ params: entry.params ?? EMPTY_PARAMS,
46
+ missing: result !== null && !result.pending && node === null,
63
47
  }
64
48
  }
65
49
 
66
- /**
67
- * Whether a read has definitively settled — data present, a hard error, or a
68
- * completed empty fetch. Guards `missing` (and the missing policy) against the
69
- * one-render false-empty before a cold shape's first fetch is even in flight.
70
- */
71
- export function useSettled(result: LiveResult | null, key: string | null): boolean {
72
- const sawPending = useRef(new Map<string, boolean>())
73
- useEffect(() => {
74
- if (key !== null && result?.pending === true) sawPending.current.set(key, true)
75
- }, [key, result?.pending])
76
- if (key === null || result === null) return false
77
- if (result.node !== null) return true
78
- if (result.error !== undefined) return true
79
- return !result.pending && sawPending.current.get(key) === true
80
- }
81
-
82
- // ── path math ──────────────────────────────────────────────────────────────
83
-
84
- /** The raw path of a live node (empty when cold). */
85
- export function pathOf(node: Node | null): string {
86
- return node ? rawOf(node.path) : ''
87
- }
88
-
89
- /** Best-effort anchor id from a stored ref (`@<id>` → id, else the raw ref). */
90
- export function idHint(ref: string): string {
91
- return ref.startsWith('@') ? ref.slice(1) : ref
92
- }
93
-
94
- /** Best-effort path from a stored ref (a `/…` ref is already a path). */
95
- export function pathHint(ref: string): string {
96
- return ref.startsWith('/') ? ref : ''
50
+ function idAnchor(target: NodePath): NodeId | null {
51
+ const path = Path.from(target)
52
+ return path.ast.anchor.kind === 'id' && path.ast.steps.length === 0 ? path.ast.anchor.id : null
97
53
  }
98
54
 
99
- /** The parent absolute path, or null at (or above) root. */
100
- export function parentPath(raw: string): string | null {
101
- const norm = raw.replace(/\/+$/, '')
102
- if (norm === '' || norm === '/') return null
103
- const idx = norm.lastIndexOf('/')
104
- return idx <= 0 ? '/' : norm.slice(0, idx)
105
- }
106
-
107
- /**
108
- * The inclusive, root-first path chain for an absolute path — the ancestry the
109
- * kernel tree encodes in the slug path (`/a/b/c` → `/`, `/a`, `/a/b`, `/a/b/c`).
110
- * The node itself is the last element, so breadcrumbs map the chain directly.
111
- */
112
- export function chainPaths(raw: string): string[] {
113
- const norm = raw.replace(/\/+$/, '')
114
- if (norm === '' || norm === '/') return ['/']
115
- const out = ['/']
116
- let acc = ''
117
- for (const seg of norm.split('/').filter(Boolean)) {
118
- acc += `/${seg}`
119
- out.push(acc)
120
- }
121
- return out
122
- }
55
+ export type { Node, NodeId }