@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/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 from 'plexspaces:actor/host@0.1.0'.
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
- // @ts-ignore
18
- import { send as hostSend, ask as hostAsk, log as hostLog, nowMs as hostNowMs, selfId as hostSelfId, spawn as hostSpawn, stop as hostStop, link as hostLink, unlink as hostUnlink, monitor as hostMonitor, demonitor as hostDemonitor, sendAfter as hostSendAfter, kvGet as hostKvGet, kvPut as hostKvPut, kvDelete as hostKvDelete, kvList as hostKvList, tsWrite as hostTsWrite, tsRead as hostTsRead, tsTake as hostTsTake, tsReadAll as hostTsReadAll, lockAcquire as hostLockAcquire, lockRelease as hostLockRelease, lockRenew as hostLockRenew, blobUpload as hostBlobUpload, blobDownload as hostBlobDownload, blobDelete as hostBlobDelete, blobList as hostBlobList, pgJoin as hostPgJoin, pgLeave as hostPgLeave, pgMembers as hostPgMembers, pgBroadcast as hostPgBroadcast, poolCheckout as hostPoolCheckout, poolCheckin as hostPoolCheckin, poolGetMetrics as hostPoolGetMetrics, createShardGroup as hostCreateShardGroup, bulkUpdateShardGroup as hostBulkUpdateShardGroup, mapShardGroup as hostMapShardGroup, scatterGather as hostScatterGather, broadcastShardGroup as hostBroadcastShardGroup, reduceShardGroup as hostReduceShardGroup, allReduceShardGroup as hostAllReduceShardGroup, barrierShardGroup as hostBarrierShardGroup, spawnActors as hostSpawnActors, applicationMetricsAdd as hostApplicationMetricsAdd, applicationGetMetrics as hostApplicationGetMetrics, applicationGetStatus as hostApplicationGetStatus, httpFetch as hostHttpFetch,
19
- // @ts-expect-error Virtual import
20
- } from 'plexspaces:actor/host@0.1.0';
21
- // @ts-ignore
22
- import { register as hostRegistryRegister, unregister as hostRegistryUnregister, lookup as hostRegistryLookup, lookupByAlias as hostRegistryLookupByAlias, discover as hostRegistryDiscover, heartbeat as hostRegistryHeartbeat,
23
- // @ts-expect-error Virtual import
24
- } from 'plexspaces:actor/registry@0.1.0';
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 actorId - Unique ID for the new actor (empty = auto-generated ULID)
327
- * @param initConfig - Optional config passed to the new actor's init()
328
- * @returns Spawned actor ID string (may be auto-generated if actorId was empty)
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, actorId = '', initConfig) {
331
- const configBytes = encodeWitPayloadUtf8(initConfig !== undefined ? JSON.stringify(initConfig) : '{}');
332
- const result = safeCall(hostSpawn, moduleRef, actorId, configBytes);
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.kvPutJson(key, entry);
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.kvGet(cursorKey);
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.kvGetJson(`${prefix}seq:${seq}`);
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.kvPut(cursorKey, String(newCursor));
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
- * const http = new ServiceHttpClient("payments-api");
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 {};