@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,319 @@
|
|
|
1
|
+
import type { GatherBuilder, OrderInput, QuerySource } from '@astrale-os/kernel-client/graph'
|
|
2
|
+
import type {
|
|
3
|
+
LiveResult,
|
|
4
|
+
ReadOptions as StoreReadOptions,
|
|
5
|
+
ShapeInput,
|
|
6
|
+
} from '@astrale-os/kernel-client/store'
|
|
7
|
+
import type { Node } from '@astrale-os/kernel-core'
|
|
8
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
BindTypeMismatchError,
|
|
12
|
+
normalizeRef,
|
|
13
|
+
type AnyBoundNode,
|
|
14
|
+
type BoundNode,
|
|
15
|
+
type DefOf,
|
|
16
|
+
type NodeRefInput,
|
|
17
|
+
type SchemaBoundView,
|
|
18
|
+
type TypeNames,
|
|
19
|
+
} from '@astrale-os/kernel-client'
|
|
20
|
+
import { shapeInputToAST, stableStringify, stripCursors } from '@astrale-os/kernel-client/store'
|
|
21
|
+
import { compileDomain } from '@astrale-os/kernel-core/domain'
|
|
22
|
+
/**
|
|
23
|
+
* Binder-over-memory composition (API §6, §1.7) — consumed by the `graph/query/`
|
|
24
|
+
* overloads. Binds wire `Node`s from the shared memory to stable `BoundNode`s
|
|
25
|
+
* (an unchanged wire node re-binds to the SAME instance), and composes those
|
|
26
|
+
* binders with a `useSyncExternalStore` read substrate (`useShape`) into the typed
|
|
27
|
+
* `useNode`/`useChildren` reads. A typed read and its untyped twin ride the SAME
|
|
28
|
+
* memory shape, so they are one cache entry, one liveness, one overlay.
|
|
29
|
+
*/
|
|
30
|
+
import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react'
|
|
31
|
+
|
|
32
|
+
import type { ReadState, ReadOptions } from '../graph/memory/read/envelope'
|
|
33
|
+
import type { TypedChildrenResult, TypedNodeResult } from './types'
|
|
34
|
+
|
|
35
|
+
import { useSessionKernel } from '../auth/session-kernel'
|
|
36
|
+
import { idleReadState, isIdleInput } from '../graph/memory/read/idle'
|
|
37
|
+
import { useMemory } from '../graph/memory/scope'
|
|
38
|
+
import { bindingFor } from './binding'
|
|
39
|
+
import { schemaOf, tagSchema } from './tag'
|
|
40
|
+
|
|
41
|
+
// ── binder hooks ────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
interface OneCache<S extends Schema> {
|
|
44
|
+
wire: Node | null
|
|
45
|
+
binding: SchemaBoundView<S>
|
|
46
|
+
type: string
|
|
47
|
+
bound: BoundNode<unknown, S> | null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Bind one wire node as `type`; stable while the wire node, binding, and type hold. */
|
|
51
|
+
export function useBoundNode<S extends Schema, T extends TypeNames<S>>(
|
|
52
|
+
binding: SchemaBoundView<S>,
|
|
53
|
+
type: T,
|
|
54
|
+
wire: Node | null | undefined,
|
|
55
|
+
): BoundNode<DefOf<S, T>, S> | null {
|
|
56
|
+
const ref = useRef<OneCache<S> | null>(null)
|
|
57
|
+
const node = wire ?? null
|
|
58
|
+
let entry = ref.current
|
|
59
|
+
if (entry === null || entry.wire !== node || entry.binding !== binding || entry.type !== type) {
|
|
60
|
+
entry = {
|
|
61
|
+
wire: node,
|
|
62
|
+
binding,
|
|
63
|
+
type,
|
|
64
|
+
bound: node === null ? null : (binding.bind(type, node) as BoundNode<unknown, S>),
|
|
65
|
+
}
|
|
66
|
+
ref.current = entry
|
|
67
|
+
}
|
|
68
|
+
return entry.bound as BoundNode<DefOf<S, T>, S> | null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ManyCache<S extends Schema> {
|
|
72
|
+
binding: SchemaBoundView<S>
|
|
73
|
+
type: string
|
|
74
|
+
perNode: WeakMap<Node, BoundNode<unknown, S>>
|
|
75
|
+
last: readonly BoundNode<unknown, S>[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Bind many wire nodes as `type`; per-node identity + a stable array on no
|
|
80
|
+
* change. `skipMismatch` drops nodes whose class is not `type` (a mixed-class
|
|
81
|
+
* child set) instead of throwing.
|
|
82
|
+
*/
|
|
83
|
+
export function useBoundNodes<S extends Schema, T extends TypeNames<S>>(
|
|
84
|
+
binding: SchemaBoundView<S>,
|
|
85
|
+
type: T,
|
|
86
|
+
wires: readonly Node[],
|
|
87
|
+
skipMismatch = false,
|
|
88
|
+
): readonly BoundNode<DefOf<S, T>, S>[] {
|
|
89
|
+
const ref = useRef<ManyCache<S> | null>(null)
|
|
90
|
+
if (ref.current === null || ref.current.binding !== binding || ref.current.type !== type) {
|
|
91
|
+
ref.current = { binding, type, perNode: new WeakMap(), last: [] }
|
|
92
|
+
}
|
|
93
|
+
const cache = ref.current
|
|
94
|
+
const next: BoundNode<unknown, S>[] = []
|
|
95
|
+
for (const w of wires) {
|
|
96
|
+
let b = cache.perNode.get(w)
|
|
97
|
+
if (b === undefined) {
|
|
98
|
+
try {
|
|
99
|
+
b = binding.bind(type, w) as BoundNode<unknown, S>
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (skipMismatch && error instanceof BindTypeMismatchError) continue
|
|
102
|
+
throw error
|
|
103
|
+
}
|
|
104
|
+
cache.perNode.set(w, b)
|
|
105
|
+
}
|
|
106
|
+
next.push(b)
|
|
107
|
+
}
|
|
108
|
+
if (sameArray(cache.last, next)) return cache.last as readonly BoundNode<DefOf<S, T>, S>[]
|
|
109
|
+
cache.last = next
|
|
110
|
+
return next as readonly BoundNode<DefOf<S, T>, S>[]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface EndpointCache<S extends Schema> {
|
|
114
|
+
binding: SchemaBoundView<S>
|
|
115
|
+
perNode: WeakMap<Node, AnyBoundNode<S>>
|
|
116
|
+
last: readonly AnyBoundNode<S>[]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Bind many wire nodes classlessly (traversal endpoints); per-node + stable array. */
|
|
120
|
+
export function useBoundEndpoints<S extends Schema>(
|
|
121
|
+
binding: SchemaBoundView<S>,
|
|
122
|
+
wires: readonly Node[],
|
|
123
|
+
): readonly AnyBoundNode<S>[] {
|
|
124
|
+
const ref = useRef<EndpointCache<S> | null>(null)
|
|
125
|
+
if (ref.current === null || ref.current.binding !== binding) {
|
|
126
|
+
ref.current = { binding, perNode: new WeakMap(), last: [] }
|
|
127
|
+
}
|
|
128
|
+
const cache = ref.current
|
|
129
|
+
const next = wires.map((w) => {
|
|
130
|
+
let b = cache.perNode.get(w)
|
|
131
|
+
if (b === undefined) {
|
|
132
|
+
b = binding.bindAny(w)
|
|
133
|
+
cache.perNode.set(w, b)
|
|
134
|
+
}
|
|
135
|
+
return b
|
|
136
|
+
})
|
|
137
|
+
if (sameArray(cache.last, next)) return cache.last
|
|
138
|
+
cache.last = next
|
|
139
|
+
return next
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function sameArray<A>(a: readonly A[], b: readonly A[]): boolean {
|
|
143
|
+
return a.length === b.length && a.every((x, i) => x === b[i])
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── read substrate ────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/** Base read options plus the window growth mode (a `useChildren`-specific extra). */
|
|
149
|
+
export type ShapeOptions = ReadOptions & { window?: 'append' | 'paged' }
|
|
150
|
+
|
|
151
|
+
/** Store-facing options — render-time concerns (`suspend`/`keepPrevious`) stay here. */
|
|
152
|
+
function toStoreOptions(opts?: ShapeOptions): StoreReadOptions | undefined {
|
|
153
|
+
if (opts === undefined) return undefined
|
|
154
|
+
const out: StoreReadOptions = {}
|
|
155
|
+
if (opts.live !== undefined) out.live = opts.live
|
|
156
|
+
if (opts.window !== undefined) out.window = opts.window
|
|
157
|
+
if (opts.with !== undefined) out.with = opts.with as unknown as StoreReadOptions['with']
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A stable string identity for a (shape, options) pair — the subscription key. */
|
|
162
|
+
function keyOf(shape: ShapeInput, opts?: ShapeOptions): string {
|
|
163
|
+
const ast = stripCursors(shapeInputToAST(shape))
|
|
164
|
+
return `${stableStringify(ast)}|${opts?.live === false ? '0' : '1'}|${opts?.window ?? ''}|${opts?.with ?? ''}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Subscribe to a memory shape. `null` shape → idle (`null`, no I/O). A cold
|
|
169
|
+
* shape suspends unless `suspend: false`. The returned `LiveResult` is the
|
|
170
|
+
* unified read envelope — callers project `node`/`children`/`nodes`.
|
|
171
|
+
*/
|
|
172
|
+
export function useShape(
|
|
173
|
+
shape: ShapeInput | null | undefined,
|
|
174
|
+
opts?: ShapeOptions,
|
|
175
|
+
): LiveResult | null {
|
|
176
|
+
const memory = useMemory()
|
|
177
|
+
const storeOpts = toStoreOptions(opts)
|
|
178
|
+
const key = isIdleInput(shape) ? null : keyOf(shape, opts)
|
|
179
|
+
|
|
180
|
+
const subscribe = useCallback(
|
|
181
|
+
(onChange: () => void): (() => void) => {
|
|
182
|
+
if (isIdleInput(shape)) return () => {}
|
|
183
|
+
return memory.subscribe(shape, onChange, storeOpts)
|
|
184
|
+
},
|
|
185
|
+
// The shape's canonical identity is `key`; the captured `shape` is equivalent.
|
|
186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
187
|
+
[memory, key],
|
|
188
|
+
)
|
|
189
|
+
const getSnapshot = useCallback(
|
|
190
|
+
(): LiveResult | null => (isIdleInput(shape) ? null : memory.getSnapshot(shape, storeOpts)),
|
|
191
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
|
+
[memory, key],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
|
196
|
+
|
|
197
|
+
if (!isIdleInput(shape) && opts?.suspend !== false && memory.peek(shape, storeOpts) === null) {
|
|
198
|
+
// Cold FAILURE (§1.4): a rejected fetch with no data throws to the boundary;
|
|
199
|
+
// re-throwing the promise would re-arm a fetch each retry (infinite loop).
|
|
200
|
+
if (snapshot !== null && snapshot.error !== undefined) throw snapshot.error
|
|
201
|
+
throw memory.promise(shape, storeOpts)
|
|
202
|
+
}
|
|
203
|
+
return snapshot
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── typed reads (schema-first useNode, BoundNode-first useChildren) ──────────────
|
|
207
|
+
|
|
208
|
+
export function useNode<S extends Schema, T extends TypeNames<S>>(
|
|
209
|
+
schema: S,
|
|
210
|
+
type: T,
|
|
211
|
+
ref: NodeRefInput | null | undefined,
|
|
212
|
+
opts?: ReadOptions,
|
|
213
|
+
): TypedNodeResult<S, T> {
|
|
214
|
+
const binding = bindingFor(useSessionKernel(), schema, useMemory())
|
|
215
|
+
const shape = isIdleInput(ref) ? null : normalizeRef(ref)
|
|
216
|
+
const snap = useShape(shape, opts)
|
|
217
|
+
const wire = snap?.node ?? null
|
|
218
|
+
const bound = useBoundNode(binding, type, wire)
|
|
219
|
+
if (bound !== null) tagSchema(bound, schema)
|
|
220
|
+
const live = snap === null ? idleReadState() : readStateOf(snap)
|
|
221
|
+
return useMemo(
|
|
222
|
+
() => ({ ...live, node: bound }),
|
|
223
|
+
[bound, live.pending, live.error, live.live, live.seq],
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function useChildren<S extends Schema, T extends TypeNames<S>>(
|
|
228
|
+
parent: AnyBoundNode<S> | null | undefined,
|
|
229
|
+
opts: ReadOptions & {
|
|
230
|
+
type: T
|
|
231
|
+
order?: OrderInput
|
|
232
|
+
limit?: number
|
|
233
|
+
window?: 'append' | 'paged'
|
|
234
|
+
},
|
|
235
|
+
): TypedChildrenResult<S, T> {
|
|
236
|
+
const kernel = useSessionKernel()
|
|
237
|
+
const memory = useMemory()
|
|
238
|
+
const schema = isIdleInput(parent) ? null : requireSchema(parent)
|
|
239
|
+
const binding = schema === null ? null : bindingFor(kernel, schema, memory)
|
|
240
|
+
|
|
241
|
+
const parentRef = isIdleInput(parent) ? null : `@${parent.id}`
|
|
242
|
+
const classRef = schema === null ? undefined : classRefFor(schema, opts.type)
|
|
243
|
+
const shape =
|
|
244
|
+
parentRef === null
|
|
245
|
+
? null
|
|
246
|
+
: (q: QuerySource): GatherBuilder =>
|
|
247
|
+
q.from(parentRef).children({
|
|
248
|
+
...(classRef !== undefined ? { classes: [classRef] } : {}),
|
|
249
|
+
...(opts.order !== undefined ? { order: opts.order } : {}),
|
|
250
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const snap = useShape(shape, opts)
|
|
254
|
+
const wires = snap?.children ?? EMPTY
|
|
255
|
+
const bound = useBoundNodes(
|
|
256
|
+
(binding ?? UNBOUND) as unknown as SchemaBoundView<S>,
|
|
257
|
+
opts.type,
|
|
258
|
+
binding === null ? EMPTY : wires,
|
|
259
|
+
true,
|
|
260
|
+
)
|
|
261
|
+
if (schema !== null) for (const b of bound) tagSchema(b, schema)
|
|
262
|
+
|
|
263
|
+
const live = snap === null ? idleReadState() : readStateOf(snap)
|
|
264
|
+
const more = snap?.more ?? IDLE_MORE
|
|
265
|
+
return useMemo(
|
|
266
|
+
() => ({ ...live, more, children: bound }),
|
|
267
|
+
[bound, more, live.pending, live.error, live.live, live.seq],
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
const EMPTY: readonly never[] = Object.freeze([])
|
|
274
|
+
const IDLE_MORE = { has: false, load: () => Promise.resolve(), pending: false } as const
|
|
275
|
+
const UNBOUND = {} as never
|
|
276
|
+
|
|
277
|
+
function readStateOf(snap: {
|
|
278
|
+
pending: boolean
|
|
279
|
+
error: unknown
|
|
280
|
+
live: boolean
|
|
281
|
+
seq: number
|
|
282
|
+
refetch(): Promise<void>
|
|
283
|
+
}): ReadState {
|
|
284
|
+
return {
|
|
285
|
+
pending: snap.pending,
|
|
286
|
+
error: snap.error,
|
|
287
|
+
live: snap.live,
|
|
288
|
+
seq: snap.seq,
|
|
289
|
+
refetch: snap.refetch,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Recover the schema a BoundNode was bound under; a clear error if it is untagged. */
|
|
294
|
+
export function requireSchema(node: object): Schema {
|
|
295
|
+
const schema = schemaOf(node)
|
|
296
|
+
if (schema === undefined) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
'useChildren: the parent was not produced by a shell-react typed binding — ' +
|
|
299
|
+
'obtain it from useNode / useDomain(schema).bind() so its schema is known.',
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
return schema
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const compiledCache = new WeakMap<Schema, ReturnType<typeof compileDomain>>()
|
|
306
|
+
|
|
307
|
+
/** The descent class ref for a type name (class or interface), or undefined. */
|
|
308
|
+
function classRefFor(schema: Schema, type: string): string | undefined {
|
|
309
|
+
let compiled = compiledCache.get(schema)
|
|
310
|
+
if (compiled === undefined) {
|
|
311
|
+
compiled = compileDomain(schema)
|
|
312
|
+
compiledCache.set(schema, compiled)
|
|
313
|
+
}
|
|
314
|
+
const accessor = compiled.$ as unknown as {
|
|
315
|
+
c(name: string): { path?: { class?: { raw: string } } } | undefined
|
|
316
|
+
i(name: string): { path?: { class?: { raw: string } } } | undefined
|
|
317
|
+
}
|
|
318
|
+
return accessor.c(type)?.path?.class?.raw ?? accessor.i(type)?.path?.class?.raw
|
|
319
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bindingFor(kernel, schema) — memoized `withSchema` handles (API §6). The
|
|
3
|
+
* `withSchema` handle is a *binding*, never a "view". Memoizes
|
|
4
|
+
* `createSchemaBoundView` per (session, schema) so a binding — and the BoundNodes
|
|
5
|
+
* it mints — keep referential identity across renders. Each binding is wrapped so
|
|
6
|
+
* every BoundNode it hands back is schema-tagged (the traversal hooks read it
|
|
7
|
+
* back) and its `id → tree path` recorded (the write capability's index).
|
|
8
|
+
*/
|
|
9
|
+
import type { FnMap, NodeRefInput, SchemaBoundView } from '@astrale-os/kernel-client'
|
|
10
|
+
import type { GraphMemory } from '@astrale-os/kernel-client/store'
|
|
11
|
+
import type { Node } from '@astrale-os/kernel-core'
|
|
12
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
13
|
+
|
|
14
|
+
import { createSchemaBoundView } from '@astrale-os/kernel-client'
|
|
15
|
+
|
|
16
|
+
import type { SessionView } from '../auth/act-as.context'
|
|
17
|
+
|
|
18
|
+
import { tagSchema } from './tag'
|
|
19
|
+
import { recordPath, writableFor } from './write'
|
|
20
|
+
|
|
21
|
+
type AnyBinding = SchemaBoundView<Schema, FnMap>
|
|
22
|
+
|
|
23
|
+
/** One tagged binding per (kernel, schema) — shared across the whole tree. */
|
|
24
|
+
const bindingCache = new WeakMap<object, WeakMap<Schema, AnyBinding>>()
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve (and memoize) the tagged schema-bound handle for `schema` on `kernel`.
|
|
28
|
+
* `kernel` is the EFFECTIVE dispatch kernel (root or `<ActAs>` — §4); the cache is
|
|
29
|
+
* keyed on it, so an acted-as subtree gets its own memoized bindings for free.
|
|
30
|
+
* `memory` is the SAME scoped memory the read hooks use (1:1 with `kernel`).
|
|
31
|
+
*/
|
|
32
|
+
export function bindingFor<S extends Schema>(
|
|
33
|
+
kernel: SessionView,
|
|
34
|
+
schema: S,
|
|
35
|
+
memory: GraphMemory,
|
|
36
|
+
): SchemaBoundView<S, FnMap> {
|
|
37
|
+
let bySchema = bindingCache.get(kernel)
|
|
38
|
+
if (bySchema === undefined) {
|
|
39
|
+
bySchema = new WeakMap()
|
|
40
|
+
bindingCache.set(kernel, bySchema)
|
|
41
|
+
}
|
|
42
|
+
let binding = bySchema.get(schema)
|
|
43
|
+
if (binding === undefined) {
|
|
44
|
+
const { writable, pathIndex } = writableFor(kernel, memory)
|
|
45
|
+
binding = tagBinding(createSchemaBoundView(writable, schema) as AnyBinding, schema, pathIndex)
|
|
46
|
+
bySchema.set(schema, binding)
|
|
47
|
+
}
|
|
48
|
+
return binding as SchemaBoundView<S, FnMap>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Wrap a binding so `bind`/`bindAny`/`as` outputs carry a schema tag, and every
|
|
53
|
+
* bound wire node's `id → tree path` is recorded (the write cap's index); the
|
|
54
|
+
* bare-call dispatch and every other member pass straight through.
|
|
55
|
+
*/
|
|
56
|
+
function tagBinding(
|
|
57
|
+
binding: AnyBinding,
|
|
58
|
+
schema: Schema,
|
|
59
|
+
pathIndex: Map<string, string>,
|
|
60
|
+
): AnyBinding {
|
|
61
|
+
const handler: ProxyHandler<AnyBinding> = {
|
|
62
|
+
apply: (target, _thisArg, args) =>
|
|
63
|
+
(target as unknown as (...a: unknown[]) => unknown)(...(args as unknown[])),
|
|
64
|
+
get: (target, prop, receiver) => {
|
|
65
|
+
const value = Reflect.get(target, prop, receiver) as unknown
|
|
66
|
+
if (prop === 'bind') {
|
|
67
|
+
return (type: string, node: Node, opts?: unknown) => {
|
|
68
|
+
recordPath(pathIndex, node)
|
|
69
|
+
return tagSchema(
|
|
70
|
+
(value as (t: string, n: Node, o?: unknown) => unknown)(type, node, opts),
|
|
71
|
+
schema,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (prop === 'bindAny') {
|
|
76
|
+
return (node: Node) => {
|
|
77
|
+
recordPath(pathIndex, node)
|
|
78
|
+
return tagSchema((value as (n: Node) => unknown)(node), schema)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (prop === 'as') {
|
|
82
|
+
return (credential: unknown) =>
|
|
83
|
+
tagBinding((value as (c: unknown) => AnyBinding)(credential), schema, pathIndex)
|
|
84
|
+
}
|
|
85
|
+
return value
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
return new Proxy(binding, handler)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type { NodeRefInput }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDomain(schema) (API §6) — the typed domain binding, `kernel.withSchema(schema)`,
|
|
3
|
+
* memoized per (effective session, schema). The BoundNodes it mints ride the
|
|
4
|
+
* shared scoped memory, so typed writes are optimistic and revalidate (§6).
|
|
5
|
+
*/
|
|
6
|
+
import type { FnMap, SchemaBoundView } from '@astrale-os/kernel-client'
|
|
7
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
8
|
+
|
|
9
|
+
import { useSessionKernel } from '../auth/session-kernel'
|
|
10
|
+
import { useMemory } from '../graph/memory/scope'
|
|
11
|
+
import { bindingFor } from './binding'
|
|
12
|
+
|
|
13
|
+
/** The typed domain binding — `kernel.withSchema(schema)`, memoized per (session, schema). */
|
|
14
|
+
export function useDomain<S extends Schema>(schema: S): SchemaBoundView<S, FnMap> {
|
|
15
|
+
return bindingFor(useSessionKernel(), schema, useMemory())
|
|
16
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
2
|
+
|
|
3
|
+
import { compileDomain } from '@astrale-os/kernel-core/domain'
|
|
4
|
+
/**
|
|
5
|
+
* `useFunction` (API §6) — `useMethod`'s sibling for a domain's top-level
|
|
6
|
+
* standalone functions (the `functions` map of `defineSchema`). Dispatched by the
|
|
7
|
+
* compiled FunctionPath from the compiled-domain accessor, never string-built.
|
|
8
|
+
* Dispatch is correct but not compile-time typed (see the note in schema/types.ts).
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useMemo, useRef } from 'react'
|
|
11
|
+
|
|
12
|
+
import type { Action } from '../graph/action'
|
|
13
|
+
import type { FunctionNames, FunctionParams, FunctionResult } from './types'
|
|
14
|
+
|
|
15
|
+
import { useAction } from '../graph/action'
|
|
16
|
+
import { useDomain } from './domain.hook'
|
|
17
|
+
|
|
18
|
+
export interface FunctionOptions {
|
|
19
|
+
/** Extra failure channel; the action still rejects and records `.error`. */
|
|
20
|
+
onError?: (error: unknown) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const compiledCache = new WeakMap<Schema, ReturnType<typeof compileDomain>>()
|
|
24
|
+
|
|
25
|
+
/** The dispatch path for a standalone function, from the compiled accessor. */
|
|
26
|
+
function functionPathOf(schema: Schema, name: string): string {
|
|
27
|
+
let compiled = compiledCache.get(schema)
|
|
28
|
+
if (compiled === undefined) {
|
|
29
|
+
compiled = compileDomain(schema)
|
|
30
|
+
compiledCache.set(schema, compiled)
|
|
31
|
+
}
|
|
32
|
+
const entry = compiled.$.f(name)
|
|
33
|
+
if (entry === undefined) {
|
|
34
|
+
throw new Error(`Unknown function '${name}' in schema '${schema.domain}'`)
|
|
35
|
+
}
|
|
36
|
+
return entry.path.domain.raw
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type RawCall = (method: string, params: unknown, opts?: unknown) => Promise<unknown>
|
|
40
|
+
|
|
41
|
+
export function useFunction<S extends Schema, F extends FunctionNames<S>>(
|
|
42
|
+
schema: S,
|
|
43
|
+
fn: F,
|
|
44
|
+
opts?: FunctionOptions,
|
|
45
|
+
): Action<[FunctionParams<S, F>], FunctionResult<S, F>> {
|
|
46
|
+
const binding = useDomain(schema)
|
|
47
|
+
const path = useMemo(() => functionPathOf(schema, String(fn)), [schema, fn])
|
|
48
|
+
|
|
49
|
+
const onErrorRef = useRef(opts?.onError)
|
|
50
|
+
onErrorRef.current = opts?.onError
|
|
51
|
+
|
|
52
|
+
const call = useCallback(
|
|
53
|
+
async (params: FunctionParams<S, F>): Promise<FunctionResult<S, F>> => {
|
|
54
|
+
try {
|
|
55
|
+
return (await (binding.raw.call as unknown as RawCall)(path, params)) as FunctionResult<
|
|
56
|
+
S,
|
|
57
|
+
F
|
|
58
|
+
>
|
|
59
|
+
} catch (error) {
|
|
60
|
+
onErrorRef.current?.(error)
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[binding, path],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return useAction(call)
|
|
68
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// schema — the typing substrate (API §6). Typed kernel/domain bindings, typed
|
|
2
|
+
// reads, method/function dispatch. See SHELL_REACT_API.md.
|
|
3
|
+
export { useKernel } from './kernel.hook'
|
|
4
|
+
export { useDomain } from './domain.hook'
|
|
5
|
+
export { useNode, useChildren, useBoundNode, useBoundNodes, useBoundEndpoints } from './bind-read'
|
|
6
|
+
export { useOut, useIn } from '../graph/query/neighbors.hook'
|
|
7
|
+
export { useMethod } from './method.hook'
|
|
8
|
+
export type { MethodOptions } from './method.hook'
|
|
9
|
+
export { useFunction } from './function.hook'
|
|
10
|
+
export type { FunctionOptions } from './function.hook'
|
|
11
|
+
export type {
|
|
12
|
+
AutoMethodAction,
|
|
13
|
+
BoundMethodAction,
|
|
14
|
+
EndpointResult,
|
|
15
|
+
FunctionNames,
|
|
16
|
+
FunctionParams,
|
|
17
|
+
FunctionResult,
|
|
18
|
+
MethodIsStatic,
|
|
19
|
+
MethodNames,
|
|
20
|
+
MethodParams,
|
|
21
|
+
MethodResult,
|
|
22
|
+
TargetedMethodAction,
|
|
23
|
+
TypedChildrenResult,
|
|
24
|
+
TypedNodeResult,
|
|
25
|
+
} from './types'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useKernel (API §6) — the typed kernel, `kernel.withSchema(KernelSchema)`,
|
|
3
|
+
* memoized per (effective) session. The kernel's own schema baked in; dispatch
|
|
4
|
+
* and reads run as the subtree's principal over its scoped memory (§4).
|
|
5
|
+
*/
|
|
6
|
+
import type { FnMap, SchemaBoundView } from '@astrale-os/kernel-client'
|
|
7
|
+
|
|
8
|
+
import { KernelSchema } from '@astrale-os/kernel-core'
|
|
9
|
+
|
|
10
|
+
import { useSessionKernel } from '../auth/session-kernel'
|
|
11
|
+
import { useMemory } from '../graph/memory/scope'
|
|
12
|
+
import { bindingFor } from './binding'
|
|
13
|
+
|
|
14
|
+
/** The typed kernel — `kernel.withSchema(KernelSchema)`, memoized (§4-scoped). */
|
|
15
|
+
export function useKernel(): SchemaBoundView<typeof KernelSchema, FnMap> {
|
|
16
|
+
return bindingFor(useSessionKernel(), KernelSchema, useMemory())
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { NodeRefInput, TypeNames } from '@astrale-os/kernel-client'
|
|
2
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
3
|
+
|
|
4
|
+
import { resolveAllMethods } from '@astrale-os/kernel-dsl'
|
|
5
|
+
/**
|
|
6
|
+
* `useMethod` (API §6) — typed method dispatch with call state. Three forms,
|
|
7
|
+
* selected by the method's static-ness and whether `on` is given:
|
|
8
|
+
* static → action(params)
|
|
9
|
+
* instance + on → action(params) (bound to that node)
|
|
10
|
+
* instance, no on → action(ref, params)
|
|
11
|
+
* Params/result come from the method's Zod contracts.
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback, useMemo, useRef } from 'react'
|
|
14
|
+
|
|
15
|
+
import type { Action } from '../graph/action'
|
|
16
|
+
import type { AutoMethodAction, BoundMethodAction, MethodNames } from './types'
|
|
17
|
+
|
|
18
|
+
import { useAction } from '../graph/action'
|
|
19
|
+
import { useDomain } from './domain.hook'
|
|
20
|
+
|
|
21
|
+
export interface MethodOptions {
|
|
22
|
+
/** Bind the action to this node — dispatch as `action(params)`. */
|
|
23
|
+
on?: NodeRefInput
|
|
24
|
+
/** Extra failure channel; the action still rejects and records `.error`. */
|
|
25
|
+
onError?: (error: unknown) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Dispatch = Record<string, (params: unknown) => Promise<unknown>>
|
|
29
|
+
|
|
30
|
+
export function useMethod<S extends Schema, T extends TypeNames<S>, M extends MethodNames<S, T>>(
|
|
31
|
+
schema: S,
|
|
32
|
+
type: T,
|
|
33
|
+
method: M,
|
|
34
|
+
opts: MethodOptions & { on: NodeRefInput },
|
|
35
|
+
): BoundMethodAction<S, T, M>
|
|
36
|
+
export function useMethod<S extends Schema, T extends TypeNames<S>, M extends MethodNames<S, T>>(
|
|
37
|
+
schema: S,
|
|
38
|
+
type: T,
|
|
39
|
+
method: M,
|
|
40
|
+
opts?: MethodOptions,
|
|
41
|
+
): AutoMethodAction<S, T, M>
|
|
42
|
+
export function useMethod<S extends Schema, T extends TypeNames<S>, M extends MethodNames<S, T>>(
|
|
43
|
+
schema: S,
|
|
44
|
+
type: T,
|
|
45
|
+
method: M,
|
|
46
|
+
opts?: MethodOptions,
|
|
47
|
+
): Action<never[], unknown> {
|
|
48
|
+
const binding = useDomain(schema) as unknown as Dispatcher
|
|
49
|
+
const on = opts?.on
|
|
50
|
+
const isStatic = useMemo(() => detectStatic(schema, type, String(method)), [schema, type, method])
|
|
51
|
+
|
|
52
|
+
const onErrorRef = useRef(opts?.onError)
|
|
53
|
+
onErrorRef.current = opts?.onError
|
|
54
|
+
|
|
55
|
+
const call = useCallback(
|
|
56
|
+
async (...args: unknown[]): Promise<unknown> => {
|
|
57
|
+
const name = String(method)
|
|
58
|
+
try {
|
|
59
|
+
if (on !== undefined) return await instance(binding, type as string, on, name, args[0])
|
|
60
|
+
if (isStatic) return await staticCall(binding, type as string, name, args[0])
|
|
61
|
+
return await instance(binding, type as string, args[0] as NodeRefInput, name, args[1])
|
|
62
|
+
} catch (error) {
|
|
63
|
+
onErrorRef.current?.(error)
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
[binding, type, method, on, isStatic],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return useAction(call)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The bare-call dispatch signature of a `SchemaBoundView`, address-only. */
|
|
74
|
+
type Dispatcher = (type: string, ref?: NodeRefInput) => unknown
|
|
75
|
+
|
|
76
|
+
function staticCall(
|
|
77
|
+
binding: Dispatcher,
|
|
78
|
+
type: string,
|
|
79
|
+
method: string,
|
|
80
|
+
params: unknown,
|
|
81
|
+
): Promise<unknown> {
|
|
82
|
+
return (binding(type) as unknown as Dispatch)[method]!(params)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function instance(
|
|
86
|
+
binding: Dispatcher,
|
|
87
|
+
type: string,
|
|
88
|
+
ref: NodeRefInput,
|
|
89
|
+
method: string,
|
|
90
|
+
params: unknown,
|
|
91
|
+
): Promise<unknown> {
|
|
92
|
+
return (binding(type, ref) as unknown as Dispatch)[method]!(params)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Read the method's `static` flag from the schema (own + inherited). */
|
|
96
|
+
function detectStatic(schema: Schema, type: string, method: string): boolean {
|
|
97
|
+
const def =
|
|
98
|
+
(schema.classes as Record<string, unknown>)[type] ??
|
|
99
|
+
(schema.interfaces as Record<string, unknown>)[type]
|
|
100
|
+
if (def === undefined) return false
|
|
101
|
+
const methods = resolveAllMethods(def as Parameters<typeof resolveAllMethods>[0])
|
|
102
|
+
return methods[method]?.config.static === true
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-tagging of raw nodes (API §6, the bindAny bridge). A module-level tag
|
|
3
|
+
* records the schema each BoundNode was bound under, so the traversal hooks can
|
|
4
|
+
* recover it from the node alone (`useChildren`/`useOut`/`useIn` re-bind endpoints).
|
|
5
|
+
*/
|
|
6
|
+
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
7
|
+
|
|
8
|
+
const schemaOfNode = new WeakMap<object, Schema>()
|
|
9
|
+
|
|
10
|
+
/** Record the schema a BoundNode was bound under; returns the node unchanged. */
|
|
11
|
+
export function tagSchema<N>(node: N, schema: Schema): N {
|
|
12
|
+
if (node !== null && typeof node === 'object') schemaOfNode.set(node, schema)
|
|
13
|
+
return node
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The schema a BoundNode was bound under, if it came from a shell-react binding. */
|
|
17
|
+
export function schemaOf(node: object): Schema | undefined {
|
|
18
|
+
return schemaOfNode.get(node)
|
|
19
|
+
}
|