@phreshos/node 0.1.4 → 0.1.5

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/events.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Subscribable } from "@phreshos/core";
2
2
  type Wait = (event: string | null, signal: AbortSignal, timeout?: number) => Promise<unknown>;
3
- /** Adapt authoritative one-event waits into the shared Subscribable contract. */
3
+ /** Adapts authoritative one-event waits into the shared Subscribable contract. */
4
4
  export default class Events<Definitions extends object, Fallback = never> {
5
5
  private readonly names;
6
6
  private readonly wait;
@@ -8,6 +8,6 @@ export default class Events<Definitions extends object, Fallback = never> {
8
8
  readonly subscribe: Subscribable<Definitions, Fallback>["subscribe"];
9
9
  readonly waitFor: Subscribable<Definitions, Fallback>["waitFor"];
10
10
  readonly events: Subscribable<Definitions, Fallback>["events"];
11
- readonly observe: Subscribable<Definitions, Fallback>["observe"];
11
+ private listen;
12
12
  }
13
13
  export {};
package/dist/events.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Adapt authoritative one-event waits into the shared Subscribable contract. */
1
+ /** Adapts authoritative one-event waits into the shared Subscribable contract. */
2
2
  export default class Events {
3
3
  names;
4
4
  wait;
@@ -6,73 +6,112 @@ export default class Events {
6
6
  this.names = names;
7
7
  this.wait = wait;
8
8
  }
9
- subscribe = ((event, subscriber) => {
10
- const controller = new AbortController();
11
- void (async () => {
12
- while (!controller.signal.aborted) {
13
- try {
14
- subscriber(await this.wait(event, controller.signal, 86_400_000));
15
- }
16
- catch (error) {
17
- if (!controller.signal.aborted && timeout(error))
18
- continue;
19
- if (!controller.signal.aborted)
20
- controller.abort();
21
- }
22
- }
23
- })();
24
- return () => controller.abort();
9
+ subscribe = ((eventOrSubscriber, subscriber) => {
10
+ if (typeof eventOrSubscriber === "string")
11
+ return this.listen(eventOrSubscriber, subscriber);
12
+ if (this.names.length) {
13
+ const stops = this.names.map(event => this.listen(event, message => eventOrSubscriber({ event, message })));
14
+ return () => stops.forEach(stop => stop());
15
+ }
16
+ return this.listen(null, value => {
17
+ const capture = value;
18
+ if (typeof capture.event === "string")
19
+ eventOrSubscriber({ event: capture.event, message: capture.payload });
20
+ });
25
21
  });
26
22
  waitFor = ((event, timeout) => {
27
23
  return this.wait(event, new AbortController().signal, timeout);
28
24
  });
29
- events = ((event, options = {}) => {
30
- const wait = this.wait;
31
- return (async function* () {
32
- const controller = new AbortController();
33
- const abort = () => controller.abort(options.signal?.reason);
34
- options.signal?.addEventListener("abort", abort, { once: true });
35
- try {
36
- while (!controller.signal.aborted) {
37
- try {
38
- yield await wait(event, controller.signal, 86_400_000);
39
- }
40
- catch (error) {
41
- if (!controller.signal.aborted && timeout(error))
42
- continue;
43
- throw error;
44
- }
45
- }
46
- }
47
- finally {
48
- options.signal?.removeEventListener("abort", abort);
49
- controller.abort();
25
+ events = ((eventOrOptions = {}, namedOptions = {}) => {
26
+ if (typeof eventOrOptions === "string") {
27
+ return stream((subscriber, impossible) => this.listen(eventOrOptions, subscriber, impossible), namedOptions);
28
+ }
29
+ return stream((subscriber, impossible) => {
30
+ if (this.names.length) {
31
+ const stops = this.names.map(event => this.listen(event, message => subscriber({ event, message }), impossible));
32
+ return () => stops.forEach(stop => stop());
50
33
  }
51
- })();
34
+ return this.listen(null, value => {
35
+ const capture = value;
36
+ if (typeof capture.event === "string")
37
+ subscriber({ event: capture.event, message: capture.payload });
38
+ }, impossible);
39
+ }, eventOrOptions);
52
40
  });
53
- observe = ((observer) => {
54
- if (this.names.length) {
55
- const stops = this.names.map(event => this.subscribe(event, message => observer({ event, message })));
56
- return () => stops.forEach(stop => stop());
57
- }
41
+ listen(event, subscriber, impossible) {
58
42
  const controller = new AbortController();
59
43
  void (async () => {
60
44
  while (!controller.signal.aborted) {
61
45
  try {
62
- const capture = await this.wait(null, controller.signal, 86_400_000);
63
- observer({ event: capture.event, message: capture.payload });
46
+ subscriber(await this.wait(event, controller.signal, 86_400_000));
64
47
  }
65
48
  catch (error) {
66
49
  if (!controller.signal.aborted && timeout(error))
67
50
  continue;
68
51
  if (!controller.signal.aborted)
69
- controller.abort();
52
+ impossible?.(failure(error));
53
+ controller.abort();
70
54
  }
71
55
  }
72
56
  })();
73
57
  return () => controller.abort();
74
- });
58
+ }
59
+ }
60
+ function stream(register, options = {}) {
61
+ const capacity = options.capacity ?? 64;
62
+ if (capacity !== Infinity && (!Number.isInteger(capacity) || capacity < 0)) {
63
+ throw new Error("An event queue capacity must be a non-negative integer or Infinity");
64
+ }
65
+ return (async function* () {
66
+ const queue = [];
67
+ let ended = false;
68
+ let failure = null;
69
+ let wake = null;
70
+ const stop = register(message => {
71
+ if (ended || failure)
72
+ return;
73
+ if (queue.length >= capacity)
74
+ failure = new Error(`Event queue exceeded its capacity of ${capacity}`);
75
+ else
76
+ queue.push(message);
77
+ wake?.();
78
+ wake = null;
79
+ }, error => {
80
+ if (ended || failure)
81
+ return;
82
+ failure = error;
83
+ wake?.();
84
+ wake = null;
85
+ });
86
+ const abort = () => {
87
+ ended = true;
88
+ wake?.();
89
+ wake = null;
90
+ };
91
+ options.signal?.addEventListener("abort", abort, { once: true });
92
+ if (options.signal?.aborted)
93
+ abort();
94
+ try {
95
+ while (!ended) {
96
+ if (queue.length) {
97
+ yield queue.shift();
98
+ continue;
99
+ }
100
+ if (failure)
101
+ throw failure;
102
+ await new Promise(resolve => { wake = resolve; });
103
+ }
104
+ }
105
+ finally {
106
+ ended = true;
107
+ stop();
108
+ options.signal?.removeEventListener("abort", abort);
109
+ }
110
+ })();
75
111
  }
76
112
  function timeout(error) {
77
113
  return error instanceof Error && /timeout|timed out/i.test(error.message);
78
114
  }
115
+ function failure(error) {
116
+ return error instanceof Error ? error : new Error(String(error));
117
+ }
package/dist/main.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { gatewayAddress } from "./address.js";
2
2
  export { Client, ClientService, Endpoint, Process, Program, Server, ServerService, System, type ProgramProcessRunEvent, type ProgramProcessRunOptions } from "./system.js";
3
- export { Service, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
3
+ export { Service, type ServiceKey, type ServiceLifecycle, type ServiceLifecycleEvents } from "@phreshos/core";
4
4
  export { resolveHome } from "./home.js";
5
5
  export { Project, type Manifest, type PackedProject, type ProjectMode, type ProjectOptions, type ProjectRunOptions } from "./project.js";
package/dist/system.js CHANGED
@@ -377,6 +377,7 @@ class EndpointOperations extends Events {
377
377
  system;
378
378
  owner;
379
379
  endpoint;
380
+ lifecycle;
380
381
  constructor(system, owner, endpoint) {
381
382
  super([], (event, signal, timeout) => event === null
382
383
  ? transport(system).api({ capability: "endpoint", operation: "wait", process: owner.identity, endpoint, event, timeout }, signal)
@@ -386,6 +387,9 @@ class EndpointOperations extends Events {
386
387
  this.system = system;
387
388
  this.owner = owner;
388
389
  this.endpoint = endpoint;
390
+ this.lifecycle = new Events(["start", "stop"], (event, signal, timeout) => {
391
+ return waitEndpointLifecycle(system, owner, endpoint, event, signal, timeout);
392
+ });
389
393
  }
390
394
  process() { return Promise.resolve(this.owner); }
391
395
  async exists() {
@@ -418,12 +422,14 @@ class ServerEndpoint extends ServerBase {
418
422
  system;
419
423
  owner;
420
424
  endpoint = "server";
425
+ lifecycle;
421
426
  base;
422
427
  constructor(system, owner) {
423
428
  super();
424
429
  this.system = system;
425
430
  this.owner = owner;
426
431
  this.base = new EndpointOperations(system, owner, "server");
432
+ this.lifecycle = this.base.lifecycle;
427
433
  bindEvents(this, this.base);
428
434
  }
429
435
  process() { return this.base.process(); }
@@ -450,11 +456,13 @@ class ServerEndpoint extends ServerBase {
450
456
  }
451
457
  class ClientEndpoint extends ClientBase {
452
458
  endpoint = "client";
459
+ lifecycle;
453
460
  window;
454
461
  base;
455
462
  constructor(system, owner) {
456
463
  super();
457
464
  this.base = new EndpointOperations(system, owner, "client");
465
+ this.lifecycle = this.base.lifecycle;
458
466
  bindEvents(this, this.base);
459
467
  this.window = new SystemWindow(system, owner);
460
468
  }
@@ -497,16 +505,17 @@ class SystemWindow extends Events {
497
505
  await transport(this.system).control({ capability: "window", operation, input: { process: this.process.identity, ...input } });
498
506
  }
499
507
  }
500
- class ServiceBase extends Events {
508
+ class ServiceBase {
501
509
  system;
502
510
  key;
503
511
  name;
512
+ lifecycle;
504
513
  constructor(system, key) {
505
- super(["enable", "disable"], (event, signal, timeout) => transport(system).api({
506
- capability: "service", operation: "wait", scope: "lifecycle", key, event, timeout
507
- }, signal));
508
514
  this.system = system;
509
515
  this.key = key;
516
+ this.lifecycle = new Events(["enable", "disable"], (event, signal, timeout) => transport(system).api({
517
+ capability: "service", operation: "wait", scope: "lifecycle", key, event, timeout
518
+ }, signal));
510
519
  this.name = key.name;
511
520
  }
512
521
  async enabled() { return await transport(this.system).api({ capability: "service", operation: "enabled", key: this.key }); }
@@ -517,18 +526,35 @@ export class ServerService extends CoreServerService {
517
526
  constructor() { super(); }
518
527
  }
519
528
  class ServerServiceHandle extends ServerService {
529
+ system;
530
+ key;
520
531
  name;
521
- channel;
532
+ lifecycle;
522
533
  base;
523
534
  constructor(system, key) {
524
535
  super();
536
+ this.system = system;
537
+ this.key = key;
525
538
  this.base = new ServiceBase(system, key);
526
539
  this.name = key.name;
527
- this.channel = new ServerServiceChannelHandle(system, key);
528
- Object.assign(this, eventsOf(this.base));
540
+ this.lifecycle = this.base.lifecycle;
541
+ bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
542
+ capability: "service", operation: "wait", scope: "events", key, event, timeout
543
+ }, signal)));
529
544
  }
530
545
  enabled() { return this.base.enabled(); }
531
546
  waitReady(timeout) { return this.base.waitReady(timeout); }
547
+ publish = (event, payload) => {
548
+ void transport(this.system).api({ capability: "service", operation: "publish", key: this.key, event, payload });
549
+ };
550
+ async ask(event, payload) {
551
+ return await transport(this.system).api({ capability: "service", operation: "ask", key: this.key, event, payload });
552
+ }
553
+ timeout(milliseconds) {
554
+ return { ask: (event, payload) => transport(this.system).api({
555
+ capability: "service", operation: "ask", key: this.key, event, payload, timeout: milliseconds
556
+ }) };
557
+ }
532
558
  }
533
559
  /** Node-SDK handle for a Service provided by a Client Endpoint. */
534
560
  export class ClientService extends CoreClientService {
@@ -536,40 +562,20 @@ export class ClientService extends CoreClientService {
536
562
  }
537
563
  class ClientServiceHandle extends ClientService {
538
564
  name;
539
- channel;
565
+ lifecycle;
540
566
  base;
541
567
  constructor(system, key) {
542
568
  super();
543
569
  this.base = new ServiceBase(system, key);
544
570
  this.name = key.name;
545
- this.channel = new ClientServiceChannelHandle(system, key);
546
- Object.assign(this, eventsOf(this.base));
571
+ this.lifecycle = this.base.lifecycle;
572
+ bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
573
+ capability: "service", operation: "wait", scope: "events", key, event, timeout
574
+ }, signal)));
547
575
  }
548
576
  enabled() { return this.base.enabled(); }
549
577
  waitReady(timeout) { return this.base.waitReady(timeout); }
550
578
  }
551
- class ClientServiceChannelHandle extends Events {
552
- system;
553
- key;
554
- constructor(system, key) {
555
- super([], (event, signal, timeout) => transport(system).api({ capability: "service", operation: "wait", scope: "channel", key, event, timeout }, signal));
556
- this.system = system;
557
- this.key = key;
558
- }
559
- }
560
- class ServerServiceChannelHandle extends ClientServiceChannelHandle {
561
- async ask(event, payload) {
562
- return await transport(this.system).api({ capability: "service", operation: "ask", key: this.key, event, payload });
563
- }
564
- timeout(milliseconds) {
565
- return { ask: (event, payload) => transport(this.system).api({
566
- capability: "service", operation: "ask", key: this.key, event, payload, timeout: milliseconds
567
- }) };
568
- }
569
- publish(event, payload) {
570
- void transport(this.system).api({ capability: "service", operation: "publish", key: this.key, event, payload });
571
- }
572
- }
573
579
  async function listProcesses(system, program) {
574
580
  const processes = [];
575
581
  let offset = 0;
@@ -590,6 +596,22 @@ async function* command(system, request) {
590
596
  };
591
597
  }
592
598
  }
599
+ async function waitEndpointLifecycle(system, owner, endpoint, event, signal, timeout = 10_000) {
600
+ if (event !== "start" && event !== "stop")
601
+ throw new Error(`An Endpoint lifecycle has no "${event}" event`);
602
+ const processEventName = event === "start" ? "endpointStart" : "endpointStop";
603
+ const deadline = Date.now() + timeout;
604
+ while (true) {
605
+ const value = await transport(system).control({
606
+ capability: "process",
607
+ operation: "wait",
608
+ input: { process: owner.identity, event: processEventName, timeout: Math.max(0, deadline - Date.now()) }
609
+ }, signal);
610
+ const changed = processEvent(system, value);
611
+ if (changed === owner[endpoint])
612
+ return undefined;
613
+ }
614
+ }
593
615
  function processEvent(system, value) {
594
616
  const waited = value;
595
617
  const payload = waited.payload;
@@ -615,8 +637,7 @@ function eventsOf(events) {
615
637
  return {
616
638
  subscribe: events.subscribe,
617
639
  waitFor: events.waitFor,
618
- events: events.events,
619
- observe: events.observe
640
+ events: events.events
620
641
  };
621
642
  }
622
643
  function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/node",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Node.js access to PhreshOS and Program projects.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -32,7 +32,7 @@
32
32
  "prepack": "node --run build"
33
33
  },
34
34
  "dependencies": {
35
- "@phreshos/core": "^0.1.25",
35
+ "@phreshos/core": "^0.1.26",
36
36
  "adm-zip": "^0.6.0",
37
37
  "jiti": "^2.7.0"
38
38
  },