@astrale-os/shell-react 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * useOut / useIn (API §6) — the public typed traversal read hooks. Resolve
3
- * endpoints one-shot through `BoundNode.out/in` (`live: false`); become live with
3
+ * endpoints one-shot through `BoundNode.links.out/in` (`live: false`); become live with
4
4
  * no surface change once edge-endpoint ids land. Deviations tracked in API §6/G6.
5
5
  */
6
6
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -17,10 +17,7 @@ function useEndpoint(node, edge, side, _opts) {
17
17
  const run = useCallback(async () => {
18
18
  if (isIdleInput(node))
19
19
  return null;
20
- // AnyBoundNode omits traversal at the type level; the runtime node (bindAny
21
- // → createBoundNode) carries out/in. The edge name is typed by the caller.
22
- const t = node;
23
- const result = await (side === 'out' ? t.out(edge) : t.in(edge));
20
+ const result = await (side === 'out' ? node.links.out(edge) : node.links.in(edge));
24
21
  if (schema !== undefined)
25
22
  tagEndpoints(result, schema);
26
23
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/schema/binding.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AACrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAElE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAU5D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,WAAW,GAClB,eAAe,CAAC,CAAC,EAAE,KAAK,CAAC,CAa3B;AA0CD,YAAY,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/schema/binding.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AACrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAElE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAQ5D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,WAAW,GAClB,eAAe,CAAC,CAAC,EAAE,KAAK,CAAC,CAa3B;AAyCD,YAAY,EAAE,YAAY,EAAE,CAAA"}
@@ -15,22 +15,21 @@ export function bindingFor(kernel, schema, memory) {
15
15
  bySchema = new WeakMap();
16
16
  bindingCache.set(kernel, bySchema);
17
17
  }
18
- let binding = bySchema.get(schema);
19
- if (binding === undefined) {
20
- const { writable, pathIndex } = writableFor(kernel, memory);
21
- binding = tagBinding(createSchemaBoundView(writable, schema), schema, pathIndex);
22
- bySchema.set(schema, binding);
23
- }
18
+ const cached = bySchema.get(schema);
19
+ if (cached !== undefined)
20
+ return cached;
21
+ const { writable, pathIndex } = writableFor(kernel, memory);
22
+ const binding = tagBinding(createSchemaBoundView(writable, schema), schema, pathIndex);
23
+ bySchema.set(schema, binding);
24
24
  return binding;
25
25
  }
26
26
  /**
27
27
  * Wrap a binding so `bind`/`bindAny`/`as` outputs carry a schema tag, and every
28
28
  * bound wire node's `id → tree path` is recorded (the write cap's index); the
29
- * bare-call dispatch and every other member pass straight through.
29
+ * class handles and every other member pass straight through.
30
30
  */
31
31
  function tagBinding(binding, schema, pathIndex) {
32
32
  const handler = {
33
- apply: (target, _thisArg, args) => target(...args),
34
33
  get: (target, prop, receiver) => {
35
34
  const value = Reflect.get(target, prop, receiver);
36
35
  if (prop === 'bind') {
@@ -46,7 +45,8 @@ function tagBinding(binding, schema, pathIndex) {
46
45
  };
47
46
  }
48
47
  if (prop === 'as') {
49
- return (credential) => tagBinding(value(credential), schema, pathIndex);
48
+ const as = value;
49
+ return (credential) => tagBinding(as(credential), schema, pathIndex);
50
50
  }
51
51
  return value;
52
52
  },
@@ -33,10 +33,10 @@ export function useMethod(schema, type, method, opts) {
33
33
  return useAction(call);
34
34
  }
35
35
  function staticCall(binding, type, method, params) {
36
- return binding(type)[method](params);
36
+ return binding.classes[type][method](params);
37
37
  }
38
38
  function instance(binding, type, ref, method, params) {
39
- return binding(type, ref)[method](params);
39
+ return binding.classes[type](ref)[method](params);
40
40
  }
41
41
  /** Read the method's `static` flag from the schema (own + inherited). */
42
42
  function detectStatic(schema, type, method) {
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/schema/write.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAElE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAGD,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,cAAc,CAQpF;AAwCD,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAK9E"}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/schema/write.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAElE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAGD,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,cAAc,CAQpF;AAkDD,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAK9E"}
@@ -19,8 +19,7 @@ export function writableFor(kernel, memory) {
19
19
  function withMemoryWrites(kernel, memory, pathIndex) {
20
20
  const base = kernel;
21
21
  const rawUpdate = base.updateNode.bind(base);
22
- return {
23
- ...kernel,
22
+ return extendCapabilities(kernel, {
24
23
  updateNode: (cls, ref, props) => {
25
24
  const path = treePathFor(pathIndex, ref);
26
25
  return path !== null ? memory.update(path, props) : rawUpdate(cls, ref, props);
@@ -28,7 +27,14 @@ function withMemoryWrites(kernel, memory, pathIndex) {
28
27
  createNode: (cls, at, props) => memory.create(cls, at, props),
29
28
  // Credential rebind stays on the raw kernel-client floor (§6 note).
30
29
  as: (cred) => base.as(cred),
31
- };
30
+ });
31
+ }
32
+ /** Clone the complete session capability object before overlaying memory writes. */
33
+ function extendCapabilities(base, extension) {
34
+ const result = Object.create(Object.getPrototypeOf(base));
35
+ Object.defineProperties(result, Object.getOwnPropertyDescriptors(base));
36
+ Object.defineProperties(result, Object.getOwnPropertyDescriptors(extension));
37
+ return result;
32
38
  }
33
39
  /** The tree path an `@<id>` write ref maps to, via the bind-time index; else null. */
34
40
  function treePathFor(pathIndex, ref) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/shell-react",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Astrale shell-react — the React face of the shell (provider, hooks, view components)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -25,10 +25,10 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@astrale-domains/shell-schema": ">=0.3.1 <1.0.0",
29
- "@astrale-os/kernel-client": ">=0.4.0 <1.0.0",
30
- "@astrale-os/kernel-core": ">=0.7.1 <1.0.0",
31
- "@astrale-os/kernel-dsl": ">=0.1.3 <1.0.0",
28
+ "@astrale-domains/shell-schema": ">=0.3.2 <1.0.0",
29
+ "@astrale-os/kernel-client": ">=0.4.5 <1.0.0",
30
+ "@astrale-os/kernel-core": ">=0.7.4 <1.0.0",
31
+ "@astrale-os/kernel-dsl": ">=0.1.4 <1.0.0",
32
32
  "jose": "^6.2.3",
33
33
  "zod": "^4.3.6"
34
34
  },
@@ -44,7 +44,7 @@
44
44
  "react-dom": "^19.2.0",
45
45
  "typescript": "~6.0.1-rc",
46
46
  "vitest": "^3.2.4",
47
- "@astrale-os/shell": "0.3.1"
47
+ "@astrale-os/shell": "0.3.2"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@astrale-os/shell": ">=0.2.0 <1.0.0",
@@ -3,7 +3,7 @@ import type { Schema } from '@astrale-os/kernel-dsl'
3
3
 
4
4
  /**
5
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
6
+ * endpoints one-shot through `BoundNode.links.out/in` (`live: false`); become live with
7
7
  * no surface change once edge-endpoint ids land. Deviations tracked in API §6/G6.
8
8
  */
9
9
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -35,24 +35,16 @@ type EndpointEnvelope<S extends Schema> = ReadState & {
35
35
  readonly nodes?: readonly AnyBoundNode<S>[]
36
36
  }
37
37
 
38
- interface Traversal {
39
- out(edge: string): Promise<unknown>
40
- in(edge: string): Promise<unknown>
41
- }
42
-
43
38
  function useEndpoint<S extends Schema>(
44
39
  node: AnyBoundNode<S> | null | undefined,
45
- edge: string,
40
+ edge: EdgeNames<S>,
46
41
  side: 'out' | 'in',
47
42
  _opts?: ReadOptions,
48
43
  ): EndpointEnvelope<S> {
49
44
  const schema = isIdleInput(node) ? undefined : schemaOf(node)
50
45
  const run = useCallback(async (): Promise<unknown> => {
51
46
  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))
47
+ const result = await (side === 'out' ? node.links.out(edge) : node.links.in(edge))
56
48
  if (schema !== undefined) tagEndpoints(result, schema)
57
49
  return result
58
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -18,10 +18,8 @@ import type { SessionView } from '../auth/act-as.context.js'
18
18
  import { tagSchema } from './tag.js'
19
19
  import { recordPath, writableFor } from './write.js'
20
20
 
21
- type AnyBinding = SchemaBoundView<Schema, FnMap>
22
-
23
21
  /** One tagged binding per (kernel, schema) — shared across the whole tree. */
24
- const bindingCache = new WeakMap<object, WeakMap<Schema, AnyBinding>>()
22
+ const bindingCache = new WeakMap<object, WeakMap<Schema, unknown>>()
25
23
 
26
24
  /**
27
25
  * Resolve (and memoize) the tagged schema-bound handle for `schema` on `kernel`.
@@ -39,28 +37,26 @@ export function bindingFor<S extends Schema>(
39
37
  bySchema = new WeakMap()
40
38
  bindingCache.set(kernel, bySchema)
41
39
  }
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>
40
+ const cached = bySchema.get(schema)
41
+ if (cached !== undefined) return cached as SchemaBoundView<S, FnMap>
42
+
43
+ const { writable, pathIndex } = writableFor(kernel, memory)
44
+ const binding = tagBinding(createSchemaBoundView(writable, schema), schema, pathIndex)
45
+ bySchema.set(schema, binding)
46
+ return binding
49
47
  }
50
48
 
51
49
  /**
52
50
  * Wrap a binding so `bind`/`bindAny`/`as` outputs carry a schema tag, and every
53
51
  * 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.
52
+ * class handles and every other member pass straight through.
55
53
  */
56
- function tagBinding(
57
- binding: AnyBinding,
58
- schema: Schema,
54
+ function tagBinding<S extends Schema>(
55
+ binding: SchemaBoundView<S, FnMap>,
56
+ schema: S,
59
57
  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[])),
58
+ ): SchemaBoundView<S, FnMap> {
59
+ const handler: ProxyHandler<SchemaBoundView<S, FnMap>> = {
64
60
  get: (target, prop, receiver) => {
65
61
  const value = Reflect.get(target, prop, receiver) as unknown
66
62
  if (prop === 'bind') {
@@ -79,8 +75,9 @@ function tagBinding(
79
75
  }
80
76
  }
81
77
  if (prop === 'as') {
82
- return (credential: unknown) =>
83
- tagBinding((value as (c: unknown) => AnyBinding)(credential), schema, pathIndex)
78
+ const as = value as SchemaBoundView<S, FnMap>['as']
79
+ return (credential: Parameters<typeof as>[0]) =>
80
+ tagBinding(as(credential), schema, pathIndex)
84
81
  }
85
82
  return value
86
83
  },
@@ -45,7 +45,7 @@ export function useMethod<S extends Schema, T extends TypeNames<S>, M extends Me
45
45
  method: M,
46
46
  opts?: MethodOptions,
47
47
  ): Action<never[], unknown> {
48
- const binding = useDomain(schema) as unknown as Dispatcher
48
+ const binding = useDomain(schema) as unknown as DynamicBinding
49
49
  const on = opts?.on
50
50
  const isStatic = useMemo(() => detectStatic(schema, type, String(method)), [schema, type, method])
51
51
 
@@ -70,26 +70,26 @@ export function useMethod<S extends Schema, T extends TypeNames<S>, M extends Me
70
70
  return useAction(call)
71
71
  }
72
72
 
73
- /** The bare-call dispatch signature of a `SchemaBoundView`, address-only. */
74
- type Dispatcher = (type: string, ref?: NodeRefInput) => unknown
73
+ type DynamicClassHandle = Dispatch & ((ref: NodeRefInput) => Dispatch)
74
+ type DynamicBinding = { classes: Record<string, DynamicClassHandle> }
75
75
 
76
76
  function staticCall(
77
- binding: Dispatcher,
77
+ binding: DynamicBinding,
78
78
  type: string,
79
79
  method: string,
80
80
  params: unknown,
81
81
  ): Promise<unknown> {
82
- return (binding(type) as unknown as Dispatch)[method]!(params)
82
+ return binding.classes[type]![method]!(params)
83
83
  }
84
84
 
85
85
  function instance(
86
- binding: Dispatcher,
86
+ binding: DynamicBinding,
87
87
  type: string,
88
88
  ref: NodeRefInput,
89
89
  method: string,
90
90
  params: unknown,
91
91
  ): Promise<unknown> {
92
- return (binding(type, ref) as unknown as Dispatch)[method]!(params)
92
+ return binding.classes[type]!(ref)[method]!(params)
93
93
  }
94
94
 
95
95
  /** Read the method's `static` flag from the schema (own + inherited). */
@@ -52,8 +52,7 @@ function withMemoryWrites(
52
52
  as(cred: CredentialInput | (() => CredentialInput)): SessionView
53
53
  }
54
54
  const rawUpdate = base.updateNode.bind(base)
55
- return {
56
- ...(kernel as object),
55
+ return extendCapabilities(kernel, {
57
56
  updateNode: (cls: PathLike, ref: PathLike, props: Properties): Promise<void> => {
58
57
  const path = treePathFor(pathIndex, ref)
59
58
  return path !== null ? memory.update(path, props) : rawUpdate(cls, ref, props)
@@ -62,7 +61,18 @@ function withMemoryWrites(
62
61
  memory.create(cls, at, props),
63
62
  // Credential rebind stays on the raw kernel-client floor (§6 note).
64
63
  as: (cred: CredentialInput | (() => CredentialInput)): SessionView => base.as(cred),
65
- } as unknown as SessionView
64
+ })
65
+ }
66
+
67
+ /** Clone the complete session capability object before overlaying memory writes. */
68
+ function extendCapabilities<Base extends object, Extension extends object>(
69
+ base: Base,
70
+ extension: Extension,
71
+ ): Base & Extension {
72
+ const result = Object.create(Object.getPrototypeOf(base)) as Base & Extension
73
+ Object.defineProperties(result, Object.getOwnPropertyDescriptors(base))
74
+ Object.defineProperties(result, Object.getOwnPropertyDescriptors(extension))
75
+ return result
66
76
  }
67
77
 
68
78
  /** The tree path an `@<id>` write ref maps to, via the bind-time index; else null. */