@plexspaces/sdk 0.1.7 → 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/host.d.ts +103 -16
- package/dist/host.js +316 -101
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -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
|
|
@@ -425,71 +722,6 @@ export class Host {
|
|
|
425
722
|
const result = safeCall(hostNowMs);
|
|
426
723
|
return typeof result === 'bigint' ? Number(result) : (typeof result === 'number' ? result : 0);
|
|
427
724
|
}
|
|
428
|
-
// ========================================================================
|
|
429
|
-
// Key-Value Store
|
|
430
|
-
// ========================================================================
|
|
431
|
-
kvGet(key) {
|
|
432
|
-
if (typeof hostKvGet !== 'function')
|
|
433
|
-
return '';
|
|
434
|
-
try {
|
|
435
|
-
return decodeWitPayloadUtf8(hostKvGet(key));
|
|
436
|
-
}
|
|
437
|
-
catch (e) {
|
|
438
|
-
return `ERROR:${e}`;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
kvPut(key, value) {
|
|
442
|
-
if (typeof hostKvPut !== 'function')
|
|
443
|
-
return '';
|
|
444
|
-
try {
|
|
445
|
-
hostKvPut(key, encodeWitPayloadUtf8(value));
|
|
446
|
-
return '';
|
|
447
|
-
}
|
|
448
|
-
catch (e) {
|
|
449
|
-
return `ERROR:${e}`;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
kvDelete(key) {
|
|
453
|
-
if (typeof hostKvDelete !== 'function')
|
|
454
|
-
return '';
|
|
455
|
-
try {
|
|
456
|
-
hostKvDelete(key);
|
|
457
|
-
return '';
|
|
458
|
-
}
|
|
459
|
-
catch (e) {
|
|
460
|
-
return `ERROR:${e}`;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
kvList(prefix) {
|
|
464
|
-
if (typeof hostKvList !== 'function')
|
|
465
|
-
return '[]';
|
|
466
|
-
try {
|
|
467
|
-
return JSON.stringify(hostKvList(prefix));
|
|
468
|
-
}
|
|
469
|
-
catch (e) {
|
|
470
|
-
return `ERROR:${e}`;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
/** Retrieve a JSON value by key. Returns parsed object or null if not found. */
|
|
474
|
-
kvGetJson(key) {
|
|
475
|
-
const raw = this.kvGet(key);
|
|
476
|
-
if (!raw || raw.startsWith('ERROR:'))
|
|
477
|
-
return null;
|
|
478
|
-
try {
|
|
479
|
-
return JSON.parse(raw);
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
/** Serialize value to JSON and store under key. Throws on write failure. */
|
|
486
|
-
kvPutJson(key, value) {
|
|
487
|
-
const serialized = JSON.stringify(value);
|
|
488
|
-
const result = this.kvPut(key, serialized);
|
|
489
|
-
if (typeof result === 'string' && result.startsWith('ERROR:')) {
|
|
490
|
-
throw new Error(`kvPutJson(${key}): ${result}`);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
725
|
/** Increment a single named application metric counter by 1. Errors are swallowed. */
|
|
494
726
|
incrCounter(applicationId, name) {
|
|
495
727
|
this.incrCounters(applicationId, { [name]: 1 });
|
|
@@ -527,27 +759,6 @@ export class Host {
|
|
|
527
759
|
return hostPayloadToBytes(safeCall(hostTsReadAll, data));
|
|
528
760
|
}
|
|
529
761
|
// ========================================================================
|
|
530
|
-
// Distributed Locks
|
|
531
|
-
// ========================================================================
|
|
532
|
-
lockAcquire(tenantId, namespace, holderId, lockName, leaseDurationSecs = 30, timeoutMs = 0) {
|
|
533
|
-
return safeCall(hostLockAcquire, tenantId, namespace, holderId, lockName, leaseDurationSecs, BigInt(timeoutMs));
|
|
534
|
-
}
|
|
535
|
-
lockRelease(lockId, tenantId, namespace, holderId, lockVersion) {
|
|
536
|
-
return safeCall(hostLockRelease, lockId, tenantId, namespace, holderId, lockVersion);
|
|
537
|
-
}
|
|
538
|
-
lockRenew(lockId, tenantId, namespace, holderId, lockVersion, leaseDurationSecs = 30) {
|
|
539
|
-
return safeCall(hostLockRenew, lockId, tenantId, namespace, holderId, lockVersion, leaseDurationSecs);
|
|
540
|
-
}
|
|
541
|
-
// ========================================================================
|
|
542
|
-
// Blob Storage
|
|
543
|
-
// ========================================================================
|
|
544
|
-
blobUpload(blobId, data, contentType = 'application/octet-stream') {
|
|
545
|
-
return safeCall(hostBlobUpload, blobId, data, contentType);
|
|
546
|
-
}
|
|
547
|
-
blobDownload(blobId) { return safeCall(hostBlobDownload, blobId); }
|
|
548
|
-
blobDelete(blobId) { return safeCall(hostBlobDelete, blobId); }
|
|
549
|
-
blobList(prefix) { return safeCall(hostBlobList, prefix); }
|
|
550
|
-
// ========================================================================
|
|
551
762
|
// Elastic pool (checkout/checkin)
|
|
552
763
|
// ========================================================================
|
|
553
764
|
/**
|
|
@@ -756,7 +967,7 @@ export class EventLog {
|
|
|
756
967
|
this.watermark++;
|
|
757
968
|
const key = `${prefix}seq:${this.watermark}`;
|
|
758
969
|
try {
|
|
759
|
-
h.
|
|
970
|
+
h.kv.putJson(key, entry);
|
|
760
971
|
}
|
|
761
972
|
catch (e) {
|
|
762
973
|
this.watermark--;
|
|
@@ -770,19 +981,19 @@ export class EventLog {
|
|
|
770
981
|
*/
|
|
771
982
|
poll(h, prefix, consumerId, limit = 100) {
|
|
772
983
|
const cursorKey = `${prefix}cursor:${consumerId}`;
|
|
773
|
-
const rawCursor = h.
|
|
984
|
+
const rawCursor = h.kv.get(cursorKey);
|
|
774
985
|
const cursor = rawCursor ? parseInt(rawCursor, 10) || 0 : 0;
|
|
775
986
|
const events = [];
|
|
776
987
|
let newCursor = cursor;
|
|
777
988
|
for (let seq = cursor + 1; seq <= this.watermark && events.length < limit; seq++) {
|
|
778
|
-
const entry = h.
|
|
989
|
+
const entry = h.kv.getJson(`${prefix}seq:${seq}`);
|
|
779
990
|
if (entry !== null) {
|
|
780
991
|
events.push(entry);
|
|
781
992
|
newCursor = seq;
|
|
782
993
|
}
|
|
783
994
|
}
|
|
784
995
|
if (newCursor !== cursor) {
|
|
785
|
-
h.
|
|
996
|
+
h.kv.put(cursorKey, String(newCursor));
|
|
786
997
|
}
|
|
787
998
|
return [events, newCursor];
|
|
788
999
|
}
|
|
@@ -793,9 +1004,13 @@ export class EventLog {
|
|
|
793
1004
|
* The link must be pre-configured in RuntimeConfig.service_links.
|
|
794
1005
|
* The host handles retries, circuit breaking, and auth injection.
|
|
795
1006
|
*
|
|
1007
|
+
* Prefer creating instances via {@link Host.httpClient} rather than calling
|
|
1008
|
+
* the constructor directly:
|
|
1009
|
+
*
|
|
796
1010
|
* @example
|
|
797
1011
|
* ```typescript
|
|
798
|
-
*
|
|
1012
|
+
* // Preferred: use the factory method
|
|
1013
|
+
* const http = host.httpClient("payments-api");
|
|
799
1014
|
* const balance = http.get("/v1/balance?account=123");
|
|
800
1015
|
* const result = http.post("/v1/transfer", { amount: 100 });
|
|
801
1016
|
* ```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +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";
|
|
9
10
|
export { AgentLoop, agentActor, defaultAgentConfig, type AgentConfig, type AgentStep, type AgentTrajectory, type AgentStepKind, } from "./agent.js";
|
package/dist/index.js
CHANGED
|
@@ -7,9 +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";
|
|
15
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 {};
|