@astrale-os/sdk 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.d.ts +7 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +15 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/dotenv.d.ts +13 -0
- package/dist/cli/dotenv.d.ts.map +1 -0
- package/dist/cli/dotenv.js +46 -0
- package/dist/cli/dotenv.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +79 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +569 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spec.d.ts +19 -0
- package/dist/cli/spec.d.ts.map +1 -0
- package/dist/cli/spec.js +31 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/config/adapter.d.ts +140 -0
- package/dist/config/adapter.d.ts.map +1 -0
- package/dist/config/adapter.js +40 -0
- package/dist/config/adapter.js.map +1 -0
- package/dist/config/define-domain.d.ts +112 -0
- package/dist/config/define-domain.d.ts.map +1 -0
- package/dist/config/define-domain.js +98 -0
- package/dist/config/define-domain.js.map +1 -0
- package/dist/config/deploy.d.ts +28 -0
- package/dist/config/deploy.d.ts.map +1 -0
- package/dist/config/deploy.js +24 -0
- package/dist/config/deploy.js.map +1 -0
- package/dist/config/index.d.ts +21 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -0
- package/dist/define/remote-function.d.ts +19 -11
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js.map +1 -1
- package/dist/dispatch/call-remote.d.ts +7 -3
- package/dist/dispatch/call-remote.d.ts.map +1 -1
- package/dist/dispatch/call-remote.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +8 -4
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/index.d.ts +1 -1
- package/dist/dispatch/index.d.ts.map +1 -1
- package/dist/dispatch/index.js.map +1 -1
- package/dist/dispatch/self.d.ts +46 -10
- package/dist/dispatch/self.d.ts.map +1 -1
- package/dist/dispatch/self.js +65 -8
- package/dist/dispatch/self.js.map +1 -1
- package/dist/domain/define.d.ts +3 -3
- package/dist/domain/define.js +3 -3
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/method/class.d.ts.map +1 -1
- package/dist/method/class.js.map +1 -1
- package/dist/method/context.d.ts +32 -7
- package/dist/method/context.d.ts.map +1 -1
- package/dist/method/index.d.ts +1 -1
- package/dist/method/index.d.ts.map +1 -1
- package/dist/method/single.d.ts +16 -11
- package/dist/method/single.d.ts.map +1 -1
- package/dist/method/single.js.map +1 -1
- package/dist/server/domain-entry.d.ts +67 -0
- package/dist/server/domain-entry.d.ts.map +1 -0
- package/dist/server/domain-entry.js +58 -0
- package/dist/server/domain-entry.js.map +1 -0
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/worker-entry.d.ts +40 -5
- package/dist/server/worker-entry.d.ts.map +1 -1
- package/dist/server/worker-entry.js +68 -19
- package/dist/server/worker-entry.js.map +1 -1
- package/package.json +12 -3
- package/src/cli/bin.ts +15 -0
- package/src/cli/dotenv.ts +45 -0
- package/src/cli/index.ts +15 -0
- package/src/cli/run.ts +675 -0
- package/src/cli/spec.ts +42 -0
- package/src/config/adapter.ts +172 -0
- package/src/config/define-domain.ts +218 -0
- package/src/config/deploy.ts +35 -0
- package/src/config/index.ts +31 -0
- package/src/define/remote-function.ts +42 -13
- package/src/dispatch/call-remote.ts +7 -2
- package/src/dispatch/dispatcher.ts +8 -4
- package/src/dispatch/index.ts +1 -1
- package/src/dispatch/self.ts +96 -10
- package/src/domain/define.ts +3 -3
- package/src/index.ts +25 -4
- package/src/method/class.ts +4 -3
- package/src/method/context.ts +38 -7
- package/src/method/index.ts +1 -1
- package/src/method/single.ts +30 -11
- package/src/server/domain-entry.ts +113 -0
- package/src/server/index.ts +3 -1
- package/src/server/worker-entry.ts +80 -20
package/src/dispatch/self.ts
CHANGED
|
@@ -2,31 +2,117 @@
|
|
|
2
2
|
* Self resolution — pipeline step 3.
|
|
3
3
|
*
|
|
4
4
|
* `_self` is a path reference to the node the non-static method runs on.
|
|
5
|
-
*
|
|
6
|
-
* build recursive paths as `${self.path}::method` (the `Path`'s
|
|
7
|
-
* yields its raw form, so template literals work unchanged).
|
|
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`.
|
|
8
10
|
*
|
|
9
11
|
* `resolveSelf` receives the bare self target (`@<id>` or a tree path) — the
|
|
10
12
|
* method ref travels in a separate dispatch channel, so `::method` never
|
|
11
13
|
* reaches here. For the `@<id>` form the parsed `Path` is an `IdPath` which
|
|
12
14
|
* already carries the node id; surfacing it on `SelfResult.id` saves every
|
|
13
|
-
* worker handler from
|
|
14
|
-
*
|
|
15
|
-
* operating on.
|
|
15
|
+
* worker handler from parsing `self.path.raw` by hand to recover the id of the
|
|
16
|
+
* node it is already operating on.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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.
|
|
19
23
|
*/
|
|
20
24
|
|
|
25
|
+
import type { Node } from '@astrale-os/kernel-core/graph'
|
|
26
|
+
|
|
21
27
|
import { IdPath, Path, type NodeId } from '@astrale-os/kernel-core'
|
|
28
|
+
import { classPathSchema } from '@astrale-os/kernel-core/domain'
|
|
29
|
+
import { absolutePathSchema } from '@astrale-os/kernel-core/tree'
|
|
30
|
+
import { z } from 'zod'
|
|
31
|
+
|
|
32
|
+
import type { Kernel } from '../method/context'
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
/** The node a non-static method runs on — its full record, id guaranteed. */
|
|
35
|
+
export type ResolvedSelfNode = Node & { id: NodeId }
|
|
36
|
+
|
|
37
|
+
/** The bare parse of a self target — no kernel, no fetch. */
|
|
38
|
+
export type ParsedSelf = {
|
|
24
39
|
path: Path
|
|
25
40
|
/** Set when `path` is an `IdPath` (i.e. `@<id>::method` calls). */
|
|
26
41
|
id?: NodeId
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
export
|
|
44
|
+
export type SelfResult = ParsedSelf & {
|
|
45
|
+
/**
|
|
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.
|
|
57
|
+
*/
|
|
58
|
+
node(opts?: { reload?: boolean }): Promise<ResolvedSelfNode>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolveSelf(ref: string): ParsedSelf {
|
|
30
62
|
const path = Path.parse(ref)
|
|
31
63
|
return path instanceof IdPath ? { path, id: path.id } : { path }
|
|
32
64
|
}
|
|
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
|
+
*/
|
|
72
|
+
export function withNode(parsed: ParsedSelf, kernel: Kernel | null): SelfResult {
|
|
73
|
+
let cached: Promise<ResolvedSelfNode> | undefined
|
|
74
|
+
const node = (opts?: { reload?: boolean }): Promise<ResolvedSelfNode> => {
|
|
75
|
+
if (opts?.reload) cached = undefined
|
|
76
|
+
if (!cached) {
|
|
77
|
+
cached = fetchSelfNode(parsed.path, kernel).catch((err: unknown) => {
|
|
78
|
+
cached = undefined // never cache a rejection — a later call may succeed
|
|
79
|
+
throw err
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
return cached
|
|
83
|
+
}
|
|
84
|
+
return { ...parsed, node }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Boundary schema for a node record off the wire. Structurally identical to
|
|
89
|
+
* kernel-core's `nodeSchema`, re-declared here from its public coercers
|
|
90
|
+
* (`classPathSchema` from `/domain`, `absolutePathSchema` from `/tree`) on
|
|
91
|
+
* purpose: importing the schema from `#graph` would pull zod into the graph
|
|
92
|
+
* barrel and cycle with `AbsolutePath` init (see `kernel/core/graph/index.ts`).
|
|
93
|
+
* Coercing through the canonical schemas yields a real `ClassPath` / `AbsolutePath`.
|
|
94
|
+
*/
|
|
95
|
+
const nodeRecordSchema = z.object({
|
|
96
|
+
class: classPathSchema(),
|
|
97
|
+
path: absolutePathSchema(),
|
|
98
|
+
props: z.record(z.string(), z.unknown()),
|
|
99
|
+
id: z.string().optional(),
|
|
100
|
+
__labels: z.array(z.string()).optional(),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
async function fetchSelfNode(path: Path, kernel: Kernel | null): Promise<ResolvedSelfNode> {
|
|
104
|
+
if (!kernel) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`self.node() needs an authenticated kernel to read "${path.raw}", but this method ran ` +
|
|
107
|
+
`without one (its auth policy yields no kernel — e.g. 'public'). Read the node from a ` +
|
|
108
|
+
`method whose auth is 'required', or supply a credential.`,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
const record = nodeRecordSchema.parse(await kernel.call(`${path.raw}::get`, {}))
|
|
112
|
+
if (record.id === undefined) {
|
|
113
|
+
throw new Error(`self.node(): "${path.raw}::get" returned a record with no id`)
|
|
114
|
+
}
|
|
115
|
+
// Validated at the wire boundary: id is now known-present, props are
|
|
116
|
+
// structurally checked. Bridge the parse output to the canonical `Node`.
|
|
117
|
+
return { ...record, id: record.id as NodeId } as ResolvedSelfNode
|
|
118
|
+
}
|
package/src/domain/define.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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
|
|
15
|
-
* runtime, the
|
|
15
|
+
* runtime, the `astrale-domain` CLI offline) and stamped onto every `binding.remoteUrl`
|
|
16
16
|
* by `materializeRemoteDomain`. There is exactly one notion of `url` in the SDK
|
|
17
17
|
* — the worker serving URL, which is also `iss` and the binding base.
|
|
18
18
|
*/
|
|
@@ -128,8 +128,8 @@ export function defineRemoteDomain<TDeps>() {
|
|
|
128
128
|
* Materialize a `RemoteDomain` at its real serving `url`: re-runs `extendCore`
|
|
129
129
|
* so every aux View/Function `binding.remoteUrl` points at the actual host,
|
|
130
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
|
-
* CLI. Returns the define-time `compiled` untouched when there is no aux to
|
|
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
133
|
* stamp. `extendCore`/`compileDomain` are pure, so this is safe to call
|
|
134
134
|
* repeatedly (and is memoized per cold isolate by the callers).
|
|
135
135
|
*/
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// ─── Method authoring ────────────────────────────────────────────────────
|
|
2
2
|
export type {
|
|
3
|
+
Kernel,
|
|
4
|
+
KernelForAuth,
|
|
3
5
|
RemoteContext,
|
|
4
6
|
RemoteHandler,
|
|
5
7
|
MethodImpl,
|
|
@@ -9,9 +11,28 @@ export type {
|
|
|
9
11
|
} from './method'
|
|
10
12
|
export { remoteMethod, remoteClassMethods, remoteInterfaceMethods } from './method'
|
|
11
13
|
|
|
12
|
-
// ─── Domain
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// ─── Domain configuration (astrale.config.ts) ────────────────────────────
|
|
15
|
+
// The author-facing way to declare a standalone domain + how it deploys. The
|
|
16
|
+
// runtime-domain COMPILER (`defineRemoteDomain`) is internal machinery — reach
|
|
17
|
+
// it via the `@astrale-os/sdk/domain` subpath when hand-rolling a worker, or
|
|
18
|
+
// (preferred) use `domainWorkerEntry` from `@astrale-os/sdk/server`.
|
|
19
|
+
export { defineDomain, deploy, defineAdapter } from './config'
|
|
20
|
+
export type {
|
|
21
|
+
ClientBinding,
|
|
22
|
+
DefineDomainConfig,
|
|
23
|
+
DomainDefinition,
|
|
24
|
+
DeployConfig,
|
|
25
|
+
AdapterSpec,
|
|
26
|
+
DeployCtx,
|
|
27
|
+
DeployResult,
|
|
28
|
+
DomainAdapter,
|
|
29
|
+
DomainInfo,
|
|
30
|
+
WatchCtx,
|
|
31
|
+
WatchHandle,
|
|
32
|
+
} from './config'
|
|
33
|
+
|
|
34
|
+
// ─── Install graph (advanced) ────────────────────────────────────────────
|
|
35
|
+
export { buildInstallGraph, buildInstallGraphHash } from './domain'
|
|
15
36
|
|
|
16
37
|
// ─── Declarative resource helpers ────────────────────────────────────────
|
|
17
38
|
// Author Views (iframe-mountable) and RemoteFunctions (standalone callables)
|
|
@@ -74,4 +95,4 @@ export {
|
|
|
74
95
|
SdkValidationError,
|
|
75
96
|
SdkResultValidationError,
|
|
76
97
|
} from './dispatch'
|
|
77
|
-
export type { SelfResult, CallRemoteFn } from './dispatch'
|
|
98
|
+
export type { SelfResult, CallRemoteFn, KernelCaller } from './dispatch'
|
package/src/method/class.ts
CHANGED
|
@@ -52,9 +52,10 @@ type InterfaceMethodHandler<
|
|
|
52
52
|
? MC extends { readonly static: true }
|
|
53
53
|
? RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, undefined, TDeps>
|
|
54
54
|
: // Non-static interface methods receive the same `self` the dispatcher
|
|
55
|
-
// actually delivers — `SelfResult` ({ path, id
|
|
56
|
-
// class-method path (`MethodImpl`) does.
|
|
57
|
-
//
|
|
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()`.
|
|
58
59
|
RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, SelfResult, TDeps>
|
|
59
60
|
: never
|
|
60
61
|
: never
|
package/src/method/context.ts
CHANGED
|
@@ -7,13 +7,43 @@
|
|
|
7
7
|
* `BoundClientSessionView` to call back into the parent kernel.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { AuthPolicy } from '@astrale-os/kernel-api/routed'
|
|
10
11
|
import type { FnMap } from '@astrale-os/kernel-client'
|
|
11
12
|
import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
|
|
12
13
|
import type { AuthContext } from '@astrale-os/kernel-core'
|
|
13
14
|
|
|
14
15
|
import type { CallRemoteFn } from '../dispatch/call-remote'
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The kernel session a handler holds: a credential-bound view over the parent
|
|
19
|
+
* kernel — the FULL dispatch surface (`call`/`stream`/`binary`/`signal`,
|
|
20
|
+
* `withSchema` typed dispatch, `as` to rebind the acting credential), not just
|
|
21
|
+
* `call`. Name it from `@astrale-os/sdk` instead of reaching into
|
|
22
|
+
* `@astrale-os/kernel-client`. (For the narrow `{ call }` contract — e.g. a
|
|
23
|
+
* tiny test double — see {@link KernelCaller}.)
|
|
24
|
+
*/
|
|
25
|
+
export type Kernel = BoundClientSessionView<FnMap>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The `kernel` a handler receives, derived from its declared `auth` policy.
|
|
29
|
+
* The runtime guarantee (see `auth/resolve.ts`) is exact:
|
|
30
|
+
* - `'required'` (the default) — the dispatcher rejects a credentialless call
|
|
31
|
+
* before `execute`, so the kernel is ALWAYS present → non-null.
|
|
32
|
+
* - `'optional'` — a kernel only when a credential was supplied → `… | null`.
|
|
33
|
+
* - `'public'` — never any inbound credential → `null`. (Public handlers that
|
|
34
|
+
* must touch the graph use `selfKernel` after verifying their own upstream.)
|
|
35
|
+
*
|
|
36
|
+
* Distributes over a `AuthPolicy` union (e.g. when `auth` is typed but not a
|
|
37
|
+
* literal) to the widened `Kernel | null` — the safe superset, matching the
|
|
38
|
+
* pre-conditional behaviour.
|
|
39
|
+
*/
|
|
40
|
+
export type KernelForAuth<TAuth extends AuthPolicy> = TAuth extends 'public'
|
|
41
|
+
? null
|
|
42
|
+
: TAuth extends 'optional'
|
|
43
|
+
? Kernel | null
|
|
44
|
+
: Kernel
|
|
45
|
+
|
|
46
|
+
export type RemoteContext<TParams, TSelf, TDeps, TKernel = Kernel | null> = {
|
|
17
47
|
/** Validated params (Zod-checked against the method's `inputSchema`). */
|
|
18
48
|
params: TParams
|
|
19
49
|
/** Auth context resolved from the inbound delegation credential. `null` for public or unauthenticated optional methods. */
|
|
@@ -26,13 +56,14 @@ export type RemoteContext<TParams, TSelf, TDeps> = {
|
|
|
26
56
|
url: string
|
|
27
57
|
/**
|
|
28
58
|
* `BoundClientSessionView` to the parent kernel, bound to the composed
|
|
29
|
-
* credential `union(delegation, self)`.
|
|
30
|
-
* `
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
59
|
+
* credential `union(delegation, self)`. Its nullability is `{@link
|
|
60
|
+
* KernelForAuth}` of the method's `auth`: non-null for the default
|
|
61
|
+
* `'required'`, `… | null` for `'optional'`, `null` for `'public'`. Use for
|
|
62
|
+
* kernel syscalls + same-domain methods. For ANOTHER worker's remote method
|
|
63
|
+
* use {@link RemoteContext.callRemote} — `kernel.call` to a remote method
|
|
64
|
+
* fails the audience check.
|
|
34
65
|
*/
|
|
35
|
-
kernel:
|
|
66
|
+
kernel: TKernel
|
|
36
67
|
/**
|
|
37
68
|
* Call another worker's remote method (a Function with `binding.remoteUrl`),
|
|
38
69
|
* re-minting the credential for the target's audience so it isn't rejected at
|
package/src/method/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RemoteContext } from './context'
|
|
1
|
+
export type { Kernel, KernelForAuth, RemoteContext } from './context'
|
|
2
2
|
export type { RemoteHandler, AnyRemoteHandler, MethodImpl } from './single'
|
|
3
3
|
export { remoteMethod } from './single'
|
|
4
4
|
export type { ClassMethodsImpl, InterfaceMethodsImpl, SchemaMethodsImpl } from './class'
|
package/src/method/single.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
import type { Schema } from '@astrale-os/kernel-dsl'
|
|
21
21
|
|
|
22
22
|
import type { SelfResult } from '../dispatch/self'
|
|
23
|
-
import type { RemoteContext } from './context'
|
|
23
|
+
import type { KernelForAuth, RemoteContext } from './context'
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Remote function handler — execute with full typed context.
|
|
@@ -35,10 +35,10 @@ import type { RemoteContext } from './context'
|
|
|
35
35
|
* dispatcher throws — that's a programmer error indicating a stub leaked into
|
|
36
36
|
* a code path that was supposed to execute it.
|
|
37
37
|
*/
|
|
38
|
-
export type RemoteHandler<TParams, TResult, TSelf, TDeps> = {
|
|
38
|
+
export type RemoteHandler<TParams, TResult, TSelf, TDeps, TAuth extends AuthPolicy = 'required'> = {
|
|
39
39
|
/** The handler body. May be async or an async generator (for `output: 'stream'`). */
|
|
40
40
|
execute?: (
|
|
41
|
-
ctx: RemoteContext<TParams, TSelf, TDeps
|
|
41
|
+
ctx: RemoteContext<TParams, TSelf, TDeps, KernelForAuth<TAuth>>,
|
|
42
42
|
) => TResult | Promise<TResult> | AsyncGenerator<TResult>
|
|
43
43
|
/**
|
|
44
44
|
* Optional REST binding — attaches a native HTTP route to this method.
|
|
@@ -53,8 +53,13 @@ export type RemoteHandler<TParams, TResult, TSelf, TDeps> = {
|
|
|
53
53
|
remoteUrl?: string
|
|
54
54
|
/** Optional human-readable description. Appears in generated docs. */
|
|
55
55
|
description?: string
|
|
56
|
-
/**
|
|
57
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Authentication policy. Defaults to `'required'` when absent. Captured as a
|
|
58
|
+
* literal type so it drives {@link KernelForAuth} on the `execute`/`authorize`
|
|
59
|
+
* context: omit it (or set `'required'`) and `ctx.kernel` is non-null;
|
|
60
|
+
* `'optional'` widens it to `… | null`; `'public'` makes it `null`.
|
|
61
|
+
*/
|
|
62
|
+
auth?: TAuth
|
|
58
63
|
/**
|
|
59
64
|
* Optional pre-execute authorization check. Runs after auth resolution and
|
|
60
65
|
* `_self` resolution, before `execute`. Throw any error to deny the call —
|
|
@@ -69,11 +74,17 @@ export type RemoteHandler<TParams, TResult, TSelf, TDeps> = {
|
|
|
69
74
|
* The kernel still enforces `has_perm` independently — `authorize` is
|
|
70
75
|
* additive ergonomic gating on top, not a replacement.
|
|
71
76
|
*/
|
|
72
|
-
authorize?: (
|
|
77
|
+
authorize?: (
|
|
78
|
+
ctx: RemoteContext<TParams, TSelf, TDeps, KernelForAuth<TAuth>>,
|
|
79
|
+
) => void | Promise<void>
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
// `TAuth = AuthPolicy` (the full union, not the `'required'` default) keeps this
|
|
83
|
+
// maximally permissive: it accepts a handler of ANY declared policy, and its
|
|
84
|
+
// `ctx.kernel` widens to `BoundClientSessionView<FnMap> | null`. Used by the
|
|
85
|
+
// dispatcher/index where the concrete policy is erased.
|
|
75
86
|
// oxlint-disable-next-line no-explicit-any
|
|
76
|
-
export type AnyRemoteHandler = RemoteHandler<any, any, any, any>
|
|
87
|
+
export type AnyRemoteHandler = RemoteHandler<any, any, any, any, AuthPolicy>
|
|
77
88
|
|
|
78
89
|
/**
|
|
79
90
|
* Fully typed method implementation — resolves params/result/self from the
|
|
@@ -84,6 +95,7 @@ export type MethodImpl<
|
|
|
84
95
|
K extends MethodClassKeys<S> & string,
|
|
85
96
|
M extends string,
|
|
86
97
|
TDeps = unknown,
|
|
98
|
+
TAuth extends AuthPolicy = 'required',
|
|
87
99
|
> =
|
|
88
100
|
ClassMethodConfig<S, K, M> extends {
|
|
89
101
|
params: infer P
|
|
@@ -91,7 +103,7 @@ export type MethodImpl<
|
|
|
91
103
|
self: unknown
|
|
92
104
|
isStatic: infer St
|
|
93
105
|
}
|
|
94
|
-
? RemoteHandler<P, R, St extends true ? undefined : SelfResult, TDeps>
|
|
106
|
+
? RemoteHandler<P, R, St extends true ? undefined : SelfResult, TDeps, TAuth>
|
|
95
107
|
: never
|
|
96
108
|
|
|
97
109
|
type ImplementableMethodName<S extends Schema, K extends MethodClassKeys<S> & string> = (
|
|
@@ -113,17 +125,24 @@ export function remoteMethod<TDeps>(): <
|
|
|
113
125
|
S extends Schema,
|
|
114
126
|
K extends MethodClassKeys<S> & string,
|
|
115
127
|
M extends ImplementableMethodName<S, K>,
|
|
128
|
+
TAuth extends AuthPolicy = 'required',
|
|
116
129
|
>(
|
|
117
130
|
schema: S,
|
|
118
131
|
className: K,
|
|
119
132
|
methodName: M,
|
|
120
|
-
impl: MethodImpl<S, K, M, TDeps>,
|
|
121
|
-
) => MethodImpl<S, K, M, TDeps>
|
|
133
|
+
impl: MethodImpl<S, K, M, TDeps, TAuth>,
|
|
134
|
+
) => MethodImpl<S, K, M, TDeps, TAuth>
|
|
122
135
|
export function remoteMethod<
|
|
123
136
|
S extends Schema,
|
|
124
137
|
K extends MethodClassKeys<S> & string,
|
|
125
138
|
M extends ImplementableMethodName<S, K>,
|
|
126
|
-
|
|
139
|
+
TAuth extends AuthPolicy = 'required',
|
|
140
|
+
>(
|
|
141
|
+
schema: S,
|
|
142
|
+
className: K,
|
|
143
|
+
methodName: M,
|
|
144
|
+
impl: MethodImpl<S, K, M, unknown, TAuth>,
|
|
145
|
+
): MethodImpl<S, K, M, unknown, TAuth>
|
|
127
146
|
export function remoteMethod(...args: unknown[]) {
|
|
128
147
|
if (args.length === 0) {
|
|
129
148
|
return (...innerArgs: unknown[]) => innerArgs[3]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `domainWorkerEntry` — the one-call worker entry for a standalone domain.
|
|
3
|
+
*
|
|
4
|
+
* It folds the three steps every hand-rolled worker used to wire by hand —
|
|
5
|
+
* compile the runtime domain (`defineRemoteDomain`), build the server
|
|
6
|
+
* (`createRemoteServer`), and wrap it in the shared fetch plumbing
|
|
7
|
+
* (`createWorkerEntry`) — into a single declaration. The author passes the raw
|
|
8
|
+
* modules (`schema` / `methods` / `views` / `functions`) plus the identity and
|
|
9
|
+
* deploy bits; the helper does the rest. This is what the cloudflare adapter's
|
|
10
|
+
* codegen emits, and the recommended surface for a hand-rolled worker.
|
|
11
|
+
*
|
|
12
|
+
* Drop down to `createWorkerEntry` + `defineRemoteDomain` (from
|
|
13
|
+
* `@astrale-os/sdk/domain`) only when the worker needs something this config
|
|
14
|
+
* can't express — e.g. a serving-URL-dependent domain build (views stamped with
|
|
15
|
+
* the live url) or a globally-injected signing key resolved per request.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Core, Schema } from '@astrale-os/kernel-dsl'
|
|
19
|
+
import type { Hono } from 'hono'
|
|
20
|
+
|
|
21
|
+
import type { AnyRemoteFunctionDef, ViewDef } from '../define'
|
|
22
|
+
import type { SchemaMethodsImpl } from '../method'
|
|
23
|
+
import type { RemoteServerConfig } from './config'
|
|
24
|
+
import type { WorkerEntry, WorkerEntryConfig } from './worker-entry'
|
|
25
|
+
|
|
26
|
+
import { defineRemoteDomain } from '../domain'
|
|
27
|
+
import { createWorkerEntry } from './worker-entry'
|
|
28
|
+
|
|
29
|
+
export interface DomainWorkerEntryConfig<S extends Schema, TEnv, TDeps> {
|
|
30
|
+
/** The domain schema. */
|
|
31
|
+
schema: S
|
|
32
|
+
/** Method implementations, typed against `schema` under `TDeps`. */
|
|
33
|
+
methods: SchemaMethodsImpl<S, TDeps>
|
|
34
|
+
/** Optional Core override (extra genesis nodes / wiring). */
|
|
35
|
+
core?: Core<S>
|
|
36
|
+
/** Views (iframe-mountable UIs) keyed by slug. */
|
|
37
|
+
views?: Record<string, ViewDef<TDeps>>
|
|
38
|
+
/** Standalone Functions (callables not bound to a class) keyed by slug. */
|
|
39
|
+
functions?: Record<string, AnyRemoteFunctionDef>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The worker's signing key (its `iss` identity material). A static JWK, or a
|
|
43
|
+
* resolver from `env` for keys injected as a secret / shared globally.
|
|
44
|
+
*/
|
|
45
|
+
privateKey: JsonWebKey | ((env: TEnv) => JsonWebKey)
|
|
46
|
+
/** Cross-domain deps by origin — verified present at install. */
|
|
47
|
+
requires?: readonly string[]
|
|
48
|
+
/** Typed colon-path the kernel calls once as __SYSTEM__ after install. */
|
|
49
|
+
postInstall?: string
|
|
50
|
+
/** Provenance stamped on `/meta`. */
|
|
51
|
+
meta?: RemoteServerConfig<TDeps>['meta']
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Map the worker `env` to the handler dependency container. Defaults to
|
|
55
|
+
* passing `env` straight through (the common case where handlers read
|
|
56
|
+
* bindings directly).
|
|
57
|
+
*/
|
|
58
|
+
deps?: (env: TEnv, url: string) => TDeps
|
|
59
|
+
/** Optional pre-built host Hono app (CORS / logging / extra routes). */
|
|
60
|
+
app?: (env: TEnv) => Hono
|
|
61
|
+
|
|
62
|
+
// ── createWorkerEntry plumbing (passed through verbatim) ──────────────────
|
|
63
|
+
resolveUrl?: WorkerEntryConfig<TEnv>['resolveUrl']
|
|
64
|
+
selfBinding?: WorkerEntryConfig<TEnv>['selfBinding']
|
|
65
|
+
routeSubrequest?: WorkerEntryConfig<TEnv>['routeSubrequest']
|
|
66
|
+
before?: WorkerEntryConfig<TEnv>['before']
|
|
67
|
+
rewriteRequest?: WorkerEntryConfig<TEnv>['rewriteRequest']
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Curried so `TDeps` can be fixed explicitly (it types `methods`/`views`) while
|
|
72
|
+
* `S` is inferred from `schema` — mirroring `defineRemoteDomain`. The common
|
|
73
|
+
* case `domainWorkerEntry<Env>()({ … })` leaves `TDeps = TEnv` (handlers read
|
|
74
|
+
* the env directly); pass both — `domainWorkerEntry<Env, Deps>()` — when the
|
|
75
|
+
* handler deps differ from the worker bindings and a `deps` mapper is supplied.
|
|
76
|
+
*/
|
|
77
|
+
export function domainWorkerEntry<TEnv, TDeps = TEnv>() {
|
|
78
|
+
return function <S extends Schema>(
|
|
79
|
+
config: DomainWorkerEntryConfig<S, TEnv, TDeps>,
|
|
80
|
+
): WorkerEntry<TEnv> {
|
|
81
|
+
const domain = defineRemoteDomain<TDeps>()({
|
|
82
|
+
schema: config.schema,
|
|
83
|
+
methods: config.methods,
|
|
84
|
+
...(config.core ? { core: config.core } : {}),
|
|
85
|
+
...(config.views ? { views: config.views } : {}),
|
|
86
|
+
...(config.functions ? { remoteFunctions: config.functions } : {}),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return createWorkerEntry<TEnv>({
|
|
90
|
+
...(config.resolveUrl ? { resolveUrl: config.resolveUrl } : {}),
|
|
91
|
+
...(config.selfBinding ? { selfBinding: config.selfBinding } : {}),
|
|
92
|
+
...(config.routeSubrequest ? { routeSubrequest: config.routeSubrequest } : {}),
|
|
93
|
+
...(config.before ? { before: config.before } : {}),
|
|
94
|
+
...(config.rewriteRequest ? { rewriteRequest: config.rewriteRequest } : {}),
|
|
95
|
+
// `createWorkerEntry` conflates the worker env and the handler deps into a
|
|
96
|
+
// single type param; we keep them distinct in the public config and bridge
|
|
97
|
+
// here. The runtime deps (and the methods typed against them) are correct;
|
|
98
|
+
// only this assembly is cast.
|
|
99
|
+
build: (url, env) =>
|
|
100
|
+
({
|
|
101
|
+
domain,
|
|
102
|
+
deps: config.deps ? config.deps(env, url) : (env as unknown as TDeps),
|
|
103
|
+
url,
|
|
104
|
+
privateKey:
|
|
105
|
+
typeof config.privateKey === 'function' ? config.privateKey(env) : config.privateKey,
|
|
106
|
+
...(config.requires && config.requires.length > 0 ? { requires: config.requires } : {}),
|
|
107
|
+
...(config.postInstall ? { postInstall: config.postInstall } : {}),
|
|
108
|
+
...(config.meta ? { meta: config.meta } : {}),
|
|
109
|
+
...(config.app ? { app: config.app(env) } : {}),
|
|
110
|
+
}) as unknown as RemoteServerConfig<TEnv>,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -4,7 +4,9 @@ export type { RemoteServer, RemoteServerHandle } from './handle'
|
|
|
4
4
|
export { derivePublicJwk } from './jwks'
|
|
5
5
|
export { requireEnv } from './require-env'
|
|
6
6
|
export { canonicalizeServingUrl } from './serving-url'
|
|
7
|
-
export { createWorkerEntry } from './worker-entry'
|
|
7
|
+
export { assets, createWorkerEntry } from './worker-entry'
|
|
8
8
|
export type { WorkerEntry, WorkerEntryConfig } from './worker-entry'
|
|
9
|
+
export { domainWorkerEntry } from './domain-entry'
|
|
10
|
+
export type { DomainWorkerEntryConfig } from './domain-entry'
|
|
9
11
|
export { workerMeta } from './worker-meta'
|
|
10
12
|
export { startNodeServer } from './start'
|