@astrale-os/shell-react 0.1.0
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/LICENSE +202 -0
- package/dist/application/apps/app-host.d.ts +31 -0
- package/dist/application/apps/app-host.d.ts.map +1 -0
- package/dist/application/apps/app-host.js +59 -0
- package/dist/application/apps/app-logo.d.ts +19 -0
- package/dist/application/apps/app-logo.d.ts.map +1 -0
- package/dist/application/apps/app-logo.js +31 -0
- package/dist/application/apps/apps.hook.d.ts +12 -0
- package/dist/application/apps/apps.hook.d.ts.map +1 -0
- package/dist/application/apps/apps.hook.js +51 -0
- package/dist/application/apps/open-app.hook.d.ts +10 -0
- package/dist/application/apps/open-app.hook.d.ts.map +1 -0
- package/dist/application/apps/open-app.hook.js +7 -0
- package/dist/application/index.d.ts +18 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +12 -0
- package/dist/application/roles.hook.d.ts +7 -0
- package/dist/application/roles.hook.d.ts.map +1 -0
- package/dist/application/roles.hook.js +23 -0
- package/dist/application/spaces/current-space.hook.d.ts +7 -0
- package/dist/application/spaces/current-space.hook.d.ts.map +1 -0
- package/dist/application/spaces/current-space.hook.js +27 -0
- package/dist/application/spaces/current-space.store.d.ts +32 -0
- package/dist/application/spaces/current-space.store.d.ts.map +1 -0
- package/dist/application/spaces/current-space.store.js +79 -0
- package/dist/application/spaces/space.hook.d.ts +5 -0
- package/dist/application/spaces/space.hook.d.ts.map +1 -0
- package/dist/application/spaces/space.hook.js +25 -0
- package/dist/application/spaces/spaces.hook.d.ts +14 -0
- package/dist/application/spaces/spaces.hook.d.ts.map +1 -0
- package/dist/application/spaces/spaces.hook.js +82 -0
- package/dist/application/user/user.hook.d.ts +10 -0
- package/dist/application/user/user.hook.d.ts.map +1 -0
- package/dist/application/user/user.hook.js +19 -0
- package/dist/application/user/user.store.d.ts +35 -0
- package/dist/application/user/user.store.d.ts.map +1 -0
- package/dist/application/user/user.store.js +51 -0
- package/dist/auth/act-as.context.d.ts +40 -0
- package/dist/auth/act-as.context.d.ts.map +1 -0
- package/dist/auth/act-as.context.js +84 -0
- package/dist/auth/auth.hook.d.ts +3 -0
- package/dist/auth/auth.hook.d.ts.map +1 -0
- package/dist/auth/auth.hook.js +25 -0
- package/dist/auth/can.hook.d.ts +40 -0
- package/dist/auth/can.hook.d.ts.map +1 -0
- package/dist/auth/can.hook.js +119 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/self.hook.d.ts +3 -0
- package/dist/auth/self.hook.d.ts.map +1 -0
- package/dist/auth/self.hook.js +11 -0
- package/dist/auth/session-kernel.d.ts +4 -0
- package/dist/auth/session-kernel.d.ts.map +1 -0
- package/dist/auth/session-kernel.js +21 -0
- package/dist/graph/action.d.ts +22 -0
- package/dist/graph/action.d.ts.map +1 -0
- package/dist/graph/action.js +85 -0
- package/dist/graph/async.d.ts +11 -0
- package/dist/graph/async.d.ts.map +1 -0
- package/dist/graph/async.js +60 -0
- package/dist/graph/call/call.hook.d.ts +21 -0
- package/dist/graph/call/call.hook.d.ts.map +1 -0
- package/dist/graph/call/call.hook.js +23 -0
- package/dist/graph/call/stream.hook.d.ts +28 -0
- package/dist/graph/call/stream.hook.d.ts.map +1 -0
- package/dist/graph/call/stream.hook.js +137 -0
- package/dist/graph/graph.context.d.ts +4 -0
- package/dist/graph/graph.context.d.ts.map +1 -0
- package/dist/graph/graph.context.js +10 -0
- package/dist/graph/guards.error.d.ts +9 -0
- package/dist/graph/guards.error.d.ts.map +1 -0
- package/dist/graph/guards.error.js +7 -0
- package/dist/graph/index.d.ts +20 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +9 -0
- package/dist/graph/memory/base.d.ts +21 -0
- package/dist/graph/memory/base.d.ts.map +1 -0
- package/dist/graph/memory/base.js +33 -0
- package/dist/graph/memory/read/envelope.d.ts +71 -0
- package/dist/graph/memory/read/envelope.d.ts.map +1 -0
- package/dist/graph/memory/read/envelope.js +29 -0
- package/dist/graph/memory/read/idle.d.ts +18 -0
- package/dist/graph/memory/read/idle.d.ts.map +1 -0
- package/dist/graph/memory/read/idle.js +16 -0
- package/dist/graph/memory/read/read.d.ts +9 -0
- package/dist/graph/memory/read/read.d.ts.map +1 -0
- package/dist/graph/memory/read/read.js +87 -0
- package/dist/graph/memory/scope.d.ts +7 -0
- package/dist/graph/memory/scope.d.ts.map +1 -0
- package/dist/graph/memory/scope.js +25 -0
- package/dist/graph/mutate/move.hook.d.ts +7 -0
- package/dist/graph/mutate/move.hook.d.ts.map +1 -0
- package/dist/graph/mutate/move.hook.js +49 -0
- package/dist/graph/mutate/mutate.hook.d.ts +17 -0
- package/dist/graph/mutate/mutate.hook.d.ts.map +1 -0
- package/dist/graph/mutate/mutate.hook.js +45 -0
- package/dist/graph/query/children.hook.d.ts +29 -0
- package/dist/graph/query/children.hook.d.ts.map +1 -0
- package/dist/graph/query/children.hook.js +64 -0
- package/dist/graph/query/graph.hook.d.ts +9 -0
- package/dist/graph/query/graph.hook.d.ts.map +1 -0
- package/dist/graph/query/graph.hook.js +5 -0
- package/dist/graph/query/neighbors.hook.d.ts +7 -0
- package/dist/graph/query/neighbors.hook.d.ts.map +1 -0
- package/dist/graph/query/neighbors.hook.js +79 -0
- package/dist/graph/query/node.hook.d.ts +14 -0
- package/dist/graph/query/node.hook.d.ts.map +1 -0
- package/dist/graph/query/node.hook.js +43 -0
- package/dist/graph/query/query.hook.d.ts +14 -0
- package/dist/graph/query/query.hook.d.ts.map +1 -0
- package/dist/graph/query/query.hook.js +23 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/intents/envelope.d.ts +33 -0
- package/dist/intents/envelope.d.ts.map +1 -0
- package/dist/intents/envelope.js +19 -0
- package/dist/intents/index.d.ts +6 -0
- package/dist/intents/index.d.ts.map +1 -0
- package/dist/intents/index.js +3 -0
- package/dist/intents/listen-intent.hook.d.ts +16 -0
- package/dist/intents/listen-intent.hook.d.ts.map +1 -0
- package/dist/intents/listen-intent.hook.js +75 -0
- package/dist/intents/reply.d.ts +9 -0
- package/dist/intents/reply.d.ts.map +1 -0
- package/dist/intents/reply.js +14 -0
- package/dist/intents/send-intent.hook.d.ts +12 -0
- package/dist/intents/send-intent.hook.d.ts.map +1 -0
- package/dist/intents/send-intent.hook.js +86 -0
- package/dist/navigation/adapters/adapter.d.ts +15 -0
- package/dist/navigation/adapters/adapter.d.ts.map +1 -0
- package/dist/navigation/adapters/adapter.js +1 -0
- package/dist/navigation/adapters/memory.adapter.d.ts +9 -0
- package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -0
- package/dist/navigation/adapters/memory.adapter.js +15 -0
- package/dist/navigation/adapters/url.adapter.d.ts +6 -0
- package/dist/navigation/adapters/url.adapter.d.ts.map +1 -0
- package/dist/navigation/adapters/url.adapter.js +59 -0
- package/dist/navigation/history.d.ts +63 -0
- package/dist/navigation/history.d.ts.map +1 -0
- package/dist/navigation/history.js +144 -0
- package/dist/navigation/hooks/ancestors.hook.d.ts +13 -0
- package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -0
- package/dist/navigation/hooks/ancestors.hook.js +56 -0
- package/dist/navigation/hooks/location.hook.d.ts +4 -0
- package/dist/navigation/hooks/location.hook.d.ts.map +1 -0
- package/dist/navigation/hooks/location.hook.js +17 -0
- package/dist/navigation/hooks/navigate.hook.d.ts +12 -0
- package/dist/navigation/hooks/navigate.hook.d.ts.map +1 -0
- package/dist/navigation/hooks/navigate.hook.js +33 -0
- package/dist/navigation/hooks/scope.hook.d.ts +12 -0
- package/dist/navigation/hooks/scope.hook.d.ts.map +1 -0
- package/dist/navigation/hooks/scope.hook.js +21 -0
- package/dist/navigation/index.d.ts +16 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +10 -0
- package/dist/navigation/link.d.ts +11 -0
- package/dist/navigation/link.d.ts.map +1 -0
- package/dist/navigation/link.js +62 -0
- package/dist/navigation/location.d.ts +48 -0
- package/dist/navigation/location.d.ts.map +1 -0
- package/dist/navigation/location.js +88 -0
- package/dist/navigation/scope.context.d.ts +64 -0
- package/dist/navigation/scope.context.d.ts.map +1 -0
- package/dist/navigation/scope.context.js +178 -0
- package/dist/navigation/snapshot.d.ts +22 -0
- package/dist/navigation/snapshot.d.ts.map +1 -0
- package/dist/navigation/snapshot.js +65 -0
- package/dist/schema/bind-read.d.ts +37 -0
- package/dist/schema/bind-read.d.ts.map +1 -0
- package/dist/schema/bind-read.js +205 -0
- package/dist/schema/binding.d.ts +21 -0
- package/dist/schema/binding.d.ts.map +1 -0
- package/dist/schema/binding.js +55 -0
- package/dist/schema/domain.hook.d.ts +10 -0
- package/dist/schema/domain.hook.d.ts.map +1 -0
- package/dist/schema/domain.hook.js +7 -0
- package/dist/schema/function.hook.d.ts +9 -0
- package/dist/schema/function.hook.d.ts.map +1 -0
- package/dist/schema/function.hook.js +40 -0
- package/dist/schema/index.d.ts +10 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +8 -0
- package/dist/schema/kernel.hook.d.ts +10 -0
- package/dist/schema/kernel.hook.d.ts.map +1 -0
- package/dist/schema/kernel.hook.js +8 -0
- package/dist/schema/method.hook.d.ts +14 -0
- package/dist/schema/method.hook.d.ts.map +1 -0
- package/dist/schema/method.hook.js +49 -0
- package/dist/schema/tag.d.ts +11 -0
- package/dist/schema/tag.d.ts.map +1 -0
- package/dist/schema/tag.js +11 -0
- package/dist/schema/types.d.ts +73 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +1 -0
- package/dist/schema/write.d.ts +15 -0
- package/dist/schema/write.d.ts.map +1 -0
- package/dist/schema/write.js +45 -0
- package/dist/selection/index.d.ts +4 -0
- package/dist/selection/index.d.ts.map +1 -0
- package/dist/selection/index.js +4 -0
- package/dist/selection/live.d.ts +21 -0
- package/dist/selection/live.d.ts.map +1 -0
- package/dist/selection/live.js +116 -0
- package/dist/selection/scope.context.d.ts +26 -0
- package/dist/selection/scope.context.d.ts.map +1 -0
- package/dist/selection/scope.context.js +100 -0
- package/dist/selection/selection.hook.d.ts +4 -0
- package/dist/selection/selection.hook.d.ts.map +1 -0
- package/dist/selection/selection.hook.js +14 -0
- package/dist/session/boot/boot.d.ts +19 -0
- package/dist/session/boot/boot.d.ts.map +1 -0
- package/dist/session/boot/boot.js +45 -0
- package/dist/session/boot/ownership.d.ts +18 -0
- package/dist/session/boot/ownership.d.ts.map +1 -0
- package/dist/session/boot/ownership.js +39 -0
- package/dist/session/boot/status.d.ts +24 -0
- package/dist/session/boot/status.d.ts.map +1 -0
- package/dist/session/boot/status.js +11 -0
- package/dist/session/connection/connection.hook.d.ts +3 -0
- package/dist/session/connection/connection.hook.d.ts.map +1 -0
- package/dist/session/connection/connection.hook.js +12 -0
- package/dist/session/connection/connection.store.d.ts +24 -0
- package/dist/session/connection/connection.store.d.ts.map +1 -0
- package/dist/session/connection/connection.store.js +31 -0
- package/dist/session/index.d.ts +8 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/kernel-client.hook.d.ts +9 -0
- package/dist/session/kernel-client.hook.d.ts.map +1 -0
- package/dist/session/kernel-client.hook.js +5 -0
- package/dist/session/kernel-instance.hook.d.ts +9 -0
- package/dist/session/kernel-instance.hook.d.ts.map +1 -0
- package/dist/session/kernel-instance.hook.js +10 -0
- package/dist/session/provider.d.ts +31 -0
- package/dist/session/provider.d.ts.map +1 -0
- package/dist/session/provider.js +165 -0
- package/dist/session/session.context.d.ts +18 -0
- package/dist/session/session.context.d.ts.map +1 -0
- package/dist/session/session.context.js +8 -0
- package/dist/session/shell.hook.d.ts +4 -0
- package/dist/session/shell.hook.d.ts.map +1 -0
- package/dist/session/shell.hook.js +15 -0
- package/dist/window/adopt.d.ts +27 -0
- package/dist/window/adopt.d.ts.map +1 -0
- package/dist/window/adopt.js +27 -0
- package/dist/window/hooks/close-guard.hook.d.ts +6 -0
- package/dist/window/hooks/close-guard.hook.d.ts.map +1 -0
- package/dist/window/hooks/close-guard.hook.js +90 -0
- package/dist/window/hooks/target-node.hook.d.ts +7 -0
- package/dist/window/hooks/target-node.hook.d.ts.map +1 -0
- package/dist/window/hooks/target-node.hook.js +77 -0
- package/dist/window/hooks/views-for.hook.d.ts +8 -0
- package/dist/window/hooks/views-for.hook.d.ts.map +1 -0
- package/dist/window/hooks/views-for.hook.js +51 -0
- package/dist/window/hooks/window.hook.d.ts +4 -0
- package/dist/window/hooks/window.hook.d.ts.map +1 -0
- package/dist/window/hooks/window.hook.js +14 -0
- package/dist/window/hooks/windows.hook.d.ts +15 -0
- package/dist/window/hooks/windows.hook.d.ts.map +1 -0
- package/dist/window/hooks/windows.hook.js +25 -0
- package/dist/window/host.d.ts +31 -0
- package/dist/window/host.d.ts.map +1 -0
- package/dist/window/host.js +270 -0
- package/dist/window/hub.d.ts +41 -0
- package/dist/window/hub.d.ts.map +1 -0
- package/dist/window/hub.js +100 -0
- package/dist/window/index.d.ts +15 -0
- package/dist/window/index.d.ts.map +1 -0
- package/dist/window/index.js +10 -0
- package/dist/window/lifecycle/mount.d.ts +47 -0
- package/dist/window/lifecycle/mount.d.ts.map +1 -0
- package/dist/window/lifecycle/mount.js +92 -0
- package/dist/window/lifecycle/states.d.ts +33 -0
- package/dist/window/lifecycle/states.d.ts.map +1 -0
- package/dist/window/lifecycle/states.js +7 -0
- package/dist/window/placement.d.ts +33 -0
- package/dist/window/placement.d.ts.map +1 -0
- package/dist/window/placement.js +57 -0
- package/dist/window/stage.d.ts +3 -0
- package/dist/window/stage.d.ts.map +1 -0
- package/dist/window/stage.js +41 -0
- package/package.json +59 -0
- package/src/application/apps/app-host.tsx +113 -0
- package/src/application/apps/app-logo.tsx +56 -0
- package/src/application/apps/apps.hook.ts +81 -0
- package/src/application/apps/open-app.hook.ts +18 -0
- package/src/application/index.ts +22 -0
- package/src/application/roles.hook.ts +41 -0
- package/src/application/spaces/current-space.hook.ts +43 -0
- package/src/application/spaces/current-space.store.ts +110 -0
- package/src/application/spaces/space.hook.ts +41 -0
- package/src/application/spaces/spaces.hook.ts +113 -0
- package/src/application/user/user.hook.ts +27 -0
- package/src/application/user/user.store.ts +85 -0
- package/src/auth/act-as.context.tsx +124 -0
- package/src/auth/auth.hook.ts +36 -0
- package/src/auth/can.hook.ts +149 -0
- package/src/auth/index.ts +6 -0
- package/src/auth/self.hook.ts +14 -0
- package/src/auth/session-kernel.ts +24 -0
- package/src/graph/action.ts +127 -0
- package/src/graph/async.ts +87 -0
- package/src/graph/call/call.hook.ts +42 -0
- package/src/graph/call/stream.hook.ts +200 -0
- package/src/graph/graph.context.ts +14 -0
- package/src/graph/guards.error.ts +58 -0
- package/src/graph/index.ts +20 -0
- package/src/graph/memory/base.ts +50 -0
- package/src/graph/memory/read/envelope.ts +98 -0
- package/src/graph/memory/read/idle.ts +24 -0
- package/src/graph/memory/read/read.ts +103 -0
- package/src/graph/memory/scope.ts +29 -0
- package/src/graph/mutate/move.hook.ts +70 -0
- package/src/graph/mutate/mutate.hook.ts +69 -0
- package/src/graph/query/children.hook.ts +136 -0
- package/src/graph/query/graph.hook.ts +13 -0
- package/src/graph/query/neighbors.hook.ts +124 -0
- package/src/graph/query/node.hook.ts +83 -0
- package/src/graph/query/query.hook.ts +42 -0
- package/src/index.ts +202 -0
- package/src/intents/envelope.ts +52 -0
- package/src/intents/index.ts +6 -0
- package/src/intents/listen-intent.hook.ts +92 -0
- package/src/intents/reply.ts +24 -0
- package/src/intents/send-intent.hook.ts +111 -0
- package/src/navigation/adapters/adapter.ts +13 -0
- package/src/navigation/adapters/memory.adapter.ts +22 -0
- package/src/navigation/adapters/url.adapter.ts +61 -0
- package/src/navigation/history.ts +170 -0
- package/src/navigation/hooks/ancestors.hook.ts +71 -0
- package/src/navigation/hooks/location.hook.ts +21 -0
- package/src/navigation/hooks/navigate.hook.ts +43 -0
- package/src/navigation/hooks/scope.hook.ts +34 -0
- package/src/navigation/index.ts +17 -0
- package/src/navigation/link.tsx +110 -0
- package/src/navigation/location.ts +122 -0
- package/src/navigation/scope.context.tsx +267 -0
- package/src/navigation/snapshot.ts +79 -0
- package/src/schema/bind-read.ts +319 -0
- package/src/schema/binding.ts +91 -0
- package/src/schema/domain.hook.ts +16 -0
- package/src/schema/function.hook.ts +68 -0
- package/src/schema/index.ts +25 -0
- package/src/schema/kernel.hook.ts +17 -0
- package/src/schema/method.hook.ts +103 -0
- package/src/schema/tag.ts +19 -0
- package/src/schema/types.ts +150 -0
- package/src/schema/write.ts +80 -0
- package/src/selection/index.ts +5 -0
- package/src/selection/live.ts +142 -0
- package/src/selection/scope.context.tsx +151 -0
- package/src/selection/selection.hook.ts +18 -0
- package/src/session/boot/boot.ts +57 -0
- package/src/session/boot/ownership.ts +54 -0
- package/src/session/boot/status.ts +32 -0
- package/src/session/connection/connection.hook.ts +16 -0
- package/src/session/connection/connection.store.ts +58 -0
- package/src/session/index.ts +8 -0
- package/src/session/kernel-client.hook.ts +13 -0
- package/src/session/kernel-instance.hook.ts +22 -0
- package/src/session/provider.tsx +205 -0
- package/src/session/session.context.ts +27 -0
- package/src/session/shell.hook.ts +19 -0
- package/src/window/adopt.ts +57 -0
- package/src/window/hooks/close-guard.hook.ts +100 -0
- package/src/window/hooks/target-node.hook.ts +94 -0
- package/src/window/hooks/views-for.hook.ts +82 -0
- package/src/window/hooks/window.hook.ts +18 -0
- package/src/window/hooks/windows.hook.ts +40 -0
- package/src/window/host.tsx +347 -0
- package/src/window/hub.ts +156 -0
- package/src/window/index.ts +17 -0
- package/src/window/lifecycle/mount.ts +134 -0
- package/src/window/lifecycle/states.ts +23 -0
- package/src/window/placement.ts +84 -0
- package/src/window/stage.tsx +46 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { AnyBoundNode, EdgeNames } from '@astrale-os/kernel-client'
|
|
2
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* useOut / useIn (API §6) — the public typed traversal read hooks. Resolve
|
|
6
|
+
* endpoints one-shot through `BoundNode.out/in` (`live: false`); become live with
|
|
7
|
+
* no surface change once edge-endpoint ids land. Deviations tracked in API §6/G6.
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
10
|
+
|
|
11
|
+
import type { EndpointResult } from '../../schema/types'
|
|
12
|
+
import type { ReadState, ReadOptions } from '../memory/read/envelope'
|
|
13
|
+
|
|
14
|
+
import { schemaOf, tagSchema } from '../../schema/tag'
|
|
15
|
+
import { isIdleInput } from '../memory/read/idle'
|
|
16
|
+
|
|
17
|
+
export function useOut<S extends Schema, E extends EdgeNames<S>>(
|
|
18
|
+
node: AnyBoundNode<S> | null | undefined,
|
|
19
|
+
edge: E,
|
|
20
|
+
opts?: ReadOptions,
|
|
21
|
+
): EndpointResult<S, E, 'to'> {
|
|
22
|
+
return useEndpoint(node, edge, 'out', opts) as unknown as EndpointResult<S, E, 'to'>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useIn<S extends Schema, E extends EdgeNames<S>>(
|
|
26
|
+
node: AnyBoundNode<S> | null | undefined,
|
|
27
|
+
edge: E,
|
|
28
|
+
opts?: ReadOptions,
|
|
29
|
+
): EndpointResult<S, E, 'from'> {
|
|
30
|
+
return useEndpoint(node, edge, 'in', opts) as unknown as EndpointResult<S, E, 'from'>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type EndpointEnvelope<S extends Schema> = ReadState & {
|
|
34
|
+
readonly node?: AnyBoundNode<S> | null
|
|
35
|
+
readonly nodes?: readonly AnyBoundNode<S>[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Traversal {
|
|
39
|
+
out(edge: string): Promise<unknown>
|
|
40
|
+
in(edge: string): Promise<unknown>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function useEndpoint<S extends Schema>(
|
|
44
|
+
node: AnyBoundNode<S> | null | undefined,
|
|
45
|
+
edge: string,
|
|
46
|
+
side: 'out' | 'in',
|
|
47
|
+
_opts?: ReadOptions,
|
|
48
|
+
): EndpointEnvelope<S> {
|
|
49
|
+
const schema = isIdleInput(node) ? undefined : schemaOf(node)
|
|
50
|
+
const run = useCallback(async (): Promise<unknown> => {
|
|
51
|
+
if (isIdleInput(node)) return null
|
|
52
|
+
// AnyBoundNode omits traversal at the type level; the runtime node (bindAny
|
|
53
|
+
// → createBoundNode) carries out/in. The edge name is typed by the caller.
|
|
54
|
+
const t = node as unknown as Traversal
|
|
55
|
+
const result = await (side === 'out' ? t.out(edge) : t.in(edge))
|
|
56
|
+
if (schema !== undefined) tagEndpoints(result, schema)
|
|
57
|
+
return result
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
}, [node?.id, edge, side])
|
|
60
|
+
|
|
61
|
+
const { data, pending, error, refetch } = useAsyncRead(isIdleInput(node) ? null : run, [
|
|
62
|
+
node?.id ?? null,
|
|
63
|
+
edge,
|
|
64
|
+
side,
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
return useMemo<EndpointEnvelope<S>>(() => {
|
|
68
|
+
const live: ReadState = { pending, error, live: false, seq: 0, refetch }
|
|
69
|
+
if (isIdleInput(node)) return { ...live, node: null, nodes: EMPTY }
|
|
70
|
+
if (Array.isArray(data)) return { ...live, nodes: data as readonly AnyBoundNode<S>[] }
|
|
71
|
+
return { ...live, node: (data ?? null) as AnyBoundNode<S> | null }
|
|
72
|
+
}, [node, data, pending, error, refetch])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
const EMPTY: readonly never[] = Object.freeze([])
|
|
78
|
+
|
|
79
|
+
function tagEndpoints(result: unknown, schema: Schema): void {
|
|
80
|
+
if (Array.isArray(result)) for (const r of result) tagSchema(r, schema)
|
|
81
|
+
else if (result !== null && result !== undefined) tagSchema(result, schema)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface AsyncState<T> {
|
|
85
|
+
data?: T
|
|
86
|
+
pending: boolean
|
|
87
|
+
error: unknown
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One-shot async read with pending/error/refetch and stale-response guarding. */
|
|
91
|
+
function useAsyncRead<T>(
|
|
92
|
+
run: (() => Promise<T>) | null,
|
|
93
|
+
deps: readonly unknown[],
|
|
94
|
+
): AsyncState<T> & { refetch(): Promise<void> } {
|
|
95
|
+
const [state, setState] = useState<AsyncState<T>>({ pending: run !== null, error: undefined })
|
|
96
|
+
const runRef = useRef(run)
|
|
97
|
+
runRef.current = run
|
|
98
|
+
const seq = useRef(0)
|
|
99
|
+
|
|
100
|
+
const refetch = useCallback((): Promise<void> => {
|
|
101
|
+
const current = runRef.current
|
|
102
|
+
if (current === null) {
|
|
103
|
+
setState({ pending: false, error: undefined, data: undefined })
|
|
104
|
+
return Promise.resolve()
|
|
105
|
+
}
|
|
106
|
+
const my = ++seq.current
|
|
107
|
+
setState((s) => ({ ...s, pending: true, error: undefined }))
|
|
108
|
+
return current().then(
|
|
109
|
+
(data) => {
|
|
110
|
+
if (my === seq.current) setState({ data, pending: false, error: undefined })
|
|
111
|
+
},
|
|
112
|
+
(error: unknown) => {
|
|
113
|
+
if (my === seq.current) setState((s) => ({ data: s.data, pending: false, error }))
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
}, [])
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
void refetch()
|
|
120
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
+
}, deps)
|
|
122
|
+
|
|
123
|
+
return { ...state, refetch }
|
|
124
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { NodeRefInput, TypeNames } from '@astrale-os/kernel-client'
|
|
2
|
+
import type { PathLike } from '@astrale-os/kernel-client/graph'
|
|
3
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* useNode (API §5 + §6) — ONE public symbol, overloaded: pass a `Schema` (+ type)
|
|
7
|
+
* for the typed `BoundNode` result, or a bare ref for the untyped `Node` result.
|
|
8
|
+
* Both forms ride the SAME memory shape — a typed read and its untyped twin are
|
|
9
|
+
* one cache entry, one liveness, one overlay. The runtime dispatches on the first
|
|
10
|
+
* argument; a given call site is always one form, so hook order is stable.
|
|
11
|
+
*/
|
|
12
|
+
import { useMemo } from 'react'
|
|
13
|
+
|
|
14
|
+
import type { TypedNodeResult } from '../../schema/types'
|
|
15
|
+
import type { ReadState, Node, ReadOptions } from '../memory/read/envelope'
|
|
16
|
+
|
|
17
|
+
import { useNode as useNodeTyped } from '../../schema/bind-read'
|
|
18
|
+
import { useRead } from '../memory/read/read'
|
|
19
|
+
|
|
20
|
+
export interface NodeResult extends ReadState {
|
|
21
|
+
/** The node, or `null` when missing ≡ masked (never throws NOT_FOUND). */
|
|
22
|
+
readonly node: Node | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A `Schema` (from `defineSchema`) — its shape (`domain`/`classes`/`interfaces`)
|
|
27
|
+
* is disjoint from a `PathLike` (`string | Path`), so this cleanly routes the
|
|
28
|
+
* typed vs untyped overload at runtime.
|
|
29
|
+
*/
|
|
30
|
+
function isSchema(value: unknown): value is Schema {
|
|
31
|
+
return (
|
|
32
|
+
typeof value === 'object' &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
'domain' in value &&
|
|
35
|
+
'classes' in value &&
|
|
36
|
+
'interfaces' in value
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One node by path — live, suspense-first; `null` ref is idle (§1.3). */
|
|
41
|
+
function useNodeUntyped(ref: PathLike | null | undefined, opts?: ReadOptions): NodeResult {
|
|
42
|
+
const r = useRead(ref, opts)
|
|
43
|
+
return useMemo(
|
|
44
|
+
() => ({
|
|
45
|
+
pending: r.pending,
|
|
46
|
+
error: r.error,
|
|
47
|
+
live: r.live,
|
|
48
|
+
seq: r.seq,
|
|
49
|
+
refetch: r.refetch,
|
|
50
|
+
node: r.first,
|
|
51
|
+
}),
|
|
52
|
+
[r],
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Typed: one node bound under `schema` as `type`. */
|
|
57
|
+
export function useNode<S extends Schema, T extends TypeNames<S>>(
|
|
58
|
+
schema: S,
|
|
59
|
+
type: T,
|
|
60
|
+
ref: NodeRefInput | null | undefined,
|
|
61
|
+
opts?: ReadOptions,
|
|
62
|
+
): TypedNodeResult<S, T>
|
|
63
|
+
/** Untyped: one node by ref — `null` ref is idle (§1.3). */
|
|
64
|
+
export function useNode(ref: PathLike | null | undefined, opts?: ReadOptions): NodeResult
|
|
65
|
+
// The implementation signature returns an erased supertype of both result
|
|
66
|
+
// shapes — every overload above is assignable to it regardless of `BoundNode`
|
|
67
|
+
// variance; callers only ever see the precise overload return types.
|
|
68
|
+
export function useNode(
|
|
69
|
+
a: unknown,
|
|
70
|
+
b?: unknown,
|
|
71
|
+
c?: unknown,
|
|
72
|
+
d?: unknown,
|
|
73
|
+
): ReadState & { readonly node: unknown } {
|
|
74
|
+
if (isSchema(a)) {
|
|
75
|
+
return useNodeTyped(
|
|
76
|
+
a,
|
|
77
|
+
b as string,
|
|
78
|
+
c as NodeRefInput | null | undefined,
|
|
79
|
+
d as ReadOptions | undefined,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
return useNodeUntyped(a as PathLike | null | undefined, b as ReadOptions | undefined)
|
|
83
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { GatherBuilder, QueryASTInput, QuerySource } from '@astrale-os/kernel-client/graph'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useQuery (API §5) — a structured read: a builder callback or an AST value;
|
|
5
|
+
* `null` is idle (§1.3). Rides the §1 envelope over the shared memory, projecting
|
|
6
|
+
* roots/first/nodes and the gathered-alias arms.
|
|
7
|
+
*/
|
|
8
|
+
import { useMemo } from 'react'
|
|
9
|
+
|
|
10
|
+
import type { ReadState, Node, ReadOptions, Windowed } from '../memory/read/envelope'
|
|
11
|
+
|
|
12
|
+
import { useRead } from '../memory/read/read'
|
|
13
|
+
|
|
14
|
+
export interface QueryResult extends ReadState, Windowed {
|
|
15
|
+
readonly nodes: readonly Node[]
|
|
16
|
+
readonly roots: readonly Node[]
|
|
17
|
+
readonly first: Node | null
|
|
18
|
+
gathered(alias: string): { readonly nodes: readonly Node[]; readonly first: Node | null }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A structured read — builder callback or AST value; `null` is idle (§1.3). */
|
|
22
|
+
export function useQuery(
|
|
23
|
+
build: ((q: QuerySource) => GatherBuilder) | QueryASTInput | null | undefined,
|
|
24
|
+
opts?: ReadOptions,
|
|
25
|
+
): QueryResult {
|
|
26
|
+
const r = useRead(build ?? null, opts)
|
|
27
|
+
return useMemo(
|
|
28
|
+
() => ({
|
|
29
|
+
pending: r.pending,
|
|
30
|
+
error: r.error,
|
|
31
|
+
live: r.live,
|
|
32
|
+
seq: r.seq,
|
|
33
|
+
refetch: r.refetch,
|
|
34
|
+
nodes: r.nodes,
|
|
35
|
+
roots: r.roots,
|
|
36
|
+
first: r.first,
|
|
37
|
+
gathered: r.gathered,
|
|
38
|
+
more: r.more,
|
|
39
|
+
}),
|
|
40
|
+
[r],
|
|
41
|
+
)
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @astrale-os/shell-react — the React face of the shell (SHELL_REACT_API.md §0).
|
|
3
|
+
* One package, one import surface (SPEC §4.2); the bounded-context directories are
|
|
4
|
+
* the module structure, not subpaths. This public barrel composes the context
|
|
5
|
+
* barrels. Settled naming (§0): useKernelClient = the raw handle (session);
|
|
6
|
+
* useKernel() = the TYPED kernel (schema, no arg); useDomain(schema) = a typed
|
|
7
|
+
* domain binding. `useNode`/`useChildren` are single overloaded symbols (untyped
|
|
8
|
+
* floor + typed overloads) exported from the graph plane.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ─── session (§2 provider + §3) ──────────────────────────────────────────────
|
|
12
|
+
export { ShellProvider } from './session'
|
|
13
|
+
export type { ShellError, ShellProviderProps } from './session'
|
|
14
|
+
export { useConnection, useKernelClient, useKernelInstance, useShell } from './session'
|
|
15
|
+
export type { KernelInstance } from './session'
|
|
16
|
+
|
|
17
|
+
// ─── auth (§4) ───────────────────────────────────────────────────────────────
|
|
18
|
+
export { ActAs, useAuth, useCan, useSelf } from './auth'
|
|
19
|
+
export type { ActAsProps } from './auth'
|
|
20
|
+
|
|
21
|
+
// ─── graph — the untyped floor + writes + dispatch (§5, §7) ──────────────────
|
|
22
|
+
// `useNode`/`useChildren` are single symbols merging the untyped floor + typed
|
|
23
|
+
// overloads.
|
|
24
|
+
export {
|
|
25
|
+
useCall,
|
|
26
|
+
useChildren,
|
|
27
|
+
useGraph,
|
|
28
|
+
useMove,
|
|
29
|
+
useMutate,
|
|
30
|
+
useNode,
|
|
31
|
+
useQuery,
|
|
32
|
+
useStream,
|
|
33
|
+
} from './graph'
|
|
34
|
+
export type {
|
|
35
|
+
ChildrenOptions,
|
|
36
|
+
ChildrenResult,
|
|
37
|
+
GraphMemory,
|
|
38
|
+
MoveAction,
|
|
39
|
+
Node,
|
|
40
|
+
NodeResult,
|
|
41
|
+
QueryResult,
|
|
42
|
+
ShapeInput,
|
|
43
|
+
UseMutateOptions,
|
|
44
|
+
} from './graph'
|
|
45
|
+
export type { CallOptions, StreamHandle, StreamOptions } from './graph'
|
|
46
|
+
|
|
47
|
+
// ─── schema — the typed substrate (§6) ───────────────────────────────────────
|
|
48
|
+
export { useDomain, useFunction, useIn, useKernel, useMethod, useOut } from './schema'
|
|
49
|
+
export type { FunctionOptions } from './schema'
|
|
50
|
+
export type { MethodOptions } from './schema'
|
|
51
|
+
export type {
|
|
52
|
+
AutoMethodAction,
|
|
53
|
+
BoundMethodAction,
|
|
54
|
+
EndpointResult,
|
|
55
|
+
FunctionNames,
|
|
56
|
+
FunctionParams,
|
|
57
|
+
FunctionResult,
|
|
58
|
+
MethodIsStatic,
|
|
59
|
+
MethodNames,
|
|
60
|
+
MethodParams,
|
|
61
|
+
MethodResult,
|
|
62
|
+
TargetedMethodAction,
|
|
63
|
+
TypedChildrenResult,
|
|
64
|
+
TypedNodeResult,
|
|
65
|
+
} from './schema'
|
|
66
|
+
|
|
67
|
+
// ─── navigation (§8) ─────────────────────────────────────────────────────────
|
|
68
|
+
export {
|
|
69
|
+
Link,
|
|
70
|
+
NavScope,
|
|
71
|
+
memoryAdapter,
|
|
72
|
+
urlAdapter,
|
|
73
|
+
useAncestors,
|
|
74
|
+
useLocation,
|
|
75
|
+
useNavScope,
|
|
76
|
+
useNavigate,
|
|
77
|
+
} from './navigation'
|
|
78
|
+
export type {
|
|
79
|
+
LinkProps,
|
|
80
|
+
MissingPolicy,
|
|
81
|
+
NavAdapter,
|
|
82
|
+
NavLocation,
|
|
83
|
+
NavScopeHandle,
|
|
84
|
+
NavScopeProps,
|
|
85
|
+
NavSnapshot,
|
|
86
|
+
NavTarget,
|
|
87
|
+
Navigate,
|
|
88
|
+
SerializedLocation,
|
|
89
|
+
} from './navigation'
|
|
90
|
+
|
|
91
|
+
// ─── selection (§9) ──────────────────────────────────────────────────────────
|
|
92
|
+
export { SelectionScope, useSelection } from './selection'
|
|
93
|
+
export type { Selection, SelectionScopeProps } from './selection'
|
|
94
|
+
|
|
95
|
+
// ─── window (§10) ────────────────────────────────────────────────────────────
|
|
96
|
+
export {
|
|
97
|
+
useCloseGuard,
|
|
98
|
+
useTargetNode,
|
|
99
|
+
useViewsFor,
|
|
100
|
+
useWindow,
|
|
101
|
+
useWindowList,
|
|
102
|
+
useWindows,
|
|
103
|
+
ViewHost,
|
|
104
|
+
ViewStage,
|
|
105
|
+
} from './window'
|
|
106
|
+
export type {
|
|
107
|
+
CloseGuard,
|
|
108
|
+
CloseVerdict,
|
|
109
|
+
ViewDescriptor,
|
|
110
|
+
ViewHostProps,
|
|
111
|
+
ViewSource,
|
|
112
|
+
ViewHostHandle,
|
|
113
|
+
ViewsForResult,
|
|
114
|
+
WindowListEntry,
|
|
115
|
+
WindowState,
|
|
116
|
+
WindowView,
|
|
117
|
+
} from './window'
|
|
118
|
+
|
|
119
|
+
// ─── intents (§11) ───────────────────────────────────────────────────────────
|
|
120
|
+
export { useIntent, useSendIntent } from './intents'
|
|
121
|
+
export type { IntentCtx, IntentPayload, IntentReply, SendIntent } from './intents'
|
|
122
|
+
|
|
123
|
+
// ─── application — the OS plane (§12) ────────────────────────────────────────
|
|
124
|
+
export {
|
|
125
|
+
AppHost,
|
|
126
|
+
AppLogo,
|
|
127
|
+
useApps,
|
|
128
|
+
useCurrentSpace,
|
|
129
|
+
useOpenApp,
|
|
130
|
+
useRoles,
|
|
131
|
+
useSpace,
|
|
132
|
+
useSpaces,
|
|
133
|
+
useUser,
|
|
134
|
+
} from './application'
|
|
135
|
+
export type {
|
|
136
|
+
AppHostFallbackState,
|
|
137
|
+
AppHostProps,
|
|
138
|
+
AppLogoProps,
|
|
139
|
+
AppsResult,
|
|
140
|
+
CurrentSpaceResult,
|
|
141
|
+
RolesResult,
|
|
142
|
+
SpaceResult,
|
|
143
|
+
SpacesResult,
|
|
144
|
+
} from './application'
|
|
145
|
+
// OS node/session types (from the shell package, single import surface).
|
|
146
|
+
export type { ApplicationNode, AppSession, RoleNode, SpaceNode, UserNode } from '@astrale-os/shell'
|
|
147
|
+
|
|
148
|
+
// ─── shared envelopes (§1) ───────────────────────────────────────────────────
|
|
149
|
+
export type { Action, ReadState, ReadOptions, Windowed } from './graph'
|
|
150
|
+
|
|
151
|
+
// ─── error taxonomy (§1.4) — guards + classes, re-exported from kernel-client ─
|
|
152
|
+
export * from './graph/guards.error'
|
|
153
|
+
|
|
154
|
+
// ─── re-exported kernel-client types (one import surface, §0 preamble) ───────
|
|
155
|
+
export {
|
|
156
|
+
DESCRIPTION_KEY,
|
|
157
|
+
NAME_KEY,
|
|
158
|
+
STATUS_KEY,
|
|
159
|
+
descriptionOf,
|
|
160
|
+
nameOf,
|
|
161
|
+
normalizeRef,
|
|
162
|
+
statusOf,
|
|
163
|
+
} from '@astrale-os/kernel-client'
|
|
164
|
+
export type {
|
|
165
|
+
AnyBoundNode,
|
|
166
|
+
BoundNode,
|
|
167
|
+
CreatableClassNames,
|
|
168
|
+
CreateProps,
|
|
169
|
+
CredentialInput,
|
|
170
|
+
DefOf,
|
|
171
|
+
EdgeNames,
|
|
172
|
+
EndpointsOf,
|
|
173
|
+
FnMap,
|
|
174
|
+
NamedNodeLike,
|
|
175
|
+
NodeRefInput,
|
|
176
|
+
SchemaBoundView,
|
|
177
|
+
TypeNames,
|
|
178
|
+
} from '@astrale-os/kernel-client'
|
|
179
|
+
export type {
|
|
180
|
+
EdgeIdentity,
|
|
181
|
+
GatherBuilder,
|
|
182
|
+
OrderInput,
|
|
183
|
+
PatchBuilder,
|
|
184
|
+
PatchInput,
|
|
185
|
+
PathLike,
|
|
186
|
+
Properties,
|
|
187
|
+
QueryASTInput,
|
|
188
|
+
QuerySource,
|
|
189
|
+
} from '@astrale-os/kernel-client/graph'
|
|
190
|
+
export type { AuthApi, Perms } from '@astrale-os/kernel-client/auth'
|
|
191
|
+
export type { Schema } from '@astrale-os/kernel-dsl'
|
|
192
|
+
|
|
193
|
+
// ─── re-exported shell types (one import surface, SPEC §4.2) ──────────────────
|
|
194
|
+
export type {
|
|
195
|
+
CloseResult,
|
|
196
|
+
KernelClient,
|
|
197
|
+
MountCredential,
|
|
198
|
+
Shell,
|
|
199
|
+
ShellConfig,
|
|
200
|
+
Window,
|
|
201
|
+
} from '@astrale-os/shell'
|
|
202
|
+
export type { IntentRegistry } from '@astrale-os/shell'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent envelope construction (API §11). The typed payload/reply projections of
|
|
3
|
+
* a registry entry, the wire constants, and `intentMessage` — the single builder
|
|
4
|
+
* both directions (send + reply) share.
|
|
5
|
+
*/
|
|
6
|
+
import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Payload of a registry entry: the `{ payload }` field for the wrapped shape,
|
|
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]
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reply of a registry entry: the `{ reply }` field when declared, else `void`
|
|
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
|
|
28
|
+
|
|
29
|
+
/** Sentinel `ctx.sender` carries for a parent-delivered intent (the shell stamps it). */
|
|
30
|
+
export const PARENT_SENDER = '<parent>'
|
|
31
|
+
/** Outbound `sender.windowId`; the receiver re-stamps it with the trusted id. */
|
|
32
|
+
const SELF_SENDER = 'self'
|
|
33
|
+
/** Correlated-reply channel name (the shell's fire-and-forget reply protocol). */
|
|
34
|
+
export const INTENT_REPLY = 'intentReply'
|
|
35
|
+
export const NOOP = (): void => {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build an intent wire message. Bare-vs-wrapped payload variance (§11 pin) is
|
|
39
|
+
* erased on the wire — the receiver reads `envelope.payload` verbatim; the cast
|
|
40
|
+
* bridges the correlated name↔payload the union type cannot express.
|
|
41
|
+
*/
|
|
42
|
+
export function intentMessage(
|
|
43
|
+
name: string,
|
|
44
|
+
payload: unknown,
|
|
45
|
+
correlationId?: string,
|
|
46
|
+
): IntentMessage {
|
|
47
|
+
return {
|
|
48
|
+
type: 'intent',
|
|
49
|
+
version: 1,
|
|
50
|
+
envelope: { name, payload, sender: { windowId: SELF_SENDER }, correlationId },
|
|
51
|
+
} as IntentMessage
|
|
52
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// intents — API §11 (useIntent, useSendIntent). See SHELL_REACT_API.md.
|
|
2
|
+
export { useIntent } from './listen-intent.hook'
|
|
3
|
+
export type { IntentCtx } from './listen-intent.hook'
|
|
4
|
+
export { useSendIntent } from './send-intent.hook'
|
|
5
|
+
export type { SendIntent } from './send-intent.hook'
|
|
6
|
+
export type { IntentPayload, IntentReply } from './envelope'
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useIntent (API §11) — subscribe ONE handler to inbound intents (post-middleware,
|
|
5
|
+
* via shell.onIntent) with the REAL sender windowId and an exactly-once,
|
|
6
|
+
* unmount-safe `ctx.reply`. The subscription identity is independent of the
|
|
7
|
+
* handler closure (latest-handler ref), so re-renders never re-register. Reply
|
|
8
|
+
* obligations are tracked per sender window so a child close drops ours before a
|
|
9
|
+
* late async handler can aim a reply at a gone counterparty.
|
|
10
|
+
*/
|
|
11
|
+
import { useEffect, useRef } from 'react'
|
|
12
|
+
|
|
13
|
+
import type { IntentPayload, IntentReply } from './envelope'
|
|
14
|
+
|
|
15
|
+
import { useShell } from '../session/shell.hook'
|
|
16
|
+
import { NOOP } from './envelope'
|
|
17
|
+
import { sendReply } from './reply'
|
|
18
|
+
|
|
19
|
+
/** Delivery context passed alongside an inbound intent's payload. */
|
|
20
|
+
export interface IntentCtx<K extends keyof IntentRegistry> {
|
|
21
|
+
/** REAL source windowId; the `'<parent>'` sentinel for a parent-delivered intent. */
|
|
22
|
+
readonly sender: string
|
|
23
|
+
/** Reply to the sender's pending request — exactly-once; dropped if it died. */
|
|
24
|
+
reply(result: IntentReply<K>): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Subscribe `handler` to inbound `name` intents. `ctx.sender` is the REAL
|
|
29
|
+
* windowId (the channel-authenticated id, or `'<parent>'` upstream); `ctx.reply`
|
|
30
|
+
* answers a correlated request once.
|
|
31
|
+
*/
|
|
32
|
+
export function useIntent<K extends keyof IntentRegistry>(
|
|
33
|
+
name: K,
|
|
34
|
+
handler: (payload: IntentPayload<K>, ctx: IntentCtx<K>) => void | Promise<void>,
|
|
35
|
+
): void {
|
|
36
|
+
const shell = useShell()
|
|
37
|
+
const handlerRef = useRef(handler)
|
|
38
|
+
handlerRef.current = handler
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
let alive = true
|
|
42
|
+
// Open reply obligations by sender window, so a child close drops ours
|
|
43
|
+
// before a late async handler can aim a reply at a gone counterparty.
|
|
44
|
+
const bySender = new Map<string, Set<() => void>>()
|
|
45
|
+
const track = (sender: string, drop: () => void): (() => void) => {
|
|
46
|
+
let set = bySender.get(sender)
|
|
47
|
+
if (set === undefined) {
|
|
48
|
+
set = new Set()
|
|
49
|
+
bySender.set(sender, set)
|
|
50
|
+
}
|
|
51
|
+
set.add(drop)
|
|
52
|
+
return () => set.delete(drop)
|
|
53
|
+
}
|
|
54
|
+
const offClose = shell.children.onClose((windowId) => {
|
|
55
|
+
const set = bySender.get(windowId)
|
|
56
|
+
if (set === undefined) return
|
|
57
|
+
for (const drop of set) drop()
|
|
58
|
+
bySender.delete(windowId)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const off = shell.onIntent(name, (message: IntentMessage<K>) => {
|
|
62
|
+
const { payload, correlationId } = message.envelope
|
|
63
|
+
const sender = message.envelope.sender.windowId
|
|
64
|
+
let settled = false
|
|
65
|
+
const untrack =
|
|
66
|
+
correlationId === undefined
|
|
67
|
+
? NOOP
|
|
68
|
+
: track(sender, () => {
|
|
69
|
+
settled = true
|
|
70
|
+
})
|
|
71
|
+
const ctx: IntentCtx<K> = {
|
|
72
|
+
sender,
|
|
73
|
+
reply(result) {
|
|
74
|
+
if (settled || !alive) return
|
|
75
|
+
settled = true
|
|
76
|
+
untrack()
|
|
77
|
+
if (correlationId === undefined) return
|
|
78
|
+
sendReply(shell, sender, correlationId, result)
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
void Promise.resolve(handlerRef.current(payload as IntentPayload<K>, ctx))
|
|
82
|
+
.catch(NOOP)
|
|
83
|
+
.finally(untrack)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return () => {
|
|
87
|
+
alive = false
|
|
88
|
+
off()
|
|
89
|
+
offClose()
|
|
90
|
+
}
|
|
91
|
+
}, [shell, name])
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reply delivery (API §11) — route a correlated reply to whichever channel the
|
|
3
|
+
* request arrived on (parent vs a child window). Best-effort: a gone counterparty
|
|
4
|
+
* drops the reply safely (counterparty-death cleanup).
|
|
5
|
+
*/
|
|
6
|
+
import type { Shell } from '@astrale-os/shell'
|
|
7
|
+
|
|
8
|
+
import { INTENT_REPLY, PARENT_SENDER, intentMessage } from './envelope'
|
|
9
|
+
|
|
10
|
+
/** Deliver a reply to whichever channel the request arrived on. Best-effort. */
|
|
11
|
+
export function sendReply(
|
|
12
|
+
shell: Shell,
|
|
13
|
+
sender: string,
|
|
14
|
+
correlationId: string,
|
|
15
|
+
result: unknown,
|
|
16
|
+
): void {
|
|
17
|
+
const message = intentMessage(INTENT_REPLY, { correlationId, result })
|
|
18
|
+
try {
|
|
19
|
+
if (sender === PARENT_SENDER) shell.parent?.send(message)
|
|
20
|
+
else shell.children.send(sender, message) // no-op if the child has detached
|
|
21
|
+
} catch {
|
|
22
|
+
// Counterparty gone — the reply is safely dropped.
|
|
23
|
+
}
|
|
24
|
+
}
|