@astrale-os/kernel-client 0.6.0-beta.59 → 0.6.0-beta.60
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/session/discovery/bundle.d.ts +5 -1
- package/dist/session/discovery/bundle.js +29 -11
- package/dist/session/discovery/discovery.d.ts +4 -1
- package/dist/session/discovery/discovery.js +34 -5
- package/dist/session/discovery/index.d.ts +2 -0
- package/dist/session/session.d.ts +3 -0
- package/dist/session/session.js +4 -0
- package/dist/session/session.typecheck.js +2 -0
- package/dist/store/memory.js +14 -2
- package/dist/store/mutation.d.ts +1 -1
- package/dist/store/mutation.js +6 -3
- package/dist/store/shape.d.ts +1 -0
- package/dist/store/shape.js +1 -0
- package/dist/store/store.js +1 -1
- package/package.json +3 -3
|
@@ -2,14 +2,18 @@ import type { bundle } from '@astrale-os/kernel-dsl/v1';
|
|
|
2
2
|
import type { publication } from '@astrale-os/kernel-protocol';
|
|
3
3
|
import type { Fetch } from '../../transport/http/index.js';
|
|
4
4
|
import type { SessionOperation, SessionPolicy } from '../context/index.js';
|
|
5
|
+
import { ClientError } from '../../errors/index.js';
|
|
5
6
|
export interface BundleDiscoveryOptions {
|
|
6
7
|
readonly fetch: Fetch;
|
|
7
8
|
readonly policy: Readonly<SessionPolicy>;
|
|
8
9
|
readonly maximumBytes: number;
|
|
9
10
|
}
|
|
11
|
+
/** Internal evidence that a selected immutable artifact is no longer served. */
|
|
12
|
+
export declare class BundleUnavailableError extends ClientError {
|
|
13
|
+
}
|
|
10
14
|
export declare class BundleDiscovery {
|
|
11
15
|
private readonly options;
|
|
12
|
-
private
|
|
16
|
+
private retained?;
|
|
13
17
|
constructor(options: BundleDiscoveryOptions);
|
|
14
18
|
get(publication: publication.Publication, operation: SessionOperation): Promise<bundle.Bundle>;
|
|
15
19
|
clear(): void;
|
|
@@ -1,39 +1,48 @@
|
|
|
1
|
+
import { bundleUrl } from '@astrale-os/kernel-protocol/publication';
|
|
1
2
|
import { decodeBundleValue } from '@astrale-os/kernel-protocol/publication/bundle';
|
|
2
3
|
import { ClientError } from '../../errors/index.js';
|
|
3
4
|
import { allowEndpoint } from '../context/index.js';
|
|
4
5
|
import { boundedResponse, requireExactResponse } from './publication.js';
|
|
5
6
|
import { runAcquisition } from './transport.js';
|
|
7
|
+
/** Internal evidence that a selected immutable artifact is no longer served. */
|
|
8
|
+
export class BundleUnavailableError extends ClientError {
|
|
9
|
+
}
|
|
6
10
|
export class BundleDiscovery {
|
|
7
11
|
options;
|
|
8
|
-
|
|
12
|
+
retained;
|
|
9
13
|
constructor(options) {
|
|
10
14
|
this.options = options;
|
|
11
15
|
}
|
|
12
16
|
async get(publication, operation) {
|
|
13
17
|
const key = bundleKey(publication);
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return retained;
|
|
18
|
+
allowEndpoint(publication.schema.bundle.href, this.options.policy);
|
|
19
|
+
if (this.retained?.key === key)
|
|
20
|
+
return this.retained.value;
|
|
21
|
+
const value = await this.fetch(publication, operation);
|
|
22
|
+
this.retained = { key, value };
|
|
23
|
+
return value;
|
|
21
24
|
}
|
|
22
25
|
clear() {
|
|
23
|
-
this.
|
|
26
|
+
this.retained = undefined;
|
|
24
27
|
}
|
|
25
28
|
async fetch(publication, operation) {
|
|
26
29
|
if (publication.schema.bundle.ref.size > this.options.maximumBytes) {
|
|
27
30
|
throw new ClientError('Bundle artifact exceeds its byte limit.');
|
|
28
31
|
}
|
|
29
32
|
const href = publication.schema.bundle.href;
|
|
30
|
-
|
|
33
|
+
const immutable = href === bundleUrl(publication.identity.issuer, publication.schema.bundle.ref);
|
|
31
34
|
const response = await runAcquisition(operation, 'bundle', 'unknown', 'Kernel bundle could not be retrieved.', (signal) => this.options.fetch(href, {
|
|
32
35
|
headers: { accept: publication.schema.bundle.ref.mediaType },
|
|
36
|
+
cache: immutable ? 'default' : 'no-cache',
|
|
33
37
|
redirect: 'manual',
|
|
34
38
|
signal,
|
|
35
39
|
}));
|
|
36
40
|
requireExactResponse(response, href, 'Bundle retrieval');
|
|
41
|
+
if (immutable && (response.status === 404 || response.status === 410)) {
|
|
42
|
+
// Cleanup must not hold discovery open on an injected body cancellation.
|
|
43
|
+
void response.body?.cancel().catch(() => undefined);
|
|
44
|
+
throw new BundleUnavailableError(`Bundle retrieval returned HTTP ${response.status}.`);
|
|
45
|
+
}
|
|
37
46
|
if (!response.ok)
|
|
38
47
|
throw new ClientError(`Bundle retrieval returned HTTP ${response.status}.`);
|
|
39
48
|
if (response.headers.get('content-type') !== publication.schema.bundle.ref.mediaType) {
|
|
@@ -51,5 +60,14 @@ export class BundleDiscovery {
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
function bundleKey(publication) {
|
|
54
|
-
|
|
63
|
+
const { href, ref } = publication.schema.bundle;
|
|
64
|
+
return JSON.stringify([
|
|
65
|
+
publication.identity.issuer,
|
|
66
|
+
new URL(href).origin,
|
|
67
|
+
publication.origin,
|
|
68
|
+
publication.schema.revision,
|
|
69
|
+
ref.digest,
|
|
70
|
+
ref.mediaType,
|
|
71
|
+
ref.size,
|
|
72
|
+
]);
|
|
55
73
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { publication } from '@astrale-os/kernel-protocol';
|
|
2
3
|
import type { SessionOperation, SessionPolicy } from '../context/index.js';
|
|
3
|
-
import type { BundleDiscovery } from './bundle.js';
|
|
4
4
|
import type { PublicationDiscovery } from './publication.js';
|
|
5
5
|
import type { SessionSnapshot } from './snapshot.js';
|
|
6
|
+
import { type BundleDiscovery } from './bundle.js';
|
|
6
7
|
export declare class SessionDiscovery {
|
|
7
8
|
readonly kernel: IssuerId;
|
|
8
9
|
private readonly policy;
|
|
@@ -12,7 +13,9 @@ export declare class SessionDiscovery {
|
|
|
12
13
|
private readonly acquisitionOperation;
|
|
13
14
|
private retained?;
|
|
14
15
|
private flight?;
|
|
16
|
+
private publicationFlight?;
|
|
15
17
|
constructor(kernel: IssuerId, policy: Readonly<SessionPolicy>, publications: PublicationDiscovery, bundles: BundleDiscovery, requireOpen: () => void, acquisitionOperation: () => SessionOperation);
|
|
18
|
+
publication(operation: SessionOperation): Promise<publication.Publication>;
|
|
16
19
|
snapshot(operation: SessionOperation): Promise<SessionSnapshot>;
|
|
17
20
|
clear(): void;
|
|
18
21
|
private acquireSnapshot;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runSessionOperation } from '../context/index.js';
|
|
2
2
|
import { rootViewCatalog } from '../views/index.js';
|
|
3
|
+
import { BundleUnavailableError } from './bundle.js';
|
|
3
4
|
import { createSessionSnapshot } from './snapshot.js';
|
|
4
5
|
export class SessionDiscovery {
|
|
5
6
|
kernel;
|
|
@@ -10,6 +11,7 @@ export class SessionDiscovery {
|
|
|
10
11
|
acquisitionOperation;
|
|
11
12
|
retained;
|
|
12
13
|
flight;
|
|
14
|
+
publicationFlight;
|
|
13
15
|
constructor(kernel, policy, publications, bundles, requireOpen, acquisitionOperation) {
|
|
14
16
|
this.kernel = kernel;
|
|
15
17
|
this.policy = policy;
|
|
@@ -18,6 +20,24 @@ export class SessionDiscovery {
|
|
|
18
20
|
this.requireOpen = requireOpen;
|
|
19
21
|
this.acquisitionOperation = acquisitionOperation;
|
|
20
22
|
}
|
|
23
|
+
publication(operation) {
|
|
24
|
+
this.requireOpen();
|
|
25
|
+
const current = this.publicationFlight;
|
|
26
|
+
if (current !== undefined)
|
|
27
|
+
return runSessionOperation(operation, () => current);
|
|
28
|
+
const flight = this.publications.revalidate(this.acquisitionOperation()).then((value) => {
|
|
29
|
+
this.requireOpen();
|
|
30
|
+
return value;
|
|
31
|
+
});
|
|
32
|
+
this.publicationFlight = flight;
|
|
33
|
+
void flight
|
|
34
|
+
.finally(() => {
|
|
35
|
+
if (this.publicationFlight === flight)
|
|
36
|
+
this.publicationFlight = undefined;
|
|
37
|
+
})
|
|
38
|
+
.catch(() => undefined);
|
|
39
|
+
return runSessionOperation(operation, () => flight);
|
|
40
|
+
}
|
|
21
41
|
snapshot(operation) {
|
|
22
42
|
this.requireOpen();
|
|
23
43
|
const current = this.flight;
|
|
@@ -39,11 +59,20 @@ export class SessionDiscovery {
|
|
|
39
59
|
this.retained = undefined;
|
|
40
60
|
}
|
|
41
61
|
async acquireSnapshot(operation) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.bundles.
|
|
46
|
-
|
|
62
|
+
let publication = await this.publication(operation);
|
|
63
|
+
let artifact;
|
|
64
|
+
try {
|
|
65
|
+
artifact = await this.bundles.get(publication, operation);
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
if (!(cause instanceof BundleUnavailableError))
|
|
69
|
+
throw cause;
|
|
70
|
+
const current = await this.publication(operation);
|
|
71
|
+
if (current.etag === publication.etag)
|
|
72
|
+
throw cause;
|
|
73
|
+
publication = current;
|
|
74
|
+
artifact = await this.bundles.get(publication, operation);
|
|
75
|
+
}
|
|
47
76
|
this.requireOpen();
|
|
48
77
|
if (this.retained?.publication.etag === publication.etag)
|
|
49
78
|
return this.retained;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { publication } from '@astrale-os/kernel-protocol';
|
|
2
3
|
import type { Fetch } from '../../transport/http/index.js';
|
|
3
4
|
import type { SessionOperation, SessionPolicy } from '../context/index.js';
|
|
4
5
|
import type { SessionSnapshot } from './snapshot.js';
|
|
5
6
|
export type { SessionSnapshot } from './snapshot.js';
|
|
6
7
|
export interface SessionDiscovery {
|
|
7
8
|
readonly kernel: IssuerId;
|
|
9
|
+
publication(operation: SessionOperation): Promise<publication.Publication>;
|
|
8
10
|
snapshot(operation: SessionOperation): Promise<SessionSnapshot>;
|
|
9
11
|
clear(): void;
|
|
10
12
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
2
|
import type { PathLike } from '@astrale-os/kernel-core/path';
|
|
3
3
|
import type { Domain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
4
|
+
import type { publication } from '@astrale-os/kernel-protocol';
|
|
4
5
|
import type { Call, Input, Result } from '../client/index.js';
|
|
5
6
|
import type { DomainBinding, GraphApi } from '../graph/index.js';
|
|
6
7
|
import type { ClientFactory, ClientPool as ClientPoolType } from '../pool/index.js';
|
|
@@ -52,6 +53,8 @@ export declare class ClientSession {
|
|
|
52
53
|
constructor(options: ClientSessionOptions);
|
|
53
54
|
/** Bind one admitted DSL Domain to authenticated graph projection and Method dispatch. */
|
|
54
55
|
bind<const DomainValue extends Domain>(domain: DomainValue): DomainBinding<DomainValue>;
|
|
56
|
+
/** Revalidate the intrinsic Publication without acquiring its bundle or Views. */
|
|
57
|
+
publication(options?: SessionControlOptions): Promise<publication.Publication>;
|
|
55
58
|
snapshot(options?: SessionControlOptions): Promise<SessionSnapshot>;
|
|
56
59
|
viewsFor(target: PathLike, options?: SessionControlOptions): Promise<ViewCatalog>;
|
|
57
60
|
dispatch(call: Call, options?: SessionRequestOptions): Promise<Result>;
|
package/dist/session/session.js
CHANGED
|
@@ -101,6 +101,10 @@ export class ClientSession {
|
|
|
101
101
|
bind(domain) {
|
|
102
102
|
return createDomainBinding(domain, this.graph, (node, method, input) => this.invokeDomainMethod(domain, node, method, input));
|
|
103
103
|
}
|
|
104
|
+
/** Revalidate the intrinsic Publication without acquiring its bundle or Views. */
|
|
105
|
+
publication(options) {
|
|
106
|
+
return this.discovery.publication(this.operation(admitControlOptions(options)));
|
|
107
|
+
}
|
|
104
108
|
snapshot(options) {
|
|
105
109
|
return this.discovery.snapshot(this.operation(admitControlOptions(options)));
|
|
106
110
|
}
|
|
@@ -10,6 +10,8 @@ void envelope;
|
|
|
10
10
|
void policy;
|
|
11
11
|
void request;
|
|
12
12
|
void snapshot;
|
|
13
|
+
const observedPublication = session.publication(control);
|
|
14
|
+
void observedPublication;
|
|
13
15
|
const dynamicReference = reference(dynamicDomain, dynamicMethod)(Path.id(NodeId('dynamic-node')));
|
|
14
16
|
void session.invoke(dynamicReference, dynamicInput);
|
|
15
17
|
const dynamicInputIsClientInput = true;
|
package/dist/store/memory.js
CHANGED
|
@@ -51,10 +51,15 @@ export class GraphMemory {
|
|
|
51
51
|
return observeRead(shape.inflight, options, this.options.lifecycle);
|
|
52
52
|
}
|
|
53
53
|
const prior = shape.response;
|
|
54
|
+
const invalidation = shape.invalidation;
|
|
54
55
|
this.publish(shape, prior === undefined ? 'loading' : this.overlay.hasAny() ? 'optimistic' : 'refreshing');
|
|
55
56
|
const inflight = this.schedule(async () => {
|
|
56
57
|
const result = await this.options.graph.query(shape.ast, queryOptions(shape.page, this.options.lifecycle, shape.expected));
|
|
57
58
|
this.options.requireOpen();
|
|
59
|
+
if (shape.invalidation !== invalidation) {
|
|
60
|
+
this.publish(shape, prior === undefined ? 'cold' : this.overlay.hasAny() ? 'optimistic' : 'stale');
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
58
63
|
const integration = this.entities.integrate(result.result);
|
|
59
64
|
shape.references = integration.references;
|
|
60
65
|
shape.confirmed = result;
|
|
@@ -82,8 +87,14 @@ export class GraphMemory {
|
|
|
82
87
|
throw error;
|
|
83
88
|
})
|
|
84
89
|
.finally(() => {
|
|
85
|
-
if (shape.inflight
|
|
86
|
-
|
|
90
|
+
if (shape.inflight !== inflight)
|
|
91
|
+
return;
|
|
92
|
+
shape.inflight = undefined;
|
|
93
|
+
if (shape.invalidation !== invalidation &&
|
|
94
|
+
shape.observed &&
|
|
95
|
+
!this.options.lifecycle.aborted) {
|
|
96
|
+
void this.read(shape, { page: shape.page }).catch((error) => this.report(error));
|
|
97
|
+
}
|
|
87
98
|
});
|
|
88
99
|
shape.inflight = inflight;
|
|
89
100
|
return observeRead(inflight, options, this.options.lifecycle);
|
|
@@ -105,6 +116,7 @@ export class GraphMemory {
|
|
|
105
116
|
invalidate(shape) {
|
|
106
117
|
this.options.requireOpen();
|
|
107
118
|
for (const candidate of shape === undefined ? this.shapes.values() : [shape]) {
|
|
119
|
+
candidate.invalidation += 1;
|
|
108
120
|
if (candidate.response === undefined)
|
|
109
121
|
continue;
|
|
110
122
|
this.publish(candidate, this.overlay.hasAny() ? 'optimistic' : 'stale');
|
package/dist/store/mutation.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import type { MutationAST as MutationASTValue, MutationInput, MutationResult } f
|
|
|
2
2
|
import type { GraphApi, MutationAuthor } from '../graph/index.js';
|
|
3
3
|
import type { GraphMemory } from './memory.js';
|
|
4
4
|
import type { StoreMutationOptions } from './store.js';
|
|
5
|
-
export declare function mutateStore(graph: GraphApi, memory: GraphMemory, input: MutationAuthor | MutationASTValue | MutationInput, options?: StoreMutationOptions): Promise<MutationResult>;
|
|
5
|
+
export declare function mutateStore(graph: GraphApi, memory: GraphMemory, lifecycle: AbortSignal, input: MutationAuthor | MutationASTValue | MutationInput, options?: StoreMutationOptions): Promise<MutationResult>;
|
package/dist/store/mutation.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MutationAST } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
-
export async function mutateStore(graph, memory, input, options = {}) {
|
|
2
|
+
export async function mutateStore(graph, memory, lifecycle, input, options = {}) {
|
|
3
3
|
if (options === null || typeof options !== 'object') {
|
|
4
4
|
throw new TypeError('Store mutation options are invalid.');
|
|
5
5
|
}
|
|
@@ -11,12 +11,15 @@ export async function mutateStore(graph, memory, input, options = {}) {
|
|
|
11
11
|
: 'format' in input
|
|
12
12
|
? MutationAST.decode(input)
|
|
13
13
|
: MutationAST.create(input);
|
|
14
|
+
const { optimistic: _optimistic, signal: callerSignal, ...request } = options;
|
|
15
|
+
const signal = callerSignal === undefined ? lifecycle : AbortSignal.any([lifecycle, callerSignal]);
|
|
16
|
+
signal.throwIfAborted();
|
|
14
17
|
const transaction = options.optimistic === false ? undefined : memory.overlay.add(ast);
|
|
15
18
|
if (transaction !== undefined)
|
|
16
19
|
memory.optimistic();
|
|
17
|
-
const { optimistic: _optimistic, ...request } = options;
|
|
18
20
|
try {
|
|
19
|
-
|
|
21
|
+
signal.throwIfAborted();
|
|
22
|
+
const result = await graph.mutate(ast, { ...request, signal });
|
|
20
23
|
if (transaction !== undefined) {
|
|
21
24
|
memory.overlay.delete(transaction);
|
|
22
25
|
memory.settleOptimistic();
|
package/dist/store/shape.d.ts
CHANGED
package/dist/store/shape.js
CHANGED
package/dist/store/store.js
CHANGED
|
@@ -76,7 +76,7 @@ export class GraphStore {
|
|
|
76
76
|
}
|
|
77
77
|
mutate(input, options) {
|
|
78
78
|
this.requireOpen();
|
|
79
|
-
return mutateStore(this.graph, this.memory, input, options);
|
|
79
|
+
return mutateStore(this.graph, this.memory, this.lifecycle.signal, input, options);
|
|
80
80
|
}
|
|
81
81
|
invalidate(query, options) {
|
|
82
82
|
this.requireOpen();
|
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.60",
|
|
4
4
|
"description": "Universal Client — protocol-first, transport-agnostic",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -67,12 +67,12 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@astrale-os/kernel-core": "0.9.0-beta.46",
|
|
69
69
|
"@astrale-os/kernel-dsl": "0.2.0-beta.33",
|
|
70
|
-
"@astrale-os/kernel-protocol": "0.5.0-beta.
|
|
70
|
+
"@astrale-os/kernel-protocol": "0.5.0-beta.51",
|
|
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.53",
|
|
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",
|