@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
@@ -1,98 +1,73 @@
1
- /**
2
- * The read envelope (API §1.1, §1.5) — the shape every read hook's result
3
- * extends (`ReadState` + `Windowed`), the `ReadOptions` accepted by every read,
4
- * and the assembly of the unified `RawRead` projection over a memory shape
5
- * (empty/idle/keep-previous builders). Dependency-light so every context can
6
- * consume it without a cycle.
7
- */
8
- import type { LiveResult } from '@astrale-os/kernel-client/store'
1
+ import type { Node as KernelNode } from '@astrale-os/sdk/graph/node'
9
2
 
10
- // ── Read envelope (§1.1) ─────────────────────────────────────────────────────
11
-
12
- /** Base of every read hook's result — stale-while-revalidate + liveness + seq. */
3
+ /** Common result state for Shell React reads. */
13
4
  export interface ReadState {
14
- /** A (re)validation is in flight; data shown alongside is last-good. */
5
+ /** A request is in flight. Last-good data may still be present. */
15
6
  readonly pending: boolean
16
- /** Last refresh failure; last-good data is retained. Cleared on next success. */
7
+ /** Last request failure. Cleared when the next request begins. */
17
8
  readonly error: unknown
18
- /** A watch subscription is active for this shape (Tier ≥ 1), or Tier-0 only. */
9
+ /** True only when an upstream subscription is active. */
19
10
  readonly live: boolean
20
- /** Journal position this result reflects (FRONTEND spec §6.1). 0 until known. */
11
+ /** Reserved journal position. Direct Graph reads report zero. */
21
12
  readonly seq: number
22
- /** Manual revalidate. Rarely needed; kept for tooling and non-live reads. */
23
13
  refetch(): Promise<void>
24
14
  }
25
15
 
26
- /** Windowed reads add a cursor-fold `more`. */
27
16
  export interface Windowed {
28
17
  readonly more: {
29
18
  readonly has: boolean
30
- load(): Promise<void>
31
19
  readonly pending: boolean
20
+ load(): Promise<void>
32
21
  }
33
22
  }
34
23
 
35
- /** Options accepted by every read hook (§1.5). */
24
+ /** Options shared by point, Query, and neighbor reads. */
36
25
  export interface ReadOptions {
37
- /** default true; false = one-shot snapshot. */
38
- live?: boolean
39
- /** default true; false → pending-without-data on cold. */
40
- suspend?: boolean
41
- /** default false; show previous shape's data while a new one confirms. */
42
- keepPrevious?: boolean
43
- /** Visibility permission on the underlying AST — a kernel permission bitmask. */
44
- with?: number
26
+ readonly signal?: AbortSignal
27
+ readonly timeoutMs?: number
28
+ /** Retain the previous successful value while the next request is pending. */
29
+ readonly keepPrevious?: boolean
45
30
  }
46
31
 
47
- /** A graph node — derived from the memory result so no direct kernel-core import is needed. */
48
- export type Node = NonNullable<LiveResult['node']>
32
+ export type Node = KernelNode
49
33
 
50
- /** Read options plus the windowing lever `useChildren`/`useQuery` add. */
51
- export type CoreReadOptions = ReadOptions & { window?: 'append' | 'paged' }
34
+ export const EMPTY_NODES: readonly Node[] = Object.freeze([])
52
35
 
53
- /**
54
- * The unified read projection: every data arm the memory materializes, over
55
- * the shell-react read envelope (`ReadState` + `Windowed`). Each hook narrows
56
- * to the arms it exposes. The store's `LiveResult` is a structural superset, so
57
- * the warm path returns it directly (stable ref).
58
- */
59
- export interface RawRead extends ReadState, Windowed {
60
- readonly nodes: readonly Node[]
61
- readonly roots: readonly Node[]
62
- readonly first: Node | null
63
- readonly children: readonly Node[]
64
- gathered(alias: string): { readonly nodes: readonly Node[]; readonly first: Node | null }
65
- }
36
+ export const NO_MORE: Windowed['more'] = Object.freeze({
37
+ has: false,
38
+ pending: false,
39
+ load: () => Promise.resolve(),
40
+ })
66
41
 
67
- export const EMPTY: readonly Node[] = []
42
+ /** Stable failure for a retained hook whose required Kernel capability does not exist. */
43
+ export class UnsupportedFeatureError extends Error {
44
+ readonly code = 'SHELL_REACT_UNSUPPORTED' as const
45
+ readonly feature: string
68
46
 
69
- const idleGathered = (): { readonly nodes: readonly Node[]; readonly first: Node | null } => ({
70
- nodes: EMPTY,
71
- first: null,
72
- })
73
- const idleMore: Windowed['more'] = { has: false, pending: false, load: () => Promise.resolve() }
47
+ constructor(feature: string) {
48
+ super(`${feature} is not supported by the current Kernel Graph contract.`)
49
+ this.name = 'UnsupportedFeatureError'
50
+ this.feature = feature
51
+ }
52
+ }
74
53
 
75
- export function emptyRead(pending: boolean): RawRead {
54
+ export function idleReadState(): ReadState {
76
55
  return {
77
- nodes: EMPTY,
78
- roots: EMPTY,
79
- first: null,
80
- children: EMPTY,
81
- gathered: idleGathered,
82
- pending,
56
+ pending: false,
83
57
  error: undefined,
84
58
  live: false,
85
59
  seq: 0,
86
- more: idleMore,
87
60
  refetch: () => Promise.resolve(),
88
61
  }
89
62
  }
90
- /** The result for a null/undefined subject (§1.3) — settled, empty, no I/O. */
91
- export const IDLE_READ = emptyRead(false)
92
- /** Cold shape with `suspend:false` (§1.4) — empty data, pending true. */
93
- export const EMPTY_PENDING = emptyRead(true)
94
63
 
95
- /** Previous shape's data while a new one confirms (§1.5 `keepPrevious`). */
96
- export function withPending(prev: LiveResult): RawRead {
97
- return { ...prev, pending: true }
64
+ export function unsupportedReadState(feature: string): ReadState {
65
+ const error = new UnsupportedFeatureError(feature)
66
+ return {
67
+ pending: false,
68
+ error,
69
+ live: false,
70
+ seq: 0,
71
+ refetch: () => Promise.reject(error),
72
+ }
98
73
  }
@@ -1,24 +1,11 @@
1
- /**
2
- * The idle-input rule (API §1.3). A read hook's `null | undefined` subject is
3
- * inert: `isIdleInput` is the one gate every read branches on, and
4
- * `idleReadState` is the settled, not-live, seq-0 envelope it returns.
5
- */
6
1
  import type { ReadState } from './envelope.js'
7
2
 
8
- /**
9
- * The chaining rule: every read hook accepts `null | undefined` for its subject
10
- * and returns an *idle* result (empty data, no I/O). This predicate is the one
11
- * gate — read hooks branch on it before touching the memory.
12
- */
13
- export function isIdleInput(subject: unknown): subject is null | undefined {
14
- return subject === null || subject === undefined
3
+ import { idleReadState as idle } from './envelope.js'
4
+
5
+ export function isIdleInput(value: unknown): value is null | undefined {
6
+ return value === null || value === undefined
15
7
  }
16
8
 
17
- /**
18
- * The idle `ReadState` a read hook returns for a `null`/`undefined` subject:
19
- * settled, not live, seq 0, `refetch` a resolved no-op.
20
- */
21
9
  export function idleReadState(): ReadState {
22
- return { pending: false, error: undefined, live: false, seq: 0, refetch: idleRefetch }
10
+ return idle()
23
11
  }
24
- const idleRefetch = (): Promise<void> => Promise.resolve()
@@ -0,0 +1,270 @@
1
+ import type { GraphStore, StoreQueryResponse, StoreSnapshot } from '@astrale-os/kernel-client/store'
2
+ import type {
3
+ QueryAstExecutionOptions,
4
+ QueryDefinition,
5
+ QueryValueBinder,
6
+ } from '@astrale-os/sdk/query'
7
+ import type { QueryAST } from '@astrale-os/sdk/query'
8
+ import type { DomainBinding } from '@astrale-os/shell'
9
+
10
+ import { bindQueries } from '@astrale-os/sdk/query'
11
+ import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
12
+
13
+ import type { GraphResource, GraphResourceState } from './resource.js'
14
+
15
+ import { useDynamicDomain } from '../schema/domain.hook.js'
16
+ import { retainRefreshScheduler } from './resource.js'
17
+ import { useGraphStore } from './store.js'
18
+
19
+ export interface QueryResourceOptions {
20
+ readonly keepPrevious?: boolean
21
+ readonly refreshInterval?: number
22
+ readonly refreshWhen?: 'always' | 'visible'
23
+ }
24
+
25
+ interface TouchedShape {
26
+ readonly ast: QueryAST
27
+ readonly options: QueryAstExecutionOptions
28
+ }
29
+
30
+ interface DefinitionSnapshot<Output> {
31
+ readonly state: GraphResourceState
32
+ readonly data?: Output
33
+ readonly error?: unknown
34
+ }
35
+
36
+ class DefinitionObserver<Input, Output, Query extends QueryAST> {
37
+ private readonly listeners = new Set<() => void>()
38
+ private subscriptions: readonly (() => void)[] = Object.freeze([])
39
+ private touched: readonly TouchedShape[] = Object.freeze([])
40
+ private current: DefinitionSnapshot<Output> = Object.freeze({ state: 'loading' })
41
+ private generation = 0
42
+ private pending: Promise<void> | null = null
43
+ private started = false
44
+ private rerunQueued = false
45
+
46
+ constructor(
47
+ private readonly store: GraphStore,
48
+ private readonly binding: DomainBinding,
49
+ private readonly definition: QueryDefinition<Input, Output, Query>,
50
+ private readonly input: Input,
51
+ ) {}
52
+
53
+ subscribe = (listener: () => void): (() => void) => {
54
+ this.listeners.add(listener)
55
+ return () => this.listeners.delete(listener)
56
+ }
57
+
58
+ getSnapshot = (): DefinitionSnapshot<Output> => this.current
59
+
60
+ start(): () => void {
61
+ this.started = true
62
+ void this.run('cached')
63
+ return () => {
64
+ this.started = false
65
+ this.generation += 1
66
+ this.subscriptions.forEach((unsubscribe) => unsubscribe())
67
+ this.subscriptions = Object.freeze([])
68
+ }
69
+ }
70
+
71
+ refresh = async (): Promise<void> => {
72
+ await this.run('refresh')
73
+ }
74
+
75
+ private run(freshness: 'cached' | 'refresh'): Promise<void> {
76
+ if (this.pending !== null && freshness === 'cached') return this.pending
77
+ const generation = ++this.generation
78
+ const touched: TouchedShape[] = []
79
+ this.update({
80
+ state: this.current.data === undefined ? 'loading' : 'refreshing',
81
+ ...(this.current.data === undefined ? {} : { data: this.current.data }),
82
+ })
83
+ const execute = async <Ast extends QueryAST>(
84
+ ast: Ast,
85
+ options: QueryAstExecutionOptions,
86
+ ): Promise<StoreQueryResponse<Ast>> => {
87
+ touched.push(Object.freeze({ ast, options }))
88
+ return this.store.read(ast, { ...options, freshness })
89
+ }
90
+ const bind = this.binding.$.bind as unknown as QueryValueBinder
91
+ const query = bindQueries(execute, { bind })
92
+ const running = query(this.definition, this.input)
93
+ .then((data) => {
94
+ if (!this.started || generation !== this.generation) return
95
+ this.touched = dedupeShapes(touched)
96
+ this.bindShapes()
97
+ this.update({ state: this.storeState(), data })
98
+ })
99
+ .catch((error: unknown) => {
100
+ if (!this.started || generation !== this.generation) return
101
+ this.touched = dedupeShapes(touched)
102
+ this.bindShapes()
103
+ this.update({
104
+ state: 'failed',
105
+ ...(this.current.data === undefined ? {} : { data: this.current.data }),
106
+ error,
107
+ })
108
+ })
109
+ .finally(() => {
110
+ if (this.pending === running) this.pending = null
111
+ })
112
+ this.pending = running
113
+ return running
114
+ }
115
+
116
+ private bindShapes(): void {
117
+ this.subscriptions.forEach((unsubscribe) => unsubscribe())
118
+ this.subscriptions = Object.freeze(
119
+ this.touched.map(({ ast, options }) =>
120
+ this.store.subscribe(ast, this.onStoreChange, options),
121
+ ),
122
+ )
123
+ }
124
+
125
+ private onStoreChange = (): void => {
126
+ const state = this.storeState()
127
+ this.update({
128
+ state,
129
+ ...(this.current.data === undefined ? {} : { data: this.current.data }),
130
+ ...(this.current.error === undefined ? {} : { error: this.current.error }),
131
+ })
132
+ if ((state === 'ready' || state === 'optimistic') && !this.rerunQueued) {
133
+ this.rerunQueued = true
134
+ queueMicrotask(() => {
135
+ this.rerunQueued = false
136
+ if (this.started) void this.run('cached')
137
+ })
138
+ }
139
+ }
140
+
141
+ private storeState(): GraphResourceState {
142
+ const snapshots = this.touched.map(({ ast, options }) => this.store.getSnapshot(ast, options))
143
+ if (snapshots.length === 0) return this.current.state
144
+ return combine(snapshots)
145
+ }
146
+
147
+ private update(next: DefinitionSnapshot<Output>): void {
148
+ if (
149
+ next.state === this.current.state &&
150
+ next.data === this.current.data &&
151
+ next.error === this.current.error
152
+ )
153
+ return
154
+ this.current = Object.freeze(next)
155
+ this.listeners.forEach((listener) => listener())
156
+ }
157
+ }
158
+
159
+ /** Execute one named SDK Query definition exclusively through the current GraphStore. */
160
+ export function useQuery<Input, Output, Query extends QueryAST>(
161
+ definition: QueryDefinition<Input, Output, Query> | null,
162
+ input: Input,
163
+ options: QueryResourceOptions = {},
164
+ ): GraphResource<Output> {
165
+ const store = useGraphStore()
166
+ const binding = useDynamicDomain()
167
+ const inputIdentity = stableIdentity(input)
168
+ const observer = useMemo(
169
+ () => (definition === null ? null : new DefinitionObserver(store, binding, definition, input)),
170
+ [store, binding, definition, inputIdentity],
171
+ )
172
+ const idle = useMemo<DefinitionSnapshot<Output>>(() => Object.freeze({ state: 'idle' }), [])
173
+ const subscribe = useCallback(
174
+ (listener: () => void) => observer?.subscribe(listener) ?? (() => {}),
175
+ [observer],
176
+ )
177
+ const snapshot = useCallback(() => observer?.getSnapshot() ?? idle, [observer, idle])
178
+ const current = useSyncExternalStore(subscribe, snapshot, snapshot)
179
+ useEffect(() => observer?.start(), [observer])
180
+
181
+ const identity = definition === null ? 'idle' : `${definition.id}\u0000${inputIdentity}`
182
+ useEffect(() => {
183
+ if (observer === null || options.refreshInterval === undefined) return
184
+ return retainRefreshScheduler(
185
+ store,
186
+ identity,
187
+ observer.refresh,
188
+ options.refreshInterval,
189
+ options.refreshWhen ?? 'visible',
190
+ )
191
+ }, [store, observer, identity, options.refreshInterval, options.refreshWhen])
192
+
193
+ const previousRef = useRef<{ readonly identity: string; readonly data: Output } | undefined>(
194
+ undefined,
195
+ )
196
+ const previous =
197
+ options.keepPrevious && previousRef.current?.identity !== identity
198
+ ? previousRef.current
199
+ : undefined
200
+ useEffect(() => {
201
+ if (current.data !== undefined) {
202
+ previousRef.current = Object.freeze({ identity, data: current.data })
203
+ }
204
+ }, [identity, current.data])
205
+
206
+ return useMemo(
207
+ () =>
208
+ Object.freeze({
209
+ state: current.state,
210
+ data: current.data,
211
+ error: current.error,
212
+ ...(previous === undefined ? {} : { previous }),
213
+ transitioning: current.state === 'loading' || current.state === 'refreshing',
214
+ live: false as const,
215
+ sequence: 0 as const,
216
+ refresh: observer?.refresh ?? (() => Promise.resolve()),
217
+ more: Object.freeze({ has: false, pending: false, load: () => Promise.resolve() }),
218
+ }),
219
+ [current, previous, observer],
220
+ )
221
+ }
222
+
223
+ function dedupeShapes(input: readonly TouchedShape[]): readonly TouchedShape[] {
224
+ const seen = new Set<string>()
225
+ return Object.freeze(
226
+ input.filter(({ ast, options }) => {
227
+ const key = `${JSON.stringify(ast)}\u0000${JSON.stringify(options.page)}`
228
+ if (seen.has(key)) return false
229
+ seen.add(key)
230
+ return true
231
+ }),
232
+ )
233
+ }
234
+
235
+ function combine(snapshots: readonly StoreSnapshot[]): GraphResourceState {
236
+ if (snapshots.some((snapshot) => snapshot.state === 'cold')) return 'loading'
237
+ for (const state of ['failed', 'loading', 'refreshing', 'optimistic', 'stale'] as const) {
238
+ if (snapshots.some((snapshot) => snapshot.state === state)) return state
239
+ }
240
+ return 'ready'
241
+ }
242
+
243
+ function stableIdentity(value: unknown): string {
244
+ const seen = new WeakSet<object>()
245
+ const encode = (input: unknown): unknown => {
246
+ if (input === null) return ['null']
247
+ if (typeof input !== 'object') {
248
+ if (typeof input === 'function' || typeof input === 'symbol' || typeof input === 'bigint') {
249
+ throw new TypeError('Query input must be portable and serializable.')
250
+ }
251
+ if (input === undefined) return ['undefined']
252
+ if (typeof input === 'number' && !Number.isFinite(input)) {
253
+ throw new TypeError('Query input numbers must be finite.')
254
+ }
255
+ return [typeof input, input]
256
+ }
257
+ if (seen.has(input)) throw new TypeError('Query input cannot contain cycles.')
258
+ seen.add(input)
259
+ if (Array.isArray(input)) return ['array', input.map(encode)]
260
+ return [
261
+ 'object',
262
+ Object.fromEntries(
263
+ Object.keys(input as Record<string, unknown>)
264
+ .sort()
265
+ .map((key) => [key, encode((input as Record<string, unknown>)[key])]),
266
+ ),
267
+ ]
268
+ }
269
+ return JSON.stringify(encode(value))
270
+ }