@astrale-os/sdk 0.6.0-beta.35 → 0.6.0-beta.36
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/README.md +10 -0
- package/dist/execution/cache/cache.d.ts +17 -0
- package/dist/execution/cache/cache.js +11 -0
- package/dist/execution/cache/index.d.ts +2 -0
- package/dist/execution/cache/index.js +2 -0
- package/dist/execution/cache/memory.d.ts +13 -0
- package/dist/execution/cache/memory.js +48 -0
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.js +1 -0
- package/dist/execution/invocation/admission/age.d.ts +57 -0
- package/dist/execution/invocation/admission/age.js +208 -0
- package/dist/execution/invocation/admission/authentication.d.ts +2 -0
- package/dist/execution/invocation/admission/authentication.js +6 -2
- package/dist/execution/invocation/admission/index.d.ts +3 -2
- package/dist/execution/invocation/admission/index.js +3 -2
- package/dist/execution/invocation/admission/target.d.ts +11 -0
- package/dist/execution/invocation/admission/target.js +22 -3
- package/dist/execution/invocation/handler.js +8 -2
- package/dist/execution/invocation/invocation.d.ts +6 -0
- package/dist/execution/invocation/invocation.js +92 -17
- package/dist/execution/serve.d.ts +8 -0
- package/dist/execution/serve.js +7 -0
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/service/service.d.ts +6 -0
- package/dist/platform/schema/index.d.ts +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -73,6 +73,16 @@ on the Kernel's workflow engine: a call returns its `K.Execution` at once, and t
|
|
|
73
73
|
restarts with journaled Steps, durable timers and signals. Serve it with `serve({ durable })`. See
|
|
74
74
|
[the durable Workflow guide](docs/durable-workflows.md).
|
|
75
75
|
|
|
76
|
+
## Admission age
|
|
77
|
+
|
|
78
|
+
A callable declared with `admission: { maxAge: <seconds> }` (1 to 300) lets its deployment reuse an
|
|
79
|
+
admission the source Kernel granted, for the same Kernel, caller, target and Policy-read input fields,
|
|
80
|
+
while it is younger than that age: such a call skips its `Function.admit` round trip. Revocation then
|
|
81
|
+
reaches it within that age. Credential verification, the release pin and input validation still run
|
|
82
|
+
on every call. Admissions are kept in the deployment cache, `serve({ cache })`: a memory cache by
|
|
83
|
+
default, the Cache API on Cloudflare, and none with `cache: false`. See
|
|
84
|
+
[the admission age guide](docs/admission-age.md).
|
|
85
|
+
|
|
76
86
|
## Node Class icons
|
|
77
87
|
|
|
78
88
|
Every Node Class authored through the SDK must explicitly declare a self-contained SVG icon.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disposable entries one deployment keeps across its requests, and across its isolates where the
|
|
3
|
+
* platform shares them. An entry may vanish at any time and never outlives its expiry. A reader
|
|
4
|
+
* treats a failed or late read as a miss, so nothing that must persist or be coordinated belongs
|
|
5
|
+
* here: every consumer stays correct when the cache keeps nothing.
|
|
6
|
+
*/
|
|
7
|
+
export interface DeploymentCache {
|
|
8
|
+
/** The value kept under `key`, or undefined when it expired, vanished or was never kept. */
|
|
9
|
+
get(key: string): Promise<string | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Keep `value` under `key` until `expiresAt` (Unix milliseconds) at the latest. The cache may
|
|
12
|
+
* drop it sooner, or not keep it at all.
|
|
13
|
+
*/
|
|
14
|
+
put(key: string, value: string, expiresAt: number): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/** Capture a caller-supplied cache, receiver-bound, after checking its shape. */
|
|
17
|
+
export declare function deploymentCache(input: DeploymentCache): DeploymentCache;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Capture a caller-supplied cache, receiver-bound, after checking its shape. */
|
|
2
|
+
export function deploymentCache(input) {
|
|
3
|
+
if (input === null ||
|
|
4
|
+
typeof input !== 'object' ||
|
|
5
|
+
Array.isArray(input) ||
|
|
6
|
+
typeof input.get !== 'function' ||
|
|
7
|
+
typeof input.put !== 'function') {
|
|
8
|
+
throw new TypeError('Deployment cache is invalid.');
|
|
9
|
+
}
|
|
10
|
+
return Object.freeze({ get: input.get.bind(input), put: input.put.bind(input) });
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DeploymentCache } from './cache.js';
|
|
2
|
+
/** Entries one memory cache keeps by default before it evicts the least recently used. */
|
|
3
|
+
export declare const DEFAULT_MEMORY_CACHE_ENTRIES = 1024;
|
|
4
|
+
export interface MemoryCacheOptions {
|
|
5
|
+
/** Most entries kept at once; the least recently used is evicted first. */
|
|
6
|
+
readonly maximumEntries?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A deployment cache held in this process or isolate: bounded, least recently used first out, and
|
|
10
|
+
* an entry is gone at its expiry. It keeps values only, never a pending operation, so no request
|
|
11
|
+
* waits on another's I/O.
|
|
12
|
+
*/
|
|
13
|
+
export declare function memoryCache(options?: MemoryCacheOptions, now?: () => number): DeploymentCache;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Entries one memory cache keeps by default before it evicts the least recently used. */
|
|
2
|
+
export const DEFAULT_MEMORY_CACHE_ENTRIES = 1_024;
|
|
3
|
+
/**
|
|
4
|
+
* A deployment cache held in this process or isolate: bounded, least recently used first out, and
|
|
5
|
+
* an entry is gone at its expiry. It keeps values only, never a pending operation, so no request
|
|
6
|
+
* waits on another's I/O.
|
|
7
|
+
*/
|
|
8
|
+
export function memoryCache(options = {}, now = Date.now) {
|
|
9
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options))
|
|
10
|
+
invalid();
|
|
11
|
+
const maximum = options.maximumEntries ?? DEFAULT_MEMORY_CACHE_ENTRIES;
|
|
12
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) {
|
|
13
|
+
throw new TypeError('Memory cache maximumEntries must be a positive integer.');
|
|
14
|
+
}
|
|
15
|
+
const entries = new Map();
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
async get(key) {
|
|
18
|
+
if (typeof key !== 'string')
|
|
19
|
+
invalid();
|
|
20
|
+
const entry = entries.get(key);
|
|
21
|
+
if (entry === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
entries.delete(key);
|
|
24
|
+
if (entry.expiresAt <= now())
|
|
25
|
+
return undefined;
|
|
26
|
+
entries.set(key, entry);
|
|
27
|
+
return entry.value;
|
|
28
|
+
},
|
|
29
|
+
async put(key, value, expiresAt) {
|
|
30
|
+
if (typeof key !== 'string' || typeof value !== 'string' || !Number.isFinite(expiresAt)) {
|
|
31
|
+
invalid();
|
|
32
|
+
}
|
|
33
|
+
entries.delete(key);
|
|
34
|
+
if (expiresAt <= now())
|
|
35
|
+
return;
|
|
36
|
+
while (entries.size >= maximum) {
|
|
37
|
+
const oldest = entries.keys().next().value;
|
|
38
|
+
if (oldest === undefined)
|
|
39
|
+
break;
|
|
40
|
+
entries.delete(oldest);
|
|
41
|
+
}
|
|
42
|
+
entries.set(key, Object.freeze({ value, expiresAt }));
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function invalid() {
|
|
47
|
+
throw new TypeError('Memory cache input is invalid.');
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { serve } from './serve.js';
|
|
2
2
|
export type { ExecutionContext, ServedDomain, ServeInput } from './serve.js';
|
|
3
|
+
export { DEFAULT_MEMORY_CACHE_ENTRIES, memoryCache, type DeploymentCache, type MemoryCacheOptions, } from './cache/index.js';
|
|
3
4
|
export { createIdentity, maximumExchangeTtl, type ExchangeOptions, type Identity, type IdentityInput, } from './identity/index.js';
|
|
4
5
|
export type { DurableEngine, DurableServing, DurableServingProvider } from './workflows/index.js';
|
|
5
6
|
export { isCanonicalDurableRequest } from './workflows/route.js';
|
package/dist/execution/index.js
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type * as invocation from '@astrale-os/kernel-protocol/invocation';
|
|
2
|
+
import type { RuntimeRegistry } from '../../../application/runtime/index.js';
|
|
3
|
+
import type { DeploymentCache } from '../../cache/index.js';
|
|
4
|
+
import type { Admission, InvocationTarget } from './target.js';
|
|
5
|
+
/**
|
|
6
|
+
* How long reading a kept admission may take before the call asks its Kernel instead. Reads of a
|
|
7
|
+
* shared platform cache answer in single-digit milliseconds; `Function.admit` costs tens.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ADMISSION_RECALL_TIMEOUT_MS = 30;
|
|
10
|
+
/** The callables a call can be admitted to, when one of them declares an admission age. */
|
|
11
|
+
export interface AdmissionScope {
|
|
12
|
+
/** Keys of the implementations the call target names: an admission to any other is not kept. */
|
|
13
|
+
readonly callables: ReadonlySet<string>;
|
|
14
|
+
/** The input fields their Policies read, the only part of the input an admission depends on. */
|
|
15
|
+
readonly fields: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whose admission one call asks for: the source Kernel, the release this deployment serves, and
|
|
19
|
+
* the caller its credential proved, verified here before any kept admission is read.
|
|
20
|
+
*/
|
|
21
|
+
export interface AdmissionPartition {
|
|
22
|
+
readonly kernel: string;
|
|
23
|
+
readonly release: string;
|
|
24
|
+
/** Absent for a call that carries no credential. */
|
|
25
|
+
readonly caller?: {
|
|
26
|
+
readonly principal: string;
|
|
27
|
+
readonly identity: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The scope of a call whose target names, before the graph is read, implementations of which one
|
|
32
|
+
* declares an admission age. Undefined otherwise: a target that names no local implementation (a
|
|
33
|
+
* callable Node id, a path ending in a traversal), only callables without an age, or a durable
|
|
34
|
+
* Workflow, which runs only as a Kernel Execution. The Path language dispatches exactly, so a named
|
|
35
|
+
* target selects one declaration; the fields are still the union over every implementation named.
|
|
36
|
+
*/
|
|
37
|
+
export declare function admissionScope(registry: RuntimeRegistry, call: invocation.Call): AdmissionScope | undefined;
|
|
38
|
+
/** Whether any implementation of this registry declares an admission age. */
|
|
39
|
+
export declare function declaresAdmissionAge(registry: RuntimeRegistry): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The cache key of one call's admission: a SHA-256 over the source Kernel, the served release, the
|
|
42
|
+
* verified principal and caller, the canonical call target (receiver included) and the input
|
|
43
|
+
* projected on the fields its Policy reads. A missing field differs from `null`, object keys are
|
|
44
|
+
* sorted at every depth, and no other part of the input enters the key.
|
|
45
|
+
*/
|
|
46
|
+
export declare function admissionKey(scope: AdmissionScope, partition: AdmissionPartition, call: invocation.Call): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* The target of an admission this deployment kept under `key`, while it is younger than the age it
|
|
49
|
+
* carried. A missing, expired, malformed, out-of-scope, failed or late read is a miss.
|
|
50
|
+
*/
|
|
51
|
+
export declare function recallAdmission(cache: DeploymentCache, key: string, scope: AdmissionScope): Promise<InvocationTarget | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* Keep a successful admission that carries an age, until `sentAt` plus that age, `sentAt` being when
|
|
54
|
+
* `Function.admit` was asked. Written in the background: the call never waits on the cache, and a
|
|
55
|
+
* cache that fails to keep it only costs a later call its admission round trip.
|
|
56
|
+
*/
|
|
57
|
+
export declare function keepAdmission(cache: DeploymentCache, key: string, scope: AdmissionScope, admitted: Admission, sentAt: number, background: (task: () => PromiseLike<unknown>) => void): void;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { callableAddress, Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { admissionInputFields, MAXIMUM_ADMISSION_AGE_SECONDS, } from '@astrale-os/kernel-dsl/v1/language';
|
|
3
|
+
import { Path } from '../../../platform/graph/path/index.js';
|
|
4
|
+
import { target as acceptTarget } from './target.js';
|
|
5
|
+
/**
|
|
6
|
+
* How long reading a kept admission may take before the call asks its Kernel instead. Reads of a
|
|
7
|
+
* shared platform cache answer in single-digit milliseconds; `Function.admit` costs tens.
|
|
8
|
+
*/
|
|
9
|
+
export const ADMISSION_RECALL_TIMEOUT_MS = 30;
|
|
10
|
+
/** Version of the key and entry encodings below; a change makes every kept admission a miss. */
|
|
11
|
+
const ENCODING = 'astrale.admission/1';
|
|
12
|
+
const ENTRY_FIELDS = new Set(['v', 'callable', 'receiver', 'expiresAt']);
|
|
13
|
+
/**
|
|
14
|
+
* The scope of a call whose target names, before the graph is read, implementations of which one
|
|
15
|
+
* declares an admission age. Undefined otherwise: a target that names no local implementation (a
|
|
16
|
+
* callable Node id, a path ending in a traversal), only callables without an age, or a durable
|
|
17
|
+
* Workflow, which runs only as a Kernel Execution. The Path language dispatches exactly, so a named
|
|
18
|
+
* target selects one declaration; the fields are still the union over every implementation named.
|
|
19
|
+
*/
|
|
20
|
+
export function admissionScope(registry, call) {
|
|
21
|
+
const names = declarationsNamedBy(call.target);
|
|
22
|
+
const named = registry.implementations.filter(({ callable }) => names(callable.key));
|
|
23
|
+
if (named.every(({ callable }) => callable.admission === undefined))
|
|
24
|
+
return undefined;
|
|
25
|
+
if (named.some((implementation) => implementation.kind === 'workflow' && implementation.durable)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const fields = new Set();
|
|
29
|
+
for (const { callable } of named) {
|
|
30
|
+
for (const field of admissionInputFields(callable.policy))
|
|
31
|
+
fields.add(field);
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
callables: new Set(named.map(({ callable }) => callable.key)),
|
|
35
|
+
fields: Object.freeze([...fields].sort()),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Whether any implementation of this registry declares an admission age. */
|
|
39
|
+
export function declaresAdmissionAge(registry) {
|
|
40
|
+
return registry.implementations.some(({ callable }) => callable.admission !== undefined);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The cache key of one call's admission: a SHA-256 over the source Kernel, the served release, the
|
|
44
|
+
* verified principal and caller, the canonical call target (receiver included) and the input
|
|
45
|
+
* projected on the fields its Policy reads. A missing field differs from `null`, object keys are
|
|
46
|
+
* sorted at every depth, and no other part of the input enters the key.
|
|
47
|
+
*/
|
|
48
|
+
export async function admissionKey(scope, partition, call) {
|
|
49
|
+
const encoded = JSON.stringify([
|
|
50
|
+
ENCODING,
|
|
51
|
+
partition.kernel,
|
|
52
|
+
partition.release,
|
|
53
|
+
partition.caller?.principal ?? null,
|
|
54
|
+
partition.caller?.identity ?? null,
|
|
55
|
+
canonical(call.target),
|
|
56
|
+
projection(call.input, scope.fields),
|
|
57
|
+
]);
|
|
58
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(encoded));
|
|
59
|
+
return `admission/${hex(new Uint8Array(digest))}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The target of an admission this deployment kept under `key`, while it is younger than the age it
|
|
63
|
+
* carried. A missing, expired, malformed, out-of-scope, failed or late read is a miss.
|
|
64
|
+
*/
|
|
65
|
+
export async function recallAdmission(cache, key, scope) {
|
|
66
|
+
const value = await boundedRead(cache, key);
|
|
67
|
+
return value === undefined ? undefined : keptTarget(value, scope, Date.now());
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Keep a successful admission that carries an age, until `sentAt` plus that age, `sentAt` being when
|
|
71
|
+
* `Function.admit` was asked. Written in the background: the call never waits on the cache, and a
|
|
72
|
+
* cache that fails to keep it only costs a later call its admission round trip.
|
|
73
|
+
*/
|
|
74
|
+
export function keepAdmission(cache, key, scope, admitted, sentAt, background) {
|
|
75
|
+
if (admitted.maxAge === undefined || !scope.callables.has(admitted.target.callable))
|
|
76
|
+
return;
|
|
77
|
+
const expiresAt = sentAt + admitted.maxAge * 1000;
|
|
78
|
+
const value = JSON.stringify({
|
|
79
|
+
v: 1,
|
|
80
|
+
callable: admitted.target.callable,
|
|
81
|
+
...(admitted.target.receiver === undefined ? {} : { receiver: admitted.target.receiver }),
|
|
82
|
+
expiresAt,
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
background(async () => {
|
|
86
|
+
try {
|
|
87
|
+
await cache.put(key, value, expiresAt);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// A cache that cannot keep this admission leaves the next call to its Kernel.
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// A Server that no longer accepts background work keeps nothing; the call is unaffected.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function keptTarget(value, scope, now) {
|
|
99
|
+
let entry;
|
|
100
|
+
try {
|
|
101
|
+
entry = JSON.parse(value);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
if (!isRecord(entry) || Reflect.ownKeys(entry).some((key) => !ENTRY_FIELDS.has(key))) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const { v, callable, receiver, expiresAt } = entry;
|
|
110
|
+
if (v !== 1 ||
|
|
111
|
+
typeof callable !== 'string' ||
|
|
112
|
+
!scope.callables.has(callable) ||
|
|
113
|
+
typeof expiresAt !== 'number' ||
|
|
114
|
+
!Number.isSafeInteger(expiresAt) ||
|
|
115
|
+
expiresAt <= now ||
|
|
116
|
+
expiresAt > now + MAXIMUM_ADMISSION_AGE_SECONDS * 1000) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
return acceptTarget({ callable, ...(receiver === undefined ? {} : { receiver }) });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** One read bounded in time; a cache error is a miss. No pending read is shared across calls. */
|
|
127
|
+
async function boundedRead(cache, key) {
|
|
128
|
+
let timer;
|
|
129
|
+
const read = Promise.resolve()
|
|
130
|
+
.then(() => cache.get(key))
|
|
131
|
+
.then((value) => (typeof value === 'string' ? value : undefined), () => undefined);
|
|
132
|
+
const late = new Promise((resolve) => {
|
|
133
|
+
timer = setTimeout(() => resolve(undefined), ADMISSION_RECALL_TIMEOUT_MS);
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
return await Promise.race([read, late]);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (timer !== undefined)
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** The declarations `target` names before the graph is read, as a test on a callable key. */
|
|
144
|
+
function declarationsNamedBy(target) {
|
|
145
|
+
if (typeof target !== 'string')
|
|
146
|
+
return (key) => target.callable === key;
|
|
147
|
+
let path;
|
|
148
|
+
try {
|
|
149
|
+
path = Path.parse(target);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return () => false;
|
|
153
|
+
}
|
|
154
|
+
const { anchor, steps } = path.ast;
|
|
155
|
+
const last = steps.at(-1);
|
|
156
|
+
const first = steps[0];
|
|
157
|
+
return (key) => {
|
|
158
|
+
const coordinate = callableAddress(key);
|
|
159
|
+
if (coordinate.kind === 'method') {
|
|
160
|
+
if (last?.kind !== 'method' || last.name !== coordinate.name)
|
|
161
|
+
return false;
|
|
162
|
+
if (last.dispatch === 'instance')
|
|
163
|
+
return last.class === Key.of(coordinate.owner);
|
|
164
|
+
return (anchor.kind === 'domain' &&
|
|
165
|
+
anchor.origin === coordinate.owner.origin &&
|
|
166
|
+
steps.length === 2 &&
|
|
167
|
+
first?.kind === 'projection' &&
|
|
168
|
+
first.projection.kind === 'class' &&
|
|
169
|
+
first.projection.name === coordinate.owner.name);
|
|
170
|
+
}
|
|
171
|
+
return (anchor.kind === 'domain' &&
|
|
172
|
+
anchor.origin === coordinate.origin &&
|
|
173
|
+
steps.length === 1 &&
|
|
174
|
+
first?.kind === 'projection' &&
|
|
175
|
+
first.projection.kind === 'function' &&
|
|
176
|
+
first.projection.name === coordinate.name);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** The input on the fields a Policy reads: each present field with its value, or its name alone. */
|
|
180
|
+
function projection(input, fields) {
|
|
181
|
+
if (fields.length === 0)
|
|
182
|
+
return [];
|
|
183
|
+
if (!isRecord(input))
|
|
184
|
+
return ['other'];
|
|
185
|
+
return [
|
|
186
|
+
'object',
|
|
187
|
+
...fields.map((field) => Object.hasOwn(input, field) ? [field, canonical(input[field])] : [field]),
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
/** Canonical JSON text of a portable value: object keys sorted at every depth. */
|
|
191
|
+
function canonical(value) {
|
|
192
|
+
if (Array.isArray(value))
|
|
193
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
194
|
+
if (value !== null && typeof value === 'object') {
|
|
195
|
+
const object = value;
|
|
196
|
+
return `{${Object.keys(object)
|
|
197
|
+
.sort()
|
|
198
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(object[key])}`)
|
|
199
|
+
.join(',')}}`;
|
|
200
|
+
}
|
|
201
|
+
return JSON.stringify(value);
|
|
202
|
+
}
|
|
203
|
+
function isRecord(input) {
|
|
204
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input);
|
|
205
|
+
}
|
|
206
|
+
function hex(bytes) {
|
|
207
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
208
|
+
}
|
|
@@ -13,3 +13,5 @@ export type InvocationAuthentication = AnonymousInvocation | AuthenticatedInvoca
|
|
|
13
13
|
*/
|
|
14
14
|
export type InvocationSessions = InvocationAuthentication['graph'];
|
|
15
15
|
export declare function authentication(callable: ResolvedCallable, request: HandlerRequest, domain: Domain, authority: InvocationAuthority): Promise<InvocationAuthentication>;
|
|
16
|
+
/** Verify the credential a call carries and admit the caller it proves, whatever the callable. */
|
|
17
|
+
export declare function credentialAuthentication(request: HandlerRequest, domain: Domain, authority: InvocationAuthority): Promise<AuthenticatedInvocation>;
|
|
@@ -5,10 +5,14 @@ import { protocolError } from '../result/index.js';
|
|
|
5
5
|
import { requireVia } from './authority.js';
|
|
6
6
|
const ANONYMOUS = Object.freeze({ kind: 'anonymous' });
|
|
7
7
|
export async function authentication(callable, request, domain, authority) {
|
|
8
|
-
|
|
9
|
-
if (callable.auth === 'anonymous' && credential === undefined) {
|
|
8
|
+
if (callable.auth === 'anonymous' && request.credential === undefined) {
|
|
10
9
|
return anonymous(await authority.self({ via: requireVia(request), domain, context: request.context }));
|
|
11
10
|
}
|
|
11
|
+
return credentialAuthentication(request, domain, authority);
|
|
12
|
+
}
|
|
13
|
+
/** Verify the credential a call carries and admit the caller it proves, whatever the callable. */
|
|
14
|
+
export async function credentialAuthentication(request, domain, authority) {
|
|
15
|
+
const credential = request.credential;
|
|
12
16
|
if (credential === undefined) {
|
|
13
17
|
throw protocolError('AUTH_MISSING', 'Authentication is required.');
|
|
14
18
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { ADMISSION_RECALL_TIMEOUT_MS, admissionKey, admissionScope, declaresAdmissionAge, keepAdmission, recallAdmission, type AdmissionPartition, type AdmissionScope, } from './age.js';
|
|
1
2
|
export { authority, requireVia } from './authority.js';
|
|
2
3
|
export type { AuthenticatedInvocation, InvocationAdmissionInput, InvocationAuthenticationInput, InvocationAuthority, InvocationSelfInput, SelfInvocation, } from './authority.js';
|
|
3
|
-
export { authentication, type AnonymousInvocation, type InvocationAuthentication, type InvocationSessions, } from './authentication.js';
|
|
4
|
+
export { authentication, credentialAuthentication, type AnonymousInvocation, type InvocationAuthentication, type InvocationSessions, } from './authentication.js';
|
|
4
5
|
export { input } from './input.js';
|
|
5
|
-
export { target, type InvocationTarget } from './target.js';
|
|
6
|
+
export { admission, target, type Admission, type InvocationTarget } from './target.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
export { ADMISSION_RECALL_TIMEOUT_MS, admissionKey, admissionScope, declaresAdmissionAge, keepAdmission, recallAdmission, } from './age.js';
|
|
1
2
|
export { authority, requireVia } from './authority.js';
|
|
2
|
-
export { authentication, } from './authentication.js';
|
|
3
|
+
export { authentication, credentialAuthentication, } from './authentication.js';
|
|
3
4
|
export { input } from './input.js';
|
|
4
|
-
export { target } from './target.js';
|
|
5
|
+
export { admission, target } from './target.js';
|
|
@@ -4,5 +4,16 @@ export interface InvocationTarget {
|
|
|
4
4
|
readonly callable: SemanticKey.Callable;
|
|
5
5
|
readonly receiver?: NodePath;
|
|
6
6
|
}
|
|
7
|
+
/** The source Kernel's answer to `Function.admit`. */
|
|
8
|
+
export interface Admission {
|
|
9
|
+
readonly target: InvocationTarget;
|
|
10
|
+
/**
|
|
11
|
+
* How long, in seconds from when it was asked, this deployment may reuse the admission: the
|
|
12
|
+
* callable's declared admission age bounded by the Kernel cap. Absent: this call only.
|
|
13
|
+
*/
|
|
14
|
+
readonly maxAge?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Admit the `Function.admit` answer: the selected target and the admission age it carries. */
|
|
17
|
+
export declare function admission(input: unknown): Admission;
|
|
7
18
|
/** Admit the portable target selected by the execution Identity owner. */
|
|
8
19
|
export declare function target(input: unknown): InvocationTarget;
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { callableAddress, Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { isAdmissionMaxAge } from '@astrale-os/kernel-dsl/v1/language';
|
|
2
3
|
import { NodePath as acceptNodePath } from '../../../platform/graph/path/index.js';
|
|
3
4
|
import { InvocationDefect } from '../result/index.js';
|
|
5
|
+
/** Admit the `Function.admit` answer: the selected target and the admission age it carries. */
|
|
6
|
+
export function admission(input) {
|
|
7
|
+
const value = record(input);
|
|
8
|
+
if (Reflect.ownKeys(value).some((key) => key !== 'callable' && key !== 'receiver' && key !== 'maxAge')) {
|
|
9
|
+
invalid();
|
|
10
|
+
}
|
|
11
|
+
const { maxAge } = value;
|
|
12
|
+
if (maxAge !== undefined && !isAdmissionMaxAge(maxAge))
|
|
13
|
+
invalid();
|
|
14
|
+
const selected = target({
|
|
15
|
+
callable: value.callable,
|
|
16
|
+
...(value.receiver === undefined ? {} : { receiver: value.receiver }),
|
|
17
|
+
});
|
|
18
|
+
return Object.freeze({ target: selected, ...(maxAge === undefined ? {} : { maxAge }) });
|
|
19
|
+
}
|
|
4
20
|
/** Admit the portable target selected by the execution Identity owner. */
|
|
5
21
|
export function target(input) {
|
|
6
|
-
|
|
7
|
-
invalid();
|
|
8
|
-
const value = input;
|
|
22
|
+
const value = record(input);
|
|
9
23
|
if (!Object.hasOwn(value, 'callable') ||
|
|
10
24
|
Reflect.ownKeys(value).some((key) => key !== 'callable' && key !== 'receiver') ||
|
|
11
25
|
typeof value.callable !== 'string') {
|
|
@@ -25,6 +39,11 @@ export function target(input) {
|
|
|
25
39
|
}
|
|
26
40
|
return Object.freeze({ callable, ...(receiver === undefined ? {} : { receiver }) });
|
|
27
41
|
}
|
|
42
|
+
function record(input) {
|
|
43
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
44
|
+
invalid();
|
|
45
|
+
return input;
|
|
46
|
+
}
|
|
28
47
|
function invalid() {
|
|
29
48
|
throw new InvocationDefect('INVOCATION_TARGET_INVALID', 'Invocation target is invalid.');
|
|
30
49
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInlineWorkflowRunner } from '../../application/workflow/runner/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { deploymentCache } from '../cache/index.js';
|
|
3
|
+
import { authority, declaresAdmissionAge } from './admission/index.js';
|
|
3
4
|
import { invoke } from './invocation.js';
|
|
4
5
|
import { projectError } from './result/index.js';
|
|
5
6
|
/** Compose one ready semantic Handler below Kernel Server transport ownership. */
|
|
@@ -8,7 +9,7 @@ export function createHandler(input) {
|
|
|
8
9
|
invalid();
|
|
9
10
|
if (!Object.hasOwn(input, 'runtime') ||
|
|
10
11
|
!Object.hasOwn(input, 'authority') ||
|
|
11
|
-
Reflect.ownKeys(input).some((key) => key !== 'runtime' && key !== 'authority' && key !== 'workflows')) {
|
|
12
|
+
Reflect.ownKeys(input).some((key) => key !== 'runtime' && key !== 'authority' && key !== 'workflows' && key !== 'cache')) {
|
|
12
13
|
invalid();
|
|
13
14
|
}
|
|
14
15
|
const capturedAuthority = authority(input.authority);
|
|
@@ -17,6 +18,10 @@ export function createHandler(input) {
|
|
|
17
18
|
invalid();
|
|
18
19
|
}
|
|
19
20
|
const capturedWorkflows = Object.freeze({ run: workflows.run.bind(workflows) });
|
|
21
|
+
// A Domain whose callables declare no admission age never reads its cache.
|
|
22
|
+
const cache = input.cache === undefined || !declaresAdmissionAge(input.runtime.loaded.registry)
|
|
23
|
+
? undefined
|
|
24
|
+
: deploymentCache(input.cache);
|
|
20
25
|
return Object.freeze({
|
|
21
26
|
async dispatch(request) {
|
|
22
27
|
try {
|
|
@@ -24,6 +29,7 @@ export function createHandler(input) {
|
|
|
24
29
|
runtime: input.runtime,
|
|
25
30
|
authority: capturedAuthority,
|
|
26
31
|
workflows: capturedWorkflows,
|
|
32
|
+
...(cache === undefined ? {} : { cache }),
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
catch (cause) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { HandlerRequest, HandlerResult } from '@astrale-os/kernel-server';
|
|
2
2
|
import type { WorkflowRunner } from '../../application/workflow/runner/index.js';
|
|
3
|
+
import type { DeploymentCache } from '../cache/index.js';
|
|
3
4
|
import type { InitializedRuntime } from '../runtime/index.js';
|
|
4
5
|
import type { InvocationAuthority } from './admission/index.js';
|
|
5
6
|
export interface InvocationInput {
|
|
6
7
|
readonly runtime: InitializedRuntime;
|
|
7
8
|
readonly authority: InvocationAuthority;
|
|
8
9
|
readonly workflows: WorkflowRunner;
|
|
10
|
+
/**
|
|
11
|
+
* Where admissions of callables that declare an admission age are kept for the next calls. Absent:
|
|
12
|
+
* every call is admitted by its source Kernel.
|
|
13
|
+
*/
|
|
14
|
+
readonly cache?: DeploymentCache;
|
|
9
15
|
}
|
|
10
16
|
export declare function invoke(request: HandlerRequest, inputConfig: InvocationInput): Promise<HandlerResult>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
2
|
import * as release from '@astrale-os/kernel-protocol/release';
|
|
3
|
-
import { authentication, input, requireVia,
|
|
3
|
+
import { admission as acceptAdmission, admissionKey, admissionScope, authentication, credentialAuthentication, input, keepAdmission, recallAdmission, requireVia, } from './admission/index.js';
|
|
4
4
|
import { action, context, select, workflow } from './dispatch/index.js';
|
|
5
5
|
import { active, settle } from './lifecycle/index.js';
|
|
6
6
|
import { project, protocolError } from './result/index.js';
|
|
@@ -15,27 +15,102 @@ export async function invoke(request, inputConfig) {
|
|
|
15
15
|
if (request.credential !== undefined && typeof request.credential !== 'string') {
|
|
16
16
|
throw protocolError('AUTH_INVALID', 'Credential is invalid.');
|
|
17
17
|
}
|
|
18
|
-
const
|
|
18
|
+
const admission = {
|
|
19
19
|
call: request.call,
|
|
20
20
|
...(request.credential === undefined ? {} : { credential: request.credential }),
|
|
21
21
|
via: requireVia(request),
|
|
22
22
|
domain: inputConfig.runtime.loaded.domain,
|
|
23
23
|
context: request.context,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
};
|
|
25
|
+
const { cache } = inputConfig;
|
|
26
|
+
const scope = cache === undefined
|
|
27
|
+
? undefined
|
|
28
|
+
: admissionScope(inputConfig.runtime.loaded.registry, request.call);
|
|
29
|
+
if (cache === undefined || scope === undefined) {
|
|
30
|
+
return dispatchAdmitted(request, inputConfig, await admittedTarget(inputConfig, admission));
|
|
31
|
+
}
|
|
32
|
+
// The caller is verified before any kept admission is read: an unproven credential never learns
|
|
33
|
+
// whether another caller's admission is kept. A credential this verification refuses is decided
|
|
34
|
+
// as without a cache, the Kernel's admission first.
|
|
35
|
+
let established;
|
|
36
|
+
if (request.credential !== undefined) {
|
|
37
|
+
try {
|
|
38
|
+
established = await credentialAuthentication(request, inputConfig.runtime.loaded.domain, inputConfig.authority);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return dispatchAdmitted(request, inputConfig, await admittedTarget(inputConfig, admission));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let admitted;
|
|
45
|
+
try {
|
|
46
|
+
admitted = await recallOrAdmit(cache, scope, admission, inputConfig, established);
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
established?.close();
|
|
50
|
+
throw cause;
|
|
34
51
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
return dispatchAdmitted(request, inputConfig, admitted, established);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The admission of a call whose callable declares an admission age: one this deployment kept while
|
|
56
|
+
* it is younger than the age it carried, else the source Kernel's, kept for the calls that follow.
|
|
57
|
+
*/
|
|
58
|
+
async function recallOrAdmit(cache, scope, admission, inputConfig, established) {
|
|
59
|
+
let key;
|
|
60
|
+
try {
|
|
61
|
+
key = await admissionKey(scope, {
|
|
62
|
+
kernel: admission.via.issuer,
|
|
63
|
+
release: inputConfig.runtime.loaded.release.document.digest,
|
|
64
|
+
...(established === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: {
|
|
67
|
+
caller: {
|
|
68
|
+
principal: established.caller.principal,
|
|
69
|
+
identity: established.caller.identity,
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
}, admission.call);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Without a key nothing is kept or read: the Kernel admits this call.
|
|
76
|
+
return admittedTarget(inputConfig, admission);
|
|
77
|
+
}
|
|
78
|
+
const kept = await recallAdmission(cache, key, scope);
|
|
79
|
+
if (kept !== undefined)
|
|
80
|
+
return kept;
|
|
81
|
+
const sentAt = Date.now();
|
|
82
|
+
const admitted = await admit(inputConfig.authority, admission);
|
|
83
|
+
if (admitted === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
keepAdmission(cache, key, scope, admitted, sentAt, admission.context.background);
|
|
86
|
+
return admitted.target;
|
|
87
|
+
}
|
|
88
|
+
/** The target the source Kernel admits, or undefined when it no longer places the call here. */
|
|
89
|
+
async function admittedTarget(inputConfig, admission) {
|
|
90
|
+
return (await admit(inputConfig.authority, admission))?.target;
|
|
91
|
+
}
|
|
92
|
+
/** Run the admitted target: everything after admission, identical however it was admitted. */
|
|
93
|
+
async function dispatchAdmitted(request, inputConfig, admitted, established) {
|
|
94
|
+
let admittedAuthentication = established;
|
|
95
|
+
let close = established === undefined ? undefined : once(established.close);
|
|
38
96
|
try {
|
|
97
|
+
if (admitted === undefined) {
|
|
98
|
+
close?.();
|
|
99
|
+
return MISS;
|
|
100
|
+
}
|
|
101
|
+
const selected = select(inputConfig.runtime.loaded.registry, admitted);
|
|
102
|
+
if (selected === undefined) {
|
|
103
|
+
close?.();
|
|
104
|
+
return MISS;
|
|
105
|
+
}
|
|
106
|
+
if (selected.implementation.kind === 'workflow' && selected.implementation.durable) {
|
|
107
|
+
// The Kernel records a durable call as an Execution and runs it on the engine; running it
|
|
108
|
+
// inline here would silently drop durability.
|
|
109
|
+
throw protocolError('UNSUPPORTED', 'A durable Workflow runs only as a Kernel Execution.');
|
|
110
|
+
}
|
|
111
|
+
input(selected.implementation.callable, request.call.input, request.context.binary);
|
|
112
|
+
admittedAuthentication ??= await authentication(selected.implementation.callable, request, inputConfig.runtime.loaded.domain, inputConfig.authority);
|
|
113
|
+
close ??= once(admittedAuthentication.close);
|
|
39
114
|
active(request.context);
|
|
40
115
|
const dispatchContext = context(request, inputConfig.runtime, admittedAuthentication, selected.self);
|
|
41
116
|
const output = selected.implementation.kind === 'action'
|
|
@@ -44,7 +119,7 @@ export async function invoke(request, inputConfig) {
|
|
|
44
119
|
return settle(project(selected.implementation.callable, output), close);
|
|
45
120
|
}
|
|
46
121
|
catch (cause) {
|
|
47
|
-
close();
|
|
122
|
+
close?.();
|
|
48
123
|
throw cause;
|
|
49
124
|
}
|
|
50
125
|
}
|
|
@@ -64,7 +139,7 @@ async function admit(authority, admission) {
|
|
|
64
139
|
return undefined;
|
|
65
140
|
throw cause;
|
|
66
141
|
}
|
|
67
|
-
return
|
|
142
|
+
return acceptAdmission(admitted);
|
|
68
143
|
}
|
|
69
144
|
function refusedAsMiss(cause) {
|
|
70
145
|
return (cause instanceof errors.ProtocolError &&
|
|
@@ -2,6 +2,7 @@ import type { ServerCloseOptions } from '@astrale-os/kernel-server';
|
|
|
2
2
|
import type { release } from '@astrale-os/kernel-server';
|
|
3
3
|
import type { Runtime } from '../application/runtime/index.js';
|
|
4
4
|
import type { WorkflowRunner } from '../application/workflow/runner/index.js';
|
|
5
|
+
import type { DeploymentCache } from './cache/index.js';
|
|
5
6
|
import type { Identity } from './identity/index.js';
|
|
6
7
|
import type { ReleaseLoader } from './service/index.js';
|
|
7
8
|
import type { ServiceServerOptions } from './service/index.js';
|
|
@@ -18,6 +19,13 @@ export interface ServeInput<Environment, RuntimeValue extends Runtime, RuntimeEn
|
|
|
18
19
|
readonly server: ServiceServerOptions;
|
|
19
20
|
readonly delivery?: (environment: Environment) => release.delivery.Provider | undefined;
|
|
20
21
|
readonly workflows?: WorkflowRunner;
|
|
22
|
+
/**
|
|
23
|
+
* The deployment's cache of disposable entries, created once with the Service. It keeps the
|
|
24
|
+
* admissions of callables whose Schema declares an admission age, so that a call within that age
|
|
25
|
+
* skips its `Function.admit` round trip. Defaults to a bounded memory cache of this process or
|
|
26
|
+
* isolate; `false` keeps nothing, and every call is admitted by its source Kernel.
|
|
27
|
+
*/
|
|
28
|
+
readonly cache?: ((environment: Environment) => DeploymentCache) | false;
|
|
21
29
|
/**
|
|
22
30
|
* Serve durable Workflows to the workflow engine; required when the Release declares any.
|
|
23
31
|
* Undefined for an environment that names no engine: engine requests are then refused.
|
package/dist/execution/serve.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoryCache } from './cache/index.js';
|
|
1
2
|
import { createService } from './service/index.js';
|
|
2
3
|
/** Create one provider-neutral Fetch entrypoint around a lazily initialized current Service. */
|
|
3
4
|
export function serve(input) {
|
|
@@ -11,6 +12,9 @@ export function serve(input) {
|
|
|
11
12
|
return service;
|
|
12
13
|
if (typeof input.identity !== 'function')
|
|
13
14
|
invalid();
|
|
15
|
+
if (input.cache !== undefined && input.cache !== false && typeof input.cache !== 'function') {
|
|
16
|
+
invalid();
|
|
17
|
+
}
|
|
14
18
|
const runtimeEnvironment = input.initialize === undefined
|
|
15
19
|
? environment
|
|
16
20
|
: input.initialize(environment);
|
|
@@ -22,6 +26,9 @@ export function serve(input) {
|
|
|
22
26
|
server: input.server,
|
|
23
27
|
...(input.delivery === undefined ? {} : { delivery: input.delivery(environment) }),
|
|
24
28
|
...(input.workflows === undefined ? {} : { workflows: input.workflows }),
|
|
29
|
+
...(input.cache === false
|
|
30
|
+
? {}
|
|
31
|
+
: { cache: input.cache === undefined ? memoryCache() : input.cache(environment) }),
|
|
25
32
|
...(input.durable === undefined
|
|
26
33
|
? {}
|
|
27
34
|
: { durable: (request) => input.durable(environment, request) }),
|
|
@@ -54,6 +54,7 @@ export function createService(input) {
|
|
|
54
54
|
runtime,
|
|
55
55
|
authority: selectedIdentity,
|
|
56
56
|
...(input.workflows === undefined ? {} : { workflows: input.workflows }),
|
|
57
|
+
...(input.cache === undefined ? {} : { cache: input.cache }),
|
|
57
58
|
}));
|
|
58
59
|
running = Object.freeze({ release, runtime, server });
|
|
59
60
|
if (!state.accepting)
|
|
@@ -2,6 +2,7 @@ import type { ApplicationHost, ServerCloseOptions, ServerOptions } from '@astral
|
|
|
2
2
|
import type { release } from '@astrale-os/kernel-server';
|
|
3
3
|
import type { Runtime } from '../../application/runtime/index.js';
|
|
4
4
|
import type { WorkflowRunner } from '../../application/workflow/runner/index.js';
|
|
5
|
+
import type { DeploymentCache } from '../cache/index.js';
|
|
5
6
|
import type { Identity } from '../identity/index.js';
|
|
6
7
|
import type { ServedRelease } from '../runtime/index.js';
|
|
7
8
|
import type { DurableServing, DurableServingProvider } from '../workflows/index.js';
|
|
@@ -18,6 +19,11 @@ export interface ServiceInput<Environment, RuntimeValue extends Runtime = Runtim
|
|
|
18
19
|
*/
|
|
19
20
|
readonly delivery?: release.delivery.Provider;
|
|
20
21
|
readonly workflows?: WorkflowRunner;
|
|
22
|
+
/**
|
|
23
|
+
* Where admissions of callables that declare an admission age are kept for the next calls.
|
|
24
|
+
* Absent: every call is admitted by its source Kernel.
|
|
25
|
+
*/
|
|
26
|
+
readonly cache?: DeploymentCache;
|
|
21
27
|
/**
|
|
22
28
|
* Serve the Release's durable Workflows. A provider is resolved on the first engine request,
|
|
23
29
|
* shared by concurrent requests, and retried after initialization fails.
|
|
@@ -18,7 +18,7 @@ export type { ClassIcon } from './class-icon.js';
|
|
|
18
18
|
export type { StoredPropertyInputOf } from './property-input.js';
|
|
19
19
|
export type { CallablePolicyBuilder, CallablePolicyContext, ClassRuleBuilder, ClassRuleContext, PolicyCheckContext, CallableProjection, AuthoredMethodProjection, AuthoredPropertyProjection, ClassIdentityProjection, ClassOwnerProjection, CoreEdge, CoreEndpoint, CoreNode, DefinedSchema, DirectedEdgeClass, ClassReferenceProjection, ClassProjection, FunctionProjection, EdgeClass, Function, Method, MethodProjection, Lazy, NodeClass, Policy, PolicyExistsContext, PolicyNodeBinder, PolicyPatternContext, Property, PropertyProjection, ResolvedDomainOfInput, View, } from '@astrale-os/kernel-dsl/v1/builder';
|
|
20
20
|
export type { Array, Object, Value } from '@astrale-os/kernel-dsl/value';
|
|
21
|
-
export type { CallableExecution, ClassKind, DirectedEdgeContract, EdgeClassPolicies, NodeClassDefinition, PolicyCheckObject, PolicyObjectRef, PropertyFacet, PropertyImmutability, StateTopology, UndirectedEdgeContract, } from '@astrale-os/kernel-dsl/v1/language';
|
|
21
|
+
export type { CallableAdmission, CallableExecution, ClassKind, DirectedEdgeContract, EdgeClassPolicies, NodeClassDefinition, PolicyCheckObject, PolicyObjectRef, PropertyFacet, PropertyImmutability, StateTopology, UndirectedEdgeContract, } from '@astrale-os/kernel-dsl/v1/language';
|
|
22
22
|
export type { DomainSchema } from '@astrale-os/kernel-dsl/v1/schema';
|
|
23
23
|
/** Kernel-owned Schema values used by authored product Schemas and graph operations. */
|
|
24
24
|
export { AuthorityOperationOrder, AuthorityOperations, AuthorityRelations, KernelRootFunctions, OperationCore, operationOf, } from '@astrale-os/kernel-core/schema';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/sdk",
|
|
3
|
-
"version": "0.6.0-beta.
|
|
3
|
+
"version": "0.6.0-beta.36",
|
|
4
4
|
"description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -257,11 +257,11 @@
|
|
|
257
257
|
"registry": "https://registry.npmjs.org/"
|
|
258
258
|
},
|
|
259
259
|
"dependencies": {
|
|
260
|
-
"@astrale-os/kernel-client": "1.0.0-beta.
|
|
261
|
-
"@astrale-os/kernel-core": "1.0.0-beta.
|
|
262
|
-
"@astrale-os/kernel-dsl": "1.0.0-beta.
|
|
263
|
-
"@astrale-os/kernel-protocol": "1.0.0-beta.
|
|
264
|
-
"@astrale-os/kernel-server": "1.0.0-beta.
|
|
260
|
+
"@astrale-os/kernel-client": "1.0.0-beta.12",
|
|
261
|
+
"@astrale-os/kernel-core": "1.0.0-beta.10",
|
|
262
|
+
"@astrale-os/kernel-dsl": "1.0.0-beta.5",
|
|
263
|
+
"@astrale-os/kernel-protocol": "1.0.0-beta.10",
|
|
264
|
+
"@astrale-os/kernel-server": "1.0.0-beta.10",
|
|
265
265
|
"@lucide/icons": "1.34.0",
|
|
266
266
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
267
267
|
"cross-spawn": "7.0.6",
|