@phreshos/server 0.1.0 → 0.1.1

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
@@ -1,10 +1,15 @@
1
1
  import Events from "./events.js";
2
- import { exit, process, program } from "./domain.js";
2
+ import { exit, lifecycleEndpoint, process, program } from "./domain.js";
3
3
  import serve from "./served.js";
4
+ import ServerTheme from "./theme.js";
5
+ import { ServerDesktopWallpaper, ServerSignInWallpaper } from "./wallpaper.js";
4
6
  import wire from "./wire.js";
5
7
  class ServerHost extends Events {
8
+ theme = new ServerTheme();
9
+ signInWallpaper = new ServerSignInWallpaper();
10
+ desktopWallpaper = new ServerDesktopWallpaper();
6
11
  constructor() {
7
- super((event, listener, impossible) => wire.on("host-end", event, (...values) => listener(hostEvent(event, values)), null, impossible), observer => wire.onAll("host-end", (event, ...values) => {
12
+ super((event, listener, impossible) => wire.on("host-events", event, (...values) => listener(hostEvent(event, values)), null, impossible), observer => wire.onAll("host-events", (event, ...values) => {
8
13
  if (typeof event === "string")
9
14
  observer(event, hostEvent(event, values));
10
15
  }));
@@ -24,7 +29,7 @@ class ServerHost extends Events {
24
29
  }
25
30
  async processes() {
26
31
  const answer = await wire.request(["processes"]);
27
- return answer[0].map(process);
32
+ return answer[0].map(record => process(record));
28
33
  }
29
34
  async getProcess(identity) {
30
35
  const answer = await wire.request(["process", identity]);
@@ -32,12 +37,11 @@ class ServerHost extends Events {
32
37
  }
33
38
  }
34
39
  function hostEvent(event, values) {
35
- if (event === "serverStart" || event === "clientStart" || event === "clientStop" || event === "processCreate") {
40
+ if (event === "endpointStart" || event === "endpointStop")
41
+ return lifecycleEndpoint(values[1], values[2]);
42
+ if (event === "processCreate") {
36
43
  return process(values[1]);
37
44
  }
38
- if (event === "serverStop") {
39
- return { process: process(values[1]), code: numberOrNull(values[2]), signal: stringOrNull(values[3]) };
40
- }
41
45
  if (event === "processExit") {
42
46
  return { process: process(values[1]), ...exit(values[2], values[3]) };
43
47
  }
@@ -49,6 +53,5 @@ function hostEvent(event, values) {
49
53
  }
50
54
  return values[0];
51
55
  }
52
- function numberOrNull(value) { return typeof value === "number" ? value : null; }
53
- function stringOrNull(value) { return typeof value === "string" ? value : null; }
56
+ /** Authoritative system capabilities for the currently executing Server. */
54
57
  export const host = new ServerHost();
package/dist/main.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { host, type Host, type ClientDescription, type HostEvents, type HostProcessExit, type HostProgramUninstall, type HostServerStop, type ProgramDescription, type ServerDescription } from "./host.js";
1
+ export { host, type Host, type ClientDescription, type HostEvents, type HostProcessExit, type HostProgramUninstall, type ProgramDescription, type ServerDescription } from "./host.js";
2
2
  export { current, type Current, type CurrentClient } from "./current.js";
3
3
  export { type Answerer, type Channel } from "./channel.js";
4
- export { Client, Endpoint, Process, Program, Server, Window, type ProgramArea } from "./domain.js";
5
- export type { AnswerCapture, AnswerMessage, AnswerObserver, Askable, AskCapture, AskMessage, AskObserver, Capture, Captures, ChannelCapture, ChannelEvents, ChannelMessage, ClientTraffic, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointTraffic, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, Position, ProgramEvents, ProgramProcessExit, ProgramServerStop, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, ServerTraffic, Size, Subscribable, TimedAskable, TrafficCapture, TrafficEvents, TrafficMessage, Value, WindowEvents, WindowState } from "@phreshos/core";
4
+ export { type ProgramStartup } from "./startup.js";
5
+ export { Client, Endpoint, Process, Program, Server, type Window, type ProgramArea } from "./domain.js";
6
+ export type { AnswerCapture, AnswerMessage, AnswerObserver, Askable, AskCapture, AskMessage, AskObserver, Capture, Captures, ChannelCapture, ChannelEvents, ChannelMessage, ClientTraffic, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointTraffic, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, Position, ProgramEvents, ProgramPermissions, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, ServerTraffic, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TrafficCapture, TrafficEvents, TrafficMessage, Value, Theme, ThemeEvents, ThemeProperties, WallpaperLaunch, FileWallpaper, DesktopWallpaper, WritableTheme, WindowEvents, WindowLayer, WindowState, WindowSurface, WindowSurfaceEasing, WindowSurfaceEvents, WindowSurfaceSettings, WindowSurfaceTransaction } from "@phreshos/core";
package/dist/main.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { host } from "./host.js";
2
2
  export { current } from "./current.js";
3
3
  export {} from "./channel.js";
4
- export { Client, Endpoint, Process, Program, Server, Window } from "./domain.js";
4
+ export {} from "./startup.js";
5
+ export { Client, Endpoint, Process, Program, Server } from "./domain.js";
@@ -0,0 +1,3 @@
1
+ import type { ProgramPermissions } from "@phreshos/core";
2
+ /** Creates the persistent permission manager for one Program handle. */
3
+ export default function permissions(program: unknown): ProgramPermissions;
@@ -0,0 +1,20 @@
1
+ import wire from "./wire.js";
2
+ /** Creates the persistent permission manager for one Program handle. */
3
+ export default function permissions(program) {
4
+ return {
5
+ async get(name) {
6
+ const answer = await wire.request(["permissions", program, "get", name]);
7
+ return answer[0];
8
+ },
9
+ async getAll() {
10
+ const answer = await wire.request(["permissions", program, "getAll"]);
11
+ return answer[0];
12
+ },
13
+ async set(name, value) {
14
+ await wire.request(["permissions", program, "set", name, value]);
15
+ },
16
+ async delete(name) {
17
+ await wire.request(["permissions", program, "delete", name]);
18
+ }
19
+ };
20
+ }
@@ -0,0 +1,13 @@
1
+ import type { Launch } from "@phreshos/core";
2
+ import type { HandleAddress } from "./domain.js";
3
+ /** Persistent Process creation settings used when the system starts. */
4
+ export interface ProgramStartup {
5
+ /** Returns the configured startup launch, or `null` when disabled. */
6
+ get(): Promise<Launch | null>;
7
+ /** Enables startup with one validated Process launch. */
8
+ enable(launch?: Launch): Promise<void>;
9
+ /** Disables startup by removing the persisted Process launch. */
10
+ disable(): Promise<void>;
11
+ }
12
+ /** Bind startup operations to one canonical Program handle. */
13
+ export default function startup(program: HandleAddress): ProgramStartup;
@@ -0,0 +1,16 @@
1
+ import wire from "./wire.js";
2
+ /** Bind startup operations to one canonical Program handle. */
3
+ export default function startup(program) {
4
+ return {
5
+ async get() {
6
+ const answer = await wire.request(["startup", program, "get"]);
7
+ return answer[0];
8
+ },
9
+ async enable(launch = {}) {
10
+ await wire.request(["startup", program, "enable", launch]);
11
+ },
12
+ async disable() {
13
+ await wire.request(["startup", program, "disable"]);
14
+ }
15
+ };
16
+ }
package/dist/storage.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import type { ProgramArea, ProgramSql, ProgramStore } from "@phreshos/core";
2
+ import type { HandleAddress } from "./domain.js";
2
3
  export interface ServerArea extends ProgramArea {
3
4
  path(): Promise<string>;
4
5
  resolve(...path: string[]): Promise<string>;
5
6
  }
6
7
  /** Server-local implementation of one Program-owned filesystem area. */
7
- export declare function area(program: string, which: "data" | "cache"): ServerArea;
8
- export declare function store(program: string): ProgramStore;
9
- export declare function sql(kind: "database" | "logs", program: string): ProgramSql;
8
+ export declare function area(program: HandleAddress, which: "data" | "cache"): ServerArea;
9
+ export declare function store(program: HandleAddress): ProgramStore;
10
+ export declare function sql(kind: "database" | "logs", program: HandleAddress): ProgramSql;
10
11
  export declare function content(value: unknown): {
11
12
  stream: ReadableStream<Uint8Array>;
12
13
  extension: string;
@@ -0,0 +1,15 @@
1
+ import { type ThemeProperties } from "@phreshos/core";
2
+ import Events from "./events.js";
3
+ /** System Theme authority reached explicitly through the server boundary. */
4
+ export default class ServerTheme extends Events {
5
+ constructor();
6
+ snapshot(): Promise<Readonly<{
7
+ background: string;
8
+ foreground: string;
9
+ accent: string;
10
+ spacing: number;
11
+ radius: number;
12
+ glass: import("@phreshos/core").ThemeGlass;
13
+ }>>;
14
+ readonly update: (theme: ThemeProperties) => Promise<void>;
15
+ }
package/dist/theme.js ADDED
@@ -0,0 +1,32 @@
1
+ import { createThemeSnapshot } from "@phreshos/core";
2
+ import Events from "./events.js";
3
+ import wire from "./wire.js";
4
+ /** System Theme authority reached explicitly through the server boundary. */
5
+ export default class ServerTheme extends Events {
6
+ constructor() {
7
+ super((event, listener, impossible) => wire.on("host-theme", event, value => {
8
+ if (isObject(value))
9
+ listener(createThemeSnapshot(value));
10
+ }, null, impossible), observer => wire.onAll("host-theme", (event, value) => {
11
+ if (typeof event === "string" && isObject(value))
12
+ observer(event, createThemeSnapshot(value));
13
+ }));
14
+ }
15
+ async snapshot() {
16
+ const answer = await wire.request(["theme"]);
17
+ if (!isObject(answer[0]))
18
+ throw new Error("The host returned an invalid Theme snapshot");
19
+ return createThemeSnapshot(answer[0]);
20
+ }
21
+ update = async (theme) => {
22
+ await wire.request(["update-theme", theme]);
23
+ };
24
+ }
25
+ function isObject(value) {
26
+ if (typeof value !== "object" || value === null || Array.isArray(value))
27
+ return false;
28
+ const theme = value;
29
+ return typeof theme.background === "string" && typeof theme.foreground === "string" && typeof theme.accent === "string"
30
+ && typeof theme.spacing === "number" && typeof theme.radius === "number"
31
+ && typeof theme.glass === "object" && theme.glass !== null;
32
+ }
@@ -0,0 +1,18 @@
1
+ import type { DesktopWallpaper, FileWallpaper, WallpaperLaunch } from "@phreshos/core";
2
+ import { type Program } from "./domain.js";
3
+ declare class ServerFileWallpaper implements FileWallpaper {
4
+ private readonly surface;
5
+ constructor(surface: "sign-in" | "desktop");
6
+ set(file: string): Promise<void>;
7
+ remove(): Promise<void>;
8
+ }
9
+ /** Authoritative sign-in wallpaper control available to Server endpoints. */
10
+ export declare class ServerSignInWallpaper extends ServerFileWallpaper {
11
+ constructor();
12
+ }
13
+ /** Authoritative desktop wallpaper control available to Server endpoints. */
14
+ export declare class ServerDesktopWallpaper extends ServerFileWallpaper implements DesktopWallpaper {
15
+ constructor();
16
+ setProgram(program: Program, launch?: WallpaperLaunch): Promise<void>;
17
+ }
18
+ export {};
@@ -0,0 +1,25 @@
1
+ import { programAddress } from "./domain.js";
2
+ import wire from "./wire.js";
3
+ class ServerFileWallpaper {
4
+ surface;
5
+ constructor(surface) {
6
+ this.surface = surface;
7
+ }
8
+ async set(file) {
9
+ await wire.request(["wallpaper", this.surface, "set", file]);
10
+ }
11
+ async remove() {
12
+ await wire.request(["wallpaper", this.surface, "remove"]);
13
+ }
14
+ }
15
+ /** Authoritative sign-in wallpaper control available to Server endpoints. */
16
+ export class ServerSignInWallpaper extends ServerFileWallpaper {
17
+ constructor() { super("sign-in"); }
18
+ }
19
+ /** Authoritative desktop wallpaper control available to Server endpoints. */
20
+ export class ServerDesktopWallpaper extends ServerFileWallpaper {
21
+ constructor() { super("desktop"); }
22
+ async setProgram(program, launch = {}) {
23
+ await wire.request(["wallpaper", "desktop", "set-program", programAddress(program), launch]);
24
+ }
25
+ }
package/dist/wire.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Cleanup } from "@phreshos/core";
2
2
  import Deadline from "./deadline.js";
3
+ import type { HandleAddress } from "./domain.js";
3
4
  type Handler = (...values: unknown[]) => unknown;
4
5
  type Failure = (error: Error) => void;
5
6
  type TrafficKind = "publish" | "ask" | "answer";
@@ -11,20 +12,24 @@ declare class Wire {
11
12
  private readonly answerers;
12
13
  private readonly waiting;
13
14
  private readonly impossible;
14
- readonly identity: Promise<{
15
- process: string;
16
- program: string;
17
- }>;
15
+ private identityPromise;
18
16
  constructor();
19
17
  send(route: string, ...values: unknown[]): void;
20
18
  request(values: unknown[], timeout?: number): Promise<unknown>;
21
19
  requestWithin(values: unknown[], deadline: Deadline): Promise<unknown>;
20
+ /** Resolves this endpoint's Process address only for operations that need it. */
21
+ identity(): Promise<{
22
+ process: string;
23
+ reference: string;
24
+ }>;
22
25
  expectWithin(question: string, deadline: Deadline): Promise<unknown>;
23
26
  forget(question: string): void;
24
27
  on(route: string, event: string, handler: Handler, subject?: string | null, impossible?: Failure): Cleanup;
25
28
  onAll(route: string, handler: Handler, subject?: string | null): Cleanup;
26
29
  answer(route: string, event: string, handler: Handler): Cleanup;
27
- observe(target: string, half: "server" | "client", kind: TrafficKind, event: string | null, handler: Handler, impossible?: Failure): Cleanup;
30
+ observe(target: HandleAddress | null, half: "server" | "client", kind: TrafficKind, event: string | null, handler: Handler, impossible?: Failure): Cleanup;
31
+ /** Follow destinationless events emitted by one Endpoint. */
32
+ follow(target: HandleAddress | null, half: "server" | "client", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
28
33
  private register;
29
34
  private unregister;
30
35
  private deliver;
package/dist/wire.js CHANGED
@@ -9,10 +9,8 @@ class Wire {
9
9
  answerers = new Map();
10
10
  waiting = new Map();
11
11
  impossible = new Map();
12
- identity;
12
+ identityPromise = null;
13
13
  constructor() {
14
- let identify = () => undefined;
15
- this.identity = new Promise(resolve => { identify = resolve; });
16
14
  process.on("message", message => {
17
15
  if (!Array.isArray(message))
18
16
  return;
@@ -27,10 +25,6 @@ class Wire {
27
25
  }
28
26
  return;
29
27
  }
30
- if (route === "host-end" && values[0] === "identity" && typeof values[1] === "string" && typeof values[2] === "string") {
31
- identify({ process: values[1], program: values[2] });
32
- return;
33
- }
34
28
  if (values[0] === "answer" && typeof values[1] === "string") {
35
29
  this.settle(values[1], values.at(-1));
36
30
  return;
@@ -41,7 +35,6 @@ class Wire {
41
35
  }
42
36
  this.deliver(route, values);
43
37
  });
44
- process.send?.(["boundary", "identity"]);
45
38
  process.send?.(["boundary", "ready"]);
46
39
  }
47
40
  send(route, ...values) {
@@ -63,6 +56,25 @@ class Wire {
63
56
  this.send("end-host", "wait", question, ...values);
64
57
  });
65
58
  }
59
+ /** Resolves this endpoint's Process address only for operations that need it. */
60
+ identity() {
61
+ if (!this.identityPromise) {
62
+ const resolving = this.request(["process"]).then(value => {
63
+ const [record] = value;
64
+ if (typeof record?.identity !== "string" || typeof record.reference !== "string") {
65
+ throw new Error("The host returned an invalid Process identity");
66
+ }
67
+ return { process: record.identity, reference: record.reference };
68
+ });
69
+ const retained = resolving.catch(error => {
70
+ if (this.identityPromise === retained)
71
+ this.identityPromise = null;
72
+ throw error;
73
+ });
74
+ this.identityPromise = retained;
75
+ }
76
+ return this.identityPromise;
77
+ }
66
78
  expectWithin(question, deadline) {
67
79
  return new Promise((resolve, reject) => {
68
80
  this.send("boundary", "expect", question);
@@ -131,6 +143,19 @@ class Wire {
131
143
  this.send("end-host", "unobserve", subscription);
132
144
  });
133
145
  }
146
+ /** Follow destinationless events emitted by one Endpoint. */
147
+ follow(target, half, event, handler, impossible) {
148
+ const subscription = randomUUID();
149
+ const stop = this.on("emitted", subscription, handler);
150
+ if (impossible)
151
+ this.impossible.set(subscription, impossible);
152
+ this.send("end-host", "follow", subscription, target, half, event, impossible !== undefined);
153
+ return once(() => {
154
+ stop();
155
+ this.impossible.delete(subscription);
156
+ this.send("end-host", "unfollow", subscription);
157
+ });
158
+ }
134
159
  register(kind, route, event, subject, impossible) {
135
160
  const subscription = randomUUID();
136
161
  if (impossible)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The SDK used by a Program's server endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -13,18 +13,43 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
+ "LICENSE",
16
17
  "README.md"
17
18
  ],
19
+ "author": "Zohayr SLILEH",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/PhreshOS/server.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/PhreshOS/server/issues"
27
+ },
28
+ "homepage": "https://github.com/PhreshOS/server#readme",
29
+ "keywords": [
30
+ "phreshos",
31
+ "server",
32
+ "sdk",
33
+ "typescript"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "provenance": true
38
+ },
39
+ "packageManager": "bun@1.3.14",
18
40
  "scripts": {
41
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
19
42
  "check": "tsc --noEmit",
20
- "build": "tsc --noEmit false --outDir dist --rootDir source",
43
+ "build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
44
+ "verify:package": "node scripts/verify-package.mjs",
45
+ "verify": "node --run check && node --run build && node --run verify:package",
21
46
  "prepack": "node --run build"
22
47
  },
23
48
  "peerDependencies": {
24
- "@phreshos/core": "^0.1.0"
49
+ "@phreshos/core": "^0.1.1"
25
50
  },
26
51
  "devDependencies": {
27
- "@phreshos/core": "0.1.0",
52
+ "@phreshos/core": "^0.1.1",
28
53
  "@types/node": "^26.2.0",
29
54
  "typescript": "^6.0.3"
30
55
  }