@astrale-os/sdk 0.1.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/dist/auth/check.d.ts +1 -2
- package/dist/auth/check.d.ts.map +1 -1
- package/dist/auth/check.js +2 -6
- package/dist/auth/check.js.map +1 -1
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/issuer-mint.d.ts +53 -0
- package/dist/auth/issuer-mint.d.ts.map +1 -0
- package/dist/auth/issuer-mint.js +35 -0
- package/dist/auth/issuer-mint.js.map +1 -0
- package/dist/auth/kernel-client.d.ts +8 -2
- package/dist/auth/kernel-client.d.ts.map +1 -1
- package/dist/auth/kernel-client.js +22 -46
- package/dist/auth/kernel-client.js.map +1 -1
- package/dist/cli/dotenv.d.ts +8 -0
- package/dist/cli/dotenv.d.ts.map +1 -1
- package/dist/cli/dotenv.js +18 -0
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/log.d.ts +11 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +17 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/publish.d.ts +35 -0
- package/dist/cli/publish.d.ts.map +1 -0
- package/dist/cli/publish.js +271 -0
- package/dist/cli/publish.js.map +1 -0
- package/dist/cli/run.d.ts +17 -2
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +102 -111
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/spec.d.ts.map +1 -1
- package/dist/cli/spec.js +1 -0
- package/dist/cli/spec.js.map +1 -1
- package/dist/config/adapter.d.ts +12 -11
- package/dist/config/adapter.d.ts.map +1 -1
- package/dist/config/adapter.js.map +1 -1
- package/dist/config/define-domain.d.ts +88 -27
- package/dist/config/define-domain.d.ts.map +1 -1
- package/dist/config/define-domain.js +79 -33
- package/dist/config/define-domain.js.map +1 -1
- package/dist/config/deploy.d.ts +1 -1
- package/dist/config/deploy.js +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/define/remote-function.d.ts +32 -17
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js +10 -9
- package/dist/define/remote-function.js.map +1 -1
- package/dist/define/view.d.ts +8 -6
- package/dist/define/view.d.ts.map +1 -1
- package/dist/define/view.js +8 -6
- package/dist/define/view.js.map +1 -1
- package/dist/deploy/check.d.ts +6 -6
- package/dist/deploy/check.d.ts.map +1 -1
- package/dist/deploy/check.js +1 -1
- package/dist/deploy/check.js.map +1 -1
- package/dist/deploy/index.d.ts +0 -1
- package/dist/deploy/index.d.ts.map +1 -1
- package/dist/deploy/index.js +0 -1
- package/dist/deploy/index.js.map +1 -1
- package/dist/deploy/meta.d.ts +11 -0
- package/dist/deploy/meta.d.ts.map +1 -1
- package/dist/deploy/meta.js +23 -0
- package/dist/deploy/meta.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts +17 -4
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +15 -3
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/execute.d.ts +6 -2
- package/dist/dispatch/execute.d.ts.map +1 -1
- package/dist/dispatch/execute.js +1 -0
- package/dist/dispatch/execute.js.map +1 -1
- package/dist/dispatch/identity.d.ts +12 -10
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +10 -15
- package/dist/dispatch/identity.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 +1 -1
- package/dist/dispatch/index.js.map +1 -1
- package/dist/dispatch/self.d.ts +25 -37
- package/dist/dispatch/self.d.ts.map +1 -1
- package/dist/dispatch/self.js +60 -30
- package/dist/dispatch/self.js.map +1 -1
- package/dist/domain/binding.d.ts +18 -0
- package/dist/domain/binding.d.ts.map +1 -0
- package/dist/domain/binding.js +29 -0
- package/dist/domain/binding.js.map +1 -0
- package/dist/domain/build-spec.d.ts.map +1 -1
- package/dist/domain/build-spec.js +22 -7
- package/dist/domain/build-spec.js.map +1 -1
- package/dist/domain/define.d.ts +28 -43
- package/dist/domain/define.d.ts.map +1 -1
- package/dist/domain/define.js +23 -60
- package/dist/domain/define.js.map +1 -1
- package/dist/domain/extend-functions.d.ts +48 -0
- package/dist/domain/extend-functions.d.ts.map +1 -0
- package/dist/domain/extend-functions.js +84 -0
- package/dist/domain/extend-functions.js.map +1 -0
- package/dist/domain/extend-views.d.ts +45 -0
- package/dist/domain/extend-views.d.ts.map +1 -0
- package/dist/domain/extend-views.js +116 -0
- package/dist/domain/extend-views.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/method/class.d.ts +2 -2
- package/dist/method/class.d.ts.map +1 -1
- package/dist/method/class.js.map +1 -1
- package/dist/method/context.d.ts +18 -3
- package/dist/method/context.d.ts.map +1 -1
- package/dist/method/index.d.ts +2 -2
- package/dist/method/index.d.ts.map +1 -1
- package/dist/method/index.js.map +1 -1
- package/dist/method/single.d.ts +25 -38
- package/dist/method/single.d.ts.map +1 -1
- package/dist/method/single.js +1 -11
- package/dist/method/single.js.map +1 -1
- package/dist/server/auxiliary-routes.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.js +14 -3
- package/dist/server/auxiliary-routes.js.map +1 -1
- package/dist/server/create.d.ts +2 -15
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/create.js +15 -51
- package/dist/server/create.js.map +1 -1
- package/dist/server/domain-entry.d.ts +10 -0
- package/dist/server/domain-entry.d.ts.map +1 -1
- package/dist/server/domain-entry.js +2 -0
- package/dist/server/domain-entry.js.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +0 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/worker-entry.d.ts +3 -3
- package/dist/server/worker-entry.js +3 -3
- package/package.json +10 -11
- package/src/auth/check.ts +2 -6
- package/src/auth/index.ts +3 -1
- package/src/auth/issuer-mint.ts +75 -0
- package/src/auth/kernel-client.ts +31 -63
- package/src/cli/dotenv.ts +20 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/log.ts +18 -0
- package/src/cli/publish.ts +341 -0
- package/src/cli/run.ts +109 -125
- package/src/cli/spec.ts +1 -0
- package/src/config/adapter.ts +9 -11
- package/src/config/define-domain.ts +184 -53
- package/src/config/deploy.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/define/remote-function.ts +56 -21
- package/src/define/view.ts +8 -6
- package/src/deploy/check.ts +7 -7
- package/src/deploy/index.ts +0 -1
- package/src/deploy/meta.ts +25 -0
- package/src/dispatch/dispatcher.ts +32 -9
- package/src/dispatch/execute.ts +7 -2
- package/src/dispatch/identity.ts +17 -22
- package/src/dispatch/index.ts +1 -1
- package/src/dispatch/self.ts +117 -41
- package/src/domain/binding.ts +37 -0
- package/src/domain/build-spec.ts +32 -7
- package/src/domain/define.ts +71 -78
- package/src/domain/extend-functions.ts +106 -0
- package/src/domain/extend-views.ts +151 -0
- package/src/index.ts +36 -7
- package/src/method/class.ts +11 -7
- package/src/method/context.ts +19 -3
- package/src/method/index.ts +8 -2
- package/src/method/single.ts +35 -42
- package/src/server/auxiliary-routes.ts +14 -3
- package/src/server/create.ts +15 -51
- package/src/server/domain-entry.ts +12 -0
- package/src/server/index.ts +0 -1
- package/src/server/worker-entry.ts +3 -3
- package/src/deploy/hash-spec.ts +0 -31
- package/src/domain/extend-core.ts +0 -301
- package/src/server/worker-meta.ts +0 -25
|
@@ -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
|
|
4
|
-
*
|
|
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,16 +11,16 @@
|
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
21
|
+
import { DomainOrigin } from '@astrale-os/kernel-core/domain'
|
|
22
22
|
|
|
23
|
-
import type {
|
|
23
|
+
import type { AnyRemoteFunctionDef, ViewDef } from '../define'
|
|
24
24
|
import type { SchemaMethodsImpl } from '../method'
|
|
25
25
|
|
|
26
26
|
// A registry holds views / functions of every deps + param/result shape. Their
|
|
@@ -28,19 +28,69 @@ import type { SchemaMethodsImpl } from '../method'
|
|
|
28
28
|
// site; here they're held loosely and, crucially, kept OUT of `TDeps` inference
|
|
29
29
|
// — so `TDeps` is fixed by `methods` alone (the authoritative deps source) and a
|
|
30
30
|
// view authored with the default `unknown` deps can't fight `methods`'s `Env`.
|
|
31
|
-
// oxlint-disable no-explicit-any
|
|
31
|
+
// oxlint-disable-next-line no-explicit-any
|
|
32
32
|
type AnyViewDef = ViewDef<any>
|
|
33
|
-
|
|
34
|
-
//
|
|
33
|
+
// The function bag is the canonical, variance-correct one from `../define` (it
|
|
34
|
+
// must accept functions of any auth policy — see the note there). Aliased to
|
|
35
|
+
// keep the local usages below terse.
|
|
36
|
+
type AnyFunctionDef = AnyRemoteFunctionDef
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* binding. Always written out explicitly (`client: { dir: 'client' }`) — there
|
|
41
|
-
* is no boolean shorthand, so the source folder is never implicit.
|
|
39
|
+
* Optional presentation metadata for the domain, served verbatim on the
|
|
40
|
+
* worker's `/meta` endpoint (see `../deploy/meta`) so a host UI can brand a
|
|
41
|
+
* freshly-installed domain. Both the block and every field are optional.
|
|
42
42
|
*/
|
|
43
|
-
export
|
|
43
|
+
export interface DomainManifest {
|
|
44
|
+
/** Domain logo — an inline SVG string or a `data:` URL (no extra validation). */
|
|
45
|
+
logo?: string
|
|
46
|
+
/**
|
|
47
|
+
* Slug of one of THIS domain's own views to open first — the domain's entry
|
|
48
|
+
* surface. This is the SERVED form: a plain slug, stamped verbatim onto
|
|
49
|
+
* `/meta`. Authors don't write the slug directly — they pass the view OBJECT
|
|
50
|
+
* to `defineDomain` (see {@link DomainManifestConfig.entrypoint}), which
|
|
51
|
+
* resolves it to this slug. Pinned at app install to the `View` node at
|
|
52
|
+
* `/<origin>/views/<slug>` via the app's `entrypoint` edge.
|
|
53
|
+
*/
|
|
54
|
+
entrypoint?: string
|
|
55
|
+
/**
|
|
56
|
+
* The ROLES this domain declares — named capability identities a workspace
|
|
57
|
+
* registers when an app for this domain is installed, then assigns to users
|
|
58
|
+
* (`default: true` roles are auto-assigned to the installing user). The
|
|
59
|
+
* domain itself grants each role its resource permissions (typically in its
|
|
60
|
+
* `postInstall`); declaring a role here only names it. Slugs are validated
|
|
61
|
+
* (lowercase slug charset, unique) at `defineDomain`.
|
|
62
|
+
*/
|
|
63
|
+
roles?: readonly DomainRoleDecl[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One declared role of the domain (see {@link DomainManifest.roles}). */
|
|
67
|
+
export interface DomainRoleDecl {
|
|
68
|
+
/** Stable role slug, unique within the domain (e.g. `"editor"`). */
|
|
69
|
+
slug: string
|
|
70
|
+
/** Display name; defaults to the slug. */
|
|
71
|
+
name?: string
|
|
72
|
+
/** What holding this role means — shown in permission UIs. */
|
|
73
|
+
description?: string
|
|
74
|
+
/** Auto-assign this role to the user installing an app for this domain. */
|
|
75
|
+
default?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The author-facing manifest accepted by {@link defineDomain} — identical to the
|
|
80
|
+
* served {@link DomainManifest} except `entrypoint` is the view OBJECT (a value
|
|
81
|
+
* of the `views` map) rather than its slug. `defineDomain` resolves it to the
|
|
82
|
+
* slug by identity, so a renamed or dropped view is a compile error at the
|
|
83
|
+
* reference site, never a stale string (the same guarantee `postInstall` gives
|
|
84
|
+
* for functions).
|
|
85
|
+
*/
|
|
86
|
+
export interface DomainManifestConfig extends Omit<DomainManifest, 'entrypoint'> {
|
|
87
|
+
/**
|
|
88
|
+
* The view to open first — the domain's entry surface. Pass the view OBJECT
|
|
89
|
+
* from this domain's own `views` map (e.g. `entrypoint: views.welcome`).
|
|
90
|
+
* Resolved to its slug here and served as {@link DomainManifest.entrypoint}.
|
|
91
|
+
*/
|
|
92
|
+
entrypoint?: AnyViewDef
|
|
93
|
+
}
|
|
44
94
|
|
|
45
95
|
export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
|
|
46
96
|
/** The domain schema (from `schema/`). Its `.domain` seeds the default origin. */
|
|
@@ -77,11 +127,14 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
|
|
|
77
127
|
*/
|
|
78
128
|
functions?: Record<string, AnyFunctionDef>
|
|
79
129
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
130
|
+
* Optional presentation metadata stamped onto `/meta`. When `manifest.entrypoint`
|
|
131
|
+
* is set it MUST be a view from this domain's own `views` map — passed as the
|
|
132
|
+
* OBJECT (`entrypoint: views.welcome`), resolved to its slug here. A view that
|
|
133
|
+
* isn't in `views` is a compile error at the reference site, and the
|
|
134
|
+
* resolution throws loudly if it's somehow absent — so a typo can never dangle
|
|
135
|
+
* to a NOT_FOUND view path at install.
|
|
83
136
|
*/
|
|
84
|
-
|
|
137
|
+
manifest?: DomainManifestConfig
|
|
85
138
|
/**
|
|
86
139
|
* The domain's **addressing name** (the graph slug it mounts under, e.g.
|
|
87
140
|
* `'crm.acme.dev'`). Defaults to `schema.domain`. Must be a name, never a
|
|
@@ -94,11 +147,25 @@ export interface DefineDomainConfig<S extends Schema, TDeps, TEnv = unknown> {
|
|
|
94
147
|
/** Cross-domain deps, by origin. Verified present on the instance at install. */
|
|
95
148
|
requires?: readonly string[]
|
|
96
149
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
150
|
+
* Where the Domain node physically lives in the graph TREE — an absolute tree
|
|
151
|
+
* path whose LAST segment is the origin (e.g. `'/domains/crm.acme.dev'`).
|
|
152
|
+
* Optional; defaults to `/domains/<origin>`. This moves ONLY the physical
|
|
153
|
+
* `has_parent` position: the domain's `installed_in` edge and EVERY typed
|
|
154
|
+
* address (`/:<origin>`, `/:<origin>:Class`, …) stay ROOT-mounted, so
|
|
155
|
+
* addressing is unchanged. A platform domain that must stay top-level sets its
|
|
156
|
+
* own origin path (e.g. `'/workspace.astrale.ai'`).
|
|
100
157
|
*/
|
|
101
|
-
|
|
158
|
+
path?: string
|
|
159
|
+
/**
|
|
160
|
+
* The function the kernel runs once after install, as __SYSTEM__ — where the
|
|
161
|
+
* domain seeds itself / posts its own grants. Reference it from the `functions`
|
|
162
|
+
* map: `postInstall: functions.seed`. The SDK derives its path by identity, so a
|
|
163
|
+
* typo or a renamed key is a compile error here, never a stale string. It is
|
|
164
|
+
* always a standalone function (a domain bootstrap belongs to the domain, not to
|
|
165
|
+
* a class) under THIS domain — you never write the origin, and the kernel
|
|
166
|
+
* resolves it relative to wherever the domain is installed.
|
|
167
|
+
*/
|
|
168
|
+
postInstall?: AnyFunctionDef
|
|
102
169
|
}
|
|
103
170
|
|
|
104
171
|
export interface DomainDefinition {
|
|
@@ -114,11 +181,13 @@ export interface DomainDefinition {
|
|
|
114
181
|
deps?: (env: any, url: string) => any
|
|
115
182
|
views?: Record<string, ViewDef>
|
|
116
183
|
functions?: Record<string, AnyFunctionDef>
|
|
117
|
-
/**
|
|
118
|
-
|
|
184
|
+
/** Presentation metadata for `/meta`, validated at definition time. */
|
|
185
|
+
manifest?: DomainManifest
|
|
119
186
|
origin: string
|
|
120
187
|
requires: readonly string[]
|
|
121
188
|
postInstall?: string
|
|
189
|
+
/** Physical tree path for the Domain node; default `/domains/<origin>`. */
|
|
190
|
+
path?: string
|
|
122
191
|
}
|
|
123
192
|
|
|
124
193
|
export function defineDomain<S extends Schema, TDeps, TEnv = unknown>(
|
|
@@ -159,57 +228,119 @@ export function defineDomain<S extends Schema, TDeps, TEnv = unknown>(
|
|
|
159
228
|
} catch {
|
|
160
229
|
throw new Error(
|
|
161
230
|
`defineDomain: invalid \`requires\` entry "${dep}". Use the dependency's origin slug ` +
|
|
162
|
-
'(lowercase FQDN-like, e.g. "
|
|
231
|
+
'(lowercase FQDN-like, e.g. "example.astrale.ai"), not a URL.',
|
|
163
232
|
)
|
|
164
233
|
}
|
|
165
234
|
})
|
|
166
235
|
|
|
236
|
+
// `path`, when set, is the Domain's physical tree path. Validate the
|
|
237
|
+
// two invariants the serializer/kernel rely on, string-only (no AbsolutePath
|
|
238
|
+
// import — this module must stay worker-safe): it is absolute with at least one
|
|
239
|
+
// segment, and its LAST segment equals the origin (the kernel derives origin
|
|
240
|
+
// from the mount basename in a few places, e.g. has_parent slug + get-access-
|
|
241
|
+
// token origin recovery). Full structural parse happens at build time.
|
|
242
|
+
if (config.path !== undefined) {
|
|
243
|
+
const loc = config.path
|
|
244
|
+
if (!loc.startsWith('/') || loc === '/') {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`defineDomain: \`path\` must be an absolute graph path with at least ` +
|
|
247
|
+
`one segment (e.g. "/domains/${origin}"), not "${loc}".`,
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
if (loc.split('/').pop() !== origin) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`defineDomain: \`path\` last segment must equal the origin "${origin}" ` +
|
|
253
|
+
`(e.g. "/domains/${origin}" or "/${origin}"), got "${loc}".`,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A `manifest.entrypoint` view is resolved to its slug below (in the return),
|
|
259
|
+
// where a view absent from `views` throws — see `resolveManifest`.
|
|
260
|
+
|
|
261
|
+
// Declared roles must carry valid, unique slugs — a role slug becomes a graph
|
|
262
|
+
// path segment and an identity subject, so the kernel's reserved separators
|
|
263
|
+
// (`/`, `:`, `@`) can never appear in one. Fail at authoring time.
|
|
264
|
+
if (config.manifest?.roles !== undefined) {
|
|
265
|
+
const seen = new Set<string>()
|
|
266
|
+
for (const role of config.manifest.roles) {
|
|
267
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(role.slug)) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`defineDomain: invalid \`manifest.roles\` slug "${role.slug}". ` +
|
|
270
|
+
'Use a lowercase slug (letters, digits, "_", "-"), e.g. "editor".',
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
if (seen.has(role.slug)) {
|
|
274
|
+
throw new Error(`defineDomain: duplicate \`manifest.roles\` slug "${role.slug}".`)
|
|
275
|
+
}
|
|
276
|
+
seen.add(role.slug)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
167
280
|
return {
|
|
168
281
|
schema: config.schema,
|
|
169
282
|
methods: config.methods as DomainDefinition['methods'],
|
|
170
283
|
...(config.deps ? { deps: config.deps as DomainDefinition['deps'] } : {}),
|
|
171
284
|
...(config.views ? { views: config.views as Record<string, ViewDef> } : {}),
|
|
172
285
|
...(config.functions ? { functions: config.functions } : {}),
|
|
173
|
-
...(config.
|
|
286
|
+
...(config.manifest ? { manifest: resolveManifest(config.manifest, config.views) } : {}),
|
|
174
287
|
origin,
|
|
175
288
|
requires,
|
|
176
|
-
...(config.
|
|
177
|
-
|
|
289
|
+
...(config.path ? { path: config.path } : {}),
|
|
290
|
+
...(config.postInstall !== undefined
|
|
291
|
+
? { postInstall: normalizePostInstall(config.postInstall, origin, config.functions) }
|
|
178
292
|
: {}),
|
|
179
293
|
}
|
|
180
294
|
}
|
|
181
295
|
|
|
182
296
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
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).
|
|
297
|
+
* Lower the author-facing manifest (view OBJECT for `entrypoint`) to the served
|
|
298
|
+
* {@link DomainManifest} (slug for `entrypoint`). The entrypoint view is matched
|
|
299
|
+
* to its `views`-map key by IDENTITY — the same mechanism `normalizePostInstall`
|
|
300
|
+
* uses for functions — so a view absent from `views` (one from another domain,
|
|
301
|
+
* or dropped) throws here instead of dangling to a NOT_FOUND view path at install.
|
|
193
302
|
*/
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
|
|
303
|
+
function resolveManifest(
|
|
304
|
+
manifest: DomainManifestConfig,
|
|
305
|
+
views: Record<string, AnyViewDef> | undefined,
|
|
306
|
+
): DomainManifest {
|
|
307
|
+
const { entrypoint, ...rest } = manifest
|
|
308
|
+
if (entrypoint === undefined) return rest
|
|
309
|
+
const slug = views ? Object.entries(views).find(([, def]) => def === entrypoint)?.[0] : undefined
|
|
310
|
+
if (slug === undefined) {
|
|
311
|
+
const viewKeys = Object.keys(views ?? {})
|
|
197
312
|
throw new Error(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`
|
|
313
|
+
"defineDomain: `manifest.entrypoint` must be one of this domain's own views " +
|
|
314
|
+
'(pass the view object, e.g. `entrypoint: views.welcome`). ' +
|
|
315
|
+
`Available views: ${viewKeys.length > 0 ? viewKeys.map((k) => `"${k}"`).join(', ') : '(none)'}.`,
|
|
201
316
|
)
|
|
202
317
|
}
|
|
203
|
-
|
|
318
|
+
return { ...rest, entrypoint: slug }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolve a `postInstall` function reference to the colon-path the bundle carries.
|
|
323
|
+
* The slug is the `functions` map key the reference is registered under (found by
|
|
324
|
+
* identity), so a renamed key is a compile error at the reference site AND the
|
|
325
|
+
* derived path follows the rename. The origin is never the author's to supply
|
|
326
|
+
* (postInstall is always a standalone function of THIS domain) and the path is
|
|
327
|
+
* mount-agnostic — the kernel resolves it wherever the domain is installed.
|
|
328
|
+
*/
|
|
329
|
+
function normalizePostInstall(
|
|
330
|
+
postInstall: AnyFunctionDef,
|
|
331
|
+
origin: string,
|
|
332
|
+
functions: Record<string, AnyFunctionDef> | undefined,
|
|
333
|
+
): string {
|
|
334
|
+
const slug = functions
|
|
335
|
+
? Object.entries(functions).find(([, def]) => def === postInstall)?.[0]
|
|
336
|
+
: undefined
|
|
337
|
+
if (slug === undefined) {
|
|
204
338
|
throw new Error(
|
|
205
|
-
|
|
206
|
-
`
|
|
339
|
+
"defineDomain: `postInstall` must reference a function from this domain's `functions` map " +
|
|
340
|
+
'(e.g. `postInstall: functions.seed`).',
|
|
207
341
|
)
|
|
208
342
|
}
|
|
209
|
-
|
|
210
|
-
return postInstall.startsWith('/:')
|
|
211
|
-
? `/:${origin}${postInstall.slice(2 + slug.length)}`
|
|
212
|
-
: `/${origin}${postInstall.slice(1 + slug.length)}`
|
|
343
|
+
return `/:${origin}:function.${slug}`
|
|
213
344
|
}
|
|
214
345
|
|
|
215
346
|
function schemaDomain(schema: Schema): string | undefined {
|
package/src/config/deploy.ts
CHANGED
|
@@ -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
|
|
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 }))
|
package/src/config/index.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export { defineDomain } from './define-domain'
|
|
17
|
-
export type {
|
|
17
|
+
export type { DefineDomainConfig, DomainDefinition, DomainManifest } from './define-domain'
|
|
18
18
|
|
|
19
19
|
export { deploy } from './deploy'
|
|
20
20
|
export type { DeployConfig } from './deploy'
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authoring a standalone remote function — a callable not bound to a class.
|
|
3
3
|
* (The verb keeps the `defineRemoteFunction` name; the node it materializes is
|
|
4
|
-
* the canonical kernel `Function` class, the former
|
|
4
|
+
* the canonical kernel `Function` class, replacing the former remote-function wrapper.)
|
|
5
5
|
*
|
|
6
6
|
* Each entry in `defineRemoteDomain({ remoteFunctions: { ... } })` becomes:
|
|
7
|
-
* - a graph node at `/${origin}/
|
|
8
|
-
* as the kernel `Function` class
|
|
9
|
-
* `
|
|
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 —
|
|
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
|
-
* `
|
|
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'
|
|
@@ -22,6 +23,7 @@ import type { BoundClientSessionView } from '@astrale-os/kernel-client/session'
|
|
|
22
23
|
import type { Context } from 'hono'
|
|
23
24
|
import type { z } from 'zod'
|
|
24
25
|
|
|
26
|
+
import type { DomainIssuer } from '../auth/issuer-mint'
|
|
25
27
|
import type { CallRemoteFn } from '../dispatch/call-remote'
|
|
26
28
|
import type { AuthForPolicy, KernelForAuth } from '../method/context'
|
|
27
29
|
|
|
@@ -46,12 +48,20 @@ export type RemoteFunctionContext<
|
|
|
46
48
|
/**
|
|
47
49
|
* `BoundClientSessionView` to the parent kernel, bound to the composed
|
|
48
50
|
* credential `union(delegation, self)` — same shape as `RemoteContext.kernel`
|
|
49
|
-
* for `remoteMethod
|
|
51
|
+
* for `remoteMethod`, including the typed graph read/write sugar flattened onto
|
|
52
|
+
* it (`kernel.get` / `kernel.children` / `kernel.query`,
|
|
53
|
+
* `kernel.createNode` / `kernel.mutate(patch)`, …) over the `function.get` /
|
|
54
|
+
* `function.mutate` syscalls. Nullability follows {@link KernelForAuth} of the
|
|
50
55
|
* function's `auth`: non-null for the default `'required'`, `… | null` for
|
|
51
56
|
* `'optional'`, `null` for `'public'` (use {@link RemoteFunctionContext.selfKernel}
|
|
52
57
|
* there, after verifying the upstream).
|
|
53
58
|
*/
|
|
54
59
|
kernel: TKernel
|
|
60
|
+
/**
|
|
61
|
+
* Mint seam for identities THIS DOMAIN issues — same contract as
|
|
62
|
+
* {@link RemoteContext.issuer}.
|
|
63
|
+
*/
|
|
64
|
+
issuer: DomainIssuer
|
|
55
65
|
/**
|
|
56
66
|
* Call another worker's remote method, re-minting the credential for the
|
|
57
67
|
* target's audience. Same contract as {@link RemoteContext.callRemote}: throws
|
|
@@ -77,12 +87,6 @@ export type RemoteFunctionDef<
|
|
|
77
87
|
TDeps = unknown,
|
|
78
88
|
TAuth extends AuthPolicy = 'required',
|
|
79
89
|
> = {
|
|
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
90
|
/** Zod schema for the call's parameters. */
|
|
87
91
|
inputSchema: z.ZodType<TParams>
|
|
88
92
|
/** Zod schema for the call's result. */
|
|
@@ -118,11 +122,42 @@ export type RemoteFunctionDef<
|
|
|
118
122
|
description?: string
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
125
|
+
// Loosely-typed "bag" for the per-domain function maps. The PRECISE typing is
|
|
126
|
+
// enforced at each `defineRemoteFunction` call site; this only has to HOLD defs
|
|
127
|
+
// of ANY auth policy. We deliberately do NOT pin a single concrete `TAuth`:
|
|
128
|
+
// `RemoteFunctionContext` carries `kernel: KernelForAuth<TAuth>` (an
|
|
129
|
+
// auth-dependent type that resolves to a concrete union), so any single concrete
|
|
130
|
+
// policy — `'required'` OR the full `AuthPolicy` union — rejects the others when
|
|
131
|
+
// a handler is assigned into the bag under parameter contravariance (a public
|
|
132
|
+
// handler wants `kernel: null`, a required one `kernel: Kernel`; neither fits a
|
|
133
|
+
// fixed bag). Relaxing the `authorize`/`execute` PARAMS to `any` is what makes
|
|
134
|
+
// the bag accept every policy; soundness is unchanged — the bag was already
|
|
135
|
+
// `any, any, any`, and the real contract is checked at the call site.
|
|
136
|
+
// oxlint-disable no-explicit-any
|
|
137
|
+
export type AnyRemoteFunctionDef = Omit<
|
|
138
|
+
RemoteFunctionDef<any, any, any, AuthPolicy>,
|
|
139
|
+
'authorize' | 'execute'
|
|
140
|
+
> & {
|
|
141
|
+
authorize?: (ctx: any) => void | Promise<void>
|
|
142
|
+
execute: (ctx: any) => any
|
|
143
|
+
}
|
|
144
|
+
// oxlint-enable no-explicit-any
|
|
145
|
+
|
|
146
|
+
// Compile-time regression guards (pure `type` aliases — zero runtime emit, but
|
|
147
|
+
// checked by `pnpm typecheck` since this file is under `src/`). Each asserts a
|
|
148
|
+
// function of the given auth policy still FITS the bag. If `AnyRemoteFunctionDef`
|
|
149
|
+
// ever narrows back and rejects a policy, the matching `_Assert<false>` errors.
|
|
150
|
+
type _AssertTrue<T extends true> = T
|
|
151
|
+
type _FnFitsBag<D> = D extends AnyRemoteFunctionDef ? true : false
|
|
152
|
+
// Exported only so `noUnusedLocals` treats the assertions as used — it is NOT
|
|
153
|
+
// re-exported by `../define`, so it never reaches the package's public surface.
|
|
154
|
+
// A regression that makes the bag reject a policy turns the matching
|
|
155
|
+
// `_AssertTrue<false>` into a compile error here.
|
|
156
|
+
export type _AuthPolicyBagGuards = [
|
|
157
|
+
_AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'public'>>>,
|
|
158
|
+
_AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'optional'>>>,
|
|
159
|
+
_AssertTrue<_FnFitsBag<RemoteFunctionDef<unknown, unknown, unknown, 'required'>>>,
|
|
160
|
+
]
|
|
126
161
|
|
|
127
162
|
/**
|
|
128
163
|
* Identity helper for authoring a RemoteFunction. Returns its argument
|
package/src/define/view.ts
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
10
|
+
* (`/<viewsFolder>/<slug>` by default).
|
|
10
11
|
*
|
|
11
|
-
* The
|
|
12
|
-
* lands in `Function.binding.remoteUrl` is
|
|
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
|
package/src/deploy/check.ts
CHANGED
|
@@ -6,14 +6,14 @@ import type { Meta } from './meta'
|
|
|
6
6
|
import { MetaSchema } from './meta'
|
|
7
7
|
|
|
8
8
|
export type DeployCheckOptions = {
|
|
9
|
-
/** Deployed worker URL (e.g. `https://
|
|
9
|
+
/** Deployed worker URL (e.g. `https://example.astrale.ai`). */
|
|
10
10
|
url: string
|
|
11
11
|
/**
|
|
12
|
-
* Expected `meta.schemaHash` — the hash of the
|
|
13
|
-
* (
|
|
14
|
-
* advertises a `schemaHash` but this is absent, the check fails
|
|
15
|
-
* than silently "passing" an unverified deploy. Per-domain
|
|
16
|
-
* compute it (`
|
|
12
|
+
* Expected `meta.schemaHash` — the hash of the domain's install graph (via
|
|
13
|
+
* `buildInstallGraphHash(domain, url)`). REQUIRED to verify schema drift: when
|
|
14
|
+
* the worker advertises a `schemaHash` but this is absent, the check fails
|
|
15
|
+
* loudly rather than silently "passing" an unverified deploy. Per-domain
|
|
16
|
+
* deploy scripts compute it (`buildInstallGraphHash(...)`) and pass it.
|
|
17
17
|
*/
|
|
18
18
|
expectedSchemaHash?: string
|
|
19
19
|
/**
|
|
@@ -96,7 +96,7 @@ async function checkSchemaHash(
|
|
|
96
96
|
if (!opts.expectedSchemaHash) {
|
|
97
97
|
throw new Error(
|
|
98
98
|
`worker advertises schemaHash=${meta.schemaHash} but no expectedSchemaHash was provided — ` +
|
|
99
|
-
`cannot verify schema drift (pass expectedSchemaHash, e.g.
|
|
99
|
+
`cannot verify schema drift (pass expectedSchemaHash, e.g. buildInstallGraphHash(domain, url))`,
|
|
100
100
|
)
|
|
101
101
|
}
|
|
102
102
|
if (meta.schemaHash !== opts.expectedSchemaHash) {
|
package/src/deploy/index.ts
CHANGED
package/src/deploy/meta.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { z } from 'zod'
|
|
12
12
|
|
|
13
13
|
export const MetaSchema = z.object({
|
|
14
|
+
// The version of the meta payload. Incremented on any change to the payload
|
|
15
|
+
version: z.number().int().min(1).default(1),
|
|
14
16
|
/** Base URL where JWKS is published. Always stamped by `createRemoteServer`
|
|
15
17
|
* and required by `deployCheck` to verify JWKS reachability. */
|
|
16
18
|
iss: z.string().min(1),
|
|
@@ -20,6 +22,29 @@ export const MetaSchema = z.object({
|
|
|
20
22
|
schemaHash: z.string().optional(),
|
|
21
23
|
/** Local directory name under `kernel/domains/<...>/` — used to auto-resolve the local spec. */
|
|
22
24
|
domainName: z.string().optional(),
|
|
25
|
+
/**
|
|
26
|
+
* Optional presentation metadata declared on the domain (`defineDomain`):
|
|
27
|
+
* `logo` (inline SVG / `data:` URL), `entrypoint` (a view slug — the domain's
|
|
28
|
+
* entry surface), and the domain's declared `roles` (registered by a workspace
|
|
29
|
+
* at app install). Served verbatim so a host UI can brand a freshly-installed
|
|
30
|
+
* domain.
|
|
31
|
+
*/
|
|
32
|
+
manifest: z
|
|
33
|
+
.object({
|
|
34
|
+
logo: z.string().optional(),
|
|
35
|
+
entrypoint: z.string().optional(),
|
|
36
|
+
roles: z
|
|
37
|
+
.array(
|
|
38
|
+
z.object({
|
|
39
|
+
slug: z.string().min(1),
|
|
40
|
+
name: z.string().optional(),
|
|
41
|
+
description: z.string().optional(),
|
|
42
|
+
default: z.boolean().optional(),
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
.optional(),
|
|
46
|
+
})
|
|
47
|
+
.optional(),
|
|
23
48
|
})
|
|
24
49
|
|
|
25
50
|
export type Meta = z.infer<typeof MetaSchema>
|