@astrale-os/sdk 0.2.0 → 0.3.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.
Files changed (160) hide show
  1. package/LICENSE +202 -0
  2. package/dist/auth/check.d.ts +1 -2
  3. package/dist/auth/check.d.ts.map +1 -1
  4. package/dist/auth/check.js +2 -6
  5. package/dist/auth/check.js.map +1 -1
  6. package/dist/auth/index.d.ts +2 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +1 -0
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/auth/issuer-mint.d.ts +53 -0
  11. package/dist/auth/issuer-mint.d.ts.map +1 -0
  12. package/dist/auth/issuer-mint.js +35 -0
  13. package/dist/auth/issuer-mint.js.map +1 -0
  14. package/dist/auth/kernel-client.d.ts +8 -2
  15. package/dist/auth/kernel-client.d.ts.map +1 -1
  16. package/dist/auth/kernel-client.js +22 -46
  17. package/dist/auth/kernel-client.js.map +1 -1
  18. package/dist/cli/dotenv.d.ts +8 -0
  19. package/dist/cli/dotenv.d.ts.map +1 -1
  20. package/dist/cli/dotenv.js +18 -0
  21. package/dist/cli/dotenv.js.map +1 -1
  22. package/dist/cli/index.d.ts +1 -1
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +1 -1
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/log.d.ts +11 -0
  27. package/dist/cli/log.d.ts.map +1 -0
  28. package/dist/cli/log.js +17 -0
  29. package/dist/cli/log.js.map +1 -0
  30. package/dist/cli/publish.d.ts +35 -0
  31. package/dist/cli/publish.d.ts.map +1 -0
  32. package/dist/cli/publish.js +271 -0
  33. package/dist/cli/publish.js.map +1 -0
  34. package/dist/cli/run.d.ts +17 -1
  35. package/dist/cli/run.d.ts.map +1 -1
  36. package/dist/cli/run.js +85 -101
  37. package/dist/cli/run.js.map +1 -1
  38. package/dist/cli/spec.d.ts.map +1 -1
  39. package/dist/cli/spec.js +1 -0
  40. package/dist/cli/spec.js.map +1 -1
  41. package/dist/config/define-domain.d.ts +79 -2
  42. package/dist/config/define-domain.d.ts.map +1 -1
  43. package/dist/config/define-domain.js +58 -1
  44. package/dist/config/define-domain.js.map +1 -1
  45. package/dist/config/index.d.ts +1 -1
  46. package/dist/config/index.d.ts.map +1 -1
  47. package/dist/define/remote-function.d.ts +23 -3
  48. package/dist/define/remote-function.d.ts.map +1 -1
  49. package/dist/define/remote-function.js +1 -1
  50. package/dist/define/remote-function.js.map +1 -1
  51. package/dist/deploy/check.d.ts +6 -6
  52. package/dist/deploy/check.d.ts.map +1 -1
  53. package/dist/deploy/check.js +1 -1
  54. package/dist/deploy/check.js.map +1 -1
  55. package/dist/deploy/index.d.ts +0 -1
  56. package/dist/deploy/index.d.ts.map +1 -1
  57. package/dist/deploy/index.js +0 -1
  58. package/dist/deploy/index.js.map +1 -1
  59. package/dist/deploy/meta.d.ts +11 -0
  60. package/dist/deploy/meta.d.ts.map +1 -1
  61. package/dist/deploy/meta.js +23 -0
  62. package/dist/deploy/meta.js.map +1 -1
  63. package/dist/dispatch/dispatcher.d.ts +17 -4
  64. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  65. package/dist/dispatch/dispatcher.js +15 -3
  66. package/dist/dispatch/dispatcher.js.map +1 -1
  67. package/dist/dispatch/execute.d.ts +6 -2
  68. package/dist/dispatch/execute.d.ts.map +1 -1
  69. package/dist/dispatch/execute.js +1 -0
  70. package/dist/dispatch/execute.js.map +1 -1
  71. package/dist/dispatch/identity.d.ts +11 -10
  72. package/dist/dispatch/identity.d.ts.map +1 -1
  73. package/dist/dispatch/identity.js +4 -3
  74. package/dist/dispatch/identity.js.map +1 -1
  75. package/dist/dispatch/index.d.ts +1 -1
  76. package/dist/dispatch/index.d.ts.map +1 -1
  77. package/dist/dispatch/index.js +1 -1
  78. package/dist/dispatch/index.js.map +1 -1
  79. package/dist/dispatch/self.d.ts +25 -37
  80. package/dist/dispatch/self.d.ts.map +1 -1
  81. package/dist/dispatch/self.js +60 -30
  82. package/dist/dispatch/self.js.map +1 -1
  83. package/dist/domain/build-spec.d.ts.map +1 -1
  84. package/dist/domain/build-spec.js +15 -3
  85. package/dist/domain/build-spec.js.map +1 -1
  86. package/dist/domain/define.d.ts +17 -32
  87. package/dist/domain/define.d.ts.map +1 -1
  88. package/dist/domain/define.js +12 -31
  89. package/dist/domain/define.js.map +1 -1
  90. package/dist/domain/extend-functions.d.ts +13 -9
  91. package/dist/domain/extend-functions.d.ts.map +1 -1
  92. package/dist/domain/extend-functions.js +28 -13
  93. package/dist/domain/extend-functions.js.map +1 -1
  94. package/dist/index.d.ts +7 -3
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +12 -4
  97. package/dist/index.js.map +1 -1
  98. package/dist/method/class.d.ts +2 -2
  99. package/dist/method/class.d.ts.map +1 -1
  100. package/dist/method/class.js.map +1 -1
  101. package/dist/method/context.d.ts +18 -3
  102. package/dist/method/context.d.ts.map +1 -1
  103. package/dist/method/index.d.ts +2 -2
  104. package/dist/method/index.d.ts.map +1 -1
  105. package/dist/method/index.js.map +1 -1
  106. package/dist/method/single.d.ts +25 -38
  107. package/dist/method/single.d.ts.map +1 -1
  108. package/dist/method/single.js +1 -11
  109. package/dist/method/single.js.map +1 -1
  110. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  111. package/dist/server/auxiliary-routes.js +13 -2
  112. package/dist/server/auxiliary-routes.js.map +1 -1
  113. package/dist/server/create.d.ts +2 -15
  114. package/dist/server/create.d.ts.map +1 -1
  115. package/dist/server/create.js +15 -51
  116. package/dist/server/create.js.map +1 -1
  117. package/dist/server/domain-entry.d.ts +10 -0
  118. package/dist/server/domain-entry.d.ts.map +1 -1
  119. package/dist/server/domain-entry.js +2 -0
  120. package/dist/server/domain-entry.js.map +1 -1
  121. package/dist/server/index.d.ts +0 -1
  122. package/dist/server/index.d.ts.map +1 -1
  123. package/dist/server/index.js +0 -1
  124. package/dist/server/index.js.map +1 -1
  125. package/package.json +10 -8
  126. package/src/auth/check.ts +2 -6
  127. package/src/auth/index.ts +2 -0
  128. package/src/auth/issuer-mint.ts +75 -0
  129. package/src/auth/kernel-client.ts +31 -63
  130. package/src/cli/dotenv.ts +20 -0
  131. package/src/cli/index.ts +1 -1
  132. package/src/cli/log.ts +18 -0
  133. package/src/cli/publish.ts +341 -0
  134. package/src/cli/run.ts +92 -113
  135. package/src/cli/spec.ts +1 -0
  136. package/src/config/define-domain.ts +158 -5
  137. package/src/config/index.ts +1 -1
  138. package/src/define/remote-function.ts +47 -7
  139. package/src/deploy/check.ts +7 -7
  140. package/src/deploy/index.ts +0 -1
  141. package/src/deploy/meta.ts +25 -0
  142. package/src/dispatch/dispatcher.ts +32 -9
  143. package/src/dispatch/execute.ts +7 -2
  144. package/src/dispatch/identity.ts +11 -10
  145. package/src/dispatch/index.ts +1 -1
  146. package/src/dispatch/self.ts +117 -41
  147. package/src/domain/build-spec.ts +15 -1
  148. package/src/domain/define.ts +26 -38
  149. package/src/domain/extend-functions.ts +35 -15
  150. package/src/index.ts +29 -5
  151. package/src/method/class.ts +11 -7
  152. package/src/method/context.ts +19 -3
  153. package/src/method/index.ts +8 -2
  154. package/src/method/single.ts +35 -42
  155. package/src/server/auxiliary-routes.ts +13 -2
  156. package/src/server/create.ts +15 -51
  157. package/src/server/domain-entry.ts +12 -0
  158. package/src/server/index.ts +0 -1
  159. package/src/deploy/hash-spec.ts +0 -31
  160. package/src/server/worker-meta.ts +0 -25
@@ -1,30 +1,26 @@
1
1
  /**
2
- * Self resolution pipeline step 3.
3
- *
4
- * `_self` is a path reference to the node the non-static method runs on.
5
- * `resolveSelf` is a thin PARSE: the caller's string becomes a `Path`, and
6
- * handlers build recursive paths as `${self.path}::method` (the `Path`'s
7
- * `toString` yields its raw form, so template literals work unchanged). It does
8
- * NOT touch the graph — the whole dispatch pipeline (resolve → authenticate →
9
- * validate → self → execute) reaches the handler without a single `Node::get`.
10
- *
11
- * `resolveSelf` receives the bare self target (`@<id>` or a tree path) — the
12
- * method ref travels in a separate dispatch channel, so `::method` never
13
- * reaches here. For the `@<id>` form the parsed `Path` is an `IdPath` which
14
- * already carries the node id; surfacing it on `SelfResult.id` saves every
15
- * worker handler from parsing `self.path.raw` by hand to recover the id of the
16
- * node it is already operating on.
17
- *
18
- * `withNode` enriches the parsed self with the lazy `node()` accessor — the one
19
- * place a handler can fetch its own node's full record (`{ id, class, props }`)
20
- * without hand-rolling a `::get`. Construction stays split from parsing so
21
- * `resolveSelf` remains pure and trivially testable; the dispatcher attaches the
22
- * accessor once it holds the request's kernel.
2
+ * Self resolution stays parse-only: `_self` becomes a Path/IdPath with no graph
3
+ * read. The dispatcher later attaches lazy node()/call helpers once it has a kernel.
23
4
  */
24
5
 
6
+ import type {
7
+ BindCtx,
8
+ BoundNode,
9
+ InstanceDispatch,
10
+ NodeBinder,
11
+ ProxyCaller,
12
+ TypeMethods,
13
+ TypeNames,
14
+ } from '@astrale-os/kernel-client/schema'
25
15
  import type { Node } from '@astrale-os/kernel-core/graph'
16
+ import type { Schema } from '@astrale-os/kernel-dsl'
26
17
 
27
- import { IdPath, Path, type NodeId } from '@astrale-os/kernel-core'
18
+ import {
19
+ createInstanceProxy,
20
+ defaultCaller,
21
+ extractMethodsByType,
22
+ } from '@astrale-os/kernel-client/schema'
23
+ import { IdPath, K, Path, type NodeId } from '@astrale-os/kernel-core'
28
24
  import { classPathSchema } from '@astrale-os/kernel-core/domain'
29
25
  import { absolutePathSchema } from '@astrale-os/kernel-core/tree'
30
26
  import { z } from 'zod'
@@ -43,32 +39,31 @@ export type ParsedSelf = {
43
39
 
44
40
  export type SelfResult = ParsedSelf & {
45
41
  /**
46
- * Lazily read THIS node's full record via one `::get` on the handler's
47
- * kernel. Memoized for the dispatch: repeated and concurrent calls share a
48
- * single in-flight fetch, and a failure clears the cache so it stays
49
- * retryable. Pass `{ reload: true }` to force a fresh read.
50
- *
51
- * The result is a point-in-time SNAPSHOT, not a live view — for
52
- * read-modify-write, treat it as the value as of the read (or `reload`).
53
- * `props` is returned structurally validated only, NOT typed against the
54
- * node's declared schema: the kernel does not hydrate declared props at
55
- * runtime, so a node-typed `self` would be unsound. Read the concrete backend
56
- * off `node.class` (a `ClassPath`) — that is the persisted discriminator.
42
+ * Lazy point-in-time self read. Memoized per dispatch; `{ reload: true }`
43
+ * forces a fresh `function.get`.
57
44
  */
58
45
  node(opts?: { reload?: boolean }): Promise<ResolvedSelfNode>
59
46
  }
60
47
 
48
+ /** Schema-typed self: short-keyed props and typed same-instance call proxy. */
49
+ export type TypedSelf<D, S extends Schema = Schema> = ParsedSelf & {
50
+ /**
51
+ * Fetch THIS node as a typed `BoundNode<D>`. Memoized exactly like
52
+ * {@link SelfResult.node} — one in-flight `function.get`, rejection-clearing,
53
+ * `{ reload: true }` refetches — so the zero-extra-graph-read dispatch
54
+ * invariant holds: nothing is read until called, then at most once.
55
+ */
56
+ node(opts?: { reload?: boolean }): Promise<BoundNode<D, S>>
57
+ /** Own instance methods — typed, address-only (`<selfPath>::method`), no read. */
58
+ readonly call: InstanceDispatch<D>
59
+ }
60
+
61
61
  export function resolveSelf(ref: string): ParsedSelf {
62
62
  const path = Path.parse(ref)
63
63
  return path instanceof IdPath ? { path, id: path.id } : { path }
64
64
  }
65
65
 
66
- /**
67
- * Attach the memoized `node()` accessor, binding it to the handler's kernel.
68
- * `kernel` is `null` for methods whose auth policy yields none (`public`, or an
69
- * unauthenticated `optional`) — there `node()` rejects with a clear error
70
- * rather than reading the graph unauthenticated.
71
- */
66
+ /** Attach memoized node(); unauthenticated contexts reject instead of reading. */
72
67
  export function withNode(parsed: ParsedSelf, kernel: Kernel | null): SelfResult {
73
68
  let cached: Promise<ResolvedSelfNode> | undefined
74
69
  const node = (opts?: { reload?: boolean }): Promise<ResolvedSelfNode> => {
@@ -84,6 +79,73 @@ export function withNode(parsed: ParsedSelf, kernel: Kernel | null): SelfResult
84
79
  return { ...parsed, node }
85
80
  }
86
81
 
82
+ const EMPTY_METHODS: TypeMethods = { kind: 'class', methods: new Set() }
83
+ const methodMapCache = new WeakMap<object, Map<string, TypeMethods>>()
84
+
85
+ /** The type's method allow-list + kind, computed once per binder. */
86
+ function methodsFor<S extends Schema>(binder: NodeBinder<S>, typeName: string): TypeMethods {
87
+ let map = methodMapCache.get(binder)
88
+ if (!map) {
89
+ map = extractMethodsByType(binder.schema)
90
+ methodMapCache.set(binder, map)
91
+ }
92
+ return map.get(typeName) ?? EMPTY_METHODS
93
+ }
94
+
95
+ function noKernelCaller(ref: string): ProxyCaller {
96
+ return () =>
97
+ Promise.reject(
98
+ new Error(
99
+ `self.call needs an authenticated kernel to dispatch on "${ref}", but this method ran ` +
100
+ `without one (its auth policy yields no kernel — e.g. 'public').`,
101
+ ),
102
+ )
103
+ }
104
+
105
+ /**
106
+ * Schema-typed self helper: lazy BoundNode plus address-only call proxy. With no
107
+ * kernel, both node() and call thunks fail loudly.
108
+ */
109
+ export function withBoundNode<S extends Schema>(
110
+ parsed: ParsedSelf,
111
+ kernel: Kernel | null,
112
+ binder: NodeBinder<S>,
113
+ typeName: string,
114
+ ): TypedSelf<unknown, S> {
115
+ const ctx: BindCtx = kernel
116
+ ? { caller: defaultCaller(kernel), read: kernel }
117
+ : { caller: null, read: null }
118
+
119
+ let cached: Promise<BoundNode<unknown, S>> | undefined
120
+ const node = (opts?: { reload?: boolean }): Promise<BoundNode<unknown, S>> => {
121
+ if (opts?.reload) cached = undefined
122
+ if (!cached) {
123
+ cached = fetchSelfNode(parsed.path, kernel)
124
+ .then(
125
+ (record) =>
126
+ binder.bind(typeName as TypeNames<S>, record, ctx, {
127
+ validate: 'warn',
128
+ }) as unknown as BoundNode<unknown, S>,
129
+ )
130
+ .catch((err: unknown) => {
131
+ cached = undefined // never cache a rejection — a later call may succeed
132
+ throw err
133
+ })
134
+ }
135
+ return cached
136
+ }
137
+
138
+ const info = methodsFor(binder, typeName)
139
+ const call = createInstanceProxy(
140
+ ctx.caller ?? noKernelCaller(parsed.path.raw),
141
+ parsed.path.raw,
142
+ info.methods,
143
+ info.kind === 'class' ? typeName : undefined,
144
+ ) as InstanceDispatch<unknown>
145
+
146
+ return { ...parsed, node, call }
147
+ }
148
+
87
149
  /**
88
150
  * Boundary schema for a node record off the wire. Structurally identical to
89
151
  * kernel-core's `nodeSchema`, re-declared here from its public coercers
@@ -108,9 +170,23 @@ async function fetchSelfNode(path: Path, kernel: Kernel | null): Promise<Resolve
108
170
  `method whose auth is 'required', or supply a credential.`,
109
171
  )
110
172
  }
111
- const record = nodeRecordSchema.parse(await kernel.call(`${path.raw}::get`, {}))
173
+ // function.get soft-masks missing/unreadable roots; empty page means no self node.
174
+ const result = (await kernel.call(K.$.f('get').path.domain.raw, {
175
+ roots: [path.raw],
176
+ depth: 0,
177
+ })) as {
178
+ nodes?: unknown[]
179
+ }
180
+ const first = result?.nodes?.[0]
181
+ if (first === undefined) {
182
+ throw new Error(
183
+ `self.node(): "${path.raw}" was not found or is not visible to the caller ` +
184
+ `(function.get returned no node).`,
185
+ )
186
+ }
187
+ const record = nodeRecordSchema.parse(first)
112
188
  if (record.id === undefined) {
113
- throw new Error(`self.node(): "${path.raw}::get" returned a record with no id`)
189
+ throw new Error(`self.node(): "${path.raw}" returned a record with no id`)
114
190
  }
115
191
  // Validated at the wire boundary: id is now known-present, props are
116
192
  // structurally checked. Bridge the parse output to the canonical `Node`.
@@ -21,6 +21,7 @@ import type { Graph, WireGraph } from '@astrale-os/kernel-core'
21
21
  import type { BoundMethod, FunctionSchema, ViewSchema } from '@astrale-os/kernel-core/domain'
22
22
  import type { Schema } from '@astrale-os/kernel-dsl'
23
23
 
24
+ import { AbsolutePath } from '@astrale-os/kernel-core'
24
25
  import { hashInstallGraph, serialize, zodToJsonSchema } from '@astrale-os/kernel-core/domain'
25
26
 
26
27
  import type { AnyRemoteHandler } from '../method/single'
@@ -42,12 +43,21 @@ export function buildInstallGraph<S extends Schema>(
42
43
  url: string,
43
44
  ): WireGraph {
44
45
  const { compiled, auxiliary } = materializeRemoteDomain(domain, url)
46
+ // Two-axis placement: `parent` = the folder the Domain node physically lives
47
+ // under. `path` names the FULL node path (basename === origin);
48
+ // unset defaults to /domains/<origin> — the "/domains" placement policy lives
49
+ // HERE (the kernel default is Root → /<origin>). `mount` is deliberately NOT
50
+ // passed: installed_in + typed addresses stay root-mounted (kernel default).
51
+ const parent = domain.path
52
+ ? AbsolutePath.parse(domain.path).parent()
53
+ : AbsolutePath.from('domains')
45
54
  return buildSpecInternal(
46
55
  compiled,
47
56
  domain.methods,
48
57
  url,
49
58
  auxiliary?.functionSchemas ?? [],
50
59
  auxiliary?.viewSchemas ?? [],
60
+ parent,
51
61
  ).toWire() as WireGraph
52
62
  }
53
63
 
@@ -71,6 +81,7 @@ function buildSpecInternal(
71
81
  url: string,
72
82
  functionSchemas: FunctionSchema[],
73
83
  viewSchemas: ViewSchema[],
84
+ parent: AbsolutePath,
74
85
  ): Graph {
75
86
  const serialized = serializeMethodsWithStubs(compiled, methods, url)
76
87
  // Method impls + standalone-function-member impls travel in the same callable
@@ -79,7 +90,10 @@ function buildSpecInternal(
79
90
  // view_for, View class). The serializer emits method nodes from the IR,
80
91
  // function/view members from `compiled.$.refs.{functions,views}`, pulling each
81
92
  // impl by ref.
82
- const tree = serialize(compiled, [...serialized, ...functionSchemas], { views: viewSchemas })
93
+ const tree = serialize(compiled, [...serialized, ...functionSchemas], {
94
+ views: viewSchemas,
95
+ parent,
96
+ })
83
97
  return tree.toGraph()
84
98
  }
85
99
 
@@ -1,20 +1,6 @@
1
1
  /**
2
- * `defineRemoteDomain` — turn a typed schema + methods map into a mountable domain.
3
- *
4
- * `views` and `functions` entries become first-class DOMAIN MEMBERS: a `View` /
5
- * `Function` node attached to the Domain via an `of_domain` edge (slug
6
- * `view.<slug>` / `function.<slug>`), laid out at `/<origin>/{views,functions}/<slug>`.
7
- * Their contract half (the member ref) threads through `compileDomain`; their
8
- * impl/binding half threads through `serialize`. The same entries'
9
- * `render` / `execute` handlers are mounted as Hono routes by
10
- * `createRemoteServer` at `<url>/<viewsFolder|functionsFolder>/<slug>` (the route
11
- * URL is decoupled from the graph layout). Slug = map key.
12
- *
13
- * The domain definition is deployment-agnostic: it carries NO serving url. The
14
- * url is supplied late by the spec producer (`createRemoteServer({ url })` at
15
- * runtime, the `astrale-domain` CLI offline) and stamped onto every `binding.remoteUrl`
16
- * by `materializeRemoteDomain`. There is exactly one notion of `url` in the SDK
17
- * — the worker serving URL, which is also `iss` and the binding base.
2
+ * Define a remote domain. Views/functions are first-class domain members; their
3
+ * serving URL is stamped later by spec producers and is also the worker issuer.
18
4
  */
19
5
 
20
6
  import type { FunctionBinding } from '@astrale-os/kernel-api/routed'
@@ -28,12 +14,13 @@ import type { Core, Schema } from '@astrale-os/kernel-dsl'
28
14
 
29
15
  import { bindMethods, compileDomain } from '@astrale-os/kernel-core/domain'
30
16
 
17
+ import type { DomainManifest } from '../config/define-domain'
31
18
  import type { AnyRemoteFunctionDef, ViewDef } from '../define'
32
19
  import type { SchemaMethodsImpl } from '../method/class'
33
20
  import type { AnyRemoteHandler } from '../method/single'
34
21
 
35
22
  import {
36
- buildFunctionDeclarations,
23
+ assertFunctionsMatchSchema,
37
24
  buildFunctionSchemas,
38
25
  DEFAULT_FUNCTIONS_FOLDER,
39
26
  } from './extend-functions'
@@ -49,6 +36,12 @@ export type RemoteDomainConfig<S extends Schema, TDeps> = {
49
36
 
50
37
  remoteFunctions?: Record<string, AnyRemoteFunctionDef>
51
38
  functionsFolder?: string
39
+
40
+ /** Presentation metadata for `/meta` (logo / entry view). Carried verbatim. */
41
+ manifest?: DomainManifest
42
+
43
+ /** Physical tree path for the Domain node (absolute, e.g. `/domains/acme`); default `/domains/<origin>`. */
44
+ path?: string
52
45
  }
53
46
 
54
47
  /** Effective bindings + folder layout + member schemas for auxiliary routes, resolved against `url`. */
@@ -65,20 +58,22 @@ export type AuxiliaryMetadata = {
65
58
  }
66
59
 
67
60
  export type RemoteDomain<S extends Schema = Schema> = {
68
- /**
69
- * Define-time compile: full domain STRUCTURE (classes, methods, and the
70
- * view/function members' paths/names/refs — what identity, subs, and contract
71
- * resolution need). It carries NO `binding` values: bindings derive from the
72
- * serving url, which only the spec producers know — they call
73
- * `materializeRemoteDomain(domain, url)` for the install-ready compile.
74
- */
61
+ /** Define-time compile has structure only; bindings are stamped at materialization. */
75
62
  compiled: CompiledDomain<S>
76
63
  methods: BoundMethod<AnyRemoteHandler>[]
77
64
  // oxlint-disable-next-line no-explicit-any
78
65
  views?: Record<string, ViewDef<any>>
79
66
  remoteFunctions?: Record<string, AnyRemoteFunctionDef>
67
+ /** Presentation metadata for `/meta` (logo / entry view); `createRemoteServer` stamps it. */
68
+ manifest?: DomainManifest
80
69
  /** The original config, so `materializeRemoteDomain` re-materializes from source. */
81
70
  config: MaterializeInputs
71
+ /**
72
+ * Physical tree path for the Domain node (absolute string, e.g. `/domains/acme`).
73
+ * Read by `buildInstallGraph` and passed to `serialize` as `treeMount`; unset =>
74
+ * `/domains/<origin>` (applied in build-spec). Addressing is unaffected.
75
+ */
76
+ path?: string
82
77
  }
83
78
 
84
79
  /** The defineRemoteDomain inputs `materializeRemoteDomain` needs to re-materialize. */
@@ -93,14 +88,14 @@ export function defineRemoteDomain<TDeps>() {
93
88
  const viewsFolder = config.viewsFolder ?? DEFAULT_VIEWS_FOLDER
94
89
  const functionsFolder = config.functionsFolder ?? DEFAULT_FUNCTIONS_FOLDER
95
90
 
96
- // Views + functions are domain MEMBERS their contract enters compile via
97
- // the `functions` / `views` args (no Core involvement). `config.core` is the
98
- // author's own genuine genesis instance data, untouched.
91
+ // Standalone function handlers must match schema-declared function members.
92
+ assertFunctionsMatchSchema(config.schema, config.remoteFunctions)
93
+ // Standalone function handlers must match schema-declared function members.
94
+ assertFunctionsMatchSchema(config.schema, config.remoteFunctions)
99
95
  const compiled = compileDomain(
100
96
  config.schema,
101
97
  config.core,
102
98
  undefined,
103
- config.remoteFunctions ? buildFunctionDeclarations(config.remoteFunctions) : undefined,
104
99
  config.views ? buildViewDeclarations(config.views) : undefined,
105
100
  )
106
101
  const methods = bindMethods<AnyRemoteHandler>(
@@ -115,6 +110,8 @@ export function defineRemoteDomain<TDeps>() {
115
110
  methods,
116
111
  ...(config.views ? { views: config.views } : {}),
117
112
  ...(config.remoteFunctions ? { remoteFunctions: config.remoteFunctions } : {}),
113
+ ...(config.manifest ? { manifest: config.manifest } : {}),
114
+ ...(config.path ? { path: config.path } : {}),
118
115
  config: {
119
116
  userCore: config.core,
120
117
  viewsFolder,
@@ -124,15 +121,7 @@ export function defineRemoteDomain<TDeps>() {
124
121
  }
125
122
  }
126
123
 
127
- /**
128
- * Materialize a `RemoteDomain` at its real serving `url`: re-compiles the domain
129
- * and builds every member's url-stamped impl schema + binding, returning the
130
- * binding maps the auxiliary routes mount from and the member schemas `serialize`
131
- * emits from. Called by the only two spec producers — `createRemoteServer`
132
- * (`config.url`) and the `astrale-domain` CLI. Returns the define-time `compiled`
133
- * untouched when there is no aux to stamp. Pure, so safe to call repeatedly (and
134
- * memoized per cold isolate by the callers).
135
- */
124
+ /** Materialize aux member bindings against the real serving URL. Pure and repeatable. */
136
125
  export function materializeRemoteDomain<S extends Schema>(
137
126
  domain: RemoteDomain<S>,
138
127
  url: string,
@@ -147,7 +136,6 @@ export function materializeRemoteDomain<S extends Schema>(
147
136
  schema,
148
137
  config.userCore as Core<S> | undefined,
149
138
  undefined,
150
- domain.remoteFunctions ? buildFunctionDeclarations(domain.remoteFunctions) : undefined,
151
139
  domain.views ? buildViewDeclarations(domain.views) : undefined,
152
140
  )
153
141
 
@@ -3,10 +3,9 @@
3
3
  * halves a standalone-function DOMAIN MEMBER contributes — mirroring how a class
4
4
  * method splits across compile + serialize:
5
5
  *
6
- * - `FunctionDeclarations` — the CONTRACT half, fed to `compileDomain`'s 4th
7
- * arg. The member ref (`function.<slug>`), the layout
8
- * (`/<origin>/functions/<slug>`), and the `of_domain` edge slug all derive
9
- * from the map key; the kernel owns that mapping.
6
+ * - the CONTRACT half a `func({ input, output })` per slug, declared in the
7
+ * schema's `functions` group so the compiled domain carries `function.<slug>`
8
+ * on `refs.functions` (the kernel owns the ref/layout/of_domain mapping).
10
9
  * - `FunctionSchema[]` + per-slug `FunctionBinding` — the IMPL/BINDING half,
11
10
  * fed to `serialize` (url-stamped). The worker route is
12
11
  * `<url>/<functionsFolder>/<slug>`, DECOUPLED from the graph layout — so
@@ -18,7 +17,8 @@
18
17
  */
19
18
 
20
19
  import type { FunctionBinding } from '@astrale-os/kernel-api/routed'
21
- import type { FunctionDeclarations, FunctionSchema } from '@astrale-os/kernel-core/domain'
20
+ import type { FunctionSchema } from '@astrale-os/kernel-core/domain'
21
+ import type { Schema } from '@astrale-os/kernel-dsl'
22
22
 
23
23
  import { zodToJsonSchema } from '@astrale-os/kernel-core/domain'
24
24
 
@@ -45,17 +45,37 @@ function assertValidSlugs(functions: Record<string, AnyRemoteFunctionDef>): void
45
45
  }
46
46
 
47
47
  /**
48
- * Contract half — one declaration per `functions` map key, for `compileDomain`'s
49
- * `functions` arg. The slug drives ref/path/of_domain; impl/binding arrive at
50
- * serialize.
48
+ * Contract half — since "functions as first-class defineSchema members", a
49
+ * standalone function's CONTRACT is declared in the SCHEMA (`defineSchema(...,
50
+ * { functions: { seed: func({ input, output }) } })`) and `compileDomain` builds
51
+ * the member refs from it; the SDK map supplies the HANDLER half. This check
52
+ * pins the two halves together at define time: every schema-declared function
53
+ * needs a handler, every handler a declaration — a drifted key fails loudly at
54
+ * authoring, never as a boot-time missing-identity or a dangling graph member.
51
55
  */
52
- export function buildFunctionDeclarations(
53
- functions: Record<string, AnyRemoteFunctionDef>,
54
- ): FunctionDeclarations {
55
- assertValidSlugs(functions)
56
- const out: Record<string, { slug: string }> = {}
57
- for (const slug of Object.keys(functions)) out[slug] = { slug }
58
- return out
56
+ export function assertFunctionsMatchSchema(
57
+ schema: Schema,
58
+ functions: Record<string, AnyRemoteFunctionDef> | undefined,
59
+ ): void {
60
+ if (functions) assertValidSlugs(functions)
61
+ const declared = Object.keys(schema.functions ?? {})
62
+ const handled = Object.keys(functions ?? {})
63
+ const missingHandler = declared.filter((slug) => !handled.includes(slug))
64
+ const missingDecl = handled.filter((slug) => !declared.includes(slug))
65
+ if (missingHandler.length === 0 && missingDecl.length === 0) return
66
+ const parts: string[] = []
67
+ if (missingHandler.length > 0) {
68
+ parts.push(
69
+ `schema declares function(s) with no handler: ${missingHandler.map((s) => `"${s}"`).join(', ')}`,
70
+ )
71
+ }
72
+ if (missingDecl.length > 0) {
73
+ parts.push(
74
+ `handler(s) with no schema declaration: ${missingDecl.map((s) => `"${s}"`).join(', ')} ` +
75
+ `(declare them in defineSchema's \`functions\` group via \`func({ input, output })\`)`,
76
+ )
77
+ }
78
+ throw new Error(`defineRemoteDomain: functions/schema mismatch — ${parts.join('; ')}.`)
59
79
  }
60
80
 
61
81
  /**
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // ─── Method authoring ────────────────────────────────────────────────────
2
2
  export type {
3
+ MethodParams,
4
+ MethodResult,
3
5
  AuthForPolicy,
6
+ GraphApi,
4
7
  Kernel,
5
8
  KernelForAuth,
6
9
  RemoteContext,
@@ -21,6 +24,7 @@ export { defineDomain, deploy, defineAdapter } from './config'
21
24
  export type {
22
25
  DefineDomainConfig,
23
26
  DomainDefinition,
27
+ DomainManifest,
24
28
  DeployConfig,
25
29
  AdapterSpec,
26
30
  DeployCtx,
@@ -34,6 +38,25 @@ export type {
34
38
  // ─── Install graph (advanced) ────────────────────────────────────────────
35
39
  export { buildInstallGraph, buildInstallGraphHash } from './domain'
36
40
 
41
+ // ─── Graph patching (advanced) ────────────────────────────────────────
42
+ // Author-facing patch types, re-exported from kernel-core (the single source).
43
+ // Build a `Patch` and feed it straight to `ctx.kernel.mutate(patch)` (it implements
44
+ // `PatchData`); the graph sugar itself lives in `@astrale-os/kernel-client/graph`,
45
+ // flattened onto `ctx.kernel`, and `function.mutate` validates every patch kernel-side
46
+ // against kernel-core's `patchDataSchema` — no client-side re-validation needed.
47
+ export { AbsolutePath, Patch, Path } from '@astrale-os/kernel-core'
48
+ export { ClassPath } from '@astrale-os/kernel-core/domain'
49
+ export type {
50
+ EdgeCreate,
51
+ EdgeDelete,
52
+ EdgeMatcher,
53
+ EdgeUpdate,
54
+ NodeCreate,
55
+ NodeDelete,
56
+ NodeUpdate,
57
+ PatchData,
58
+ } from '@astrale-os/kernel-core'
59
+
37
60
  // ─── Declarative resource helpers ────────────────────────────────────────
38
61
  // Author Views (iframe-mountable) and RemoteFunctions (standalone callables)
39
62
  // at the domain definition site. Each `defineView` / `defineRemoteFunction`
@@ -53,10 +76,10 @@ export type { ViewDef, ViewRenderContext, RemoteFunctionDef, RemoteFunctionConte
53
76
  // runtime import from '@astrale-os/sdk/server' directly.
54
77
 
55
78
  // ─── Deploy ──────────────────────────────────────────────────────────────
56
- // `deployCheck` and `hashSpecFile` use Node-only modules (`node:crypto`,
57
- // `node:fs`, `node:child_process`) at module load time. Exporting them from
58
- // the barrel poisons browser bundlers (Vite/esbuild) that transitively load
59
- // every re-export. Consumers that need them import from './deploy' directly.
79
+ // `deployCheck` uses Node-only modules (`node:path`, `node:child_process`) at
80
+ // module load time. Exporting it from the barrel poisons browser bundlers
81
+ // (Vite/esbuild) that transitively load every re-export. Consumers that need it
82
+ // import from './deploy' directly.
60
83
  export { MetaSchema } from './deploy/meta'
61
84
  export type { Meta } from './deploy/meta'
62
85
 
@@ -74,6 +97,7 @@ export type {
74
97
 
75
98
  // ─── Auth ────────────────────────────────────────────────────────────────
76
99
  export type { RemoteIdentityConfig, AuthenticateResult } from './auth'
100
+ export type { DomainIssuer, MintIssuedParams } from './auth'
77
101
  export {
78
102
  authenticateRequest,
79
103
  bindSelfKernel,
@@ -101,4 +125,4 @@ export {
101
125
  SdkValidationError,
102
126
  SdkResultValidationError,
103
127
  } from './dispatch'
104
- export type { SelfResult, CallRemoteFn, KernelCaller } from './dispatch'
128
+ export type { SelfResult, TypedSelf, CallRemoteFn, KernelCaller } from './dispatch'
@@ -25,7 +25,7 @@ import type {
25
25
  } from '@astrale-os/kernel-core/domain'
26
26
  import type { ImplementableOwnKeys, Schema } from '@astrale-os/kernel-dsl'
27
27
 
28
- import type { SelfResult } from '../dispatch/self'
28
+ import type { TypedSelf } from '../dispatch/self'
29
29
  import type { MethodImpl, RemoteHandler } from './single'
30
30
 
31
31
  /** Per-class method implementations for a remote domain. */
@@ -51,12 +51,16 @@ type InterfaceMethodHandler<
51
51
  ? InterfaceMethodDefs<S, K>[M] extends { readonly config: infer MC }
52
52
  ? MC extends { readonly static: true }
53
53
  ? RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, undefined, TDeps>
54
- : // Non-static interface methods receive the same `self` the dispatcher
55
- // actually delivers — `SelfResult` ({ path, id?, node() }) exactly as
56
- // the class-method path (`MethodImpl`) does. Declared props are NOT
57
- // hydrated onto `self` (a node-typed self would be unsound); a handler
58
- // that needs the node's class/props reads them via `self.node()`.
59
- RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, SelfResult, TDeps>
54
+ : // Non-static interface methods receive the schema-typed `self` keyed by
55
+ // THIS interface's def — `self.node()` binds to a `BoundNode<D>` with
56
+ // short-keyed, def-typed `props`, `self.call` is typed, exactly as the
57
+ // class-method path (`MethodImpl`) does.
58
+ RemoteHandler<
59
+ ResolveParams<MC>,
60
+ ResolveReturn<MC>,
61
+ TypedSelf<DefForInterface<S, K>, S>,
62
+ TDeps
63
+ >
60
64
  : never
61
65
  : never
62
66
 
@@ -12,8 +12,11 @@ import type { FnMap } from '@astrale-os/kernel-client'
12
12
  import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
13
13
  import type { AuthContext } from '@astrale-os/kernel-core'
14
14
 
15
+ import type { DomainIssuer } from '../auth/issuer-mint'
15
16
  import type { CallRemoteFn } from '../dispatch/call-remote'
16
17
 
18
+ export type { GraphApi } from '@astrale-os/kernel-client/graph'
19
+
17
20
  /**
18
21
  * The kernel session a handler holds: a credential-bound view over the parent
19
22
  * kernel — the FULL dispatch surface (`call`/`stream`/`binary`/`signal`,
@@ -80,11 +83,24 @@ export type RemoteContext<
80
83
  * credential `union(delegation, self)`. Its nullability is `{@link
81
84
  * KernelForAuth}` of the method's `auth`: non-null for the default
82
85
  * `'required'`, `… | null` for `'optional'`, `null` for `'public'`. Use for
83
- * kernel syscalls + same-domain methods. For ANOTHER worker's remote method
84
- * use {@link RemoteContext.callRemote} `kernel.call` to a remote method
85
- * fails the audience check.
86
+ * kernel syscalls + same-domain methods, and for the typed graph read/write
87
+ * sugar flattened onto it (`kernel.get` / `kernel.children` / `kernel.query`,
88
+ * plus `kernel.createNode` / `kernel.updateNode` /
89
+ * `kernel.deleteNode` / `kernel.createEdge` / `kernel.updateEdge` /
90
+ * `kernel.deleteEdge` / `kernel.mutate(patch)`) over the `function.get` /
91
+ * `function.mutate` syscalls. For ANOTHER worker's remote method use
92
+ * {@link RemoteContext.callRemote} — `kernel.call` to a remote method fails the
93
+ * audience check.
86
94
  */
87
95
  kernel: TKernel
96
+ /**
97
+ * Mint seam for identities THIS DOMAIN issues (`iss` = this worker): sign a
98
+ * credential for a domain-owned principal (an App identity, a role umbrella,
99
+ * a managed user) and optionally open a kernel session AS it. See
100
+ * {@link DomainIssuer}. The kernel enforces trust/frozen/grant rules — this
101
+ * only signs under the worker's own issuer.
102
+ */
103
+ issuer: DomainIssuer
88
104
  /**
89
105
  * Call another worker's remote method (a Function with `binding.remoteUrl`),
90
106
  * re-minting the credential for the target's audience so it isn't rejected at
@@ -1,5 +1,11 @@
1
- export type { AuthForPolicy, Kernel, KernelForAuth, RemoteContext } from './context'
2
- export type { RemoteHandler, AnyRemoteHandler, MethodImpl } from './single'
1
+ export type { AuthForPolicy, GraphApi, Kernel, KernelForAuth, RemoteContext } from './context'
2
+ export type {
3
+ RemoteHandler,
4
+ AnyRemoteHandler,
5
+ MethodImpl,
6
+ MethodParams,
7
+ MethodResult,
8
+ } from './single'
3
9
  export { remoteMethod } from './single'
4
10
  export type { ClassMethodsImpl, InterfaceMethodsImpl, SchemaMethodsImpl } from './class'
5
11
  export { remoteClassMethods, remoteInterfaceMethods } from './class'