@plexspaces/sdk 0.1.5 → 0.1.8
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 +115 -0
- package/dist/actor.js +1 -4
- package/dist/agent.d.ts +239 -0
- package/dist/agent.js +324 -0
- package/dist/host.d.ts +114 -20
- package/dist/host.js +329 -107
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/wire/ws-frame-wire.d.ts +75 -0
- package/dist/wire/ws-frame-wire.js +412 -0
- package/dist/ws_thin_client.d.ts +79 -0
- package/dist/ws_thin_client.js +302 -0
- package/package.json +1 -1
- package/dist/actor.test.d.ts +0 -1
- package/dist/actor.test.js +0 -171
package/dist/host.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// PlexSpaces Host Functions (TypeScript SDK)
|
|
5
5
|
//
|
|
6
6
|
// Provides TypeScript wrappers for WIT host imports.
|
|
7
|
-
// Uses virtual imports
|
|
7
|
+
// Uses virtual imports split across 9+ per-interface modules (host-logging, host-actor, host-kv, host-ts, host-locks, host-blob, host-pool, host-shard, host-http, registry).
|
|
8
8
|
// Protobuf wire for WIT `payload` fields (matches Go WASM hostWire* / tuplespace_proto_wire).
|
|
9
9
|
import { decodeReadResponseAllTuples, decodeReadResponseFirstTuple, encodeReadRequest, encodeWriteRequest, } from './wire/tuplespace-proto-wire.js';
|
|
10
10
|
import { decodeHttpFetchResponseWire, encodeHttpFetchRequestWire } from './wire/http-fetch-proto-wire.js';
|
|
@@ -13,15 +13,18 @@ import { decodeWitPayloadUtf8, encodeWitPayloadUtf8 } from './wit-payload.js';
|
|
|
13
13
|
import { encodeRegisterRequest, encodeUnregisterRequest, encodeLookupRequest, encodeDiscoverRequest, encodeHeartbeatRequest, decodeLookupResponse, decodeDiscoverResponse, ObjectType as RegistryObjectType, } from './wire/registry-proto-wire.js';
|
|
14
14
|
import { firstGroupMember, firstGroupMemberOrThrow } from './process_groups.js';
|
|
15
15
|
// Virtual imports provided by jco componentize at runtime.
|
|
16
|
-
// These map 1:1 to the WIT host interface functions.
|
|
17
|
-
//
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
} from 'plexspaces:actor/host@0.1.0';
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
} from 'plexspaces:actor/
|
|
16
|
+
// These map 1:1 to the WIT host interface functions (split by namespace).
|
|
17
|
+
// Types resolved via paths in tsconfig.json; resolved at runtime by jco componentize.
|
|
18
|
+
import { log as hostLog, nowMs as hostNowMs } from 'plexspaces:actor/host-logging@0.1.0';
|
|
19
|
+
import { send as hostSend, ask as hostAsk, selfId as hostSelfId, spawn as hostSpawn, stop as hostStop, link as hostLink, unlink as hostUnlink, monitor as hostMonitor, demonitor as hostDemonitor, sendAfter as hostSendAfter, pgJoin as hostPgJoin, pgLeave as hostPgLeave, pgMembers as hostPgMembers, pgBroadcast as hostPgBroadcast, } from 'plexspaces:actor/host-actor@0.1.0';
|
|
20
|
+
import { kvGet as hostKvGet, kvPut as hostKvPut, kvDelete as hostKvDelete, kvList as hostKvList, kvPutWithTtl as hostKvPutWithTtl, kvGetTtl as hostKvGetTtl, kvCas as hostKvCas, kvIncrement as hostKvIncrement, kvMultiGet as hostKvMultiGet, kvMultiPut as hostKvMultiPut, alarmSet as hostAlarmSet, alarmGet as hostAlarmGet, alarmDelete as hostAlarmDelete, } from 'plexspaces:actor/host-kv@0.1.0';
|
|
21
|
+
import { tsWrite as hostTsWrite, tsRead as hostTsRead, tsTake as hostTsTake, tsReadAll as hostTsReadAll, } from 'plexspaces:actor/host-ts@0.1.0';
|
|
22
|
+
import { lockAcquire as hostLockAcquire, lockRelease as hostLockRelease, lockRenew as hostLockRenew, } from 'plexspaces:actor/host-locks@0.1.0';
|
|
23
|
+
import { blobUpload as hostBlobUpload, blobDownload as hostBlobDownload, blobDelete as hostBlobDelete, blobList as hostBlobList, } from 'plexspaces:actor/host-blob@0.1.0';
|
|
24
|
+
import { poolCheckout as hostPoolCheckout, poolCheckin as hostPoolCheckin, poolGetMetrics as hostPoolGetMetrics, } from 'plexspaces:actor/host-pool@0.1.0';
|
|
25
|
+
import { createShardGroup as hostCreateShardGroup, bulkUpdateShardGroup as hostBulkUpdateShardGroup, mapShardGroup as hostMapShardGroup, broadcastShardGroup as hostBroadcastShardGroup, reduceShardGroup as hostReduceShardGroup, allReduceShardGroup as hostAllReduceShardGroup, barrierShardGroup as hostBarrierShardGroup, scatterGather as hostScatterGather, spawnActors as hostSpawnActors, applicationMetricsAdd as hostApplicationMetricsAdd, applicationGetMetrics as hostApplicationGetMetrics, applicationGetStatus as hostApplicationGetStatus, } from 'plexspaces:actor/host-shard@0.1.0';
|
|
26
|
+
import { httpFetch as hostHttpFetch } from 'plexspaces:actor/host-http@0.1.0';
|
|
27
|
+
import { register as hostRegistryRegister, unregister as hostRegistryUnregister, lookup as hostRegistryLookup, lookupByAlias as hostRegistryLookupByAlias, discover as hostRegistryDiscover, heartbeat as hostRegistryHeartbeat, } from 'plexspaces:actor/registry@0.1.0';
|
|
25
28
|
/**
|
|
26
29
|
* Safe call helper — returns empty string if function is undefined.
|
|
27
30
|
*/
|
|
@@ -62,6 +65,39 @@ function hostErrorPrefixBytes(raw) {
|
|
|
62
65
|
}
|
|
63
66
|
return true;
|
|
64
67
|
}
|
|
68
|
+
/** Encode bytes to base64 string without using btoa (works in Node.js and WASM). */
|
|
69
|
+
function bytesToBase64(bytes) {
|
|
70
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
71
|
+
let result = '';
|
|
72
|
+
const len = bytes.length;
|
|
73
|
+
for (let i = 0; i < len; i += 3) {
|
|
74
|
+
const b0 = bytes[i];
|
|
75
|
+
const b1 = i + 1 < len ? bytes[i + 1] : 0;
|
|
76
|
+
const b2 = i + 2 < len ? bytes[i + 2] : 0;
|
|
77
|
+
result += chars[b0 >> 2] + chars[((b0 & 3) << 4) | (b1 >> 4)] + (i + 1 < len ? chars[((b1 & 15) << 2) | (b2 >> 6)] : '=') + (i + 2 < len ? chars[b2 & 63] : '=');
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/** Decode base64 string to bytes without using atob (works in Node.js and WASM). */
|
|
82
|
+
function base64ToBytes(b64) {
|
|
83
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
84
|
+
const clean = b64.replace(/=+$/, '');
|
|
85
|
+
const len = clean.length;
|
|
86
|
+
const bytes = new Uint8Array(Math.floor(len * 3 / 4));
|
|
87
|
+
let pos = 0;
|
|
88
|
+
for (let i = 0; i < len; i += 4) {
|
|
89
|
+
const c0 = chars.indexOf(clean[i]);
|
|
90
|
+
const c1 = chars.indexOf(clean[i + 1]);
|
|
91
|
+
const c2 = i + 2 < len ? chars.indexOf(clean[i + 2]) : 0;
|
|
92
|
+
const c3 = i + 3 < len ? chars.indexOf(clean[i + 3]) : 0;
|
|
93
|
+
bytes[pos++] = (c0 << 2) | (c1 >> 4);
|
|
94
|
+
if (i + 2 < len)
|
|
95
|
+
bytes[pos++] = ((c1 & 15) << 4) | (c2 >> 2);
|
|
96
|
+
if (i + 3 < len)
|
|
97
|
+
bytes[pos++] = ((c2 & 3) << 6) | c3;
|
|
98
|
+
}
|
|
99
|
+
return bytes.subarray(0, pos);
|
|
100
|
+
}
|
|
65
101
|
/**
|
|
66
102
|
* Tuple space helper: list-in, list-out API.
|
|
67
103
|
* Use null in patterns for wildcards. Consistent with Python host.ts and Go host.TS().
|
|
@@ -273,6 +309,240 @@ export class Registry {
|
|
|
273
309
|
}
|
|
274
310
|
}
|
|
275
311
|
export { RegistryObjectType };
|
|
312
|
+
/**
|
|
313
|
+
* KVStore provides namespaced KV operations.
|
|
314
|
+
* Equivalent to Cloudflare DO's this.state.storage API.
|
|
315
|
+
* Access via host.kv.get(), host.kv.put() etc.
|
|
316
|
+
*/
|
|
317
|
+
export class KVStore {
|
|
318
|
+
get(key) {
|
|
319
|
+
if (typeof hostKvGet !== 'function')
|
|
320
|
+
return null;
|
|
321
|
+
try {
|
|
322
|
+
const v = decodeWitPayloadUtf8(hostKvGet(key));
|
|
323
|
+
return v || null;
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
put(key, value) {
|
|
330
|
+
if (typeof hostKvPut !== 'function')
|
|
331
|
+
return;
|
|
332
|
+
try {
|
|
333
|
+
hostKvPut(key, encodeWitPayloadUtf8(value));
|
|
334
|
+
}
|
|
335
|
+
catch { /* ignore */ }
|
|
336
|
+
}
|
|
337
|
+
delete(key) {
|
|
338
|
+
if (typeof hostKvDelete !== 'function')
|
|
339
|
+
return;
|
|
340
|
+
try {
|
|
341
|
+
hostKvDelete(key);
|
|
342
|
+
}
|
|
343
|
+
catch { /* ignore */ }
|
|
344
|
+
}
|
|
345
|
+
list(prefix) {
|
|
346
|
+
if (typeof hostKvList !== 'function')
|
|
347
|
+
return [];
|
|
348
|
+
try {
|
|
349
|
+
return hostKvList(prefix);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
putWithTtl(key, value, ttlSeconds) {
|
|
356
|
+
if (typeof hostKvPutWithTtl !== 'function')
|
|
357
|
+
return;
|
|
358
|
+
hostKvPutWithTtl(key, encodeWitPayloadUtf8(value), BigInt(ttlSeconds));
|
|
359
|
+
}
|
|
360
|
+
getTtl(key) {
|
|
361
|
+
if (typeof hostKvGetTtl !== 'function')
|
|
362
|
+
return 0;
|
|
363
|
+
try {
|
|
364
|
+
return Number(hostKvGetTtl(key));
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
cas(key, expected, newValue) {
|
|
371
|
+
if (typeof hostKvCas !== 'function')
|
|
372
|
+
return false;
|
|
373
|
+
const expectedBytes = expected !== null ? encodeWitPayloadUtf8(expected) : new Uint8Array(0);
|
|
374
|
+
return hostKvCas(key, expectedBytes, encodeWitPayloadUtf8(newValue));
|
|
375
|
+
}
|
|
376
|
+
increment(key, delta) {
|
|
377
|
+
if (typeof hostKvIncrement !== 'function')
|
|
378
|
+
return 0;
|
|
379
|
+
try {
|
|
380
|
+
return Number(hostKvIncrement(key, BigInt(delta)));
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
multiGet(keys) {
|
|
387
|
+
if (typeof hostKvMultiGet !== 'function')
|
|
388
|
+
return keys.map(() => null);
|
|
389
|
+
try {
|
|
390
|
+
const keysJson = encodeWitPayloadUtf8(JSON.stringify(keys));
|
|
391
|
+
const resultBytes = hostKvMultiGet(keysJson);
|
|
392
|
+
const resultJson = decodeWitPayloadUtf8(resultBytes);
|
|
393
|
+
const items = JSON.parse(resultJson);
|
|
394
|
+
return items.map(v => { if (v === null)
|
|
395
|
+
return null; const b = base64ToBytes(v); return new TextDecoder().decode(b); });
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return keys.map(() => null);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
multiPut(entries) {
|
|
402
|
+
if (typeof hostKvMultiPut !== 'function')
|
|
403
|
+
return;
|
|
404
|
+
const encoded = {};
|
|
405
|
+
for (const [k, v] of Object.entries(entries)) {
|
|
406
|
+
encoded[k] = bytesToBase64(new TextEncoder().encode(v));
|
|
407
|
+
}
|
|
408
|
+
const entriesJson = encodeWitPayloadUtf8(JSON.stringify(encoded));
|
|
409
|
+
hostKvMultiPut(entriesJson);
|
|
410
|
+
}
|
|
411
|
+
getJson(key) {
|
|
412
|
+
const raw = this.get(key);
|
|
413
|
+
if (!raw || raw.startsWith('ERROR:'))
|
|
414
|
+
return null;
|
|
415
|
+
try {
|
|
416
|
+
return JSON.parse(raw);
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
putJson(key, value) {
|
|
423
|
+
const serialized = JSON.stringify(value);
|
|
424
|
+
this.put(key, serialized);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* AlarmClient provides durable alarm scheduling.
|
|
429
|
+
* Equivalent to Cloudflare DO's setAlarm/getAlarm/deleteAlarm.
|
|
430
|
+
* Access via host.alarm.set(), host.alarm.get(), host.alarm.delete() etc.
|
|
431
|
+
*/
|
|
432
|
+
export class AlarmClient {
|
|
433
|
+
set(timestampMs) {
|
|
434
|
+
if (typeof hostAlarmSet !== 'function')
|
|
435
|
+
return;
|
|
436
|
+
hostAlarmSet(BigInt(timestampMs));
|
|
437
|
+
}
|
|
438
|
+
setIn(delayMs) {
|
|
439
|
+
if (typeof hostNowMs !== 'function')
|
|
440
|
+
return;
|
|
441
|
+
const now = Number(hostNowMs());
|
|
442
|
+
this.set(now + delayMs);
|
|
443
|
+
}
|
|
444
|
+
get() {
|
|
445
|
+
if (typeof hostAlarmGet !== 'function')
|
|
446
|
+
return 0;
|
|
447
|
+
try {
|
|
448
|
+
return Number(hostAlarmGet());
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
delete() {
|
|
455
|
+
if (typeof hostAlarmDelete !== 'function')
|
|
456
|
+
return;
|
|
457
|
+
hostAlarmDelete();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* LockClient provides distributed lock operations.
|
|
462
|
+
* Access via host.locks.acquire(), host.locks.release() etc.
|
|
463
|
+
*/
|
|
464
|
+
export class LockClient {
|
|
465
|
+
acquire(holderId, lockName, leaseDurationSecs, timeoutMs) {
|
|
466
|
+
const result = hostLockAcquire(holderId, lockName, leaseDurationSecs, timeoutMs);
|
|
467
|
+
if (typeof result === 'string')
|
|
468
|
+
throw new Error(result);
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
release(lockId, holderId, lockVersion) {
|
|
472
|
+
const result = hostLockRelease(lockId, holderId, lockVersion);
|
|
473
|
+
if (typeof result === 'string')
|
|
474
|
+
throw new Error(result);
|
|
475
|
+
}
|
|
476
|
+
renew(lockId, holderId, lockVersion, leaseDurationSecs) {
|
|
477
|
+
const result = hostLockRenew(lockId, holderId, lockVersion, leaseDurationSecs);
|
|
478
|
+
if (typeof result === 'string')
|
|
479
|
+
throw new Error(result);
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* BlobClient provides blob storage operations.
|
|
485
|
+
* Access via host.blob.upload(), host.blob.download() etc.
|
|
486
|
+
*/
|
|
487
|
+
export class BlobClient {
|
|
488
|
+
upload(name, data, contentType) {
|
|
489
|
+
const result = hostBlobUpload(name, data, contentType);
|
|
490
|
+
if (typeof result !== 'string' || result.startsWith('ERROR:'))
|
|
491
|
+
throw new Error(String(result));
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
download(blobId) {
|
|
495
|
+
const result = hostBlobDownload(blobId);
|
|
496
|
+
if (typeof result === 'string')
|
|
497
|
+
throw new Error(result);
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
delete(blobId) {
|
|
501
|
+
const result = hostBlobDelete(blobId);
|
|
502
|
+
if (typeof result === 'string')
|
|
503
|
+
throw new Error(result);
|
|
504
|
+
}
|
|
505
|
+
list(prefix) {
|
|
506
|
+
const result = hostBlobList(prefix);
|
|
507
|
+
if (Array.isArray(result))
|
|
508
|
+
return result;
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* ActorRef is a lightweight handle to a named virtual actor.
|
|
514
|
+
* Equivalent to Cloudflare DO's env.BINDING.get(id).
|
|
515
|
+
* Use getActorRef() to obtain one.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* const room = getActorRef("ChatRoomActor", roomId, "default");
|
|
520
|
+
* const result = room.ask("send_message", payload, BigInt(5000));
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
export class ActorRef {
|
|
524
|
+
constructor(actorType, name, namespace) {
|
|
525
|
+
this.actorId = `${name}//${actorType}::${namespace}@*`;
|
|
526
|
+
}
|
|
527
|
+
id() { return this.actorId; }
|
|
528
|
+
tell(msgType, payload) {
|
|
529
|
+
hostSend(this.actorId, msgType, payload);
|
|
530
|
+
}
|
|
531
|
+
ask(msgType, payload, timeoutMs) {
|
|
532
|
+
const result = hostAsk(this.actorId, msgType, payload, timeoutMs);
|
|
533
|
+
if (typeof result === 'string')
|
|
534
|
+
throw new Error(result);
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* getActorRef constructs an ActorRef for a named virtual actor.
|
|
540
|
+
* The actor is created automatically on first contact (virtual actor pattern).
|
|
541
|
+
* Equivalent to Cloudflare: `const room = env.CHAT_ROOM.get(roomId)`
|
|
542
|
+
*/
|
|
543
|
+
export function getActorRef(actorType, name, namespace) {
|
|
544
|
+
return new ActorRef(actorType, name, namespace);
|
|
545
|
+
}
|
|
276
546
|
export class Host {
|
|
277
547
|
constructor() {
|
|
278
548
|
this.processGroups = new ProcessGroups();
|
|
@@ -280,6 +550,33 @@ export class Host {
|
|
|
280
550
|
this.ts = new TupleSpace(this);
|
|
281
551
|
/** Object registry: register, discover, and look up actors and services. */
|
|
282
552
|
this.registry = new Registry();
|
|
553
|
+
/** Namespaced KV store: host.kv.get(), host.kv.put() etc. */
|
|
554
|
+
this.kv = new KVStore();
|
|
555
|
+
/** Durable alarm client: host.alarm.set(), host.alarm.get() etc. */
|
|
556
|
+
this.alarm = new AlarmClient();
|
|
557
|
+
/** Distributed lock client: host.locks.acquire(), host.locks.release() etc. */
|
|
558
|
+
this.locks = new LockClient();
|
|
559
|
+
/** Blob storage client: host.blob.upload(), host.blob.download() etc. */
|
|
560
|
+
this.blob = new BlobClient();
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Create an ergonomic HTTP client for a named service link.
|
|
564
|
+
*
|
|
565
|
+
* The link must be pre-configured in RuntimeConfig.service_links.
|
|
566
|
+
* The host handles retries, circuit breaking, and auth injection.
|
|
567
|
+
*
|
|
568
|
+
* @param linkName - Service link name (e.g. "payments-api")
|
|
569
|
+
* @returns A {@link ServiceHttpClient} bound to that link
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* ```typescript
|
|
573
|
+
* const http = host.httpClient("payments-api");
|
|
574
|
+
* const balance = http.get("/v1/balance?account=123");
|
|
575
|
+
* const result = http.post("/v1/transfer", { amount: 100 });
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
httpClient(linkName) {
|
|
579
|
+
return new ServiceHttpClient(linkName);
|
|
283
580
|
}
|
|
284
581
|
// ========================================================================
|
|
285
582
|
// Messaging
|
|
@@ -322,14 +619,21 @@ export class Host {
|
|
|
322
619
|
// ========================================================================
|
|
323
620
|
/**
|
|
324
621
|
* Spawn a new actor through the framework-owned actor spawn path exposed by the host.
|
|
622
|
+
* Returns the canonical actor ID assigned by the framework — use this ID (not actorName)
|
|
623
|
+
* for all subsequent ask/send/stop calls.
|
|
624
|
+
*
|
|
325
625
|
* @param moduleRef - Actor type/module reference (must be deployed)
|
|
326
|
-
* @param
|
|
327
|
-
*
|
|
328
|
-
*
|
|
626
|
+
* @param actorName - Requested name for the new actor. The framework forms the full canonical
|
|
627
|
+
* ID from this name, moduleRef, namespace and node. Pass empty string to
|
|
628
|
+
* let the framework auto-generate a ULID name.
|
|
629
|
+
* @param role - Disambiguation key used ONLY when multiple actors in the same supervisor share
|
|
630
|
+
* the same actor_type (moduleRef). Pass empty string when moduleRef is unique.
|
|
631
|
+
* @param args - Key-value init arguments forwarded to the new actor's init()
|
|
632
|
+
* @returns Canonical actor ID string assigned by the framework
|
|
329
633
|
*/
|
|
330
|
-
spawn(moduleRef,
|
|
331
|
-
const
|
|
332
|
-
const result = safeCall(hostSpawn, moduleRef,
|
|
634
|
+
spawn(moduleRef, actorName = '', role = '', args = {}) {
|
|
635
|
+
const argsJson = Object.keys(args).length > 0 ? JSON.stringify(args) : '{}';
|
|
636
|
+
const result = safeCall(hostSpawn, moduleRef, actorName, role, argsJson);
|
|
333
637
|
if (typeof result === 'string' && result.startsWith('ERROR:')) {
|
|
334
638
|
throw new Error(result);
|
|
335
639
|
}
|
|
@@ -418,71 +722,6 @@ export class Host {
|
|
|
418
722
|
const result = safeCall(hostNowMs);
|
|
419
723
|
return typeof result === 'bigint' ? Number(result) : (typeof result === 'number' ? result : 0);
|
|
420
724
|
}
|
|
421
|
-
// ========================================================================
|
|
422
|
-
// Key-Value Store
|
|
423
|
-
// ========================================================================
|
|
424
|
-
kvGet(key) {
|
|
425
|
-
if (typeof hostKvGet !== 'function')
|
|
426
|
-
return '';
|
|
427
|
-
try {
|
|
428
|
-
return decodeWitPayloadUtf8(hostKvGet(key));
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
return `ERROR:${e}`;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
kvPut(key, value) {
|
|
435
|
-
if (typeof hostKvPut !== 'function')
|
|
436
|
-
return '';
|
|
437
|
-
try {
|
|
438
|
-
hostKvPut(key, encodeWitPayloadUtf8(value));
|
|
439
|
-
return '';
|
|
440
|
-
}
|
|
441
|
-
catch (e) {
|
|
442
|
-
return `ERROR:${e}`;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
kvDelete(key) {
|
|
446
|
-
if (typeof hostKvDelete !== 'function')
|
|
447
|
-
return '';
|
|
448
|
-
try {
|
|
449
|
-
hostKvDelete(key);
|
|
450
|
-
return '';
|
|
451
|
-
}
|
|
452
|
-
catch (e) {
|
|
453
|
-
return `ERROR:${e}`;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
kvList(prefix) {
|
|
457
|
-
if (typeof hostKvList !== 'function')
|
|
458
|
-
return '[]';
|
|
459
|
-
try {
|
|
460
|
-
return JSON.stringify(hostKvList(prefix));
|
|
461
|
-
}
|
|
462
|
-
catch (e) {
|
|
463
|
-
return `ERROR:${e}`;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
/** Retrieve a JSON value by key. Returns parsed object or null if not found. */
|
|
467
|
-
kvGetJson(key) {
|
|
468
|
-
const raw = this.kvGet(key);
|
|
469
|
-
if (!raw || raw.startsWith('ERROR:'))
|
|
470
|
-
return null;
|
|
471
|
-
try {
|
|
472
|
-
return JSON.parse(raw);
|
|
473
|
-
}
|
|
474
|
-
catch {
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
/** Serialize value to JSON and store under key. Throws on write failure. */
|
|
479
|
-
kvPutJson(key, value) {
|
|
480
|
-
const serialized = JSON.stringify(value);
|
|
481
|
-
const result = this.kvPut(key, serialized);
|
|
482
|
-
if (typeof result === 'string' && result.startsWith('ERROR:')) {
|
|
483
|
-
throw new Error(`kvPutJson(${key}): ${result}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
725
|
/** Increment a single named application metric counter by 1. Errors are swallowed. */
|
|
487
726
|
incrCounter(applicationId, name) {
|
|
488
727
|
this.incrCounters(applicationId, { [name]: 1 });
|
|
@@ -520,27 +759,6 @@ export class Host {
|
|
|
520
759
|
return hostPayloadToBytes(safeCall(hostTsReadAll, data));
|
|
521
760
|
}
|
|
522
761
|
// ========================================================================
|
|
523
|
-
// Distributed Locks
|
|
524
|
-
// ========================================================================
|
|
525
|
-
lockAcquire(tenantId, namespace, holderId, lockName, leaseDurationSecs = 30, timeoutMs = 0) {
|
|
526
|
-
return safeCall(hostLockAcquire, tenantId, namespace, holderId, lockName, leaseDurationSecs, BigInt(timeoutMs));
|
|
527
|
-
}
|
|
528
|
-
lockRelease(lockId, tenantId, namespace, holderId, lockVersion) {
|
|
529
|
-
return safeCall(hostLockRelease, lockId, tenantId, namespace, holderId, lockVersion);
|
|
530
|
-
}
|
|
531
|
-
lockRenew(lockId, tenantId, namespace, holderId, lockVersion, leaseDurationSecs = 30) {
|
|
532
|
-
return safeCall(hostLockRenew, lockId, tenantId, namespace, holderId, lockVersion, leaseDurationSecs);
|
|
533
|
-
}
|
|
534
|
-
// ========================================================================
|
|
535
|
-
// Blob Storage
|
|
536
|
-
// ========================================================================
|
|
537
|
-
blobUpload(blobId, data, contentType = 'application/octet-stream') {
|
|
538
|
-
return safeCall(hostBlobUpload, blobId, data, contentType);
|
|
539
|
-
}
|
|
540
|
-
blobDownload(blobId) { return safeCall(hostBlobDownload, blobId); }
|
|
541
|
-
blobDelete(blobId) { return safeCall(hostBlobDelete, blobId); }
|
|
542
|
-
blobList(prefix) { return safeCall(hostBlobList, prefix); }
|
|
543
|
-
// ========================================================================
|
|
544
762
|
// Elastic pool (checkout/checkin)
|
|
545
763
|
// ========================================================================
|
|
546
764
|
/**
|
|
@@ -749,7 +967,7 @@ export class EventLog {
|
|
|
749
967
|
this.watermark++;
|
|
750
968
|
const key = `${prefix}seq:${this.watermark}`;
|
|
751
969
|
try {
|
|
752
|
-
h.
|
|
970
|
+
h.kv.putJson(key, entry);
|
|
753
971
|
}
|
|
754
972
|
catch (e) {
|
|
755
973
|
this.watermark--;
|
|
@@ -763,19 +981,19 @@ export class EventLog {
|
|
|
763
981
|
*/
|
|
764
982
|
poll(h, prefix, consumerId, limit = 100) {
|
|
765
983
|
const cursorKey = `${prefix}cursor:${consumerId}`;
|
|
766
|
-
const rawCursor = h.
|
|
984
|
+
const rawCursor = h.kv.get(cursorKey);
|
|
767
985
|
const cursor = rawCursor ? parseInt(rawCursor, 10) || 0 : 0;
|
|
768
986
|
const events = [];
|
|
769
987
|
let newCursor = cursor;
|
|
770
988
|
for (let seq = cursor + 1; seq <= this.watermark && events.length < limit; seq++) {
|
|
771
|
-
const entry = h.
|
|
989
|
+
const entry = h.kv.getJson(`${prefix}seq:${seq}`);
|
|
772
990
|
if (entry !== null) {
|
|
773
991
|
events.push(entry);
|
|
774
992
|
newCursor = seq;
|
|
775
993
|
}
|
|
776
994
|
}
|
|
777
995
|
if (newCursor !== cursor) {
|
|
778
|
-
h.
|
|
996
|
+
h.kv.put(cursorKey, String(newCursor));
|
|
779
997
|
}
|
|
780
998
|
return [events, newCursor];
|
|
781
999
|
}
|
|
@@ -786,9 +1004,13 @@ export class EventLog {
|
|
|
786
1004
|
* The link must be pre-configured in RuntimeConfig.service_links.
|
|
787
1005
|
* The host handles retries, circuit breaking, and auth injection.
|
|
788
1006
|
*
|
|
1007
|
+
* Prefer creating instances via {@link Host.httpClient} rather than calling
|
|
1008
|
+
* the constructor directly:
|
|
1009
|
+
*
|
|
789
1010
|
* @example
|
|
790
1011
|
* ```typescript
|
|
791
|
-
*
|
|
1012
|
+
* // Preferred: use the factory method
|
|
1013
|
+
* const http = host.httpClient("payments-api");
|
|
792
1014
|
* const balance = http.get("/v1/balance?account=123");
|
|
793
1015
|
* const result = http.post("/v1/transfer", { amount: 100 });
|
|
794
1016
|
* ```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { PlexSpacesActor, WorkflowActor } from "./actor.js";
|
|
2
2
|
export { decodeWitPayloadUtf8, encodeWitPayloadUtf8 } from "./wit-payload.js";
|
|
3
3
|
export { actor, gen_server_actor, event_actor, fsm_actor, workflow_actor, handler, init_handler, run_handler, signal_handler, query_handler, getActorDefinition, } from "./decorators.js";
|
|
4
|
-
export { Host, ProcessGroups, TupleSpace, host, pgFirst, pgFirstOrThrow, ServiceHttpClient } from "./host.js";
|
|
4
|
+
export { Host, ProcessGroups, TupleSpace, host, pgFirst, pgFirstOrThrow, ServiceHttpClient, ActorRef, getActorRef } from "./host.js";
|
|
5
5
|
export { ActorRouter } from "./router.js";
|
|
6
6
|
export { defaultRetryConfig, withRetry, type RetryConfig, } from "./workflow.js";
|
|
7
7
|
export { LeaderWorkerClient, listWorkerNodeIds, } from "./leader_worker.js";
|
|
8
8
|
export { ActorID } from "./actor_id.js";
|
|
9
|
+
export { WsThinClient, type ThinClientOptions, type ThinNodePingResult, } from "./ws_thin_client.js";
|
|
10
|
+
export { AgentLoop, agentActor, defaultAgentConfig, type AgentConfig, type AgentStep, type AgentTrajectory, type AgentStepKind, } from "./agent.js";
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
export { PlexSpacesActor, WorkflowActor } from "./actor.js";
|
|
8
8
|
export { decodeWitPayloadUtf8, encodeWitPayloadUtf8 } from "./wit-payload.js";
|
|
9
9
|
export { actor, gen_server_actor, event_actor, fsm_actor, workflow_actor, handler, init_handler, run_handler, signal_handler, query_handler, getActorDefinition, } from "./decorators.js";
|
|
10
|
-
export { Host, ProcessGroups, TupleSpace, host, pgFirst, pgFirstOrThrow, ServiceHttpClient } from "./host.js";
|
|
10
|
+
export { Host, ProcessGroups, TupleSpace, host, pgFirst, pgFirstOrThrow, ServiceHttpClient, ActorRef, getActorRef } from "./host.js";
|
|
11
11
|
export { ActorRouter } from "./router.js";
|
|
12
12
|
export { defaultRetryConfig, withRetry, } from "./workflow.js";
|
|
13
13
|
export { LeaderWorkerClient, listWorkerNodeIds, } from "./leader_worker.js";
|
|
14
14
|
export { ActorID } from "./actor_id.js";
|
|
15
|
+
export { WsThinClient, } from "./ws_thin_client.js";
|
|
16
|
+
export { AgentLoop, agentActor, defaultAgentConfig, } from "./agent.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
type WireU8 = Uint8Array<ArrayBuffer>;
|
|
2
|
+
/**
|
|
3
|
+
* Encode a tell (fire-and-forget) frame.
|
|
4
|
+
* actor_type carries the full canonical actor ID when it contains '@'.
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeWsFrameTell(requestId: string, actorId: string, msgType: string, payloadBytes: Uint8Array): WireU8;
|
|
7
|
+
/**
|
|
8
|
+
* Encode an ask (request-reply) frame.
|
|
9
|
+
* timeout field 15 in AskReplyRequest is a google.protobuf.Duration: message{seconds=1, nanos=2}.
|
|
10
|
+
*/
|
|
11
|
+
export declare function encodeWsFrameAsk(requestId: string, actorId: string, msgType: string, payloadBytes: Uint8Array, timeoutMs: number): WireU8;
|
|
12
|
+
/**
|
|
13
|
+
* Encode a node registration handshake frame (must be first frame after WS upgrade).
|
|
14
|
+
* resourceHints advertises browser capabilities as NodeResourceHints (field 11).
|
|
15
|
+
*/
|
|
16
|
+
export declare function encodeWsFrameNodeRegister(requestId: string, nodeId: string, nodeAddress: string, capabilities: Record<string, string>, resourceHints?: {
|
|
17
|
+
cpuPercent?: number;
|
|
18
|
+
memoryAvailableMb?: number;
|
|
19
|
+
availableCores?: number;
|
|
20
|
+
}): WireU8;
|
|
21
|
+
/**
|
|
22
|
+
* Encode a heartbeat frame to keep the WS session alive.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeWsFrameHeartbeat(requestId: string, nodeId: string): WireU8;
|
|
25
|
+
/**
|
|
26
|
+
* Encode a node ping frame (SWIM-compatible, carries resource hints in the response).
|
|
27
|
+
*/
|
|
28
|
+
export declare function encodeWsFrameNodePing(requestId: string, sourceNodeId: string, sequenceNumber: number): WireU8;
|
|
29
|
+
export type WsFrameDecoded = {
|
|
30
|
+
type: 'node_register_ack';
|
|
31
|
+
requestId: string;
|
|
32
|
+
success: boolean;
|
|
33
|
+
assignedNodeId: string;
|
|
34
|
+
errorMessage: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'ask_response';
|
|
37
|
+
requestId: string;
|
|
38
|
+
success: boolean;
|
|
39
|
+
payloadJson: unknown;
|
|
40
|
+
errorMessage: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'tell_response';
|
|
43
|
+
requestId: string;
|
|
44
|
+
success: boolean;
|
|
45
|
+
errorMessage: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'heartbeat_ack';
|
|
48
|
+
requestId: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'node_ping_response';
|
|
51
|
+
requestId: string;
|
|
52
|
+
nodeId: string;
|
|
53
|
+
cpuPercent: number;
|
|
54
|
+
memoryAvailableMb: number;
|
|
55
|
+
availableCores: number;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'incoming_tell';
|
|
58
|
+
requestId: string;
|
|
59
|
+
actorId: string;
|
|
60
|
+
msgType: string;
|
|
61
|
+
payloadJson: unknown;
|
|
62
|
+
} | {
|
|
63
|
+
type: 'error';
|
|
64
|
+
requestId: string;
|
|
65
|
+
code: number;
|
|
66
|
+
message: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'unknown';
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Decode a binary WebSocket frame into a typed discriminated union.
|
|
72
|
+
* Unknown fields are skipped (forward-compatible).
|
|
73
|
+
*/
|
|
74
|
+
export declare function decodeWsFrame(bytes: Uint8Array): WsFrameDecoded;
|
|
75
|
+
export {};
|