@astrale-os/kernel-client 0.6.0-beta.40 → 0.6.0-beta.42

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.
@@ -1,3 +1,3 @@
1
- export { createSchema, type SchemaApi, type SchemaCall, type SchemaOptions, type DomainBundle, type DomainInfo, type InstallResult, } from './schema.js';
1
+ export { createSchema, DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS, type SchemaApi, type SchemaCall, type SchemaOptions, type DomainBundle, type DomainInfo, type InstallResult, } from './schema.js';
2
2
  export { acceptOperationId, createOperationId } from './operation.js';
3
3
  export type { DomainRequest, InstallReceipt, InstallRequest, OperationId, UninstallReceipt, UninstallRequest, } from './contracts.js';
@@ -1,2 +1,2 @@
1
- export { createSchema, } from './schema.js';
1
+ export { createSchema, DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS, } from './schema.js';
2
2
  export { acceptOperationId, createOperationId } from './operation.js';
@@ -5,5 +5,5 @@ export function acceptOperationId(input) {
5
5
  }
6
6
  /** Allocate one cryptographically generated operation identity for a new lifecycle request. */
7
7
  export function createOperationId() {
8
- return acceptOperationId(globalThis.crypto.randomUUID());
8
+ return acceptOperationId(crypto.randomUUID());
9
9
  }
@@ -5,6 +5,7 @@ import type { RequestOptions } from '../transport/index.js';
5
5
  import type { InstallRequest, UninstallReceipt, UninstallRequest } from './contracts.js';
6
6
  export type SchemaCall = (call: Call, options?: RequestOptions) => Promise<Result>;
7
7
  export interface SchemaOptions extends protocolSchema.installation.introspection.Limits {
8
+ readonly lifecycleTimeoutMs?: number;
8
9
  }
9
10
  export type DomainInfo = protocolSchema.installation.introspection.DomainInfo;
10
11
  export type DomainBundle = protocolSchema.installation.introspection.DomainBundle;
@@ -15,5 +16,6 @@ export interface SchemaApi {
15
16
  inspect(origin: Key.Origin, options?: RequestOptions): Promise<DomainInfo>;
16
17
  bundle(origin: Key.Origin, options?: RequestOptions): Promise<DomainBundle>;
17
18
  }
19
+ export declare const DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS = 110000;
18
20
  /** Bind the canonical Schema lifecycle syscalls to one caller-scoped value-call capability. */
19
21
  export declare function createSchema(call: SchemaCall, limits: SchemaOptions): SchemaApi;
@@ -7,6 +7,7 @@ import { acceptOutput } from './admission.js';
7
7
  import { introspect } from './introspection.js';
8
8
  const INSTALL_PATH = Path.project(install).raw;
9
9
  const UNINSTALL_PATH = Path.project(uninstall).raw;
10
+ export const DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS = 110_000;
10
11
  /** Bind the canonical Schema lifecycle syscalls to one caller-scoped value-call capability. */
11
12
  export function createSchema(call, limits) {
12
13
  if (typeof call !== 'function')
@@ -14,7 +15,7 @@ export function createSchema(call, limits) {
14
15
  const admittedLimits = admitLimits(limits);
15
16
  return Object.freeze({
16
17
  async install(request, options) {
17
- const result = await call(invocation(INSTALL_PATH, request), options);
18
+ const result = await call(invocation(INSTALL_PATH, request), lifecycleOptions(options, admittedLimits.lifecycleTimeoutMs));
18
19
  if (result.kind !== 'binary') {
19
20
  throw new TypeError('Schema install returned a non-binary result.');
20
21
  }
@@ -30,7 +31,7 @@ export function createSchema(call, limits) {
30
31
  return decoded;
31
32
  },
32
33
  async uninstall(request, options) {
33
- const output = value(await call(invocation(UNINSTALL_PATH, request), options));
34
+ const output = value(await call(invocation(UNINSTALL_PATH, request), lifecycleOptions(options, admittedLimits.lifecycleTimeoutMs)));
34
35
  return acceptOutput(uninstallOutput, output);
35
36
  },
36
37
  async inspect(origin, options) {
@@ -68,13 +69,22 @@ function value(result) {
68
69
  return result.value;
69
70
  }
70
71
  function admitLimits(input) {
72
+ const lifecycleTimeoutMs = input?.lifecycleTimeoutMs ?? DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS;
71
73
  if (input === null ||
72
74
  typeof input !== 'object' ||
73
75
  !Number.isSafeInteger(input.maximumPublicationBytes) ||
74
76
  input.maximumPublicationBytes < 1 ||
75
77
  !Number.isSafeInteger(input.maximumBundleBytes) ||
76
- input.maximumBundleBytes < 1) {
78
+ input.maximumBundleBytes < 1 ||
79
+ !Number.isSafeInteger(lifecycleTimeoutMs) ||
80
+ lifecycleTimeoutMs < 1 ||
81
+ lifecycleTimeoutMs > 2_147_483_647) {
77
82
  throw new TypeError('Schema introspection limits are invalid.');
78
83
  }
79
- return Object.freeze({ ...input });
84
+ return Object.freeze({ ...input, lifecycleTimeoutMs });
85
+ }
86
+ function lifecycleOptions(options, timeoutMs) {
87
+ if (options?.timeoutMs !== undefined)
88
+ return options;
89
+ return Object.freeze({ ...options, timeoutMs });
80
90
  }
@@ -228,12 +228,13 @@ function refreshBoundary(credentialExpiry) {
228
228
  : credentialExpiry - DESTINATION_CREDENTIAL_REFRESH_SKEW_MS;
229
229
  }
230
230
  async function routeIntegrity(sourceCredential, key, learned) {
231
- if (globalThis.crypto?.subtle === undefined) {
231
+ const webCrypto = typeof crypto === 'undefined' ? undefined : crypto;
232
+ if (webCrypto?.subtle === undefined) {
232
233
  throw new TypeError('Persistent Session route integrity requires Web Crypto HMAC support.');
233
234
  }
234
235
  const algorithm = Object.freeze({ name: 'HMAC', hash: 'SHA-256' });
235
- const cryptographicKey = await globalThis.crypto.subtle.importKey('raw', new TextEncoder().encode(sourceCredential), algorithm, false, ['sign']);
236
- const signature = await globalThis.crypto.subtle.sign(algorithm, cryptographicKey, new TextEncoder().encode(integrityMaterial(key, learned)));
236
+ const cryptographicKey = await webCrypto.subtle.importKey('raw', new TextEncoder().encode(sourceCredential), algorithm, false, ['sign']);
237
+ const signature = await webCrypto.subtle.sign(algorithm, cryptographicKey, new TextEncoder().encode(integrityMaterial(key, learned)));
237
238
  return encodeBase64Url(new Uint8Array(signature));
238
239
  }
239
240
  async function matchesIntegrity(sourceCredential, key, learned, expected) {
@@ -281,10 +282,11 @@ function canonicalExpr(input) {
281
282
  return { kind: input.kind, operands: input.operands.map(canonicalExpr) };
282
283
  }
283
284
  async function digestText(input) {
284
- if (globalThis.crypto?.subtle === undefined) {
285
+ const webCrypto = typeof crypto === 'undefined' ? undefined : crypto;
286
+ if (webCrypto?.subtle === undefined) {
285
287
  throw new TypeError('Session route caching requires Web Crypto SHA-256 support.');
286
288
  }
287
- const bytes = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
289
+ const bytes = await webCrypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
288
290
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
289
291
  }
290
292
  function jwtExpiry(input) {
@@ -30,6 +30,7 @@ export interface ClientSessionOptions {
30
30
  readonly maximumBundleBytes?: number;
31
31
  readonly maximumResponseBytes?: number;
32
32
  readonly timeoutMs?: number;
33
+ readonly schemaLifecycleTimeoutMs?: number;
33
34
  }
34
35
  export type ConnectOptions = Omit<ClientSessionOptions, 'kernel' | 'policy'> & {
35
36
  readonly url: string;
@@ -4,7 +4,7 @@ import { SessionError } from '../errors/index.js';
4
4
  import { createDomainBinding } from '../graph/domain/binding.js';
5
5
  import { createGraph } from '../graph/index.js';
6
6
  import { ClientPool } from '../pool/index.js';
7
- import { createSchema } from '../schema/index.js';
7
+ import { createSchema, DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS } from '../schema/index.js';
8
8
  import { AutoTransport } from '../transport/auto/index.js';
9
9
  import { HttpTransport } from '../transport/http/index.js';
10
10
  import { WebSocketTransport } from '../transport/websocket/index.js';
@@ -46,11 +46,12 @@ export class ClientSession {
46
46
  const maximumBundleBytes = positive(options.maximumBundleBytes ?? DEFAULT_MAXIMUM_BUNDLE_BYTES, 'maximumBundleBytes');
47
47
  const maximumResponseBytes = positive(options.maximumResponseBytes ?? DEFAULT_MAXIMUM_RESPONSE_BYTES, 'maximumResponseBytes');
48
48
  const timeoutMs = platformDuration(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 'timeoutMs');
49
+ const schemaLifecycleTimeoutMs = platformDuration(options.schemaLifecycleTimeoutMs ?? DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS, 'schemaLifecycleTimeoutMs');
49
50
  this.timeoutMs = timeoutMs;
50
51
  const websocketAvailable = options.client !== undefined ||
51
52
  options.pool !== undefined ||
52
53
  options.websocket !== undefined ||
53
- typeof globalThis.WebSocket === 'function';
54
+ typeof WebSocket === 'function';
54
55
  const client = options.client ??
55
56
  defaultClientFactory({
56
57
  fetch: fetchImpl,
@@ -92,6 +93,7 @@ export class ClientSession {
92
93
  this.schema = createSchema((request, options) => this.dispatch(request, options), {
93
94
  maximumPublicationBytes,
94
95
  maximumBundleBytes,
96
+ lifecycleTimeoutMs: schemaLifecycleTimeoutMs,
95
97
  });
96
98
  this.graph = createGraph((call, options) => this.call(call, options));
97
99
  }
@@ -28,4 +28,3 @@ const abstractDomain = schema.resolve(defineSchema('client.reference.typecheck',
28
28
  }));
29
29
  // @ts-expect-error Abstract Methods are declarations and cannot become Client references.
30
30
  reference(abstractDomain, abstractDomain.classes.AbstractItem.methods.abstractMethod);
31
- void {};
@@ -13,9 +13,6 @@ const edgeProjection = edgeSource.select({
13
13
  const read = store.read(nodeGraph, options);
14
14
  const peek = store.peek(nodeProjection, options);
15
15
  const snapshot = store.getSnapshot(edgeProjection, options);
16
- // @ts-expect-error ready is the successful local snapshot state; fresh is retired.
17
- const retiredFreshSnapshot = { state: 'fresh' };
18
- void retiredFreshSnapshot;
19
16
  store.subscribe(nodeGraph, () => undefined, options);
20
17
  store.observe(nodeProjection, (value) => {
21
18
  if ('response' in value && value.response !== undefined) {
@@ -35,6 +32,4 @@ store.subscribe(nodeGraph, () => undefined);
35
32
  store.getSnapshot(nodeGraph);
36
33
  // @ts-expect-error Query-page identity is required for observations.
37
34
  store.observe(nodeGraph, () => undefined);
38
- // @ts-expect-error watch is reserved for a future remote-liveness protocol.
39
- store.watch(nodeGraph, () => undefined, options);
40
35
  void {};
@@ -9,8 +9,11 @@ export function emitWarning(warning, observer) {
9
9
  try {
10
10
  if (observer)
11
11
  observer(warning);
12
- else
13
- globalThis.console?.warn(formatClientWarning(warning));
12
+ else {
13
+ const runtimeConsole = globalThis
14
+ .console;
15
+ runtimeConsole?.warn(formatClientWarning(warning));
16
+ }
14
17
  }
15
18
  catch {
16
19
  // Warning observation cannot change invocation settlement.
@@ -141,7 +141,7 @@ export class HttpTransport {
141
141
  }
142
142
  }
143
143
  async fetchResponse(codec, encoded, trace, control) {
144
- return this.fetchImpl(this.url, {
144
+ const init = {
145
145
  method: 'POST',
146
146
  redirect: 'manual',
147
147
  credentials: 'omit',
@@ -155,7 +155,8 @@ export class HttpTransport {
155
155
  },
156
156
  body: toBody(encoded),
157
157
  signal: control.signal,
158
- });
158
+ };
159
+ return this.fetchImpl(this.url, init);
159
160
  }
160
161
  async decodeBinary(response, requestId, binaryId, streaming, control) {
161
162
  if (binaryId !== String(requestId)) {
@@ -81,7 +81,7 @@ export class WebSocketConnection {
81
81
  control.close();
82
82
  return Promise.reject(invocationFailure(`WebSocket request exceeds maximumRequestBytes (${this.maximumRequestBytes}).`, 'send'));
83
83
  }
84
- if (this.socket.bufferedAmount + encodedBytes > this.maximumBufferedBytes) {
84
+ if (socketBufferedAmount(this.socket) + encodedBytes > this.maximumBufferedBytes) {
85
85
  control.close();
86
86
  return Promise.reject(invocationFailure(`WebSocket connection exceeds maximumBufferedBytes (${this.maximumBufferedBytes}).`, 'send'));
87
87
  }
@@ -105,7 +105,7 @@ export class WebSocketConnection {
105
105
  const encoded = this.codec.encode(protocol.decodeCancelRequest({ version: protocol.VERSION, kind: 'cancel', requestId }));
106
106
  const bytes = frameSize(encoded);
107
107
  if (bytes > this.maximumRequestBytes ||
108
- this.socket.bufferedAmount + bytes > this.maximumBufferedBytes) {
108
+ socketBufferedAmount(this.socket) + bytes > this.maximumBufferedBytes) {
109
109
  throw new WebSocketBindingFailure('WebSocket cancellation exceeds the connection send policy.', 1008, 'Client send policy exceeded');
110
110
  }
111
111
  this.socket.send(socketData(encoded));
@@ -180,6 +180,9 @@ export class WebSocketConnection {
180
180
  this.shutdown(invocationFailure(message, this.opened ? 'receive' : 'connect'));
181
181
  }
182
182
  }
183
+ function socketBufferedAmount(socket) {
184
+ return socket.bufferedAmount ?? 0;
185
+ }
183
186
  async function normalizeFrame(input) {
184
187
  if (typeof input === 'string')
185
188
  return input;
@@ -140,7 +140,7 @@ function admitCodecName(input) {
140
140
  throw new TypeError('codec must be "json" or "msgpack".');
141
141
  }
142
142
  function admitWebSocket(input) {
143
- const implementation = input ?? globalThis.WebSocket;
143
+ const implementation = input ?? (typeof WebSocket === 'undefined' ? undefined : WebSocket);
144
144
  if (typeof implementation !== 'function') {
145
145
  throw new TypeError('WebSocketTransport requires a WebSocket implementation.');
146
146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-client",
3
- "version": "0.6.0-beta.40",
3
+ "version": "0.6.0-beta.42",
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.30",
69
- "@astrale-os/kernel-dsl": "0.2.0-beta.23",
70
- "@astrale-os/kernel-protocol": "0.5.0-beta.33",
68
+ "@astrale-os/kernel-core": "0.9.0-beta.31",
69
+ "@astrale-os/kernel-dsl": "0.2.0-beta.24",
70
+ "@astrale-os/kernel-protocol": "0.5.0-beta.34",
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.35",
75
+ "@astrale-os/kernel-server": "0.5.0-beta.36",
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",