@astrale-os/sdk 0.1.10 → 0.2.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 (77) hide show
  1. package/dist/auth/index.d.ts +1 -1
  2. package/dist/auth/index.d.ts.map +1 -1
  3. package/dist/auth/index.js +1 -1
  4. package/dist/auth/index.js.map +1 -1
  5. package/dist/cli/run.d.ts +0 -1
  6. package/dist/cli/run.d.ts.map +1 -1
  7. package/dist/cli/run.js +17 -10
  8. package/dist/cli/run.js.map +1 -1
  9. package/dist/config/adapter.d.ts +12 -11
  10. package/dist/config/adapter.d.ts.map +1 -1
  11. package/dist/config/adapter.js.map +1 -1
  12. package/dist/config/define-domain.d.ts +13 -29
  13. package/dist/config/define-domain.d.ts.map +1 -1
  14. package/dist/config/define-domain.js +22 -33
  15. package/dist/config/define-domain.js.map +1 -1
  16. package/dist/config/deploy.d.ts +1 -1
  17. package/dist/config/deploy.js +1 -1
  18. package/dist/config/index.d.ts +1 -1
  19. package/dist/config/index.d.ts.map +1 -1
  20. package/dist/define/remote-function.d.ts +9 -14
  21. package/dist/define/remote-function.d.ts.map +1 -1
  22. package/dist/define/remote-function.js +9 -8
  23. package/dist/define/remote-function.js.map +1 -1
  24. package/dist/define/view.d.ts +8 -6
  25. package/dist/define/view.d.ts.map +1 -1
  26. package/dist/define/view.js +8 -6
  27. package/dist/define/view.js.map +1 -1
  28. package/dist/dispatch/identity.d.ts +10 -9
  29. package/dist/dispatch/identity.d.ts.map +1 -1
  30. package/dist/dispatch/identity.js +9 -15
  31. package/dist/dispatch/identity.js.map +1 -1
  32. package/dist/domain/binding.d.ts +18 -0
  33. package/dist/domain/binding.d.ts.map +1 -0
  34. package/dist/domain/binding.js +29 -0
  35. package/dist/domain/binding.js.map +1 -0
  36. package/dist/domain/build-spec.d.ts.map +1 -1
  37. package/dist/domain/build-spec.js +10 -7
  38. package/dist/domain/build-spec.js.map +1 -1
  39. package/dist/domain/define.d.ts +27 -27
  40. package/dist/domain/define.d.ts.map +1 -1
  41. package/dist/domain/define.js +31 -49
  42. package/dist/domain/define.js.map +1 -1
  43. package/dist/domain/extend-functions.d.ts +44 -0
  44. package/dist/domain/extend-functions.d.ts.map +1 -0
  45. package/dist/domain/extend-functions.js +69 -0
  46. package/dist/domain/extend-functions.js.map +1 -0
  47. package/dist/domain/extend-views.d.ts +45 -0
  48. package/dist/domain/extend-views.d.ts.map +1 -0
  49. package/dist/domain/extend-views.js +116 -0
  50. package/dist/domain/extend-views.js.map +1 -0
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/server/auxiliary-routes.js +1 -1
  56. package/dist/server/auxiliary-routes.js.map +1 -1
  57. package/dist/server/worker-entry.d.ts +3 -3
  58. package/dist/server/worker-entry.js +3 -3
  59. package/package.json +2 -5
  60. package/src/auth/index.ts +1 -1
  61. package/src/cli/run.ts +17 -12
  62. package/src/config/adapter.ts +9 -11
  63. package/src/config/define-domain.ts +34 -56
  64. package/src/config/deploy.ts +1 -1
  65. package/src/config/index.ts +1 -1
  66. package/src/define/remote-function.ts +9 -14
  67. package/src/define/view.ts +8 -6
  68. package/src/dispatch/identity.ts +15 -21
  69. package/src/domain/binding.ts +37 -0
  70. package/src/domain/build-spec.ts +18 -7
  71. package/src/domain/define.ts +67 -62
  72. package/src/domain/extend-functions.ts +86 -0
  73. package/src/domain/extend-views.ts +151 -0
  74. package/src/index.ts +7 -2
  75. package/src/server/auxiliary-routes.ts +1 -1
  76. package/src/server/worker-entry.ts +3 -3
  77. package/src/domain/extend-core.ts +0 -301
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `defineDomain` — the WORKER-SAFE definition of a domain: what the domain *is*
3
- * (its `schema`, `methods`, `deps`, `views`, standalone `functions`, `client`
4
- * SPA) plus its addressing identity (`origin`, `requires`, `postInstall`). It
3
+ * (its `schema`, `methods`, `deps`, `views`, standalone `functions`) plus its
4
+ * addressing identity (`origin`, `requires`, `postInstall`). It
5
5
  * deliberately carries NO deployment adapter — the adapter (`cloudflare(...)`,
6
6
  * `astrale(...)`) is node-only code (filesystem, wrangler) that must never enter
7
7
  * the worker bundle. The author wires this in a `domain.ts` the generated worker
@@ -11,14 +11,14 @@
11
11
  * The modules are wired EXPLICITLY here — imported and passed in — not
12
12
  * discovered from magic folder names. A renamed or mistyped module is a compile
13
13
  * error at this call site, never a silently-missing worker route. The adapter
14
- * reads this one definition for everything it codegens; there is no second
15
- * filesystem probe to drift from it. `defineDomain` itself builds no server and
16
- * boots no kernel — it validates and packages the declaration.
14
+ * reads this one definition for domain-side codegen; frontend source folders
15
+ * live in adapter env config. `defineDomain` itself builds no server and boots
16
+ * no kernel — it validates and packages the declaration.
17
17
  */
18
18
 
19
19
  import type { Schema } from '@astrale-os/kernel-dsl'
20
20
 
21
- import { DomainOrigin, extractDomainSlug } from '@astrale-os/kernel-core/domain'
21
+ import { DomainOrigin } from '@astrale-os/kernel-core/domain'
22
22
 
23
23
  import type { RemoteFunctionDef, ViewDef } from '../define'
24
24
  import type { SchemaMethodsImpl } from '../method'
@@ -33,15 +33,6 @@ type AnyViewDef = ViewDef<any>
33
33
  type AnyFunctionDef = RemoteFunctionDef<any, any, any>
34
34
  // oxlint-enable no-explicit-any
35
35
 
36
- /**
37
- * The domain's client SPA binding. Its presence is the whole signal — there is
38
- * no `existsSync('client/')` probe. `dir` is the project-relative source folder
39
- * (e.g. `'client'`); the worker serves its built SPA under `/ui` via its Assets
40
- * binding. Always written out explicitly (`client: { dir: 'client' }`) — there
41
- * is no boolean shorthand, so the source folder is never implicit.
42
- */
43
- export type ClientBinding = { dir: string }
44
-
45
36
  export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
46
37
  /** The domain schema (from `schema/`). Its `.domain` seeds the default origin. */
47
38
  schema: S
@@ -76,12 +67,6 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
76
67
  * by slug. Omit when the domain has none.
77
68
  */
78
69
  functions?: Record<string, AnyFunctionDef>
79
- /**
80
- * The domain's client SPA, e.g. `{ dir: 'client' }`. Its presence enables it
81
- * (no folder probing); `dir` is the project-relative source folder, built and
82
- * served under `/ui`. Omit for a domain with no SPA.
83
- */
84
- client?: ClientBinding
85
70
  /**
86
71
  * The domain's **addressing name** (the graph slug it mounts under, e.g.
87
72
  * `'crm.acme.dev'`). Defaults to `schema.domain`. Must be a name, never a
@@ -94,11 +79,15 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
94
79
  /** Cross-domain deps, by origin. Verified present on the instance at install. */
95
80
  requires?: readonly string[]
96
81
  /**
97
- * Astrale Path (usually an AbsolutePath to a `functions/` entry) the kernel
98
- * calls once after install, as __SYSTEM__ where the domain posts its own
99
- * grants / seed.
82
+ * The function the kernel runs once after install, as __SYSTEM__ — where the
83
+ * domain seeds itself / posts its own grants. Reference it from the `functions`
84
+ * map: `postInstall: functions.seed`. The SDK derives its path by identity, so a
85
+ * typo or a renamed key is a compile error here, never a stale string. It is
86
+ * always a standalone function (a domain bootstrap belongs to the domain, not to
87
+ * a class) under THIS domain — you never write the origin, and the kernel
88
+ * resolves it relative to wherever the domain is installed.
100
89
  */
101
- postInstall?: string
90
+ postInstall?: AnyFunctionDef
102
91
  }
103
92
 
104
93
  export interface DomainDefinition {
@@ -114,8 +103,6 @@ export interface DomainDefinition {
114
103
  deps?: (env: any, url: string) => any
115
104
  views?: Record<string, ViewDef>
116
105
  functions?: Record<string, AnyFunctionDef>
117
- /** Normalized client binding (resolved `dir`), or absent when the domain has no SPA. */
118
- client?: { dir: string }
119
106
  origin: string
120
107
  requires: readonly string[]
121
108
  postInstall?: string
@@ -170,46 +157,37 @@ export function defineDomain<S extends Schema, TDeps, TEnv = unknown>(
170
157
  ...(config.deps ? { deps: config.deps as DomainDefinition['deps'] } : {}),
171
158
  ...(config.views ? { views: config.views as Record<string, ViewDef> } : {}),
172
159
  ...(config.functions ? { functions: config.functions } : {}),
173
- ...(config.client ? { client: config.client } : {}),
174
160
  origin,
175
161
  requires,
176
- ...(config.postInstall
177
- ? { postInstall: normalizePostInstall(config.postInstall, origin) }
162
+ ...(config.postInstall !== undefined
163
+ ? { postInstall: normalizePostInstall(config.postInstall, origin, config.functions) }
178
164
  : {}),
179
165
  }
180
166
  }
181
167
 
182
168
  /**
183
- * Validate a `postInstall` hook path and align its leading origin segment with
184
- * the canonical (lowercased) origin. Only the typed colon forms are accepted
185
- * (`/:<origin>:class.X:seed`, `/:<origin>:interface.Ops:seed`) mirroring the
186
- * kernel's own origin guard, which refuses absolute tree paths because they
187
- * cannot prove their origin from the string alone. The hook is often authored
188
- * as `` `/:${schema.domain}:…` `` where `schema.domain` keeps its source
189
- * casing, but the kernel stores graph nodes (and runs its guard) under the
190
- * lowercased origin — so the origin segment is re-stamped canonical. Rejects a
191
- * tree path or one pointing at a different domain (the kernel calls the hook
192
- * as __SYSTEM__ and refuses a foreign target).
169
+ * Resolve a `postInstall` function reference to the colon-path the bundle carries.
170
+ * The slug is the `functions` map key the reference is registered under (found by
171
+ * identity), so a renamed key is a compile error at the reference site AND the
172
+ * derived path follows the rename. The origin is never the author's to supply
173
+ * (postInstall is always a standalone function of THIS domain) and the path is
174
+ * mount-agnostic the kernel resolves it wherever the domain is installed.
193
175
  */
194
- function normalizePostInstall(postInstall: string, origin: string): string {
195
- const slug = extractDomainSlug(postInstall)
196
- if (slug === null) {
197
- throw new Error(
198
- `defineDomain: \`postInstall\` must be a typed colon-path under "/:${origin}" ` +
199
- `(e.g. "/:${origin}:class.Note:seed" or "/:${origin}:interface.Ops:seed"); ` +
200
- `absolute tree paths are not accepted — got "${postInstall}".`,
201
- )
202
- }
203
- if (slug.toLowerCase() !== origin) {
176
+ function normalizePostInstall(
177
+ postInstall: AnyFunctionDef,
178
+ origin: string,
179
+ functions: Record<string, AnyFunctionDef> | undefined,
180
+ ): string {
181
+ const slug = functions
182
+ ? Object.entries(functions).find(([, def]) => def === postInstall)?.[0]
183
+ : undefined
184
+ if (slug === undefined) {
204
185
  throw new Error(
205
- `defineDomain: \`postInstall\` "${postInstall}" must resolve under the domain origin ` +
206
- `"/${origin}" — the kernel calls it as __SYSTEM__ and refuses a hook pointing at another domain.`,
186
+ "defineDomain: `postInstall` must reference a function from this domain's `functions` map " +
187
+ '(e.g. `postInstall: functions.seed`).',
207
188
  )
208
189
  }
209
- // Re-stamp the origin segment with its canonical (lowercased) form.
210
- return postInstall.startsWith('/:')
211
- ? `/:${origin}${postInstall.slice(2 + slug.length)}`
212
- : `/${origin}${postInstall.slice(1 + slug.length)}`
190
+ return `/:${origin}:function.${slug}`
213
191
  }
214
192
 
215
193
  function schemaDomain(schema: Schema): string | undefined {
@@ -9,7 +9,7 @@
9
9
  * is authored in `astrale.config.ts` — a Node-only module the CLI loads but the
10
10
  * worker never imports — so the adapter stays out of the bundle.
11
11
  *
12
- * export const domain = defineDomain({ schema, methods, deps, views, client })
12
+ * export const domain = defineDomain({ schema, methods, deps, views })
13
13
  *
14
14
  * import { domain } from './domain'
15
15
  * export default deploy(domain, cloudflare({ dev, prod }))
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  export { defineDomain } from './define-domain'
17
- export type { ClientBinding, DefineDomainConfig, DomainDefinition } from './define-domain'
17
+ export type { DefineDomainConfig, DomainDefinition } from './define-domain'
18
18
 
19
19
  export { deploy } from './deploy'
20
20
  export type { DeployConfig } from './deploy'
@@ -4,16 +4,17 @@
4
4
  * the canonical kernel `Function` class, the former distribution `RemoteFunction`.)
5
5
  *
6
6
  * Each entry in `defineRemoteDomain({ remoteFunctions: { ... } })` becomes:
7
- * - a graph node at `/${origin}/core/<functionsFolder>/<slug>`, materialized
8
- * as the kernel `Function` class by `buildCorePath` so kernel discovery /
9
- * `View.resolve` can list it. The `core/` anchor appears in the GRAPH path only.
7
+ * - a graph node at `/${origin}/functions/<slug>` — a first-class domain MEMBER,
8
+ * materialized as the kernel `Function` class and attached to the Domain via an
9
+ * `of_domain` edge (slug `function.<slug>`), so it is addressable by the
10
+ * semantic path `/:${origin}:function.<slug>` (the form a `postInstall` uses).
10
11
  * - a Hono route on the worker at the path implied by `binding`
11
- * (`/<functionsFolder>/<slug>` POST by default — no `core/` in the URL).
12
+ * (`/<functionsFolder>/<slug>` POST by default — the route URL is decoupled
13
+ * from the graph layout).
12
14
  *
13
- * The slug = the map key (single source of truth, no duplication).
14
- *
15
- * `ref` is auto-derived as `function.<slug>` if omittedused as
16
- * `Function.ref` on the graph node and as the dispatch key.
15
+ * The slug = the map key (single source of truth, no duplication). The member
16
+ * ref (`function.<slug>`), the layout (`/<origin>/functions/<slug>`), and the
17
+ * `of_domain` edge slug are all DERIVED from it there is nothing else to name.
17
18
  */
18
19
 
19
20
  import type { AuthPolicy, FunctionBinding } from '@astrale-os/kernel-api/routed'
@@ -77,12 +78,6 @@ export type RemoteFunctionDef<
77
78
  TDeps = unknown,
78
79
  TAuth extends AuthPolicy = 'required',
79
80
  > = {
80
- /**
81
- * Canonical callable identity. Auto-derived as `function.<slug>` (where
82
- * `<slug>` is the map key) when omitted. Stored as `Function.ref` on the
83
- * graph node and used by the kernel dispatcher to route the call.
84
- */
85
- ref?: string
86
81
  /** Zod schema for the call's parameters. */
87
82
  inputSchema: z.ZodType<TParams>
88
83
  /** Zod schema for the call's result. */
@@ -2,14 +2,16 @@
2
2
  * Authoring a `View` — iframe-mountable callable served by the domain worker.
3
3
  *
4
4
  * Each entry in `defineRemoteDomain({ views: { ... } })` becomes both:
5
- * - a graph node at `/${origin}/core/<viewsFolder>/<slug>` (auto-materialized
6
- * by the SDK; the `core/` segment is the universal anchor injected by
7
- * `buildCorePath`. `<slug>` is the map key, so it lives in exactly one place)
5
+ * - a first-class domain MEMBER: a `View` node at `/${origin}/views/<slug>`
6
+ * attached to the Domain via an `of_domain` edge (slug `view.<slug>`),
7
+ * addressable as `/:${origin}:view.<slug>`. `<slug>` is the map key, so it
8
+ * lives in exactly one place.
8
9
  * - a Hono route on the worker at the path implied by `binding`
9
- * (`/<viewsFolder>/<slug>` by default — note: no `core/` in the URL).
10
+ * (`/<viewsFolder>/<slug>` by default).
10
11
  *
11
- * The `core/` segment appears in the GRAPH path only; the URL path that
12
- * lands in `Function.binding.remoteUrl` is `${url}/<viewsFolder>/<slug>`.
12
+ * The graph layout (`/<origin>/views/<slug>`) is a fixed kernel convention; the
13
+ * URL path that lands in `Function.binding.remoteUrl` is
14
+ * `${url}/<viewsFolder>/<slug>` — DECOUPLED from the graph layout.
13
15
  *
14
16
  * The author can override the URL via `binding` — host and/or path
15
17
  * placeholders are supported (the kernel's `route` mechanism does the
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Per-callable identity — dispatcher runtime + install-time wiring.
3
3
  *
4
- * Two flavors of callable get an identity per the install-time identity
5
- * binding: Methods on classes/interfaces (sub = MethodPath) and auto-
6
- * materialized core function nodes RemoteFunctions and Views (sub =
7
- * AbsolutePath of the node under `core/{functions,views}/<slug>`).
4
+ * Three flavors of callable get an identity per the install-time identity
5
+ * binding: Methods on classes/interfaces (sub = MethodPath), standalone-function
6
+ * MEMBERS (sub = AbsolutePath at `/<origin>/functions/<slug>`), and view MEMBERS
7
+ * (sub = AbsolutePath at `/<origin>/views/<slug>`).
8
8
  *
9
9
  * At server startup the method dispatcher and the View / RemoteFunction
10
10
  * route mounter pre-compute, for every materialized callable, the
@@ -54,11 +54,12 @@ export type AuxBuckets<T> = {
54
54
  }
55
55
 
56
56
  /**
57
- * slug → `AbsolutePath.raw` for each auto-materialized aux node
58
- * (RemoteFunction / View). These nodes live at
59
- * `/<origin>/core/{functions,views}/<slug>` and have no class+method
60
- * decomposition that would justify a MethodPath — their identity is their
61
- * graph position.
57
+ * slug → `AbsolutePath.raw` for each aux callable MEMBER node:
58
+ * - standalone-function members (`kind: 'function'`) at `/<origin>/functions/<slug>`;
59
+ * - view members (`kind: 'view'`) at `/<origin>/views/<slug>` (plus any
60
+ * hand-authored/legacy core `View` nodes, `kind: 'core'` className `View`).
61
+ * Neither has a class+method decomposition that would justify a MethodPath —
62
+ * their identity is their graph position.
62
63
  */
63
64
  export type AuxIdentityPaths = AuxBuckets<string>
64
65
 
@@ -66,20 +67,13 @@ export function collectAuxIdentityPaths(compiled: CompiledDomain): AuxIdentityPa
66
67
  const views: Record<string, string> = {}
67
68
  const remoteFunctions: Record<string, string> = {}
68
69
  for (const c of resolveCallables(compiled)) {
69
- if (c.kind !== 'core' || !c.slug) continue
70
- if (c.className === 'View') {
71
- views[c.slug] = c.sub
72
- } else if (c.className === 'Function') {
73
- // Standalone callables (the former `RemoteFunction`) materialize as the
74
- // canonical kernel `Function` class.
70
+ if (!c.slug) continue
71
+ if (c.kind === 'function') {
75
72
  remoteFunctions[c.slug] = c.sub
76
- } else {
77
- throw new Error(
78
- `collectAuxIdentityPaths: unrecognised aux callable className "${c.className}" ` +
79
- `at ${c.ref}. If you've added a new auto-materialized Function class to extendCore, ` +
80
- `extend this collector + the kernel walker (resolveCallables) to handle it.`,
81
- )
73
+ } else if (c.kind === 'view' || (c.kind === 'core' && c.className === 'View')) {
74
+ views[c.slug] = c.sub
82
75
  }
76
+ // Methods (`kind: 'method'`) get their identity via `collectMethodPaths`.
83
77
  }
84
78
  return { views, remoteFunctions }
85
79
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared binding helpers for auto-materialized members (functions + views).
3
+ *
4
+ * The worker ROUTE URL a member is served at — `<url>/<folderSlug>/<slug>` — is
5
+ * decoupled from the member's GRAPH layout (`/<origin>/{functions,views}/<slug>`,
6
+ * a fixed kernel convention). `folderSlug` names only the URL segment.
7
+ */
8
+
9
+ import type { FunctionBinding } from '@astrale-os/kernel-api/routed'
10
+
11
+ /** Join a worker-relative mount path onto the serving url (single slash). */
12
+ export function joinWorkerPath(url: string, mount: string): string {
13
+ const base = url.replace(/\/+$/, '')
14
+ return `${base}/${mount.replace(/^\/+/, '')}`
15
+ }
16
+
17
+ /**
18
+ * Resolve a member's effective binding against the serving `url`. An explicit
19
+ * `override.remoteUrl` wins; otherwise defaults to `<url>/<folderSlug>/<slug>`.
20
+ * Same trailing-slash discipline as {@link joinWorkerPath} — a `url` ending in
21
+ * `/` must not produce `//` (the kernel pins `iss` by exact string).
22
+ */
23
+ export function resolveBinding(
24
+ override: FunctionBinding | undefined,
25
+ url: string,
26
+ folderSlug: string,
27
+ slug: string,
28
+ ): FunctionBinding {
29
+ const base = url.replace(/\/+$/, '')
30
+ if (override) {
31
+ return {
32
+ ...override,
33
+ remoteUrl: override.remoteUrl ?? `${base}/${folderSlug}/${slug}`,
34
+ }
35
+ }
36
+ return { remoteUrl: `${base}/${folderSlug}/${slug}` }
37
+ }
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import type { Graph, WireGraph } from '@astrale-os/kernel-core'
21
- import type { BoundMethod, FunctionSchema } from '@astrale-os/kernel-core/domain'
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
24
  import { hashInstallGraph, serialize, zodToJsonSchema } from '@astrale-os/kernel-core/domain'
@@ -41,8 +41,14 @@ export function buildInstallGraph<S extends Schema>(
41
41
  domain: RemoteDomain<S>,
42
42
  url: string,
43
43
  ): WireGraph {
44
- const { compiled } = materializeRemoteDomain(domain, url)
45
- return buildSpecInternal(compiled, domain.methods, url).toWire() as WireGraph
44
+ const { compiled, auxiliary } = materializeRemoteDomain(domain, url)
45
+ return buildSpecInternal(
46
+ compiled,
47
+ domain.methods,
48
+ url,
49
+ auxiliary?.functionSchemas ?? [],
50
+ auxiliary?.viewSchemas ?? [],
51
+ ).toWire() as WireGraph
46
52
  }
47
53
 
48
54
  /**
@@ -63,12 +69,17 @@ function buildSpecInternal(
63
69
  compiled: RemoteDomain['compiled'],
64
70
  methods: BoundMethod<AnyRemoteHandler>[],
65
71
  url: string,
72
+ functionSchemas: FunctionSchema[],
73
+ viewSchemas: ViewSchema[],
66
74
  ): Graph {
67
75
  const serialized = serializeMethodsWithStubs(compiled, methods, url)
68
- const tree = serialize(compiled, serialized)
69
- // Views / RemoteFunctions are auto-materialized into the Core BEFORE
70
- // `compileDomain` runs (see `extend-core.ts`). By the time we reach
71
- // `serialize` here the Tree already contains their nodes/edges.
76
+ // Method impls + standalone-function-member impls travel in the same callable
77
+ // array (distinct refs: `class.X.method.y` vs `function.<slug>`); view-member
78
+ // impls travel in `options.views` (a view differs structurally — handshake,
79
+ // view_for, View class). The serializer emits method nodes from the IR,
80
+ // function/view members from `compiled.$.refs.{functions,views}`, pulling each
81
+ // impl by ref.
82
+ const tree = serialize(compiled, [...serialized, ...functionSchemas], { views: viewSchemas })
72
83
  return tree.toGraph()
73
84
  }
74
85
 
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * `defineRemoteDomain` — turn a typed schema + methods map into a mountable domain.
3
3
  *
4
- * When `views` and/or `remoteFunctions` entries are provided, the SDK
5
- * auto-materializes graph nodes
6
- * under reserved folders (`viewsFolder` / `functionsFolder`, defaults `'views'`
7
- * / `'functions'`). Views and standalone functions materialize as canonical
8
- * kernel `View` / `Function` classes by default (no per-domain class to
9
- * configure). The same entries'
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'
10
9
  * `render` / `execute` handlers are mounted as Hono routes by
11
- * `createRemoteServer`. Slug = map key.
10
+ * `createRemoteServer` at `<url>/<viewsFolder|functionsFolder>/<slug>` (the route
11
+ * URL is decoupled from the graph layout). Slug = map key.
12
12
  *
13
13
  * The domain definition is deployment-agnostic: it carries NO serving url. The
14
14
  * url is supplied late by the spec producer (`createRemoteServer({ url })` at
@@ -18,8 +18,13 @@
18
18
  */
19
19
 
20
20
  import type { FunctionBinding } from '@astrale-os/kernel-api/routed'
21
- import type { BoundMethod, CompiledDomain } from '@astrale-os/kernel-core/domain'
22
- import type { AnyEdgeDef, AnyNodeDef, Core, Schema } from '@astrale-os/kernel-dsl'
21
+ import type {
22
+ BoundMethod,
23
+ CompiledDomain,
24
+ FunctionSchema,
25
+ ViewSchema,
26
+ } from '@astrale-os/kernel-core/domain'
27
+ import type { Core, Schema } from '@astrale-os/kernel-dsl'
23
28
 
24
29
  import { bindMethods, compileDomain } from '@astrale-os/kernel-core/domain'
25
30
 
@@ -27,7 +32,12 @@ import type { AnyRemoteFunctionDef, ViewDef } from '../define'
27
32
  import type { SchemaMethodsImpl } from '../method/class'
28
33
  import type { AnyRemoteHandler } from '../method/single'
29
34
 
30
- import { DEFAULT_FUNCTIONS_FOLDER, DEFAULT_VIEWS_FOLDER, extendCore } from './extend-core'
35
+ import {
36
+ buildFunctionDeclarations,
37
+ buildFunctionSchemas,
38
+ DEFAULT_FUNCTIONS_FOLDER,
39
+ } from './extend-functions'
40
+ import { buildViewDeclarations, buildViewSchemas, DEFAULT_VIEWS_FOLDER } from './extend-views'
31
41
 
32
42
  export type RemoteDomainConfig<S extends Schema, TDeps> = {
33
43
  schema: S
@@ -35,27 +45,29 @@ export type RemoteDomainConfig<S extends Schema, TDeps> = {
35
45
  core?: Core<S>
36
46
 
37
47
  views?: Record<string, ViewDef<TDeps>>
38
- viewClass?: AnyNodeDef
39
- viewForEdgeClass?: AnyEdgeDef
40
48
  viewsFolder?: string
41
49
 
42
50
  remoteFunctions?: Record<string, AnyRemoteFunctionDef>
43
51
  functionsFolder?: string
44
52
  }
45
53
 
46
- /** Effective bindings + folder layout for auxiliary routes, resolved against `url`. */
54
+ /** Effective bindings + folder layout + member schemas for auxiliary routes, resolved against `url`. */
47
55
  export type AuxiliaryMetadata = {
48
56
  url: string
49
57
  viewsFolder: string
50
58
  functionsFolder: string
51
59
  viewBindings: Record<string, FunctionBinding>
52
60
  remoteFunctionBindings: Record<string, FunctionBinding>
61
+ /** Function members' impl/binding half (`function.<slug>`) for `serialize`. */
62
+ functionSchemas: FunctionSchema[]
63
+ /** View members' impl/binding half (`view.<slug>`) for `serialize`. */
64
+ viewSchemas: ViewSchema[]
53
65
  }
54
66
 
55
67
  export type RemoteDomain<S extends Schema = Schema> = {
56
68
  /**
57
- * Define-time compile: full domain STRUCTURE (classes, methods, and the aux
58
- * View/Function nodes' paths/names/refs — what identity, subs, and contract
69
+ * Define-time compile: full domain STRUCTURE (classes, methods, and the
70
+ * view/function members' paths/names/refs — what identity, subs, and contract
59
71
  * resolution need). It carries NO `binding` values: bindings derive from the
60
72
  * serving url, which only the spec producers know — they call
61
73
  * `materializeRemoteDomain(domain, url)` for the install-ready compile.
@@ -65,42 +77,32 @@ export type RemoteDomain<S extends Schema = Schema> = {
65
77
  // oxlint-disable-next-line no-explicit-any
66
78
  views?: Record<string, ViewDef<any>>
67
79
  remoteFunctions?: Record<string, AnyRemoteFunctionDef>
68
- /** The original config, so `materializeRemoteDomain` re-extends from source. */
69
- config: ExtendInputs
80
+ /** The original config, so `materializeRemoteDomain` re-materializes from source. */
81
+ config: MaterializeInputs
70
82
  }
71
83
 
72
- /** The defineRemoteDomain inputs `materializeRemoteDomain` needs to re-extend. */
73
- type ExtendInputs = {
84
+ /** The defineRemoteDomain inputs `materializeRemoteDomain` needs to re-materialize. */
85
+ type MaterializeInputs = {
74
86
  userCore?: Core
75
- viewClass?: AnyNodeDef
76
- viewForEdgeClass?: AnyEdgeDef
77
87
  viewsFolder: string
78
88
  functionsFolder: string
79
89
  }
80
90
 
81
91
  export function defineRemoteDomain<TDeps>() {
82
92
  return function <S extends Schema>(config: RemoteDomainConfig<S, TDeps>): RemoteDomain<S> {
83
- const hasAux = Boolean(config.views || config.remoteFunctions)
84
93
  const viewsFolder = config.viewsFolder ?? DEFAULT_VIEWS_FOLDER
85
94
  const functionsFolder = config.functionsFolder ?? DEFAULT_FUNCTIONS_FOLDER
86
95
 
87
- // Structure-only extension (no url no bindings stamped): the aux nodes
88
- // exist with their paths so subs/identity/contract resolution see them.
89
- const effectiveCore: Core | undefined = hasAux
90
- ? extendCore({
91
- schema: config.schema,
92
- origin: config.schema.domain,
93
- userCore: config.core,
94
- viewClass: config.viewClass,
95
- viewForEdgeClass: config.viewForEdgeClass,
96
- viewsFolder,
97
- views: config.views,
98
- functionsFolder,
99
- remoteFunctions: config.remoteFunctions,
100
- }).core
101
- : config.core
102
-
103
- const compiled = compileDomain(config.schema, effectiveCore as Core<S> | undefined)
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.
99
+ const compiled = compileDomain(
100
+ config.schema,
101
+ config.core,
102
+ undefined,
103
+ config.remoteFunctions ? buildFunctionDeclarations(config.remoteFunctions) : undefined,
104
+ config.views ? buildViewDeclarations(config.views) : undefined,
105
+ )
104
106
  const methods = bindMethods<AnyRemoteHandler>(
105
107
  compiled.$.schema,
106
108
  compiled.$.methods,
@@ -115,8 +117,6 @@ export function defineRemoteDomain<TDeps>() {
115
117
  ...(config.remoteFunctions ? { remoteFunctions: config.remoteFunctions } : {}),
116
118
  config: {
117
119
  userCore: config.core,
118
- viewClass: config.viewClass,
119
- viewForEdgeClass: config.viewForEdgeClass,
120
120
  viewsFolder,
121
121
  functionsFolder,
122
122
  },
@@ -125,13 +125,13 @@ export function defineRemoteDomain<TDeps>() {
125
125
  }
126
126
 
127
127
  /**
128
- * Materialize a `RemoteDomain` at its real serving `url`: re-runs `extendCore`
129
- * so every aux View/Function `binding.remoteUrl` points at the actual host,
130
- * and returns the binding maps the auxiliary routes mount from. Called by the
131
- * only two spec producers — `createRemoteServer` (`config.url`) and the
132
- * `astrale-domain` CLI. Returns the define-time `compiled` untouched when there is no aux to
133
- * stamp. `extendCore`/`compileDomain` are pure, so this is safe to call
134
- * repeatedly (and is memoized per cold isolate by the callers).
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
135
  */
136
136
  export function materializeRemoteDomain<S extends Schema>(
137
137
  domain: RemoteDomain<S>,
@@ -142,27 +142,32 @@ export function materializeRemoteDomain<S extends Schema>(
142
142
 
143
143
  const { config } = domain
144
144
  const schema = domain.compiled.$.schema as S
145
- const result = extendCore({
145
+
146
+ const compiled = compileDomain(
146
147
  schema,
147
- origin: schema.domain,
148
- userCore: config.userCore,
149
- url,
150
- viewClass: config.viewClass,
151
- viewForEdgeClass: config.viewForEdgeClass,
152
- viewsFolder: config.viewsFolder,
153
- views: domain.views,
154
- functionsFolder: config.functionsFolder,
155
- remoteFunctions: domain.remoteFunctions,
156
- })
157
- const compiled = compileDomain(schema, result.core as Core<S>)
148
+ config.userCore as Core<S> | undefined,
149
+ undefined,
150
+ domain.remoteFunctions ? buildFunctionDeclarations(domain.remoteFunctions) : undefined,
151
+ domain.views ? buildViewDeclarations(domain.views) : undefined,
152
+ )
153
+
154
+ const fns = domain.remoteFunctions
155
+ ? buildFunctionSchemas(domain.remoteFunctions, url, config.functionsFolder)
156
+ : { schemas: [], bindings: {} }
157
+ const vws = domain.views
158
+ ? buildViewSchemas(domain.views, url, config.viewsFolder, schema)
159
+ : { schemas: [], bindings: {} }
160
+
158
161
  return {
159
162
  compiled,
160
163
  auxiliary: {
161
164
  url,
162
165
  viewsFolder: config.viewsFolder,
163
166
  functionsFolder: config.functionsFolder,
164
- viewBindings: result.viewBindings,
165
- remoteFunctionBindings: result.remoteFunctionBindings,
167
+ viewBindings: vws.bindings,
168
+ remoteFunctionBindings: fns.bindings,
169
+ functionSchemas: fns.schemas,
170
+ viewSchemas: vws.schemas,
166
171
  },
167
172
  }
168
173
  }