@astrale-os/kernel-client 0.6.0-beta.56 → 0.6.0-beta.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/credential.js +2 -2
- package/dist/client/admission.d.ts +4 -1
- package/dist/client/admission.js +8 -1
- package/dist/client/request.d.ts +3 -2
- package/dist/client/request.js +3 -0
- package/dist/graph/domain/domain.typecheck.js +1 -0
- package/dist/graph/domain/project.js +10 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/session/bound.d.ts +2 -2
- package/dist/session/reference.d.ts +9 -10
- package/dist/session/reference.js +5 -3
- package/dist/session/routing/cache.d.ts +1 -2
- package/dist/session/routing/cache.js +2 -8
- package/dist/session/routing/dispatch.d.ts +3 -3
- package/dist/session/routing/dispatch.js +14 -14
- package/dist/session/routing/index.d.ts +0 -1
- package/dist/session/session.d.ts +1 -1
- package/dist/session/session.js +14 -6
- package/dist/session/session.typecheck.js +64 -2
- package/dist/transport/auto/fallback.d.ts +2 -1
- package/dist/transport/auto/fallback.js +2 -1
- package/dist/transport/http/transport.js +0 -1
- package/package.json +5 -5
- package/dist/session/routing/expectation.d.ts +0 -6
- package/dist/session/routing/expectation.js +0 -1
package/dist/auth/credential.js
CHANGED
|
@@ -8,8 +8,8 @@ export function credentialApi(call) {
|
|
|
8
8
|
return credential(await call(IDENTITY_MINT_PATH, input, options));
|
|
9
9
|
},
|
|
10
10
|
async delegate(principal, input, options) {
|
|
11
|
-
const target = Path.
|
|
12
|
-
return credential(await call(target, input, options));
|
|
11
|
+
const target = Path.staticMethod(identityOwner, identity.delegate.name).raw;
|
|
12
|
+
return credential(await call(target, { ...input, principal }, options));
|
|
13
13
|
},
|
|
14
14
|
});
|
|
15
15
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { invocation as protocol } from '@astrale-os/kernel-protocol';
|
|
2
2
|
import type { Call } from './request.js';
|
|
3
3
|
/** Parent-private Call acceptance shared by Client and Client Session. */
|
|
4
|
-
export declare function acceptCall(input:
|
|
4
|
+
export declare function acceptCall(input: {
|
|
5
|
+
readonly target: Call['target'];
|
|
6
|
+
readonly input: unknown;
|
|
7
|
+
}): protocol.Call;
|
package/dist/client/admission.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Target } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
import { Path } from '@astrale-os/kernel-core/path';
|
|
2
3
|
import * as portable from '@astrale-os/kernel-dsl/value';
|
|
3
4
|
/** Parent-private Call acceptance shared by Client and Client Session. */
|
|
@@ -5,7 +6,13 @@ export function acceptCall(input) {
|
|
|
5
6
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
6
7
|
throw new TypeError('Client call must be an object.');
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
const target = input.target;
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
target: typeof target === 'object' && target !== null && 'callable' in target
|
|
12
|
+
? Target(target)
|
|
13
|
+
: admitPath(target),
|
|
14
|
+
input: admitInput(input.input),
|
|
15
|
+
});
|
|
9
16
|
}
|
|
10
17
|
function admitPath(input) {
|
|
11
18
|
try {
|
package/dist/client/request.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { Target } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
import type { PathLike } from '@astrale-os/kernel-core/path';
|
|
2
3
|
import type * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
4
|
import type { RequestOptions, TransportRequest } from '../transport/index.js';
|
|
4
5
|
import type { Input } from './result.js';
|
|
5
6
|
/** One portable call before Path canonicalization, correlation, and credential binding. */
|
|
6
7
|
export interface Call {
|
|
7
|
-
readonly target: PathLike;
|
|
8
|
+
readonly target: PathLike | Target;
|
|
8
9
|
readonly input: Input;
|
|
9
10
|
}
|
|
10
11
|
export interface ClientDispatchOptions extends RequestOptions {
|
|
@@ -21,6 +22,6 @@ export interface AdmittedRequest {
|
|
|
21
22
|
readonly schema?: protocol.SchemaTarget;
|
|
22
23
|
readonly options: RequestOptions;
|
|
23
24
|
}
|
|
24
|
-
export declare function call(target: PathLike, input: Input): Call;
|
|
25
|
+
export declare function call(target: PathLike | Target, input: Input): Call;
|
|
25
26
|
export declare function admitRequest(input: Call, options: ClientDispatchOptions | undefined, lifecycle: AbortSignal): AdmittedRequest;
|
|
26
27
|
export declare function invocation(request: AdmittedRequest, credential?: string, trace?: protocol.Trace): TransportRequest;
|
package/dist/client/request.js
CHANGED
|
@@ -45,6 +45,9 @@ function admitRequestOptions(input) {
|
|
|
45
45
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
46
46
|
throw new TypeError('Client request options must be an object.');
|
|
47
47
|
}
|
|
48
|
+
if (Reflect.ownKeys(input).some((field) => !['signal', 'timeoutMs', 'idempotencyKey', 'delegate', 'routing', 'schema'].includes(String(field)))) {
|
|
49
|
+
throw new TypeError('Client request options contain unsupported fields.');
|
|
50
|
+
}
|
|
48
51
|
if (input.timeoutMs !== undefined &&
|
|
49
52
|
(!Number.isSafeInteger(input.timeoutMs) ||
|
|
50
53
|
input.timeoutMs < 1 ||
|
|
@@ -62,6 +62,7 @@ const source = Query.from({ nodes: [Work.classes.Resource] });
|
|
|
62
62
|
const query = source.select({ kind: 'nodes', projection: { kind: 'value' } });
|
|
63
63
|
const prototypeSource = Query.from({ nodes: [Work.classes.PrototypeMembers] });
|
|
64
64
|
const prototypeQuery = prototypeSource.select({ kind: 'nodes', projection: { kind: 'value' } });
|
|
65
|
+
// @ts-expect-error Concrete Method shortcuts are absent from a child-selected projection.
|
|
65
66
|
const resourceAcceptsDocument = documentProjection;
|
|
66
67
|
void resourceAcceptsDocument;
|
|
67
68
|
const resourceReceiverAcceptsDocument = documentProjection;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { MethodKey, PropertyKey } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
2
|
import { retainProjectedProvenance } from './provenance.js';
|
|
3
3
|
export function projectNode(selected, node, provenance, call) {
|
|
4
|
+
const invoke = async (receiver, method, input) => {
|
|
5
|
+
if (receiver.class !== method.owner.key) {
|
|
6
|
+
throw new TypeError(`Method ${method.key} requires an exact ${method.owner.key} receiver.`);
|
|
7
|
+
}
|
|
8
|
+
return call(receiver, method, input);
|
|
9
|
+
};
|
|
4
10
|
const property = propertyAccessor(selected, node.props);
|
|
5
|
-
const method = methodAccessor(selected, node,
|
|
11
|
+
const method = methodAccessor(selected, node, invoke);
|
|
6
12
|
const value = {
|
|
7
13
|
id: node.id,
|
|
8
14
|
class: node.class,
|
|
@@ -11,7 +17,7 @@ export function projectNode(selected, node, provenance, call) {
|
|
|
11
17
|
method,
|
|
12
18
|
};
|
|
13
19
|
installPropertyShortcuts(value, selected, node.props, NODE_RESERVED);
|
|
14
|
-
installMethodShortcuts(value, selected, node,
|
|
20
|
+
installMethodShortcuts(value, selected, node, invoke, NODE_RESERVED);
|
|
15
21
|
const projected = Object.freeze(value);
|
|
16
22
|
retainProjectedProvenance(projected, provenance);
|
|
17
23
|
return projected;
|
|
@@ -41,7 +47,7 @@ function propertyAccessor(selected, props) {
|
|
|
41
47
|
function methodAccessor(selected, node, call) {
|
|
42
48
|
return (input) => {
|
|
43
49
|
const method = resolveMethod(selected, input);
|
|
44
|
-
if (method.
|
|
50
|
+
if (!method.executable) {
|
|
45
51
|
throw new TypeError(`Abstract Method ${method.key} cannot be invoked.`);
|
|
46
52
|
}
|
|
47
53
|
return (value) => call(node, method, value);
|
|
@@ -98,9 +104,7 @@ function installPropertyShortcuts(target, selected, props, reserved) {
|
|
|
98
104
|
}
|
|
99
105
|
function installMethodShortcuts(target, selected, node, call, reserved) {
|
|
100
106
|
for (const member of selected.methods) {
|
|
101
|
-
if (member.
|
|
102
|
-
reserved.has(member.name) ||
|
|
103
|
-
hasOwn(target, member.name))
|
|
107
|
+
if (!member.executable || reserved.has(member.name) || hasOwn(target, member.name))
|
|
104
108
|
continue;
|
|
105
109
|
if (named(selected.methods, member.name) !== member)
|
|
106
110
|
continue;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { Target, targetPath } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
export { Client, call, type AuthenticatedClient, type Call, type ClientDispatchOptions, type ClientOptions, type Binary, type BinaryResponse, type Input, type RemoteCredential, type Result, type Stream, type StreamResult, type ValueResult, } from './client/index.js';
|
|
2
3
|
export * from './errors/index.js';
|
|
3
4
|
export { type RequestOptions, type MissControl, type StaleControl, type Transport, type TransportRequest, type TransportResult, } from './transport/index.js';
|
package/dist/index.js
CHANGED
package/dist/session/bound.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface BoundSessionSource {
|
|
|
14
14
|
readonly schema: SchemaApi;
|
|
15
15
|
dispatch(call: Call, options?: SessionRequestOptions): Promise<Result>;
|
|
16
16
|
call(call: Call, options?: SessionRequestOptions): Promise<Input>;
|
|
17
|
-
invoke<const Reference extends CallableReference
|
|
17
|
+
invoke<const Reference extends CallableReference<unknown>>(reference: Reference, input: InputOfReference<Reference>, options?: SessionRequestOptions): Promise<OutputOfReference<Reference>>;
|
|
18
18
|
stream(call: Call, options?: SessionRequestOptions): Promise<Stream>;
|
|
19
19
|
binary(call: Call, options?: SessionRequestOptions): Promise<Binary>;
|
|
20
20
|
viewsFor(target: Parameters<ClientSession['viewsFor']>[0], options?: SessionControlOptions): Promise<ViewCatalog>;
|
|
@@ -36,7 +36,7 @@ export interface BoundClientSession extends GraphApi {
|
|
|
36
36
|
readonly schema: SchemaApi;
|
|
37
37
|
dispatch(call: Call, options?: SessionRequestOptions): Promise<Result>;
|
|
38
38
|
call(call: Call, options?: SessionRequestOptions): Promise<Input>;
|
|
39
|
-
invoke<const Reference extends CallableReference
|
|
39
|
+
invoke<const Reference extends CallableReference<unknown>>(reference: Reference, input: InputOfReference<Reference>, options?: SessionRequestOptions): Promise<OutputOfReference<Reference>>;
|
|
40
40
|
stream(call: Call, options?: SessionRequestOptions): Promise<Stream>;
|
|
41
41
|
binary(call: Call, options?: SessionRequestOptions): Promise<Binary>;
|
|
42
42
|
viewsFor: BoundSessionSource['viewsFor'];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Path as PathValue, PathLike } from '@astrale-os/kernel-core/path';
|
|
2
|
-
import type { CallableInputOf, CallableOutputOf, CallableResultOf, Domain,
|
|
2
|
+
import type { CallableInputOf, CallableOutputOf, CallableResultOf, Domain, ResolvedFunction, ExecutableMethod } from '@astrale-os/kernel-dsl/v1';
|
|
3
3
|
import type { Revision } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
4
|
import { type Key as KeyType } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
5
5
|
import type { Binary, Input } from '../client/index.js';
|
|
6
6
|
declare const REFERENCE_INPUT: unique symbol;
|
|
7
7
|
declare const REFERENCE_OUTPUT: unique symbol;
|
|
8
|
-
export interface CallableReference<InputValue
|
|
8
|
+
export interface CallableReference<InputValue = Input, Output = unknown> {
|
|
9
9
|
readonly origin: KeyType.Origin;
|
|
10
10
|
readonly revision: Revision;
|
|
11
11
|
readonly callable: KeyType.Callable;
|
|
@@ -14,23 +14,22 @@ export interface CallableReference<InputValue extends Input = Input, Output = un
|
|
|
14
14
|
readonly [REFERENCE_INPUT]?: InputValue;
|
|
15
15
|
readonly [REFERENCE_OUTPUT]?: Output;
|
|
16
16
|
}
|
|
17
|
-
export interface InstanceCallableReference<InputValue
|
|
17
|
+
export interface InstanceCallableReference<InputValue = Input, Output = unknown> {
|
|
18
18
|
(receiver: PathLike): CallableReference<InputValue, Output>;
|
|
19
19
|
readonly origin: KeyType.Origin;
|
|
20
20
|
readonly revision: Revision;
|
|
21
21
|
readonly callable: KeyType.Callable;
|
|
22
22
|
readonly outputMode: 'value' | 'stream' | 'binary';
|
|
23
23
|
}
|
|
24
|
-
export type InputOfReference<Reference extends CallableReference
|
|
25
|
-
export type OutputOfReference<Reference extends CallableReference
|
|
26
|
-
type ReferenceInput<Callable> = unknown extends CallableInputOf<Callable> ? Input :
|
|
24
|
+
export type InputOfReference<Reference extends CallableReference<unknown>> = Reference extends CallableReference<infer InputValue, unknown> ? InputValue : never;
|
|
25
|
+
export type OutputOfReference<Reference extends CallableReference<unknown>> = Reference extends CallableReference<infer _Input, infer Output> ? Output : never;
|
|
26
|
+
type ReferenceInput<Callable> = unknown extends CallableInputOf<Callable> ? Input : CallableInputOf<Callable>;
|
|
27
27
|
type ReferenceOutput<Callable> = unknown extends CallableOutputOf<Callable> ? unknown : CallableResultOf<Callable, Binary>;
|
|
28
|
-
type
|
|
29
|
-
type
|
|
30
|
-
type ExecutableInstanceMethod = ResolvedMethod<unknown, unknown, false, ExecutableMethodInheritance>;
|
|
28
|
+
type ExecutableStaticMethod = ExecutableMethod<unknown, unknown, true>;
|
|
29
|
+
type ExecutableInstanceMethod = ExecutableMethod<unknown, unknown, false>;
|
|
31
30
|
/** Project one exact resolved Function or static Method into a typed Client reference. */
|
|
32
31
|
export declare function reference<const Callable extends ResolvedFunction | ExecutableStaticMethod>(domain: Pick<Domain, 'origin' | 'revision'>, callable: Callable): CallableReference<ReferenceInput<Callable>, ReferenceOutput<Callable>>;
|
|
33
32
|
/** Project one exact resolved instance Method into a receiver-bound typed Client reference. */
|
|
34
33
|
export declare function reference<const Callable extends ExecutableInstanceMethod>(domain: Pick<Domain, 'origin' | 'revision'>, callable: Callable): InstanceCallableReference<ReferenceInput<Callable>, ReferenceOutput<Callable>>;
|
|
35
|
-
export declare function admitReference(input: CallableReference): CallableReference
|
|
34
|
+
export declare function admitReference(input: CallableReference<unknown>): CallableReference<unknown>;
|
|
36
35
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Path } from '@astrale-os/kernel-core/path';
|
|
2
2
|
import { callableAddress, Key, patterns, } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
3
3
|
export function reference(domain, callable) {
|
|
4
|
-
if (
|
|
4
|
+
if (Key.origin(callable.key) !== domain.origin)
|
|
5
|
+
invalid();
|
|
6
|
+
if ('owner' in callable && !callable.executable)
|
|
5
7
|
invalid();
|
|
6
8
|
const common = Object.freeze({
|
|
7
9
|
origin: domain.origin,
|
|
@@ -12,7 +14,7 @@ export function reference(domain, callable) {
|
|
|
12
14
|
if ('owner' in callable && !callable.static) {
|
|
13
15
|
const bind = (receiver) => admitReference({
|
|
14
16
|
...common,
|
|
15
|
-
target: Path.instanceMethod(Path.from(receiver), callable.
|
|
17
|
+
target: Path.instanceMethod(Path.from(receiver), callable.key),
|
|
16
18
|
});
|
|
17
19
|
return Object.assign(bind, common);
|
|
18
20
|
}
|
|
@@ -52,7 +54,7 @@ function matchesCallable(target, callable) {
|
|
|
52
54
|
if (last?.kind !== 'method' || last.name !== coordinate.name)
|
|
53
55
|
return false;
|
|
54
56
|
if (last.dispatch === 'instance')
|
|
55
|
-
return
|
|
57
|
+
return last.class === Key.of(coordinate.owner);
|
|
56
58
|
const owner = steps.at(-2);
|
|
57
59
|
return (anchor.kind === 'domain' &&
|
|
58
60
|
anchor.origin === Key.origin(callable) &&
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { invocation } from '@astrale-os/kernel-protocol';
|
|
2
2
|
import type { EffectiveAuth } from './auth.js';
|
|
3
|
-
import type { SchemaExpectation } from './expectation.js';
|
|
4
3
|
export interface LearnedRoute {
|
|
5
4
|
readonly route: invocation.Route;
|
|
6
5
|
readonly credential?: string;
|
|
@@ -27,7 +26,7 @@ export declare class RouteCache {
|
|
|
27
26
|
private readonly keyTokens;
|
|
28
27
|
private readonly store?;
|
|
29
28
|
constructor(maximumAgeMs: number, maximumSize?: number, store?: SessionRouteStore);
|
|
30
|
-
key(source: string, call: invocation.Call, auth: EffectiveAuth
|
|
29
|
+
key(source: string, call: invocation.Call, auth: EffectiveAuth): Promise<string>;
|
|
31
30
|
get(key: string, sourceCredential?: string, now?: number): Promise<LearnedRoute | undefined>;
|
|
32
31
|
learn(key: string, route: invocation.Route, credential: string | undefined, sourceCredential?: string, now?: number): Promise<LearnedRoute>;
|
|
33
32
|
delete(key: string): void;
|
|
@@ -14,16 +14,10 @@ export class RouteCache {
|
|
|
14
14
|
this.store = store === undefined ? undefined : captureSessionRouteStore(store);
|
|
15
15
|
this.restore();
|
|
16
16
|
}
|
|
17
|
-
async key(source, call, auth
|
|
17
|
+
async key(source, call, auth) {
|
|
18
18
|
const credential = auth.credential === undefined ? 'anonymous' : await digestText(auth.credential);
|
|
19
19
|
const delegate = auth.delegate === undefined ? 'anonymous' : await digestText(canonicalDelegate(auth.delegate));
|
|
20
|
-
return JSON.stringify([
|
|
21
|
-
source,
|
|
22
|
-
call.target,
|
|
23
|
-
credential,
|
|
24
|
-
delegate,
|
|
25
|
-
expected === undefined ? null : [expected.origin, expected.revision],
|
|
26
|
-
]);
|
|
20
|
+
return JSON.stringify([source, call.target, credential, delegate]);
|
|
27
21
|
}
|
|
28
22
|
async get(key, sourceCredential, now = Date.now()) {
|
|
29
23
|
const cached = this.entries.get(key);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Result } from '../../client/index.js';
|
|
3
3
|
import type { SessionOperation, SessionPolicy } from '../context/index.js';
|
|
4
4
|
import type { SessionAuth } from './auth.js';
|
|
5
5
|
import type { RouteCache } from './cache.js';
|
|
6
6
|
import type { ClientPoolCapability } from './envelope.js';
|
|
7
|
-
import type { SchemaExpectation } from './expectation.js';
|
|
8
7
|
import type { SessionEnvelopeTransport, SessionRequestOptions } from './request.js';
|
|
8
|
+
import { acceptCall } from '../../client/index.js';
|
|
9
9
|
export interface SessionDispatcherOptions {
|
|
10
10
|
readonly kernel: IssuerId;
|
|
11
11
|
readonly sourceEndpoint: string;
|
|
@@ -20,7 +20,7 @@ export interface SessionDispatcherOptions {
|
|
|
20
20
|
export declare class SessionDispatcher {
|
|
21
21
|
private readonly state;
|
|
22
22
|
constructor(state: SessionDispatcherOptions);
|
|
23
|
-
dispatch(input:
|
|
23
|
+
dispatch(input: Parameters<typeof acceptCall>[0], options: SessionRequestOptions, operation: SessionOperation): Promise<Result>;
|
|
24
24
|
private dispatchSource;
|
|
25
25
|
private followSource;
|
|
26
26
|
private dispatchDestinationWithRecovery;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
1
2
|
import * as invocationProtocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
3
|
import { acceptCall, dispatchTrace, isAdmittedMissError, isAdmittedStaleRouteError, } from '../../client/index.js';
|
|
3
4
|
import { ClientError, ResponseError } from '../../errors/index.js';
|
|
@@ -9,25 +10,24 @@ export class SessionDispatcher {
|
|
|
9
10
|
constructor(state) {
|
|
10
11
|
this.state = state;
|
|
11
12
|
}
|
|
12
|
-
async dispatch(input, options, operation
|
|
13
|
+
async dispatch(input, options, operation) {
|
|
13
14
|
this.state.requireOpen();
|
|
14
15
|
const call = acceptCall(input);
|
|
15
16
|
// Authority is resolved before warm lookup so changed credentials or delegation cannot reuse a
|
|
16
17
|
// carrier minted for a different caller partition.
|
|
17
18
|
const auth = await resolveAuth(this.state.auth, call, operation);
|
|
18
19
|
const source = this.state.kernel;
|
|
19
|
-
const cacheKey = await this.state.cache.key(source, call, auth
|
|
20
|
+
const cacheKey = await this.state.cache.key(source, call, auth);
|
|
20
21
|
const dispatch = Object.freeze({
|
|
21
22
|
call,
|
|
22
23
|
auth,
|
|
23
24
|
source,
|
|
24
25
|
cacheKey,
|
|
25
|
-
...(expected === undefined ? {} : { expected }),
|
|
26
26
|
});
|
|
27
27
|
let learned = await this.state.cache.get(cacheKey, auth.credential);
|
|
28
28
|
if (learned !== undefined) {
|
|
29
29
|
try {
|
|
30
|
-
requireCachedRoute(learned, source,
|
|
30
|
+
requireCachedRoute(learned, source, call.target, this.state.envelopeTransport, this.state.websocketAvailable, this.state.policy);
|
|
31
31
|
}
|
|
32
32
|
catch {
|
|
33
33
|
this.state.cache.delete(cacheKey);
|
|
@@ -48,7 +48,6 @@ export class SessionDispatcher {
|
|
|
48
48
|
...sessionTransportOptions(operation, options.idempotencyKey),
|
|
49
49
|
...(state.auth.delegate === undefined ? {} : { delegate: state.auth.delegate }),
|
|
50
50
|
...(routing === undefined ? {} : { routing }),
|
|
51
|
-
...(state.expected === undefined ? {} : { schema: { revision: state.expected.revision } }),
|
|
52
51
|
}));
|
|
53
52
|
}
|
|
54
53
|
async followSource(result, state, options, operation) {
|
|
@@ -58,13 +57,13 @@ export class SessionDispatcher {
|
|
|
58
57
|
if (redirect.route.via.issuer !== state.source) {
|
|
59
58
|
throw new ClientError('Redirect issuer differs from the session-pinned source Kernel identity.');
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
requireTargetPublication(redirect.route, state.call.target);
|
|
62
61
|
const learned = await this.state.cache.learn(state.cacheKey, redirect.route, redirect.credential, state.auth.credential);
|
|
63
62
|
return this.dispatchDestinationWithRecovery(learned, state, options, operation, redirect.trace);
|
|
64
63
|
}
|
|
65
64
|
async dispatchDestinationWithRecovery(learned, state, options, operation, trace) {
|
|
66
65
|
try {
|
|
67
|
-
return await this.dispatchDestination(learned, state.call, options, operation, state.source,
|
|
66
|
+
return await this.dispatchDestination(learned, state.call, options, operation, state.source, trace);
|
|
68
67
|
}
|
|
69
68
|
catch (cause) {
|
|
70
69
|
const stale = isAdmittedStaleRouteError(cause);
|
|
@@ -88,11 +87,11 @@ export class SessionDispatcher {
|
|
|
88
87
|
return this.followSource(refreshed, state, options, operation);
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
async dispatchDestination(learned, call, options, operation, source,
|
|
90
|
+
async dispatchDestination(learned, call, options, operation, source, trace) {
|
|
92
91
|
if (learned.route.via.issuer !== source) {
|
|
93
92
|
throw new ClientError('Learned route issuer differs from the session-pinned source Kernel.');
|
|
94
93
|
}
|
|
95
|
-
|
|
94
|
+
requireTargetPublication(learned.route, call.target);
|
|
96
95
|
const envelope = selectEnvelope(learned.route.endpoint, this.state.envelopeTransport, this.state.websocketAvailable, this.state.pool, this.state.policy);
|
|
97
96
|
const requestOptions = {
|
|
98
97
|
...sessionTransportOptions(operation, options.idempotencyKey),
|
|
@@ -107,11 +106,11 @@ export class SessionDispatcher {
|
|
|
107
106
|
return result;
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
|
-
function requireCachedRoute(learned, source,
|
|
109
|
+
function requireCachedRoute(learned, source, target, envelopeTransport, websocketAvailable, policy) {
|
|
111
110
|
if (learned.route.via.issuer !== source) {
|
|
112
111
|
throw new ClientError('Learned route issuer differs from the session-pinned source Kernel.');
|
|
113
112
|
}
|
|
114
|
-
|
|
113
|
+
requireTargetPublication(learned.route, target);
|
|
115
114
|
selectEnvelopeTarget(learned.route.endpoint, envelopeTransport, websocketAvailable, policy);
|
|
116
115
|
}
|
|
117
116
|
function isRouteCompatibilityConflict(input) {
|
|
@@ -125,11 +124,12 @@ function isRouteCompatibilityConflict(input) {
|
|
|
125
124
|
return (failure?.data.code === invocationProtocol.SCHEMA_REVISION_MISMATCH ||
|
|
126
125
|
failure?.data.code === invocationProtocol.SERVICE_TRUST_REQUIRED);
|
|
127
126
|
}
|
|
128
|
-
function
|
|
129
|
-
if (
|
|
127
|
+
function requireTargetPublication(route, target) {
|
|
128
|
+
if (typeof target === 'string')
|
|
130
129
|
return;
|
|
131
130
|
const actual = route.via.publication;
|
|
132
|
-
if (actual.origin !==
|
|
131
|
+
if (actual.origin !== Key.origin(target.callable) ||
|
|
132
|
+
(target.revision !== undefined && actual.revision !== target.revision)) {
|
|
133
133
|
throw new ClientError('Resolved route does not match the expected Schema revision.');
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { admitSessionAuth, resolveAuth, type Auth, type EffectiveAuth, type SessionAuth, } from './auth.js';
|
|
2
|
-
export type { SchemaExpectation } from './expectation.js';
|
|
3
2
|
export { RouteCache, type LearnedRoute, type PersistedRoute, type SessionRouteArtifact, type SessionRouteStore, } from './cache.js';
|
|
4
3
|
export { requireResultKind, SessionDispatcher, type SessionDispatcherOptions } from './dispatch.js';
|
|
5
4
|
export { type ClientPoolCapability, type EnvelopeSelection } from './envelope.js';
|
|
@@ -56,7 +56,7 @@ export declare class ClientSession {
|
|
|
56
56
|
viewsFor(target: PathLike, options?: SessionControlOptions): Promise<ViewCatalog>;
|
|
57
57
|
dispatch(call: Call, options?: SessionRequestOptions): Promise<Result>;
|
|
58
58
|
call(call: Call, options?: SessionRequestOptions): Promise<Input>;
|
|
59
|
-
invoke<const Reference extends CallableReference
|
|
59
|
+
invoke<const Reference extends CallableReference<unknown>>(reference: Reference, input: InputOfReference<Reference>, options?: SessionRequestOptions): Promise<OutputOfReference<Reference>>;
|
|
60
60
|
stream(call: Call, options?: SessionRequestOptions): Promise<import("../client/result.js").Stream>;
|
|
61
61
|
binary(call: Call, options?: SessionRequestOptions): Promise<import("@astrale-os/kernel-core/callable").BinaryOutput>;
|
|
62
62
|
close(): void;
|
package/dist/session/session.js
CHANGED
|
@@ -119,11 +119,19 @@ export class ClientSession {
|
|
|
119
119
|
async invoke(reference, input, options) {
|
|
120
120
|
const admittedReference = admitReference(reference);
|
|
121
121
|
const admittedOptions = admitRequestOptions(options);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
const { anchor, steps } = admittedReference.target.ast;
|
|
123
|
+
const last = steps.at(-1);
|
|
124
|
+
const instance = last?.kind === 'method' && last.dispatch === 'instance'
|
|
125
|
+
? Path.fromAST({ anchor, steps: steps.slice(0, -1) }).raw
|
|
126
|
+
: undefined;
|
|
127
|
+
const result = await this.dispatcher.dispatch({
|
|
128
|
+
target: Object.freeze({
|
|
129
|
+
callable: admittedReference.callable,
|
|
130
|
+
revision: admittedReference.revision,
|
|
131
|
+
...(instance === undefined ? {} : { instance }),
|
|
132
|
+
}),
|
|
133
|
+
input,
|
|
134
|
+
}, admittedOptions, this.operation(admittedOptions));
|
|
127
135
|
const narrowed = requireResultKind(admittedReference.outputMode, result);
|
|
128
136
|
return (narrowed.kind === 'value'
|
|
129
137
|
? narrowed.value
|
|
@@ -163,7 +171,7 @@ export class ClientSession {
|
|
|
163
171
|
origin: owner.origin,
|
|
164
172
|
revision: owner.revision,
|
|
165
173
|
callable: method.key,
|
|
166
|
-
target: Path.instanceMethod(Path.id(node.id), method.
|
|
174
|
+
target: Path.instanceMethod(Path.id(node.id), method.key),
|
|
167
175
|
outputMode: method.output.mode,
|
|
168
176
|
}, input);
|
|
169
177
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NodeId } from '@astrale-os/kernel-core/graph/node';
|
|
2
2
|
import { Path } from '@astrale-os/kernel-core/path';
|
|
3
|
-
import {
|
|
3
|
+
import { K, KernelSchema } from '@astrale-os/kernel-core/schema';
|
|
4
|
+
import { defineSchema, func, method, nodeClass, schema, valueSchema, } from '@astrale-os/kernel-dsl/v1';
|
|
4
5
|
import { ClientSession, reference } from './index.js';
|
|
5
6
|
void new ClientSession(options);
|
|
6
7
|
void auth;
|
|
@@ -15,11 +16,64 @@ const dynamicInputIsClientInput = true;
|
|
|
15
16
|
const dynamicOutputIsUnknown = true;
|
|
16
17
|
void dynamicInputIsClientInput;
|
|
17
18
|
void dynamicOutputIsUnknown;
|
|
19
|
+
const renameInput = valueSchema()({
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: { name: { type: 'string' } },
|
|
22
|
+
required: ['name'],
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
});
|
|
25
|
+
const registrationInput = valueSchema()(K.functions.register.input);
|
|
26
|
+
const booleanOutput = valueSchema()({ type: 'boolean' });
|
|
27
|
+
const typedDomain = schema.resolve(defineSchema('client.input-witness.typecheck', {
|
|
28
|
+
dependencies: { kernel: KernelSchema },
|
|
29
|
+
functions: {
|
|
30
|
+
registerIdentity: func({
|
|
31
|
+
auth: 'authenticated',
|
|
32
|
+
input: registrationInput,
|
|
33
|
+
output: booleanOutput,
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
classes: {
|
|
37
|
+
Person: nodeClass({
|
|
38
|
+
methods: {
|
|
39
|
+
rename: method({ auth: 'authenticated', input: renameInput, output: booleanOutput }),
|
|
40
|
+
registerIdentity: method({
|
|
41
|
+
static: true,
|
|
42
|
+
auth: 'authenticated',
|
|
43
|
+
input: registrationInput,
|
|
44
|
+
output: booleanOutput,
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
const registerReference = reference(typedDomain, typedDomain.functions.registerIdentity);
|
|
51
|
+
const staticRegisterReference = reference(typedDomain, typedDomain.classes.Person.static.methods.registerIdentity);
|
|
52
|
+
const renameReference = reference(typedDomain, typedDomain.classes.Person.methods.rename)(Path.id(NodeId('person')));
|
|
53
|
+
void session.invoke(registerReference, registration);
|
|
54
|
+
void boundSession.invoke(staticRegisterReference, registration);
|
|
55
|
+
void session.invoke(renameReference, renameRequest);
|
|
56
|
+
const registerInputIsRequest = true;
|
|
57
|
+
const renameInputIsInterface = true;
|
|
58
|
+
const renameOutputIsBoolean = true;
|
|
59
|
+
void registerInputIsRequest;
|
|
60
|
+
void renameInputIsInterface;
|
|
61
|
+
void renameOutputIsBoolean;
|
|
62
|
+
// @ts-expect-error The exact Schema's required input fields are retained.
|
|
63
|
+
void session.invoke(renameReference, {});
|
|
64
|
+
// @ts-expect-error A Date is not the Schema's declared string input.
|
|
65
|
+
void session.invoke(renameReference, { name: new Date() });
|
|
66
|
+
// @ts-expect-error A function is not the Schema's declared string input.
|
|
67
|
+
void session.invoke(renameReference, { name: () => 'Ada' });
|
|
68
|
+
// @ts-expect-error RegisterRequest retains its nonempty identities tuple.
|
|
69
|
+
void session.invoke(registerReference, { idempotencyKey: 'key', identities: [] });
|
|
70
|
+
// @ts-expect-error An unknown-schema reference still accepts only portable Client Input.
|
|
71
|
+
void session.invoke(dynamicReference, new Date());
|
|
18
72
|
const abstractMethod = method({
|
|
19
73
|
auth: 'authenticated',
|
|
20
74
|
input: { type: 'null' },
|
|
21
75
|
output: { type: 'null' },
|
|
22
|
-
|
|
76
|
+
abstract: true,
|
|
23
77
|
});
|
|
24
78
|
const abstractDomain = schema.resolve(defineSchema('client.reference.typecheck', {
|
|
25
79
|
classes: {
|
|
@@ -28,3 +82,11 @@ const abstractDomain = schema.resolve(defineSchema('client.reference.typecheck',
|
|
|
28
82
|
}));
|
|
29
83
|
// @ts-expect-error Abstract Methods are declarations and cannot become Client references.
|
|
30
84
|
reference(abstractDomain, abstractDomain.classes.AbstractItem.methods.abstractMethod);
|
|
85
|
+
// @ts-expect-error Exact identity is derived internally, never supplied as a Session option.
|
|
86
|
+
void session.invoke(dynamicReference, dynamicInput, { expect: undefined });
|
|
87
|
+
// @ts-expect-error Raw Client options do not select a declaration.
|
|
88
|
+
const unsupportedClientOptions = { expect: undefined };
|
|
89
|
+
void unsupportedClientOptions;
|
|
90
|
+
// @ts-expect-error Internal metadata dispatch is absent from the public package facade.
|
|
91
|
+
import { dispatchTrace } from '../index.js';
|
|
92
|
+
void dispatchTrace;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NodePath } from '@astrale-os/kernel-core/path';
|
|
1
2
|
import type { RequestOptions, TransportRequest } from '../index.js';
|
|
2
3
|
import { TransportError } from '../../errors/index.js';
|
|
3
4
|
/** One invocation-owned total timeout budget shared by selection and fallback. */
|
|
@@ -13,4 +14,4 @@ export declare class InvocationBudget {
|
|
|
13
14
|
private remaining;
|
|
14
15
|
}
|
|
15
16
|
export declare function mayFallback(error: unknown): error is TransportError;
|
|
16
|
-
export declare function warningTarget(input: TransportRequest):
|
|
17
|
+
export declare function warningTarget(input: TransportRequest): NodePath;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { targetPath } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
import { TransportError } from '../../errors/index.js';
|
|
2
3
|
import { invocationFailure } from '../failure.js';
|
|
3
4
|
import { positiveInteger } from '../index.js';
|
|
@@ -66,7 +67,7 @@ export function mayFallback(error) {
|
|
|
66
67
|
error.phase !== 'timeout');
|
|
67
68
|
}
|
|
68
69
|
export function warningTarget(input) {
|
|
69
|
-
return input.call.target;
|
|
70
|
+
return targetPath(input.call.target);
|
|
70
71
|
}
|
|
71
72
|
function abortReason(signal) {
|
|
72
73
|
return signal.reason instanceof Error
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-client",
|
|
3
|
-
"version": "0.6.0-beta.
|
|
3
|
+
"version": "0.6.0-beta.58",
|
|
4
4
|
"description": "Universal Client — protocol-first, transport-agnostic",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -65,14 +65,14 @@
|
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@astrale-os/kernel-core": "0.9.0-beta.
|
|
69
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.
|
|
70
|
-
"@astrale-os/kernel-protocol": "0.5.0-beta.
|
|
68
|
+
"@astrale-os/kernel-core": "0.9.0-beta.45",
|
|
69
|
+
"@astrale-os/kernel-dsl": "0.2.0-beta.33",
|
|
70
|
+
"@astrale-os/kernel-protocol": "0.5.0-beta.49",
|
|
71
71
|
"@tanstack/pacer": "0.20.1",
|
|
72
72
|
"msgpackr": "^2.0.5"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@astrale-os/kernel-server": "0.5.0-beta.
|
|
75
|
+
"@astrale-os/kernel-server": "0.5.0-beta.51",
|
|
76
76
|
"@astrale/typescript-config": "npm:@jsr/astrale__typescript-config@~1.1.0",
|
|
77
77
|
"@types/node": "^22.0.0",
|
|
78
78
|
"typescript": "7.0.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|