@astrale-os/sdk 0.1.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 (50) hide show
  1. package/README.md +42 -0
  2. package/package.json +101 -0
  3. package/src/auth/authenticate.ts +51 -0
  4. package/src/auth/check.ts +73 -0
  5. package/src/auth/compose.ts +31 -0
  6. package/src/auth/errors.ts +32 -0
  7. package/src/auth/identity.ts +15 -0
  8. package/src/auth/index.ts +11 -0
  9. package/src/auth/kernel-client.ts +107 -0
  10. package/src/auth/resolve.ts +63 -0
  11. package/src/auth/sign.ts +36 -0
  12. package/src/auth/verify.ts +138 -0
  13. package/src/define/index.ts +9 -0
  14. package/src/define/remote-function.ts +96 -0
  15. package/src/define/view.ts +91 -0
  16. package/src/deploy/check.ts +124 -0
  17. package/src/deploy/hash-spec.ts +31 -0
  18. package/src/deploy/index.ts +3 -0
  19. package/src/deploy/meta.ts +25 -0
  20. package/src/dispatch/authorize.ts +29 -0
  21. package/src/dispatch/call-remote.ts +48 -0
  22. package/src/dispatch/dispatcher.ts +257 -0
  23. package/src/dispatch/errors.ts +94 -0
  24. package/src/dispatch/execute.ts +51 -0
  25. package/src/dispatch/identity.ts +148 -0
  26. package/src/dispatch/index.ts +17 -0
  27. package/src/dispatch/resolve.ts +78 -0
  28. package/src/dispatch/self.ts +32 -0
  29. package/src/dispatch/validate.ts +41 -0
  30. package/src/domain/build-spec.ts +127 -0
  31. package/src/domain/contract.ts +41 -0
  32. package/src/domain/define.ts +168 -0
  33. package/src/domain/extend-core.ts +287 -0
  34. package/src/domain/index.ts +4 -0
  35. package/src/index.ts +77 -0
  36. package/src/method/class.ts +148 -0
  37. package/src/method/context.ts +45 -0
  38. package/src/method/index.ts +5 -0
  39. package/src/method/single.ts +133 -0
  40. package/src/server/auxiliary-routes.ts +336 -0
  41. package/src/server/config.ts +85 -0
  42. package/src/server/create.ts +249 -0
  43. package/src/server/handle.ts +37 -0
  44. package/src/server/index.ts +10 -0
  45. package/src/server/jwks.ts +18 -0
  46. package/src/server/require-env.ts +19 -0
  47. package/src/server/serving-url.ts +28 -0
  48. package/src/server/start.ts +37 -0
  49. package/src/server/worker-entry.ts +122 -0
  50. package/src/server/worker-meta.ts +25 -0
package/src/index.ts ADDED
@@ -0,0 +1,77 @@
1
+ // ─── Method authoring ────────────────────────────────────────────────────
2
+ export type {
3
+ RemoteContext,
4
+ RemoteHandler,
5
+ MethodImpl,
6
+ ClassMethodsImpl,
7
+ InterfaceMethodsImpl,
8
+ SchemaMethodsImpl,
9
+ } from './method'
10
+ export { remoteMethod, remoteClassMethods, remoteInterfaceMethods } from './method'
11
+
12
+ // ─── Domain ──────────────────────────────────────────────────────────────
13
+ export { defineRemoteDomain, buildInstallGraph, buildInstallGraphHash } from './domain'
14
+ export type { RemoteDomain, RemoteDomainConfig } from './domain'
15
+
16
+ // ─── Declarative resource helpers ────────────────────────────────────────
17
+ // Author Views (iframe-mountable) and RemoteFunctions (standalone callables)
18
+ // at the domain definition site. Each `defineView` / `defineRemoteFunction`
19
+ // entry is auto-materialized by `defineRemoteDomain` into a graph node (a
20
+ // `View`, or the canonical kernel `Function` for standalone callables) under
21
+ // the reserved views / functions folder, keyed by its map slug; the same
22
+ // entry's `render` / `execute` (+ auth hooks) is mounted as a worker route by
23
+ // `createRemoteServer`.
24
+ export { defineView, defineRemoteFunction } from './define'
25
+ export type { ViewDef, ViewRenderContext, RemoteFunctionDef, RemoteFunctionContext } from './define'
26
+
27
+ // ─── Server ──────────────────────────────────────────────────────────────
28
+ // `createRemoteServer` dynamically imports `@hono/node-server` via `./server/start`.
29
+ // Exporting the server surface from the barrel poisons browser bundlers
30
+ // (Vite/esbuild) that traverse every re-export and try to bundle the dynamic
31
+ // chunk — which fails on Node built-ins. Consumers that need the server
32
+ // runtime import from '@astrale-os/sdk/server' directly.
33
+
34
+ // ─── Deploy ──────────────────────────────────────────────────────────────
35
+ // `deployCheck` and `hashSpecFile` use Node-only modules (`node:crypto`,
36
+ // `node:fs`, `node:child_process`) at module load time. Exporting them from
37
+ // the barrel poisons browser bundlers (Vite/esbuild) that transitively load
38
+ // every re-export. Consumers that need them import from './deploy' directly.
39
+ export { MetaSchema } from './deploy/meta'
40
+ export type { Meta } from './deploy/meta'
41
+
42
+ // ─── Route binding (re-exported from kernel-api for consumer convenience) ─
43
+ export type {
44
+ AuthPolicy,
45
+ RouteBinding,
46
+ FunctionBinding,
47
+ CredentialSource,
48
+ SingleCredentialSource,
49
+ HttpMethod,
50
+ RouteBody,
51
+ OutputMode,
52
+ } from '@astrale-os/kernel-api/routed'
53
+
54
+ // ─── Auth ────────────────────────────────────────────────────────────────
55
+ export type { RemoteIdentityConfig, AuthenticateResult } from './auth'
56
+ export { authenticateRequest, buildComposedGrant, signCredential } from './auth'
57
+ export { AuthMissingError, AuthInvalidError } from './auth'
58
+ export { assertPerm, requireOwnership, READ, EDIT, USE, SHARE, ALL } from './auth'
59
+ export type {
60
+ AuthContext,
61
+ Attestation,
62
+ Authenticated,
63
+ Delegation,
64
+ IdentityId,
65
+ IssuerId,
66
+ } from '@astrale-os/kernel-core'
67
+ export { selfGrant } from '@astrale-os/kernel-core'
68
+
69
+ // ─── Dispatch (escape hatch for custom integrations) ─────────────────────
70
+ export { SdkDispatcher, type SdkDispatcherConfig } from './dispatch'
71
+ export {
72
+ AuthorizationDeniedError,
73
+ MethodNotFoundError,
74
+ SdkValidationError,
75
+ SdkResultValidationError,
76
+ } from './dispatch'
77
+ export type { SelfResult, CallRemoteFn } from './dispatch'
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Class-level method aggregation.
3
+ *
4
+ * Where `RemoteHandler` describes one method, the types here describe a
5
+ * whole class's worth of methods. `ClassMethodsImpl<S, K, TDeps>` enforces
6
+ * that every implementable method on class `K` is provided. The interface
7
+ * variant handles abstract definitions; the schema variant indexes by class
8
+ * name.
9
+ *
10
+ * `remoteClassMethods` is the identity helper an author calls when supplying
11
+ * a class's full method record — it gives full inference for the class
12
+ * methods at once, complementing `remoteMethod` (one method at a time).
13
+ */
14
+
15
+ import type {
16
+ DefForInterface,
17
+ InterfaceMethodDefs,
18
+ InterfaceMethodKeys,
19
+ MethodClassKeys,
20
+ NonSealedMethodKeys,
21
+ OwnMethodKeys,
22
+ ResolveParams,
23
+ ResolveReturn,
24
+ SealedKeys,
25
+ } from '@astrale-os/kernel-core/domain'
26
+ import type { ImplementableOwnKeys, Schema } from '@astrale-os/kernel-dsl'
27
+
28
+ import type { SelfResult } from '../dispatch/self'
29
+ import type { MethodImpl, RemoteHandler } from './single'
30
+
31
+ /** Per-class method implementations for a remote domain. */
32
+ export type ClassMethodsImpl<
33
+ S extends Schema,
34
+ K extends MethodClassKeys<S> & string,
35
+ TDeps = unknown,
36
+ > = {
37
+ [M in OwnMethodKeys<S, K>]: MethodImpl<S, K, M, TDeps>
38
+ } & {
39
+ [M in NonSealedMethodKeys<S, K>]: MethodImpl<S, K, M, TDeps>
40
+ } & {
41
+ [M in SealedKeys<S, K>]?: never
42
+ }
43
+
44
+ /** Resolve interface method handler directly (InterfaceMethodKeys ≠ MethodKeys). */
45
+ type InterfaceMethodHandler<
46
+ S extends Schema,
47
+ K extends string,
48
+ M extends string,
49
+ TDeps,
50
+ > = M extends keyof InterfaceMethodDefs<S, K>
51
+ ? InterfaceMethodDefs<S, K>[M] extends { readonly config: infer MC }
52
+ ? MC extends { readonly static: true }
53
+ ? RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, undefined, TDeps>
54
+ : // Non-static interface methods receive the same `self` the dispatcher
55
+ // actually delivers — `SelfResult` ({ path, id? }) — exactly as the
56
+ // class-method path (`MethodImpl`) does. The node's declared props are
57
+ // NOT hydrated at runtime, so a node-typed self would be unsound.
58
+ RemoteHandler<ResolveParams<MC>, ResolveReturn<MC>, SelfResult, TDeps>
59
+ : never
60
+ : never
61
+
62
+ /** Per-interface method implementations. */
63
+ export type InterfaceMethodsImpl<
64
+ S extends Schema,
65
+ K extends InterfaceMethodKeys<S> & string,
66
+ TDeps = unknown,
67
+ > = {
68
+ [M in ImplementableOwnKeys<DefForInterface<S, K>> & string]: InterfaceMethodHandler<
69
+ S,
70
+ K,
71
+ M,
72
+ TDeps
73
+ >
74
+ }
75
+
76
+ /**
77
+ * Namespace-keyed full schema methods map, parameterized by deps.
78
+ * Handlers for classes and interfaces live under distinct `class` / `interface`
79
+ * slots, preventing name collisions between a class and an interface with the
80
+ * same name.
81
+ *
82
+ * `interface` is optional — schemas without non-abstract interface methods
83
+ * (or with no interfaces at all) do not need to provide it.
84
+ */
85
+ export type SchemaMethodsImpl<S extends Schema, TDeps = unknown> = {
86
+ class: { [K in MethodClassKeys<S> & string]: ClassMethodsImpl<S, K, TDeps> }
87
+ interface?: { [K in InterfaceMethodKeys<S> & string]: InterfaceMethodsImpl<S, K, TDeps> }
88
+ }
89
+
90
+ type RejectSealedKeys<S extends Schema, K extends string, I> = keyof I &
91
+ SealedKeys<S, K> extends never
92
+ ? I
93
+ : `Error: sealed methods must not be implemented by class '${K}': ${keyof I & SealedKeys<S, K> & string}`
94
+
95
+ /** Identity helper for authoring one class's remote methods with full schema typing. */
96
+ export function remoteClassMethods<TDeps>(): <
97
+ S extends Schema,
98
+ K extends MethodClassKeys<S> & string,
99
+ I extends ClassMethodsImpl<S, K, TDeps>,
100
+ >(
101
+ schema: S,
102
+ className: K,
103
+ impl: I & RejectSealedKeys<S, K, I>,
104
+ ) => I
105
+ export function remoteClassMethods<
106
+ S extends Schema,
107
+ K extends MethodClassKeys<S> & string,
108
+ I extends ClassMethodsImpl<S, K>,
109
+ >(schema: S, className: K, impl: I & RejectSealedKeys<S, K, I>): I
110
+ export function remoteClassMethods(...args: unknown[]) {
111
+ if (args.length === 0) {
112
+ return (...innerArgs: unknown[]) => innerArgs[2]
113
+ }
114
+
115
+ return args[2]
116
+ }
117
+
118
+ /**
119
+ * Identity helper for authoring one interface's remote methods with full
120
+ * schema typing — the interface-hosted counterpart of `remoteClassMethods`.
121
+ *
122
+ * Methods declared on an interface (e.g. a static `createNote` on `NoteOps`)
123
+ * live under the `interface:` slot of `SchemaMethodsImpl`. Without this helper
124
+ * an author has to fall back to `any` (there is no other way to name the
125
+ * per-method handler type — `InterfaceMethodHandler` is internal). Returns the
126
+ * per-interface impl object the `interface: { <Name>: … }` slot expects.
127
+ */
128
+ export function remoteInterfaceMethods<TDeps>(): <
129
+ S extends Schema,
130
+ K extends InterfaceMethodKeys<S> & string,
131
+ I extends InterfaceMethodsImpl<S, K, TDeps>,
132
+ >(
133
+ schema: S,
134
+ interfaceName: K,
135
+ impl: I,
136
+ ) => I
137
+ export function remoteInterfaceMethods<
138
+ S extends Schema,
139
+ K extends InterfaceMethodKeys<S> & string,
140
+ I extends InterfaceMethodsImpl<S, K>,
141
+ >(schema: S, interfaceName: K, impl: I): I
142
+ export function remoteInterfaceMethods(...args: unknown[]) {
143
+ if (args.length === 0) {
144
+ return (...innerArgs: unknown[]) => innerArgs[2]
145
+ }
146
+
147
+ return args[2]
148
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `RemoteContext` — what every remote method handler receives at runtime.
3
+ *
4
+ * Assembled by the dispatch pipeline before calling `execute`. Carries the
5
+ * validated params, the resolved auth context, the bound node instance (for
6
+ * non-static methods), the typed dependency container, and a
7
+ * `BoundClientSessionView` to call back into the parent kernel.
8
+ */
9
+
10
+ import type { FnMap } from '@astrale-os/kernel-client'
11
+ import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
12
+ import type { AuthContext } from '@astrale-os/kernel-core'
13
+
14
+ import type { CallRemoteFn } from '../dispatch/call-remote'
15
+
16
+ export type RemoteContext<TParams, TSelf, TDeps> = {
17
+ /** Validated params (Zod-checked against the method's `inputSchema`). */
18
+ params: TParams
19
+ /** Auth context resolved from the inbound delegation credential. `null` for public or unauthenticated optional methods. */
20
+ auth: AuthContext | null
21
+ /** Bound node instance — `undefined` for static methods. */
22
+ self: TSelf
23
+ /** Typed dependency container injected at server startup. */
24
+ deps: TDeps
25
+ /** The worker's own serving URL */
26
+ url: string
27
+ /**
28
+ * `BoundClientSessionView` to the parent kernel, bound to the composed
29
+ * credential `union(delegation, self)`. `null` when `auth: 'public'`, or
30
+ * `auth: 'optional'` with no inbound credential. Use for kernel syscalls +
31
+ * same-domain methods. For ANOTHER worker's remote method use
32
+ * {@link RemoteContext.callRemote} — `kernel.call` to a remote method fails
33
+ * the audience check.
34
+ */
35
+ kernel: BoundClientSessionView<FnMap> | null
36
+ /**
37
+ * Call another worker's remote method (a Function with `binding.remoteUrl`),
38
+ * re-minting the credential for the target's audience so it isn't rejected at
39
+ * authentication. Throws on a public/unauthenticated request (no credential
40
+ * to mint from). The calling Function's identity must hold `USE` on
41
+ * `Identity.mintDelegationCredential` AND the target method's own grants —
42
+ * `callRemote` fixes the audience, not authorization.
43
+ */
44
+ callRemote: CallRemoteFn
45
+ }
@@ -0,0 +1,5 @@
1
+ export type { RemoteContext } from './context'
2
+ export type { RemoteHandler, AnyRemoteHandler, MethodImpl } from './single'
3
+ export { remoteMethod } from './single'
4
+ export type { ClassMethodsImpl, InterfaceMethodsImpl, SchemaMethodsImpl } from './class'
5
+ export { remoteClassMethods, remoteInterfaceMethods } from './class'
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Authoring a single remote method.
3
+ *
4
+ * `RemoteHandler` is the typed shape an author writes for ONE method:
5
+ * an `execute` body plus optional REST `route` (using the kernel's
6
+ * canonical `RouteBinding`) and optional anchor `remoteUrl`.
7
+ *
8
+ * `MethodImpl` resolves params/result/self from a schema for a given
9
+ * `(class, method)` pair. `remoteMethod` is the identity helper an author
10
+ * calls per method to get full inference without writing generics by hand.
11
+ */
12
+
13
+ import type { AuthPolicy, RouteBinding } from '@astrale-os/kernel-api/routed'
14
+ import type {
15
+ MethodClassKeys,
16
+ ClassMethodConfig,
17
+ NonSealedMethodKeys,
18
+ OwnMethodKeys,
19
+ } from '@astrale-os/kernel-core/domain'
20
+ import type { Schema } from '@astrale-os/kernel-dsl'
21
+
22
+ import type { SelfResult } from '../dispatch/self'
23
+ import type { RemoteContext } from './context'
24
+
25
+ /**
26
+ * Remote function handler — execute with full typed context.
27
+ *
28
+ * `execute` is **optional** at the type level: a handler may be a pure
29
+ * binding stub (only `remoteUrl` / `route` set) when authored as a spec-side
30
+ * declaration that never runs locally — the kernel resolver short-circuits
31
+ * via `binding.remoteUrl` before reaching the handler body. The worker-side
32
+ * declaration that actually serves the call MUST provide `execute`.
33
+ *
34
+ * Runtime guard: if dispatch routes to a handler that has no `execute`, the
35
+ * dispatcher throws — that's a programmer error indicating a stub leaked into
36
+ * a code path that was supposed to execute it.
37
+ */
38
+ export type RemoteHandler<TParams, TResult, TSelf, TDeps> = {
39
+ /** The handler body. May be async or an async generator (for `output: 'stream'`). */
40
+ execute?: (
41
+ ctx: RemoteContext<TParams, TSelf, TDeps>,
42
+ ) => TResult | Promise<TResult> | AsyncGenerator<TResult>
43
+ /**
44
+ * Optional REST binding — attaches a native HTTP route to this method.
45
+ * Uses the kernel's canonical `RouteBinding` type directly.
46
+ */
47
+ route?: RouteBinding
48
+ /**
49
+ * Optional anchor URL — marks this method as anchored to a specific host.
50
+ * May contain `{name}` placeholders matching fields in the input schema.
51
+ * If absent, the method is ambient (mounts on whatever server loads the domain).
52
+ */
53
+ remoteUrl?: string
54
+ /** Optional human-readable description. Appears in generated docs. */
55
+ description?: string
56
+ /** Authentication policy. Defaults to `'required'` when absent. */
57
+ auth?: AuthPolicy
58
+ /**
59
+ * Optional pre-execute authorization check. Runs after auth resolution and
60
+ * `_self` resolution, before `execute`. Throw any error to deny the call —
61
+ * the SDK wraps it as `AuthorizationDeniedError` (mapped to wire-level
62
+ * `PERMISSION_DENIED`).
63
+ *
64
+ * Use for fine-grained checks the kernel's bit-level perms can't express
65
+ * (e.g. "only the project lead can addMember"). For straight bit-level
66
+ * checks, prefer the helpers in `@astrale-os/sdk` (`assertPerm`,
67
+ * `requireOwnership`).
68
+ *
69
+ * The kernel still enforces `has_perm` independently — `authorize` is
70
+ * additive ergonomic gating on top, not a replacement.
71
+ */
72
+ authorize?: (ctx: RemoteContext<TParams, TSelf, TDeps>) => void | Promise<void>
73
+ }
74
+
75
+ // oxlint-disable-next-line no-explicit-any
76
+ export type AnyRemoteHandler = RemoteHandler<any, any, any, any>
77
+
78
+ /**
79
+ * Fully typed method implementation — resolves params/result/self from the
80
+ * schema, wraps in `RemoteHandler` with deps.
81
+ */
82
+ export type MethodImpl<
83
+ S extends Schema,
84
+ K extends MethodClassKeys<S> & string,
85
+ M extends string,
86
+ TDeps = unknown,
87
+ > =
88
+ ClassMethodConfig<S, K, M> extends {
89
+ params: infer P
90
+ result: infer R
91
+ self: unknown
92
+ isStatic: infer St
93
+ }
94
+ ? RemoteHandler<P, R, St extends true ? undefined : SelfResult, TDeps>
95
+ : never
96
+
97
+ type ImplementableMethodName<S extends Schema, K extends MethodClassKeys<S> & string> = (
98
+ | OwnMethodKeys<S, K>
99
+ | NonSealedMethodKeys<S, K>
100
+ ) &
101
+ string
102
+
103
+ /**
104
+ * Identity helper for authoring one remote method with full schema-driven typing.
105
+ *
106
+ * Two-form calling convention:
107
+ * - `remoteMethod<TDeps>()` — curried form; captures deps type, returns a
108
+ * per-schema helper. Use when you want deps typed.
109
+ * - `remoteMethod(schema, className, methodName, impl)` — direct form with
110
+ * `unknown` deps.
111
+ */
112
+ export function remoteMethod<TDeps>(): <
113
+ S extends Schema,
114
+ K extends MethodClassKeys<S> & string,
115
+ M extends ImplementableMethodName<S, K>,
116
+ >(
117
+ schema: S,
118
+ className: K,
119
+ methodName: M,
120
+ impl: MethodImpl<S, K, M, TDeps>,
121
+ ) => MethodImpl<S, K, M, TDeps>
122
+ export function remoteMethod<
123
+ S extends Schema,
124
+ K extends MethodClassKeys<S> & string,
125
+ M extends ImplementableMethodName<S, K>,
126
+ >(schema: S, className: K, methodName: M, impl: MethodImpl<S, K, M>): MethodImpl<S, K, M>
127
+ export function remoteMethod(...args: unknown[]) {
128
+ if (args.length === 0) {
129
+ return (...innerArgs: unknown[]) => innerArgs[3]
130
+ }
131
+
132
+ return args[3]
133
+ }