@astrale-os/shell-react 0.2.9 → 0.2.10-beta.2
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.
- package/dist/application/apps/app-host.d.ts +4 -15
- package/dist/application/apps/app-host.d.ts.map +1 -1
- package/dist/application/apps/app-host.js +1 -1
- package/dist/application/apps/apps.hook.d.ts +7 -4
- package/dist/application/apps/apps.hook.d.ts.map +1 -1
- package/dist/application/apps/apps.hook.js +10 -2
- package/dist/application/apps/domains.hook.d.ts +1 -1
- package/dist/application/apps/domains.hook.d.ts.map +1 -1
- package/dist/application/apps/domains.hook.js +1 -1
- package/dist/application/apps/open-app.hook.d.ts +2 -3
- package/dist/application/apps/open-app.hook.d.ts.map +1 -1
- package/dist/application/index.d.ts +2 -4
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +2 -3
- package/dist/application/spaces/space.hook.d.ts +2 -2
- package/dist/application/spaces/space.hook.d.ts.map +1 -1
- package/dist/application/spaces/space.hook.js +2 -2
- package/dist/application/spaces/spaces.hook.d.ts +2 -3
- package/dist/application/spaces/spaces.hook.d.ts.map +1 -1
- package/dist/application/spaces/spaces.hook.js +10 -3
- package/dist/auth/act-as.context.d.ts +15 -32
- package/dist/auth/act-as.context.d.ts.map +1 -1
- package/dist/auth/act-as.context.js +22 -66
- package/dist/auth/auth.hook.d.ts +1 -0
- package/dist/auth/auth.hook.d.ts.map +1 -1
- package/dist/auth/auth.hook.js +2 -22
- package/dist/auth/can.hook.d.ts +10 -38
- package/dist/auth/can.hook.d.ts.map +1 -1
- package/dist/auth/can.hook.js +35 -110
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/self.hook.d.ts +2 -2
- package/dist/auth/self.hook.d.ts.map +1 -1
- package/dist/auth/self.hook.js +0 -7
- package/dist/auth/session-kernel.d.ts +2 -3
- package/dist/auth/session-kernel.d.ts.map +1 -1
- package/dist/auth/session-kernel.js +4 -13
- package/dist/components/boundaries.d.ts +47 -0
- package/dist/components/boundaries.d.ts.map +1 -0
- package/dist/components/boundaries.js +55 -0
- package/dist/components/can.d.ts +11 -0
- package/dist/components/can.d.ts.map +1 -0
- package/dist/components/can.js +5 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +4 -0
- package/dist/components/mount-domain-frontend.d.ts +22 -0
- package/dist/components/mount-domain-frontend.d.ts.map +1 -0
- package/dist/components/mount-domain-frontend.js +85 -0
- package/dist/components/view-app.d.ts +16 -0
- package/dist/components/view-app.d.ts.map +1 -0
- package/dist/components/view-app.js +16 -0
- package/dist/graph/call/call.hook.d.ts +8 -18
- package/dist/graph/call/call.hook.d.ts.map +1 -1
- package/dist/graph/call/call.hook.js +7 -13
- package/dist/graph/call/stream.hook.d.ts +12 -20
- package/dist/graph/call/stream.hook.d.ts.map +1 -1
- package/dist/graph/call/stream.hook.js +55 -111
- package/dist/graph/graph.context.d.ts +4 -3
- package/dist/graph/graph.context.d.ts.map +1 -1
- package/dist/graph/graph.context.js +2 -8
- package/dist/graph/guards.error.d.ts +7 -8
- package/dist/graph/guards.error.d.ts.map +1 -1
- package/dist/graph/guards.error.js +11 -7
- package/dist/graph/index.d.ts +8 -16
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/index.js +3 -7
- package/dist/graph/memory/read/envelope.d.ts +22 -54
- package/dist/graph/memory/read/envelope.d.ts.map +1 -1
- package/dist/graph/memory/read/envelope.js +26 -20
- package/dist/graph/memory/read/idle.d.ts +1 -15
- package/dist/graph/memory/read/idle.d.ts.map +1 -1
- package/dist/graph/memory/read/idle.js +4 -13
- package/dist/graph/query-definition.hook.d.ts +11 -0
- package/dist/graph/query-definition.hook.d.ts.map +1 -0
- package/dist/graph/query-definition.hook.js +205 -0
- package/dist/graph/resource.d.ts +34 -0
- package/dist/graph/resource.d.ts.map +1 -0
- package/dist/graph/resource.js +244 -0
- package/dist/graph/store.d.ts +25 -0
- package/dist/graph/store.d.ts.map +1 -0
- package/dist/graph/store.js +69 -0
- package/dist/host/actions.hook.d.ts +34 -0
- package/dist/host/actions.hook.d.ts.map +1 -0
- package/dist/host/actions.hook.js +125 -0
- package/dist/host/components.d.ts +15 -0
- package/dist/host/components.d.ts.map +1 -0
- package/dist/host/components.js +14 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +3 -0
- package/dist/index.d.ts +26 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -31
- package/dist/intents/envelope.d.ts +7 -19
- package/dist/intents/envelope.d.ts.map +1 -1
- package/dist/intents/envelope.js +2 -3
- package/dist/intents/send-intent.hook.d.ts +5 -7
- package/dist/intents/send-intent.hook.d.ts.map +1 -1
- package/dist/intents/send-intent.hook.js +17 -75
- package/dist/navigation/adapters/adapter.d.ts +5 -0
- package/dist/navigation/adapters/adapter.d.ts.map +1 -1
- package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -1
- package/dist/navigation/adapters/memory.adapter.js +4 -0
- package/dist/navigation/adapters/url.adapter.d.ts.map +1 -1
- package/dist/navigation/adapters/url.adapter.js +57 -10
- package/dist/navigation/codec.d.ts +60 -0
- package/dist/navigation/codec.d.ts.map +1 -0
- package/dist/navigation/codec.js +121 -0
- package/dist/navigation/history.d.ts +11 -9
- package/dist/navigation/history.d.ts.map +1 -1
- package/dist/navigation/history.js +6 -8
- package/dist/navigation/hooks/ancestors.hook.d.ts +5 -7
- package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -1
- package/dist/navigation/hooks/ancestors.hook.js +7 -51
- package/dist/navigation/hooks/location.hook.d.ts.map +1 -1
- package/dist/navigation/hooks/location.hook.js +4 -6
- package/dist/navigation/hooks/scope.hook.d.ts.map +1 -1
- package/dist/navigation/hooks/scope.hook.js +4 -6
- package/dist/navigation/index.d.ts +4 -2
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +2 -1
- package/dist/navigation/link.d.ts +12 -0
- package/dist/navigation/link.d.ts.map +1 -1
- package/dist/navigation/link.js +13 -2
- package/dist/navigation/location.d.ts +13 -40
- package/dist/navigation/location.d.ts.map +1 -1
- package/dist/navigation/location.js +14 -76
- package/dist/navigation/scope.context.d.ts +17 -26
- package/dist/navigation/scope.context.d.ts.map +1 -1
- package/dist/navigation/scope.context.js +140 -108
- package/dist/navigation/snapshot.d.ts +14 -19
- package/dist/navigation/snapshot.d.ts.map +1 -1
- package/dist/navigation/snapshot.js +48 -44
- package/dist/schema/action.hook.d.ts +44 -0
- package/dist/schema/action.hook.d.ts.map +1 -0
- package/dist/schema/action.hook.js +72 -0
- package/dist/schema/domain.hook.d.ts +12 -7
- package/dist/schema/domain.hook.d.ts.map +1 -1
- package/dist/schema/domain.hook.js +101 -4
- package/dist/schema/index.d.ts +9 -8
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +5 -7
- package/dist/schema/kernel.hook.d.ts +4 -9
- package/dist/schema/kernel.hook.d.ts.map +1 -1
- package/dist/schema/kernel.hook.js +4 -6
- package/dist/schema/mutation.hook.d.ts +9 -0
- package/dist/schema/mutation.hook.d.ts.map +1 -0
- package/dist/schema/mutation.hook.js +41 -0
- package/dist/schema/object.hook.d.ts +16 -0
- package/dist/schema/object.hook.d.ts.map +1 -0
- package/dist/schema/object.hook.js +46 -0
- package/dist/schema/relation.hook.d.ts +46 -0
- package/dist/schema/relation.hook.d.ts.map +1 -0
- package/dist/schema/relation.hook.js +136 -0
- package/dist/selection/live.d.ts +9 -17
- package/dist/selection/live.d.ts.map +1 -1
- package/dist/selection/live.js +65 -112
- package/dist/selection/scope.context.js +5 -4
- package/dist/session/boot/boot.d.ts +1 -13
- package/dist/session/boot/boot.d.ts.map +1 -1
- package/dist/session/boot/boot.js +9 -27
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/kernel-instance.hook.d.ts +1 -4
- package/dist/session/kernel-instance.hook.d.ts.map +1 -1
- package/dist/session/kernel-instance.hook.js +1 -5
- package/dist/session/provider.d.ts +9 -23
- package/dist/session/provider.d.ts.map +1 -1
- package/dist/session/provider.js +29 -75
- package/dist/session/session.context.d.ts +5 -0
- package/dist/session/session.context.d.ts.map +1 -1
- package/dist/window/adopt.d.ts +0 -1
- package/dist/window/adopt.d.ts.map +1 -1
- package/dist/window/hooks/target-node.hook.d.ts +10 -6
- package/dist/window/hooks/target-node.hook.d.ts.map +1 -1
- package/dist/window/hooks/target-node.hook.js +14 -72
- package/dist/window/hooks/views-for.hook.d.ts +4 -3
- package/dist/window/hooks/views-for.hook.d.ts.map +1 -1
- package/dist/window/hooks/views-for.hook.js +6 -22
- package/dist/window/hooks/windows.hook.d.ts.map +1 -1
- package/dist/window/hooks/windows.hook.js +3 -4
- package/dist/window/host.d.ts +9 -11
- package/dist/window/host.d.ts.map +1 -1
- package/dist/window/host.js +26 -62
- package/dist/window/hub.d.ts +2 -0
- package/dist/window/hub.d.ts.map +1 -1
- package/dist/window/hub.js +33 -0
- package/dist/window/index.d.ts +2 -2
- package/dist/window/index.d.ts.map +1 -1
- package/dist/window/index.js +2 -2
- package/dist/window/lifecycle/mount.d.ts +5 -44
- package/dist/window/lifecycle/mount.d.ts.map +1 -1
- package/dist/window/lifecycle/mount.js +14 -120
- package/dist/window/lifecycle/states.d.ts +0 -5
- package/dist/window/lifecycle/states.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/application/apps/app-host.tsx +11 -15
- package/src/application/apps/apps.hook.ts +13 -6
- package/src/application/apps/domains.hook.ts +1 -1
- package/src/application/apps/open-app.hook.ts +3 -4
- package/src/application/index.ts +4 -6
- package/src/application/spaces/space.hook.ts +5 -6
- package/src/application/spaces/spaces.hook.ts +14 -9
- package/src/auth/act-as.context.tsx +37 -91
- package/src/auth/auth.hook.ts +2 -30
- package/src/auth/can.hook.ts +44 -135
- package/src/auth/index.ts +1 -0
- package/src/auth/self.hook.ts +2 -9
- package/src/auth/session-kernel.ts +6 -16
- package/src/components/boundaries.tsx +113 -0
- package/src/components/can.tsx +16 -0
- package/src/components/index.ts +14 -0
- package/src/components/mount-domain-frontend.tsx +158 -0
- package/src/components/view-app.tsx +49 -0
- package/src/graph/call/call.hook.ts +22 -27
- package/src/graph/call/stream.hook.ts +66 -143
- package/src/graph/graph.context.ts +6 -11
- package/src/graph/guards.error.ts +17 -55
- package/src/graph/index.ts +23 -17
- package/src/graph/memory/read/envelope.ts +40 -65
- package/src/graph/memory/read/idle.ts +5 -18
- package/src/graph/query-definition.hook.ts +270 -0
- package/src/graph/resource.ts +358 -0
- package/src/graph/store.ts +97 -0
- package/src/host/actions.hook.ts +165 -0
- package/src/host/components.tsx +29 -0
- package/src/host/index.ts +17 -0
- package/src/index.ts +136 -124
- package/src/intents/envelope.ts +7 -23
- package/src/intents/send-intent.hook.ts +24 -79
- package/src/navigation/adapters/adapter.ts +3 -0
- package/src/navigation/adapters/memory.adapter.ts +4 -0
- package/src/navigation/adapters/url.adapter.ts +48 -8
- package/src/navigation/codec.ts +190 -0
- package/src/navigation/history.ts +20 -11
- package/src/navigation/hooks/ancestors.hook.ts +10 -60
- package/src/navigation/hooks/location.hook.ts +4 -6
- package/src/navigation/hooks/scope.hook.ts +4 -6
- package/src/navigation/index.ts +18 -2
- package/src/navigation/link.tsx +27 -2
- package/src/navigation/location.ts +26 -93
- package/src/navigation/scope.context.tsx +151 -136
- package/src/navigation/snapshot.ts +69 -50
- package/src/schema/action.hook.ts +146 -0
- package/src/schema/domain.hook.ts +127 -11
- package/src/schema/index.ts +9 -30
- package/src/schema/kernel.hook.ts +6 -13
- package/src/schema/mutation.hook.ts +63 -0
- package/src/schema/object.hook.ts +85 -0
- package/src/schema/relation.hook.ts +253 -0
- package/src/selection/live.ts +75 -125
- package/src/selection/scope.context.tsx +4 -4
- package/src/session/boot/boot.ts +8 -33
- package/src/session/index.ts +1 -0
- package/src/session/kernel-instance.hook.ts +2 -11
- package/src/session/provider.tsx +34 -103
- package/src/session/session.context.ts +6 -0
- package/src/window/adopt.ts +0 -1
- package/src/window/hooks/target-node.hook.ts +20 -85
- package/src/window/hooks/views-for.hook.ts +12 -25
- package/src/window/hooks/windows.hook.ts +3 -4
- package/src/window/host.tsx +35 -77
- package/src/window/hub.ts +33 -0
- package/src/window/index.ts +3 -3
- package/src/window/lifecycle/mount.ts +15 -160
- package/src/window/lifecycle/states.ts +1 -5
- package/dist/application/roles.hook.d.ts +0 -7
- package/dist/application/roles.hook.d.ts.map +0 -1
- package/dist/application/roles.hook.js +0 -23
- package/dist/graph/memory/base.d.ts +0 -21
- package/dist/graph/memory/base.d.ts.map +0 -1
- package/dist/graph/memory/base.js +0 -33
- package/dist/graph/memory/read/read.d.ts +0 -9
- package/dist/graph/memory/read/read.d.ts.map +0 -1
- package/dist/graph/memory/read/read.js +0 -87
- package/dist/graph/memory/scope.d.ts +0 -7
- package/dist/graph/memory/scope.d.ts.map +0 -1
- package/dist/graph/memory/scope.js +0 -25
- package/dist/graph/mutate/move.hook.d.ts +0 -7
- package/dist/graph/mutate/move.hook.d.ts.map +0 -1
- package/dist/graph/mutate/move.hook.js +0 -49
- package/dist/graph/mutate/mutate.hook.d.ts +0 -17
- package/dist/graph/mutate/mutate.hook.d.ts.map +0 -1
- package/dist/graph/mutate/mutate.hook.js +0 -45
- package/dist/graph/query/children.hook.d.ts +0 -29
- package/dist/graph/query/children.hook.d.ts.map +0 -1
- package/dist/graph/query/children.hook.js +0 -64
- package/dist/graph/query/graph.hook.d.ts +0 -9
- package/dist/graph/query/graph.hook.d.ts.map +0 -1
- package/dist/graph/query/graph.hook.js +0 -5
- package/dist/graph/query/neighbors.hook.d.ts +0 -7
- package/dist/graph/query/neighbors.hook.d.ts.map +0 -1
- package/dist/graph/query/neighbors.hook.js +0 -104
- package/dist/graph/query/node.hook.d.ts +0 -14
- package/dist/graph/query/node.hook.d.ts.map +0 -1
- package/dist/graph/query/node.hook.js +0 -43
- package/dist/graph/query/query.hook.d.ts +0 -14
- package/dist/graph/query/query.hook.d.ts.map +0 -1
- package/dist/graph/query/query.hook.js +0 -23
- package/dist/schema/bind-read.d.ts +0 -37
- package/dist/schema/bind-read.d.ts.map +0 -1
- package/dist/schema/bind-read.js +0 -205
- package/dist/schema/binding.d.ts +0 -21
- package/dist/schema/binding.d.ts.map +0 -1
- package/dist/schema/binding.js +0 -99
- package/dist/schema/function.hook.d.ts +0 -9
- package/dist/schema/function.hook.d.ts.map +0 -1
- package/dist/schema/function.hook.js +0 -40
- package/dist/schema/method.hook.d.ts +0 -14
- package/dist/schema/method.hook.d.ts.map +0 -1
- package/dist/schema/method.hook.js +0 -49
- package/dist/schema/tag.d.ts +0 -11
- package/dist/schema/tag.d.ts.map +0 -1
- package/dist/schema/tag.js +0 -11
- package/dist/schema/types.d.ts +0 -73
- package/dist/schema/types.d.ts.map +0 -1
- package/dist/schema/types.js +0 -1
- package/dist/schema/write.d.ts +0 -16
- package/dist/schema/write.d.ts.map +0 -1
- package/dist/schema/write.js +0 -56
- package/src/application/roles.hook.ts +0 -41
- package/src/graph/memory/base.ts +0 -50
- package/src/graph/memory/read/read.ts +0 -103
- package/src/graph/memory/scope.ts +0 -29
- package/src/graph/mutate/move.hook.ts +0 -70
- package/src/graph/mutate/mutate.hook.ts +0 -69
- package/src/graph/query/children.hook.ts +0 -136
- package/src/graph/query/graph.hook.ts +0 -13
- package/src/graph/query/neighbors.hook.ts +0 -159
- package/src/graph/query/node.hook.ts +0 -83
- package/src/graph/query/query.hook.ts +0 -42
- package/src/schema/bind-read.ts +0 -319
- package/src/schema/binding.ts +0 -146
- package/src/schema/function.hook.ts +0 -68
- package/src/schema/method.hook.ts +0 -103
- package/src/schema/tag.ts +0 -19
- package/src/schema/types.ts +0 -150
- package/src/schema/write.ts +0 -96
package/src/intents/envelope.ts
CHANGED
|
@@ -3,28 +3,13 @@
|
|
|
3
3
|
* a registry entry, the wire constants, and `intentMessage` — the single builder
|
|
4
4
|
* both directions (send + reply) share.
|
|
5
5
|
*/
|
|
6
|
-
import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
|
|
6
|
+
import type { IntentMessage, IntentRegistry, PayloadOf, ReplyOf } from '@astrale-os/shell'
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
* else the whole entry (a legacy bare-payload augmentation). Robust to both, so
|
|
11
|
-
* bare augmentations keep compiling unchanged.
|
|
12
|
-
*/
|
|
13
|
-
export type IntentPayload<K extends keyof IntentRegistry> = IntentRegistry[K] extends {
|
|
14
|
-
payload: infer P
|
|
15
|
-
}
|
|
16
|
-
? P
|
|
17
|
-
: IntentRegistry[K]
|
|
8
|
+
/** Payload declared by the canonical Shell intent registry entry. */
|
|
9
|
+
export type IntentPayload<K extends keyof IntentRegistry> = PayloadOf<K>
|
|
18
10
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
* (bare-payload and today's reply-less entries — forward-compatible with G4).
|
|
22
|
-
*/
|
|
23
|
-
export type IntentReply<K extends keyof IntentRegistry> = IntentRegistry[K] extends {
|
|
24
|
-
reply: infer R
|
|
25
|
-
}
|
|
26
|
-
? R
|
|
27
|
-
: void
|
|
11
|
+
/** Reply declared by the canonical Shell intent registry entry. */
|
|
12
|
+
export type IntentReply<K extends keyof IntentRegistry> = ReplyOf<K>
|
|
28
13
|
|
|
29
14
|
/** Sentinel `ctx.sender` carries for a parent-delivered intent (the shell stamps it). */
|
|
30
15
|
export const PARENT_SENDER = '<parent>'
|
|
@@ -35,9 +20,8 @@ export const INTENT_REPLY = 'intentReply'
|
|
|
35
20
|
export const NOOP = (): void => {}
|
|
36
21
|
|
|
37
22
|
/**
|
|
38
|
-
* Build an intent wire message.
|
|
39
|
-
*
|
|
40
|
-
* bridges the correlated name↔payload the union type cannot express.
|
|
23
|
+
* Build an intent wire message. The cast bridges the correlated name↔payload
|
|
24
|
+
* relation that the generic union type cannot express.
|
|
41
25
|
*/
|
|
42
26
|
export function intentMessage(
|
|
43
27
|
name: string,
|
|
@@ -1,108 +1,53 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IntentRegistry, RequestIntentOptions } from '@astrale-os/shell'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* useSendIntent (API §11) — a stable typed sender. Each call attaches a
|
|
5
|
-
* `correlationId`, subscribes for the matching `intentReply` on the origin channel
|
|
6
|
-
* BEFORE sending, then resolves with the reply's result. The promise also resolves
|
|
7
|
-
* (`void`) if the target window closes or this hook unmounts — a pending reply
|
|
8
|
-
* never strands.
|
|
9
|
-
*/
|
|
3
|
+
import { requestChildIntent, requestIntent } from '@astrale-os/shell'
|
|
10
4
|
import { useCallback, useEffect, useRef } from 'react'
|
|
11
5
|
|
|
12
6
|
import type { IntentPayload, IntentReply } from './envelope.js'
|
|
13
7
|
|
|
14
8
|
import { useShell } from '../session/shell.hook.js'
|
|
15
|
-
import { INTENT_REPLY, NOOP, intentMessage } from './envelope.js'
|
|
16
9
|
import { resolveIntentTarget } from './resolve-intent-target.js'
|
|
17
10
|
|
|
18
|
-
/**
|
|
11
|
+
/** Typed request sender backed exclusively by Shell's core correlation implementation. */
|
|
19
12
|
export interface SendIntent {
|
|
20
13
|
<K extends keyof IntentRegistry>(
|
|
21
14
|
name: K,
|
|
22
15
|
payload: IntentPayload<K>,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
): Promise<IntentReply<K> | void>
|
|
16
|
+
options?: RequestIntentOptions & { readonly to?: 'parent' | string },
|
|
17
|
+
): Promise<IntentReply<K>>
|
|
26
18
|
}
|
|
27
19
|
|
|
28
20
|
export function useSendIntent(): SendIntent {
|
|
29
21
|
const shell = useShell()
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
const lifetime = useRef(new AbortController())
|
|
23
|
+
|
|
32
24
|
useEffect(() => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
pending.clear()
|
|
37
|
-
}
|
|
25
|
+
const controller = lifetime.current.signal.aborted ? new AbortController() : lifetime.current
|
|
26
|
+
lifetime.current = controller
|
|
27
|
+
return () => controller.abort(new Error('Intent requester unmounted'))
|
|
38
28
|
}, [])
|
|
39
29
|
|
|
40
30
|
return useCallback(
|
|
41
31
|
<K extends keyof IntentRegistry>(
|
|
42
32
|
name: K,
|
|
43
33
|
payload: IntentPayload<K>,
|
|
44
|
-
|
|
45
|
-
): Promise<IntentReply<K
|
|
46
|
-
const target = resolveIntentTarget(shell,
|
|
34
|
+
options?: RequestIntentOptions & { readonly to?: 'parent' | string },
|
|
35
|
+
): Promise<IntentReply<K>> => {
|
|
36
|
+
const target = resolveIntentTarget(shell, options?.to)
|
|
47
37
|
if (target.kind === 'invalid') {
|
|
48
38
|
return Promise.reject(new Error(`useSendIntent: ${target.reason}`))
|
|
49
39
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
done = true
|
|
62
|
-
offReply()
|
|
63
|
-
offClose()
|
|
64
|
-
pending.delete(onUnmount)
|
|
65
|
-
resolve(value)
|
|
66
|
-
}
|
|
67
|
-
function fail(error: Error): void {
|
|
68
|
-
if (done) return
|
|
69
|
-
done = true
|
|
70
|
-
offReply()
|
|
71
|
-
offClose()
|
|
72
|
-
pending.delete(onUnmount)
|
|
73
|
-
reject(error)
|
|
74
|
-
}
|
|
75
|
-
const onReply = (m: IntentMessage): void => {
|
|
76
|
-
if (m.envelope.name !== INTENT_REPLY) return
|
|
77
|
-
const reply = m.envelope.payload as {
|
|
78
|
-
correlationId: string
|
|
79
|
-
result?: unknown
|
|
80
|
-
error?: { message: string }
|
|
81
|
-
}
|
|
82
|
-
if (reply.correlationId !== correlationId) return
|
|
83
|
-
if (reply.error) {
|
|
84
|
-
fail(new Error(reply.error.message))
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
settle(reply.result as IntentReply<K>)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
pending.add(onUnmount)
|
|
91
|
-
if (target.kind === 'parent') {
|
|
92
|
-
const parent = shell.parent!
|
|
93
|
-
offReply = parent.on('intent', onReply)
|
|
94
|
-
parent.send(message)
|
|
95
|
-
} else {
|
|
96
|
-
const { windowId } = target
|
|
97
|
-
offReply = shell.children.on('intent', (wid, m) => {
|
|
98
|
-
if (wid === windowId) onReply(m)
|
|
99
|
-
})
|
|
100
|
-
offClose = shell.children.onClose((wid) => {
|
|
101
|
-
if (wid === windowId) settle(undefined)
|
|
102
|
-
})
|
|
103
|
-
shell.children.send(windowId, message)
|
|
104
|
-
}
|
|
105
|
-
})
|
|
40
|
+
const signal =
|
|
41
|
+
options?.signal === undefined
|
|
42
|
+
? lifetime.current.signal
|
|
43
|
+
: AbortSignal.any([lifetime.current.signal, options.signal])
|
|
44
|
+
const requestOptions: RequestIntentOptions = {
|
|
45
|
+
signal,
|
|
46
|
+
...(options?.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
47
|
+
}
|
|
48
|
+
return target.kind === 'parent'
|
|
49
|
+
? requestIntent(shell.parent!, name, payload, requestOptions)
|
|
50
|
+
: requestChildIntent(shell.children, target.windowId, name, payload, requestOptions)
|
|
106
51
|
},
|
|
107
52
|
[shell],
|
|
108
53
|
)
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
import type { SerializedLocation } from '../history.js'
|
|
7
7
|
|
|
8
8
|
export interface NavAdapter {
|
|
9
|
+
readonly kind: 'memory' | 'browser'
|
|
9
10
|
read(): SerializedLocation | null
|
|
10
11
|
write(loc: SerializedLocation, opts: { replace: boolean }): void
|
|
12
|
+
/** Remove only this adapter's owned projection when no history entry survives. */
|
|
13
|
+
clear(opts: { replace: boolean }): void
|
|
11
14
|
subscribe(onExternalChange: () => void): () => void
|
|
12
15
|
href?(loc: SerializedLocation): string
|
|
13
16
|
}
|
|
@@ -10,10 +10,14 @@ export function memoryAdapter(initial?: SerializedLocation): NavAdapter {
|
|
|
10
10
|
let current: SerializedLocation | null = initial ?? null
|
|
11
11
|
const listeners = new Set<() => void>()
|
|
12
12
|
return {
|
|
13
|
+
kind: 'memory',
|
|
13
14
|
read: () => current,
|
|
14
15
|
write: (loc) => {
|
|
15
16
|
current = loc
|
|
16
17
|
},
|
|
18
|
+
clear: () => {
|
|
19
|
+
current = null
|
|
20
|
+
},
|
|
17
21
|
subscribe: (cb) => {
|
|
18
22
|
listeners.add(cb)
|
|
19
23
|
return () => listeners.delete(cb)
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { NodePath } from '@astrale-os/sdk/graph/path'
|
|
2
|
+
import { Key } from '@astrale-os/sdk/schema'
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* urlAdapter (API §8.1) — the History-API adapter: mirrors the location into the
|
|
3
6
|
* query string (URL form → `<a href>`), owning the nav query keys (`param`,
|
|
@@ -6,35 +9,62 @@
|
|
|
6
9
|
import type { SerializedLocation } from '../history.js'
|
|
7
10
|
import type { NavAdapter } from './adapter.js'
|
|
8
11
|
|
|
9
|
-
const VIEW_KEY = 'view'
|
|
12
|
+
const VIEW_KEY = 'astrale.view'
|
|
13
|
+
const PARAM_PREFIX = 'astrale.param.'
|
|
10
14
|
|
|
11
15
|
/** History-API adapter — the location lives in the query string. */
|
|
12
16
|
export function urlAdapter(opts?: { param?: string }): NavAdapter {
|
|
13
|
-
const param = opts?.param ?? 'node'
|
|
17
|
+
const param = opts?.param ?? 'astrale.node'
|
|
14
18
|
const hasWindow = (): boolean => typeof window !== 'undefined'
|
|
15
19
|
|
|
16
20
|
const build = (loc: SerializedLocation): string => {
|
|
17
21
|
const url = new URL(window.location.href)
|
|
18
|
-
const sp =
|
|
22
|
+
const sp = url.searchParams
|
|
19
23
|
sp.set(param, loc.node)
|
|
20
24
|
if (loc.view !== undefined) sp.set(VIEW_KEY, loc.view)
|
|
25
|
+
else sp.delete(VIEW_KEY)
|
|
26
|
+
for (const key of sp.keys()) if (key.startsWith(PARAM_PREFIX)) sp.delete(key)
|
|
21
27
|
for (const [k, v] of Object.entries(loc.params ?? {})) {
|
|
22
|
-
if (k
|
|
28
|
+
if (isSensitive(k)) throw new TypeError(`Navigation param '${k}' is sensitive.`)
|
|
29
|
+
sp.set(`${PARAM_PREFIX}${k}`, v)
|
|
23
30
|
}
|
|
24
31
|
url.search = sp.toString()
|
|
25
32
|
return `${url.pathname}${url.search}${url.hash}`
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
const clear = (): string => {
|
|
36
|
+
const url = new URL(window.location.href)
|
|
37
|
+
const sp = url.searchParams
|
|
38
|
+
sp.delete(param)
|
|
39
|
+
sp.delete(VIEW_KEY)
|
|
40
|
+
for (const key of sp.keys()) if (key.startsWith(PARAM_PREFIX)) sp.delete(key)
|
|
41
|
+
url.search = sp.toString()
|
|
42
|
+
return `${url.pathname}${url.search}${url.hash}`
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
return {
|
|
46
|
+
kind: 'browser',
|
|
29
47
|
read() {
|
|
30
48
|
if (!hasWindow()) return null
|
|
31
49
|
const sp = new URL(window.location.href).searchParams
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
50
|
+
const rawNode = sp.get(param)
|
|
51
|
+
if (rawNode === null) return null
|
|
52
|
+
let node
|
|
53
|
+
let view
|
|
54
|
+
try {
|
|
55
|
+
node = NodePath(rawNode)
|
|
56
|
+
const rawView = sp.get(VIEW_KEY)
|
|
57
|
+
if (rawView !== null) {
|
|
58
|
+
const admitted = Key(rawView)
|
|
59
|
+
if (!Key.ref(admitted).startsWith('view.')) throw new TypeError('Invalid View key.')
|
|
60
|
+
view = admitted as Key.View
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
35
65
|
const params: Record<string, string> = {}
|
|
36
66
|
for (const [k, v] of sp) {
|
|
37
|
-
if (k
|
|
67
|
+
if (k.startsWith(PARAM_PREFIX)) params[k.slice(PARAM_PREFIX.length)] = v
|
|
38
68
|
}
|
|
39
69
|
return {
|
|
40
70
|
node,
|
|
@@ -48,6 +78,12 @@ export function urlAdapter(opts?: { param?: string }): NavAdapter {
|
|
|
48
78
|
if (replace) window.history.replaceState(null, '', href)
|
|
49
79
|
else window.history.pushState(null, '', href)
|
|
50
80
|
},
|
|
81
|
+
clear({ replace }) {
|
|
82
|
+
if (!hasWindow()) return
|
|
83
|
+
const href = clear()
|
|
84
|
+
if (replace) window.history.replaceState(null, '', href)
|
|
85
|
+
else window.history.pushState(null, '', href)
|
|
86
|
+
},
|
|
51
87
|
subscribe(cb) {
|
|
52
88
|
if (!hasWindow()) return () => undefined
|
|
53
89
|
const handler = (): void => cb()
|
|
@@ -59,3 +95,7 @@ export function urlAdapter(opts?: { param?: string }): NavAdapter {
|
|
|
59
95
|
},
|
|
60
96
|
}
|
|
61
97
|
}
|
|
98
|
+
|
|
99
|
+
function isSensitive(key: string): boolean {
|
|
100
|
+
return /token|credential|authorization|secret/i.test(key)
|
|
101
|
+
}
|
|
@@ -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:
|
|
11
|
-
view?:
|
|
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(
|
|
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
|
|
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 {
|
|
2
|
-
import type {
|
|
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 {
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
10
|
-
import { useHistoryVersion, useNavCtx
|
|
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
|
|
18
|
-
|
|
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
|
}
|