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