@astrale-os/sdk 0.4.10 → 0.4.12

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 (218) hide show
  1. package/dist/auth/authenticate.d.ts +1 -1
  2. package/dist/auth/authenticate.d.ts.map +1 -1
  3. package/dist/auth/authenticate.js +2 -2
  4. package/dist/auth/authenticate.js.map +1 -1
  5. package/dist/auth/check.js +1 -1
  6. package/dist/auth/check.js.map +1 -1
  7. package/dist/auth/function-context.d.ts +1 -1
  8. package/dist/auth/function-context.d.ts.map +1 -1
  9. package/dist/auth/function-context.js +3 -3
  10. package/dist/auth/function-context.js.map +1 -1
  11. package/dist/auth/identity.d.ts +2 -0
  12. package/dist/auth/identity.d.ts.map +1 -1
  13. package/dist/auth/index.d.ts +15 -15
  14. package/dist/auth/index.d.ts.map +1 -1
  15. package/dist/auth/index.js +10 -10
  16. package/dist/auth/index.js.map +1 -1
  17. package/dist/auth/issuer-mint.d.ts +1 -1
  18. package/dist/auth/issuer-mint.d.ts.map +1 -1
  19. package/dist/auth/issuer-mint.js +2 -2
  20. package/dist/auth/issuer-mint.js.map +1 -1
  21. package/dist/auth/kernel-client.d.ts +1 -1
  22. package/dist/auth/kernel-client.d.ts.map +1 -1
  23. package/dist/auth/kernel-client.js +2 -2
  24. package/dist/auth/kernel-client.js.map +1 -1
  25. package/dist/auth/resolve.d.ts +2 -2
  26. package/dist/auth/resolve.d.ts.map +1 -1
  27. package/dist/auth/resolve.js +2 -2
  28. package/dist/auth/resolve.js.map +1 -1
  29. package/dist/auth/verify.d.ts +1 -1
  30. package/dist/auth/verify.d.ts.map +1 -1
  31. package/dist/auth/verify.js +3 -3
  32. package/dist/auth/verify.js.map +1 -1
  33. package/dist/cli/bin.js +1 -1
  34. package/dist/cli/bin.js.map +1 -1
  35. package/dist/cli/index.d.ts +2 -2
  36. package/dist/cli/index.d.ts.map +1 -1
  37. package/dist/cli/index.js +2 -2
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/cli/publish.js +1 -1
  40. package/dist/cli/publish.js.map +1 -1
  41. package/dist/cli/run.d.ts +1 -1
  42. package/dist/cli/run.d.ts.map +1 -1
  43. package/dist/cli/run.js +4 -4
  44. package/dist/cli/run.js.map +1 -1
  45. package/dist/cli/spec.d.ts +1 -1
  46. package/dist/cli/spec.d.ts.map +1 -1
  47. package/dist/cli/spec.js +2 -1
  48. package/dist/cli/spec.js.map +1 -1
  49. package/dist/config/define-domain.d.ts +7 -3
  50. package/dist/config/define-domain.d.ts.map +1 -1
  51. package/dist/config/define-domain.js +1 -0
  52. package/dist/config/define-domain.js.map +1 -1
  53. package/dist/config/deploy.d.ts +2 -2
  54. package/dist/config/deploy.d.ts.map +1 -1
  55. package/dist/config/index.d.ts +6 -6
  56. package/dist/config/index.d.ts.map +1 -1
  57. package/dist/config/index.js +3 -3
  58. package/dist/config/index.js.map +1 -1
  59. package/dist/defer/index.d.ts +24 -0
  60. package/dist/defer/index.d.ts.map +1 -0
  61. package/dist/defer/index.js +42 -0
  62. package/dist/defer/index.js.map +1 -0
  63. package/dist/define/index.d.ts +4 -4
  64. package/dist/define/index.d.ts.map +1 -1
  65. package/dist/define/index.js +2 -2
  66. package/dist/define/index.js.map +1 -1
  67. package/dist/define/remote-function.d.ts +7 -4
  68. package/dist/define/remote-function.d.ts.map +1 -1
  69. package/dist/define/remote-function.js.map +1 -1
  70. package/dist/define/view.d.ts +3 -3
  71. package/dist/define/view.d.ts.map +1 -1
  72. package/dist/deploy/check.d.ts +1 -1
  73. package/dist/deploy/check.d.ts.map +1 -1
  74. package/dist/deploy/check.js +1 -1
  75. package/dist/deploy/check.js.map +1 -1
  76. package/dist/deploy/index.d.ts +2 -2
  77. package/dist/deploy/index.d.ts.map +1 -1
  78. package/dist/deploy/index.js +2 -2
  79. package/dist/deploy/index.js.map +1 -1
  80. package/dist/dispatch/authorize.js +1 -1
  81. package/dist/dispatch/authorize.js.map +1 -1
  82. package/dist/dispatch/dispatcher.d.ts +7 -4
  83. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  84. package/dist/dispatch/dispatcher.js +56 -34
  85. package/dist/dispatch/dispatcher.js.map +1 -1
  86. package/dist/dispatch/execute.d.ts +7 -5
  87. package/dist/dispatch/execute.d.ts.map +1 -1
  88. package/dist/dispatch/execute.js +1 -0
  89. package/dist/dispatch/execute.js.map +1 -1
  90. package/dist/dispatch/identity.d.ts +2 -2
  91. package/dist/dispatch/identity.d.ts.map +1 -1
  92. package/dist/dispatch/index.d.ts +6 -6
  93. package/dist/dispatch/index.d.ts.map +1 -1
  94. package/dist/dispatch/index.js +6 -6
  95. package/dist/dispatch/index.js.map +1 -1
  96. package/dist/dispatch/resolve.d.ts +1 -1
  97. package/dist/dispatch/resolve.d.ts.map +1 -1
  98. package/dist/dispatch/self.d.ts +1 -1
  99. package/dist/dispatch/self.d.ts.map +1 -1
  100. package/dist/domain/build-spec.d.ts +1 -1
  101. package/dist/domain/build-spec.d.ts.map +1 -1
  102. package/dist/domain/build-spec.js +6 -2
  103. package/dist/domain/build-spec.js.map +1 -1
  104. package/dist/domain/contract.d.ts +1 -1
  105. package/dist/domain/contract.d.ts.map +1 -1
  106. package/dist/domain/define.d.ts +4 -4
  107. package/dist/domain/define.d.ts.map +1 -1
  108. package/dist/domain/define.js +2 -2
  109. package/dist/domain/define.js.map +1 -1
  110. package/dist/domain/extend-functions.d.ts +1 -1
  111. package/dist/domain/extend-functions.d.ts.map +1 -1
  112. package/dist/domain/extend-functions.js +1 -1
  113. package/dist/domain/extend-functions.js.map +1 -1
  114. package/dist/domain/extend-views.d.ts +1 -1
  115. package/dist/domain/extend-views.d.ts.map +1 -1
  116. package/dist/domain/extend-views.js +1 -1
  117. package/dist/domain/extend-views.js.map +1 -1
  118. package/dist/domain/index.d.ts +4 -4
  119. package/dist/domain/index.d.ts.map +1 -1
  120. package/dist/domain/index.js +3 -3
  121. package/dist/domain/index.js.map +1 -1
  122. package/dist/index.d.ts +19 -18
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +10 -10
  125. package/dist/index.js.map +1 -1
  126. package/dist/method/class.d.ts +3 -3
  127. package/dist/method/class.d.ts.map +1 -1
  128. package/dist/method/context.d.ts +6 -3
  129. package/dist/method/context.d.ts.map +1 -1
  130. package/dist/method/index.d.ts +5 -5
  131. package/dist/method/index.d.ts.map +1 -1
  132. package/dist/method/index.js +2 -2
  133. package/dist/method/index.js.map +1 -1
  134. package/dist/method/single.d.ts +2 -2
  135. package/dist/method/single.d.ts.map +1 -1
  136. package/dist/server/auxiliary-routes.d.ts +3 -3
  137. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  138. package/dist/server/auxiliary-routes.js +54 -18
  139. package/dist/server/auxiliary-routes.js.map +1 -1
  140. package/dist/server/config.d.ts +1 -1
  141. package/dist/server/config.d.ts.map +1 -1
  142. package/dist/server/create.d.ts +2 -2
  143. package/dist/server/create.d.ts.map +1 -1
  144. package/dist/server/create.js +10 -10
  145. package/dist/server/create.js.map +1 -1
  146. package/dist/server/domain-entry.d.ts +5 -5
  147. package/dist/server/domain-entry.d.ts.map +1 -1
  148. package/dist/server/domain-entry.js +2 -2
  149. package/dist/server/domain-entry.js.map +1 -1
  150. package/dist/server/index.d.ts +11 -11
  151. package/dist/server/index.d.ts.map +1 -1
  152. package/dist/server/index.js +7 -7
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/start.d.ts +1 -1
  155. package/dist/server/start.d.ts.map +1 -1
  156. package/dist/server/worker-entry.d.ts +4 -3
  157. package/dist/server/worker-entry.d.ts.map +1 -1
  158. package/dist/server/worker-entry.js +5 -5
  159. package/dist/server/worker-entry.js.map +1 -1
  160. package/dist/step/index.d.ts +4 -4
  161. package/dist/step/index.d.ts.map +1 -1
  162. package/dist/step/index.js +2 -2
  163. package/dist/step/index.js.map +1 -1
  164. package/dist/step/inline.d.ts +1 -1
  165. package/dist/step/inline.d.ts.map +1 -1
  166. package/dist/step/inline.js +1 -1
  167. package/dist/step/inline.js.map +1 -1
  168. package/package.json +3 -2
  169. package/src/auth/authenticate.ts +3 -3
  170. package/src/auth/check.ts +1 -1
  171. package/src/auth/function-context.ts +4 -4
  172. package/src/auth/identity.ts +2 -0
  173. package/src/auth/index.ts +15 -15
  174. package/src/auth/issuer-mint.ts +3 -3
  175. package/src/auth/kernel-client.ts +3 -3
  176. package/src/auth/resolve.ts +4 -4
  177. package/src/auth/verify.ts +4 -4
  178. package/src/cli/bin.ts +1 -1
  179. package/src/cli/index.ts +2 -2
  180. package/src/cli/publish.ts +1 -1
  181. package/src/cli/run.ts +6 -6
  182. package/src/cli/spec.ts +4 -3
  183. package/src/config/define-domain.ts +8 -3
  184. package/src/config/deploy.ts +2 -2
  185. package/src/config/index.ts +6 -6
  186. package/src/defer/index.ts +64 -0
  187. package/src/define/index.ts +4 -4
  188. package/src/define/remote-function.ts +7 -4
  189. package/src/define/view.ts +3 -3
  190. package/src/deploy/check.ts +2 -2
  191. package/src/deploy/index.ts +2 -2
  192. package/src/dispatch/authorize.ts +1 -1
  193. package/src/dispatch/dispatcher.ts +64 -36
  194. package/src/dispatch/execute.ts +8 -5
  195. package/src/dispatch/identity.ts +2 -2
  196. package/src/dispatch/index.ts +6 -6
  197. package/src/dispatch/resolve.ts +1 -1
  198. package/src/dispatch/self.ts +1 -1
  199. package/src/domain/build-spec.ts +8 -4
  200. package/src/domain/contract.ts +1 -1
  201. package/src/domain/define.ts +6 -6
  202. package/src/domain/extend-functions.ts +2 -2
  203. package/src/domain/extend-views.ts +2 -2
  204. package/src/domain/index.ts +4 -4
  205. package/src/index.ts +21 -18
  206. package/src/method/class.ts +3 -3
  207. package/src/method/context.ts +6 -3
  208. package/src/method/index.ts +5 -5
  209. package/src/method/single.ts +2 -2
  210. package/src/server/auxiliary-routes.ts +70 -21
  211. package/src/server/config.ts +1 -1
  212. package/src/server/create.ts +14 -14
  213. package/src/server/domain-entry.ts +7 -7
  214. package/src/server/index.ts +11 -11
  215. package/src/server/start.ts +1 -1
  216. package/src/server/worker-entry.ts +16 -8
  217. package/src/step/index.ts +4 -4
  218. package/src/step/inline.ts +2 -2
@@ -0,0 +1,64 @@
1
+ import type { Context } from 'hono'
2
+
3
+ /** Internal structural host capability; not part of the author-facing SDK contract. */
4
+ export type HandlerExecutionLifetime = {
5
+ waitUntil(promise: Promise<unknown>): void
6
+ }
7
+
8
+ /**
9
+ * Work detached from the current handler result.
10
+ *
11
+ * The callback is deliberately lazy: the SDK registers the resulting task with
12
+ * the active execution backend before it starts. Current request-based hosts
13
+ * extend their request lifetime; future durable backends may provide stronger
14
+ * execution guarantees without changing the author-facing API.
15
+ */
16
+ export type DeferredTask = () => void | PromiseLike<unknown>
17
+
18
+ export type Defer = (task: DeferredTask) => void
19
+
20
+ export type HandlerLifecycle = {
21
+ defer: Defer
22
+ settle(): Promise<void>
23
+ }
24
+
25
+ /** Bind detached work to the host, with a safe blocking fallback for other runtimes. */
26
+ export function createHandlerLifecycle(lifetime?: HandlerExecutionLifetime): HandlerLifecycle {
27
+ const pending: Promise<unknown>[] = []
28
+
29
+ return {
30
+ defer(task) {
31
+ const promise = Promise.resolve().then(task)
32
+ if (lifetime) lifetime.waitUntil(promise)
33
+ else pending.push(promise)
34
+ },
35
+ async settle() {
36
+ if (lifetime || pending.length === 0) return
37
+
38
+ // A deferred task may register more work before it settles. Drain until
39
+ // the lifecycle is stable so non-request runtimes never abandon children.
40
+ let cursor = 0
41
+ while (cursor < pending.length) {
42
+ const batch = pending.slice(cursor)
43
+ cursor = pending.length
44
+ const results = await Promise.allSettled(batch)
45
+ for (const result of results) {
46
+ if (result.status === 'rejected') {
47
+ // oxlint-disable-next-line no-console
48
+ console.error('[sdk] deferred task failed:', result.reason)
49
+ }
50
+ }
51
+ }
52
+ },
53
+ }
54
+ }
55
+
56
+ /** Hono omits its execution context in Node and in lightweight unit tests. */
57
+ export function executionLifetime(c: Context): HandlerExecutionLifetime | undefined {
58
+ try {
59
+ const host = c.executionCtx
60
+ return { waitUntil: (promise) => host.waitUntil(promise) }
61
+ } catch {
62
+ return undefined
63
+ }
64
+ }
@@ -1,10 +1,10 @@
1
- export { defineView } from './view'
2
- export type { ViewDef, ViewRenderContext } from './view'
1
+ export { defineView } from './view.js'
2
+ export type { ViewDef, ViewRenderContext } from './view.js'
3
3
 
4
- export { defineRemoteFunction } from './remote-function'
4
+ export { defineRemoteFunction } from './remote-function.js'
5
5
  export type {
6
6
  AnyRemoteFunctionDef,
7
7
  RemoteFunctionAuthorizeContext,
8
8
  RemoteFunctionContext,
9
9
  RemoteFunctionDef,
10
- } from './remote-function'
10
+ } from './remote-function.js'
@@ -23,10 +23,11 @@ import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
23
23
  import type { Context } from 'hono'
24
24
  import type { z } from 'zod'
25
25
 
26
- import type { FunctionContextApi } from '../auth/function-context'
27
- import type { DomainAuthority } from '../auth/issuer-mint'
28
- import type { AuthForPolicy, CallerContext, KernelForAuth, RemoteEnv } from '../method/context'
29
- import type { Step } from '../step'
26
+ import type { FunctionContextApi } from '../auth/function-context.js'
27
+ import type { DomainAuthority } from '../auth/issuer-mint.js'
28
+ import type { Defer } from '../defer/index.js'
29
+ import type { AuthForPolicy, CallerContext, KernelForAuth, RemoteEnv } from '../method/context.js'
30
+ import type { Step } from '../step/index.js'
30
31
 
31
32
  type RemoteFunctionBaseContext<
32
33
  TParams,
@@ -83,6 +84,8 @@ export interface RemoteFunctionContext<
83
84
  > extends RemoteFunctionBaseContext<TParams, TDeps, TKernel, TAuth> {
84
85
  /** Durable-shaped step builder. Inline today, replayable by future backends. */
85
86
  step: Step
87
+ /** Run independent work without making its completion part of this result. */
88
+ defer: Defer
86
89
  }
87
90
 
88
91
  export type RemoteFunctionDef<
@@ -24,9 +24,9 @@ import type { AuthPolicy, FunctionBinding } from '@astrale-os/kernel-api/routed'
24
24
  import type { EdgeEndpoint } from '@astrale-os/kernel-dsl'
25
25
  import type { Context } from 'hono'
26
26
 
27
- import type { FunctionContextApi } from '../auth/function-context'
28
- import type { AuthForPolicy } from '../method/context'
29
- import type { RemoteEnv } from '../method/context'
27
+ import type { FunctionContextApi } from '../auth/function-context.js'
28
+ import type { AuthForPolicy } from '../method/context.js'
29
+ import type { RemoteEnv } from '../method/context.js'
30
30
 
31
31
  export type ViewRenderContext<TDeps = unknown, TAuth extends AuthPolicy = AuthPolicy> = {
32
32
  /** Hono request context — read params, headers, query, etc. */
@@ -1,9 +1,9 @@
1
1
  import { spawnSync } from 'node:child_process'
2
2
  import { join } from 'node:path'
3
3
 
4
- import type { Meta } from './meta'
4
+ import type { Meta } from './meta.js'
5
5
 
6
- import { MetaSchema } from './meta'
6
+ import { MetaSchema } from './meta.js'
7
7
 
8
8
  export type DeployCheckOptions = {
9
9
  /** Deployed worker URL (e.g. `https://example.astrale.ai`). */
@@ -1,2 +1,2 @@
1
- export { MetaSchema, type Meta } from './meta'
2
- export { deployCheck, type DeployCheckOptions } from './check'
1
+ export { MetaSchema, type Meta } from './meta.js'
2
+ export { deployCheck, type DeployCheckOptions } from './check.js'
@@ -11,7 +11,7 @@
11
11
  * plain `Error` thrown to deny leaked out as a 500 instead of a 403.
12
12
  */
13
13
 
14
- import { AuthorizationDeniedError } from './errors'
14
+ import { AuthorizationDeniedError } from './errors.js'
15
15
 
16
16
  export async function runAuthorize<C>(
17
17
  hook: (ctx: C) => void | Promise<void>,
@@ -17,24 +17,30 @@ import type { BoundMethod, CompiledDomain } from '@astrale-os/kernel-core/domain
17
17
 
18
18
  import { createNodeBinder } from '@astrale-os/kernel-client/schema'
19
19
 
20
- import type { RemoteIdentityConfig } from '../auth/identity'
21
- import type { AnyRemoteHandler } from '../method/single'
22
- import type { MethodIndex } from './resolve'
20
+ import type { RemoteIdentityConfig } from '../auth/identity.js'
21
+ import type { AnyRemoteHandler } from '../method/single.js'
22
+ import type { MethodIndex } from './resolve.js'
23
23
 
24
- import { makeFunctionContext } from '../auth/function-context'
25
- import { makeDomainAuthority } from '../auth/issuer-mint'
26
- import { resolveInboundAuth } from '../auth/resolve'
27
- import { createInlineStep } from '../step'
28
- import { runAuthorize } from './authorize'
29
- import { MethodNotFoundError, SdkResultValidationError, SdkValidationError } from './errors'
30
- import { executeHandler } from './execute'
31
- import { buildIdentityMap } from './identity'
32
- import { resolveMethod } from './resolve'
33
- import { resolveSelf, withBoundNode, type ParsedSelf } from './self'
34
- import { validateParams, validateResult } from './validate'
24
+ import { makeFunctionContext } from '../auth/function-context.js'
25
+ import { makeDomainAuthority } from '../auth/issuer-mint.js'
26
+ import { resolveInboundAuth } from '../auth/resolve.js'
27
+ import { createHandlerLifecycle, type HandlerExecutionLifetime } from '../defer/index.js'
28
+ import { createInlineStep } from '../step/index.js'
29
+ import { runAuthorize } from './authorize.js'
30
+ import { MethodNotFoundError, SdkResultValidationError, SdkValidationError } from './errors.js'
31
+ import { executeHandler } from './execute.js'
32
+ import { buildIdentityMap } from './identity.js'
33
+ import { resolveMethod } from './resolve.js'
34
+ import { resolveSelf, withBoundNode, type ParsedSelf } from './self.js'
35
+ import { validateParams, validateResult } from './validate.js'
35
36
 
36
37
  type PathInput = string | { raw: string }
37
38
 
39
+ type SdkDispatchOpts = {
40
+ self?: string
41
+ lifetime?: HandlerExecutionLifetime
42
+ }
43
+
38
44
  export type SdkDispatcherConfig<TDeps> = {
39
45
  /** The compiled domain — source of the method layout and origin-addressed subs. */
40
46
  compiled: CompiledDomain
@@ -86,7 +92,7 @@ export class SdkDispatcher<TDeps = unknown> implements KernelAPI {
86
92
  params: unknown,
87
93
  opts?: { self?: string },
88
94
  ): Promise<unknown> {
89
- return this.run(path, credential, params, opts?.self)
95
+ return this.run(path, credential, params, opts)
90
96
  }
91
97
 
92
98
  async stream(
@@ -95,7 +101,7 @@ export class SdkDispatcher<TDeps = unknown> implements KernelAPI {
95
101
  params: unknown,
96
102
  opts?: { self?: string },
97
103
  ): Promise<AsyncGenerator<unknown>> {
98
- const result = await this.run(path, credential, params, opts?.self)
104
+ const result = await this.run(path, credential, params, opts)
99
105
  if (!isAsyncGenerator(result)) {
100
106
  throw new Error(`Method "${pathToString(path)}" did not return an async generator`)
101
107
  }
@@ -111,9 +117,9 @@ export class SdkDispatcher<TDeps = unknown> implements KernelAPI {
111
117
  path: PathInput,
112
118
  credential: CredentialInput,
113
119
  params: unknown,
114
- opts?: { self?: string },
120
+ opts?: SdkDispatchOpts,
115
121
  ): Promise<DispatchResult> {
116
- const result = await this.run(path, credential, params, opts?.self)
122
+ const result = await this.run(path, credential, params, opts)
117
123
  return isAsyncGenerator(result)
118
124
  ? { kind: 'stream', generator: result }
119
125
  : { kind: 'value', value: result }
@@ -130,8 +136,9 @@ export class SdkDispatcher<TDeps = unknown> implements KernelAPI {
130
136
  path: PathInput,
131
137
  credential: CredentialInput,
132
138
  params: unknown,
133
- selfRef?: string,
139
+ opts?: SdkDispatchOpts,
134
140
  ): Promise<unknown> {
141
+ const selfRef = opts?.self
135
142
  const bound = resolveMethod(this.methods, pathToString(path))
136
143
  if (!bound) throw new MethodNotFoundError(pathToString(path))
137
144
 
@@ -217,20 +224,37 @@ export class SdkDispatcher<TDeps = unknown> implements KernelAPI {
217
224
 
218
225
  if (handler.authorize) await runAuthorize(handler.authorize, ctx)
219
226
 
220
- const result = await executeHandler({
221
- handler,
222
- ref: bound.ref,
223
- ...ctx,
224
- step: createInlineStep({
225
- scope: {
226
- kind: 'method',
227
- ref: bound.ref,
228
- owner: bound.owner,
229
- method: bound.method,
230
- },
231
- }),
232
- })
233
- return validateOutput(bound, result)
227
+ const lifecycle = createHandlerLifecycle(opts?.lifetime)
228
+ let result: unknown
229
+ try {
230
+ result = await executeHandler({
231
+ handler,
232
+ ref: bound.ref,
233
+ ...ctx,
234
+ step: createInlineStep({
235
+ scope: {
236
+ kind: 'method',
237
+ ref: bound.ref,
238
+ owner: bound.owner,
239
+ method: bound.method,
240
+ },
241
+ }),
242
+ defer: lifecycle.defer,
243
+ })
244
+ } catch (error) {
245
+ await lifecycle.settle()
246
+ throw error
247
+ }
248
+
249
+ if (isAsyncGenerator(result)) {
250
+ return validateOutputStream(bound, result, lifecycle.settle)
251
+ }
252
+
253
+ try {
254
+ return validateOutput(bound, result)
255
+ } finally {
256
+ await lifecycle.settle()
257
+ }
234
258
  }
235
259
 
236
260
  private identityFor(bound: BoundMethod<AnyRemoteHandler>): RemoteIdentityConfig {
@@ -268,16 +292,20 @@ function pathToString(path: PathInput): string {
268
292
  */
269
293
  function validateOutput(bound: BoundMethod<AnyRemoteHandler>, result: unknown): unknown {
270
294
  if (bound.output === 'binary') return result
271
- if (isAsyncGenerator(result)) return validateOutputStream(bound, result)
272
295
  return validateChunk(bound, result)
273
296
  }
274
297
 
275
298
  async function* validateOutputStream(
276
299
  bound: BoundMethod<AnyRemoteHandler>,
277
300
  gen: AsyncGenerator<unknown>,
301
+ settle: () => Promise<void>,
278
302
  ): AsyncGenerator<unknown> {
279
- for await (const chunk of gen) {
280
- yield validateChunk(bound, chunk)
303
+ try {
304
+ for await (const chunk of gen) {
305
+ yield validateChunk(bound, chunk)
306
+ }
307
+ } finally {
308
+ await settle()
281
309
  }
282
310
  }
283
311
 
@@ -11,11 +11,12 @@ import type { FnMap } from '@astrale-os/kernel-client'
11
11
  import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
12
12
  import type { AuthContext } from '@astrale-os/kernel-core'
13
13
 
14
- import type { FunctionContextApi } from '../auth/function-context'
15
- import type { DomainAuthority } from '../auth/issuer-mint'
16
- import type { CallerContext, RemoteEnv } from '../method/context'
17
- import type { Step } from '../step'
18
- import type { SelfResult, TypedSelf } from './self'
14
+ import type { FunctionContextApi } from '../auth/function-context.js'
15
+ import type { DomainAuthority } from '../auth/issuer-mint.js'
16
+ import type { Defer } from '../defer/index.js'
17
+ import type { CallerContext, RemoteEnv } from '../method/context.js'
18
+ import type { Step } from '../step/index.js'
19
+ import type { SelfResult, TypedSelf } from './self.js'
19
20
 
20
21
  // oxlint-disable-next-line no-explicit-any
21
22
  type HandlerFn = (...args: any[]) => any
@@ -38,6 +39,7 @@ export type ExecuteParams = {
38
39
  domain: DomainAuthority
39
40
  fn: FunctionContextApi
40
41
  step: Step
42
+ defer: Defer
41
43
  }
42
44
 
43
45
  export async function executeHandler(ctx: ExecuteParams): Promise<unknown> {
@@ -58,5 +60,6 @@ export async function executeHandler(ctx: ExecuteParams): Promise<unknown> {
58
60
  domain: ctx.domain,
59
61
  fn: ctx.fn,
60
62
  step: ctx.step,
63
+ defer: ctx.defer,
61
64
  })
62
65
  }
@@ -25,8 +25,8 @@ import type { BoundMethod, CompiledDomain } from '@astrale-os/kernel-core/domain
25
25
 
26
26
  import { resolveCallables } from '@astrale-os/kernel-core/domain'
27
27
 
28
- import type { RemoteIdentityConfig } from '../auth/identity'
29
- import type { AnyRemoteHandler } from '../method/single'
28
+ import type { RemoteIdentityConfig } from '../auth/identity.js'
29
+ import type { AnyRemoteHandler } from '../method/single.js'
30
30
 
31
31
  /**
32
32
  * For each materialized method node, emit its `MethodPath` (semantic,
@@ -1,16 +1,16 @@
1
- export { SdkDispatcher, type SdkDispatcherConfig } from './dispatcher'
2
- export { resolveMethod, buildMethodIndex, type MethodIndex } from './resolve'
1
+ export { SdkDispatcher, type SdkDispatcherConfig } from './dispatcher.js'
2
+ export { resolveMethod, buildMethodIndex, type MethodIndex } from './resolve.js'
3
3
  export {
4
4
  validateParams,
5
5
  validateResult,
6
6
  type ValidationResult,
7
7
  type ResultValidationResult,
8
- } from './validate'
9
- export { resolveSelf, withBoundNode, type SelfResult, type TypedSelf } from './self'
10
- export { executeHandler, type ExecuteParams } from './execute'
8
+ } from './validate.js'
9
+ export { resolveSelf, withBoundNode, type SelfResult, type TypedSelf } from './self.js'
10
+ export { executeHandler, type ExecuteParams } from './execute.js'
11
11
  export {
12
12
  AuthorizationDeniedError,
13
13
  MethodNotFoundError,
14
14
  SdkValidationError,
15
15
  SdkResultValidationError,
16
- } from './errors'
16
+ } from './errors.js'
@@ -22,7 +22,7 @@
22
22
  import type { Path } from '@astrale-os/kernel-core'
23
23
  import type { BoundMethod } from '@astrale-os/kernel-core/domain'
24
24
 
25
- import type { AnyRemoteHandler } from '../method/single'
25
+ import type { AnyRemoteHandler } from '../method/single.js'
26
26
 
27
27
  export type MethodIndex = Map<string, BoundMethod<AnyRemoteHandler>>
28
28
 
@@ -25,7 +25,7 @@ import { classPathSchema } from '@astrale-os/kernel-core/domain'
25
25
  import { absolutePathSchema } from '@astrale-os/kernel-core/tree'
26
26
  import { z } from 'zod'
27
27
 
28
- import type { Kernel } from '../method/context'
28
+ import type { Kernel } from '../method/context.js'
29
29
 
30
30
  /** The node a non-static method runs on — its full record, id guaranteed. */
31
31
  export type ResolvedSelfNode = Node & { id: NodeId }
@@ -24,11 +24,11 @@ import type { Schema } from '@astrale-os/kernel-dsl'
24
24
  import { AbsolutePath } from '@astrale-os/kernel-core'
25
25
  import { hashInstallGraph, serialize, zodToJsonSchema } from '@astrale-os/kernel-core/domain'
26
26
 
27
- import type { AnyRemoteHandler } from '../method/single'
28
- import type { RemoteDomain } from './define'
27
+ import type { AnyRemoteHandler } from '../method/single.js'
28
+ import type { RemoteDomain } from './define.js'
29
29
 
30
- import { extractBinding } from './contract'
31
- import { materializeRemoteDomain } from './define'
30
+ import { extractBinding } from './contract.js'
31
+ import { materializeRemoteDomain } from './define.js'
32
32
 
33
33
  /**
34
34
  * Build the install-ready wire graph for a domain served at `url`: materialize
@@ -93,6 +93,10 @@ function buildSpecInternal(
93
93
  const tree = serialize(compiled, [...serialized, ...functionSchemas], {
94
94
  views: viewSchemas,
95
95
  parent,
96
+ // An install graph is the domain's signed desired state. Reinstalling it
97
+ // must converge schema and core material exactly, including removing
98
+ // properties that disappeared from a newer domain version.
99
+ onConflict: 'replace',
96
100
  })
97
101
  return tree.toGraph()
98
102
  }
@@ -14,7 +14,7 @@ import type {
14
14
  } from '@astrale-os/kernel-api/routed'
15
15
  import type { BoundMethod } from '@astrale-os/kernel-core/domain'
16
16
 
17
- import type { AnyRemoteHandler } from '../method/single'
17
+ import type { AnyRemoteHandler } from '../method/single.js'
18
18
 
19
19
  export function toSdkContract(method: BoundMethod<AnyRemoteHandler>): MountableContract {
20
20
  const handler = method.handler as {
@@ -14,17 +14,17 @@ import type { Core, Schema } from '@astrale-os/kernel-dsl'
14
14
 
15
15
  import { bindMethods, compileDomain } from '@astrale-os/kernel-core/domain'
16
16
 
17
- import type { DomainManifest } from '../config/define-domain'
18
- import type { AnyRemoteFunctionDef, ViewDef } from '../define'
19
- import type { SchemaMethodsImpl } from '../method/class'
20
- import type { AnyRemoteHandler } from '../method/single'
17
+ import type { DomainManifest } from '../config/define-domain.js'
18
+ import type { AnyRemoteFunctionDef, ViewDef } from '../define/index.js'
19
+ import type { SchemaMethodsImpl } from '../method/class.js'
20
+ import type { AnyRemoteHandler } from '../method/single.js'
21
21
 
22
22
  import {
23
23
  assertFunctionsMatchSchema,
24
24
  buildFunctionSchemas,
25
25
  DEFAULT_FUNCTIONS_FOLDER,
26
- } from './extend-functions'
27
- import { buildViewDeclarations, buildViewSchemas, DEFAULT_VIEWS_FOLDER } from './extend-views'
26
+ } from './extend-functions.js'
27
+ import { buildViewDeclarations, buildViewSchemas, DEFAULT_VIEWS_FOLDER } from './extend-views.js'
28
28
 
29
29
  export type RemoteDomainConfig<S extends Schema, TDeps> = {
30
30
  schema: S
@@ -22,9 +22,9 @@ import type { Schema } from '@astrale-os/kernel-dsl'
22
22
 
23
23
  import { zodToJsonSchema } from '@astrale-os/kernel-core/domain'
24
24
 
25
- import type { AnyRemoteFunctionDef } from '../define/remote-function'
25
+ import type { AnyRemoteFunctionDef } from '../define/remote-function.js'
26
26
 
27
- import { resolveBinding } from './binding'
27
+ import { resolveBinding } from './binding.js'
28
28
 
29
29
  /**
30
30
  * Default URL segment for a function's worker route (`<url>/functions/<slug>`).
@@ -29,9 +29,9 @@ import {
29
29
  type EdgeEndpoint,
30
30
  } from '@astrale-os/kernel-dsl'
31
31
 
32
- import type { ViewDef } from '../define/view'
32
+ import type { ViewDef } from '../define/view.js'
33
33
 
34
- import { joinWorkerPath, resolveBinding } from './binding'
34
+ import { joinWorkerPath, resolveBinding } from './binding.js'
35
35
 
36
36
  /**
37
37
  * Default URL segment for a view's worker route (`<url>/views/<slug>`). Names the
@@ -1,4 +1,4 @@
1
- export { defineRemoteDomain } from './define'
2
- export type { RemoteDomain, RemoteDomainConfig } from './define'
3
- export { buildInstallGraph, buildInstallGraphHash } from './build-spec'
4
- export { toSdkContract } from './contract'
1
+ export { defineRemoteDomain } from './define.js'
2
+ export type { RemoteDomain, RemoteDomainConfig } from './define.js'
3
+ export { buildInstallGraph, buildInstallGraphHash } from './build-spec.js'
4
+ export { toSdkContract } from './contract.js'
package/src/index.ts CHANGED
@@ -14,15 +14,15 @@ export type {
14
14
  ClassMethodsImpl,
15
15
  InterfaceMethodsImpl,
16
16
  SchemaMethodsImpl,
17
- } from './method'
18
- export { remoteMethod, remoteClassMethods, remoteInterfaceMethods } from './method'
17
+ } from './method/index.js'
18
+ export { remoteMethod, remoteClassMethods, remoteInterfaceMethods } from './method/index.js'
19
19
 
20
20
  // ─── Domain configuration (astrale.config.ts) ────────────────────────────
21
21
  // The author-facing way to declare a standalone domain + how it deploys. The
22
22
  // runtime-domain COMPILER (`defineRemoteDomain`) is internal machinery — reach
23
23
  // it via the `@astrale-os/sdk/domain` subpath when hand-rolling a worker, or
24
24
  // (preferred) use `domainWorkerEntry` from `@astrale-os/sdk/server`.
25
- export { defineDomain, deploy, defineAdapter } from './config'
25
+ export { defineDomain, deploy, defineAdapter } from './config/index.js'
26
26
  export type {
27
27
  DefineDomainConfig,
28
28
  DomainDefinition,
@@ -35,10 +35,10 @@ export type {
35
35
  DomainInfo,
36
36
  WatchCtx,
37
37
  WatchHandle,
38
- } from './config'
38
+ } from './config/index.js'
39
39
 
40
40
  // ─── Install graph (advanced) ────────────────────────────────────────────
41
- export { buildInstallGraph, buildInstallGraphHash } from './domain'
41
+ export { buildInstallGraph, buildInstallGraphHash } from './domain/index.js'
42
42
 
43
43
  // ─── Graph patching (advanced) ────────────────────────────────────────
44
44
  // Author-facing patch types, re-exported from kernel-core (the single source).
@@ -67,17 +67,20 @@ export type {
67
67
  // the reserved views / functions folder, keyed by its map slug; the same
68
68
  // entry's `render` / `execute` (+ auth hooks) is mounted as a worker route by
69
69
  // `createRemoteServer`.
70
- export { defineView, defineRemoteFunction } from './define'
70
+ export { defineView, defineRemoteFunction } from './define/index.js'
71
71
  export type {
72
72
  ViewDef,
73
73
  ViewRenderContext,
74
74
  RemoteFunctionAuthorizeContext,
75
75
  RemoteFunctionDef,
76
76
  RemoteFunctionContext,
77
- } from './define'
77
+ } from './define/index.js'
78
78
 
79
79
  // ─── Durable-shaped steps ────────────────────────────────────────────────
80
- export type { Step } from './step'
80
+ export type { Step } from './step/index.js'
81
+
82
+ // ─── Request lifecycle ───────────────────────────────────────────────────
83
+ export type { Defer, DeferredTask } from './defer/index.js'
81
84
 
82
85
  // ─── Server ──────────────────────────────────────────────────────────────
83
86
  // `createRemoteServer` dynamically imports `@hono/node-server` via `./server/start`.
@@ -91,8 +94,8 @@ export type { Step } from './step'
91
94
  // module load time. Exporting it from the barrel poisons browser bundlers
92
95
  // (Vite/esbuild) that transitively load every re-export. Consumers that need it
93
96
  // import from './deploy' directly.
94
- export { MetaSchema } from './deploy/meta'
95
- export type { Meta } from './deploy/meta'
97
+ export { MetaSchema } from './deploy/meta.js'
98
+ export type { Meta } from './deploy/meta.js'
96
99
 
97
100
  // ─── Route binding (re-exported from kernel-api for consumer convenience) ─
98
101
  export type {
@@ -107,22 +110,22 @@ export type {
107
110
  } from '@astrale-os/kernel-api/routed'
108
111
 
109
112
  // ─── Auth ────────────────────────────────────────────────────────────────
110
- export type { RemoteIdentityConfig, AuthenticateResult } from './auth'
113
+ export type { RemoteIdentityConfig, AuthenticateResult } from './auth/index.js'
111
114
  export type {
112
115
  DomainAuthority,
113
116
  FunctionContextApi,
114
117
  FunctionCredentialOptions,
115
118
  FunctionKernelOptions,
116
119
  MintIssuedParams,
117
- } from './auth'
120
+ } from './auth/index.js'
118
121
  export {
119
122
  authenticateRequest,
120
123
  buildComposedGrant,
121
124
  makeFunctionContext,
122
125
  signCredential,
123
- } from './auth'
124
- export { AuthMissingError, AuthInvalidError } from './auth'
125
- export { assertPerm, requireOwnership, READ, EDIT, USE, SHARE, ALL } from './auth'
126
+ } from './auth/index.js'
127
+ export { AuthMissingError, AuthInvalidError } from './auth/index.js'
128
+ export { assertPerm, requireOwnership, READ, EDIT, USE, SHARE, ALL } from './auth/index.js'
126
129
  export type {
127
130
  AuthContext,
128
131
  Attestation,
@@ -134,11 +137,11 @@ export type {
134
137
  export { selfGrant } from '@astrale-os/kernel-core'
135
138
 
136
139
  // ─── Dispatch (escape hatch for custom integrations) ─────────────────────
137
- export { SdkDispatcher, type SdkDispatcherConfig } from './dispatch'
140
+ export { SdkDispatcher, type SdkDispatcherConfig } from './dispatch/index.js'
138
141
  export {
139
142
  AuthorizationDeniedError,
140
143
  MethodNotFoundError,
141
144
  SdkValidationError,
142
145
  SdkResultValidationError,
143
- } from './dispatch'
144
- export type { SelfResult, TypedSelf } from './dispatch'
146
+ } from './dispatch/index.js'
147
+ export type { SelfResult, TypedSelf } from './dispatch/index.js'
@@ -25,9 +25,9 @@ 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 { TypedSelf } from '../dispatch/self'
29
- import type { DomainKernel } from './context'
30
- import type { MethodImpl, RemoteHandler } from './single'
28
+ import type { TypedSelf } from '../dispatch/self.js'
29
+ import type { DomainKernel } from './context.js'
30
+ import type { MethodImpl, RemoteHandler } from './single.js'
31
31
 
32
32
  /** Per-class method implementations for a remote domain. */
33
33
  export type ClassMethodsImpl<
@@ -14,9 +14,10 @@ import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
14
14
  import type { AuthContext } from '@astrale-os/kernel-core'
15
15
  import type { Schema } from '@astrale-os/kernel-dsl'
16
16
 
17
- import type { FunctionContextApi } from '../auth/function-context'
18
- import type { DomainAuthority } from '../auth/issuer-mint'
19
- import type { Step } from '../step'
17
+ import type { FunctionContextApi } from '../auth/function-context.js'
18
+ import type { DomainAuthority } from '../auth/issuer-mint.js'
19
+ import type { Defer } from '../defer/index.js'
20
+ import type { Step } from '../step/index.js'
20
21
 
21
22
  export type { GraphApi } from '@astrale-os/kernel-client/graph'
22
23
 
@@ -127,4 +128,6 @@ export interface RemoteContext<
127
128
  > extends RemoteBaseContext<TParams, TSelf, TDeps, TKernel, TAuth> {
128
129
  /** Durable-shaped step builder. Inline today, replayable by future backends. */
129
130
  step: Step
131
+ /** Run independent work without making its completion part of this result. */
132
+ defer: Defer
130
133
  }