@phreshos/server 0.1.17 → 0.1.19

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 CHANGED
@@ -10,47 +10,38 @@ active testing. The architecture's components will be released in stages as
10
10
  their contracts and integrations are verified.
11
11
 
12
12
  `@phreshos/server` is not intended to be used on its own. It requires the
13
- shared contracts from `@phreshos/core` and a compatible system host to provide
13
+ shared contracts from `@phreshos/core` and a compatible System runtime to provide
14
14
  its runtime boundary.
15
15
 
16
16
  It uses the domain objects and shared contracts from `@phreshos/core` through
17
17
  a peer dependency. It does not redefine those objects, own client-side
18
- capabilities, or contain host and transport implementations.
19
-
20
- Its `Host` contract exposes the observable system Theme with replacement
21
- authority, authoritative Program and Process discovery, runtime Program
22
- creation, lifecycle events, and publicly served values. `host.theme.snapshot()`
23
- explicitly and asynchronously reads the current value,
24
- `subscribe("change", listener)` receives only complete replacements published
25
- after registration, and `update()` asynchronously validates and replaces the
26
- Theme through the system authority. A subscription has no initial delivery or
27
- replay.
28
-
29
- `host.signInWallpaper` and `host.desktopWallpaper` are independent direct Host
30
- capabilities. A served image or HTML file is selected by its generated
31
- filename, while the desktop may instead select a Program that declares a
32
- Client:
18
+ capabilities, or contain System and transport implementations.
19
+
20
+ Its `System` contract exposes the complete unresolved Appearance with
21
+ replacement authority, authoritative Program and Process discovery, runtime
22
+ Program creation, lifecycle events, and flat public uploads.
23
+ `system.appearance.snapshot()` explicitly reads current authoritative state;
24
+ `subscribe("change", listener)` receives only replacements published after
25
+ registration, and `update()` validates and persists a complete replacement.
26
+ Wallpaper filenames are ordinary Appearance values rather than separate SDK
27
+ capabilities:
33
28
 
34
29
  ```ts
35
- const served = await host.serve(file)
36
-
37
- await host.signInWallpaper.set(served.file)
38
- await host.desktopWallpaper.set(served.file)
39
- await host.desktopWallpaper.setProgram(program, {
40
- name: "wallpaper",
41
- server: false,
42
- client: { location: "/ambient" },
43
- options: { mode: "calm" }
30
+ const upload = await system.uploads.write(file)
31
+ const bytes = await system.uploads.bytes(upload.file)
32
+ const appearance = await system.appearance.snapshot()
33
+
34
+ await system.appearance.update({
35
+ ...appearance,
36
+ desktopWallpaper: {
37
+ ...appearance.desktopWallpaper,
38
+ light: upload.file
39
+ }
44
40
  })
45
41
 
46
- await host.signInWallpaper.remove()
47
- await host.desktopWallpaper.remove()
42
+ void bytes
48
43
  ```
49
44
 
50
- Selecting or removing a desktop choice exits the previous wallpaper Process.
51
- Its Client and visual state are system-managed; complete Process exit remains
52
- available and reveals the bundled desktop fallback.
53
-
54
45
  Its JavaScript entry point adapts the Process IPC boundary to these contracts.
55
46
  The SDK owns callbacks, waits, queues, and their cleanup; the boundary owns
56
47
  only the forwarding registrations requested by the SDK.
@@ -84,7 +75,7 @@ resulting desktop material.
84
75
  The package provides two contextual runtime entry points:
85
76
 
86
77
  ```ts
87
- import { host, current } from "@phreshos/server"
78
+ import { system, current } from "@phreshos/server"
88
79
  ```
89
80
 
90
81
  It also re-exports the shared Core runtime classes—`Program`, `Process`,
@@ -115,26 +106,26 @@ generator that opens immediately, yields ordered `stdout` and `stderr` command
115
106
  chunks, completes only after installation succeeds, and throws the owning
116
107
  System error when installation fails. Their `Storage` values also expose
117
108
  `path()` and traversal-safe `resolve()`, because filesystem work is performed
118
- locally in the Server SDK after the Host supplies only the area root.
119
- Object descriptions passed to `host.program.create()` therefore require an
109
+ locally in the Server SDK after the System supplies only the area root.
110
+ Object descriptions passed to `system.program.create()` therefore require an
120
111
  explicit absolute storage root as well as at least one declared Endpoint.
121
112
 
122
- `host.storage` implements that same refined `Storage` contract against the
113
+ `system.storage` implements that same refined `Storage` contract against the
123
114
  native operating-system home directory. The System supplies the authoritative
124
115
  root; traversal and symbolic-link escape remain rejected by the SDK before any
125
116
  filesystem operation.
126
117
 
127
- Host registries are separated by owner. Reads, commands, and the complete
118
+ System registries are separated by owner. Reads, commands, and the complete
128
119
  subscription contract live on their relevant capability rather than directly
129
- on `host`:
120
+ on `system`:
130
121
 
131
122
  ```ts
132
- const programs = await host.program.list()
133
- const program = await host.program.find("counter")
134
- const processes = await host.process.list()
123
+ const programs = await system.program.list()
124
+ const program = await system.program.find("counter")
125
+ const processes = await system.process.list()
135
126
 
136
- const stopPrograms = host.program.subscribe("create", value => undefined)
137
- const stopProcesses = host.process.subscribe("exit", value => undefined)
127
+ const stopPrograms = system.program.subscribe("create", value => undefined)
128
+ const stopProcesses = system.process.subscribe("exit", value => undefined)
138
129
  ```
139
130
 
140
131
  A Program owns its scoped Process capability:
@@ -154,7 +145,7 @@ const shared = await program.process.findOrCreate({
154
145
  launches converge on one named Process; a different launch for that name
155
146
  rejects instead of reconfiguring the existing Process.
156
147
 
157
- `host.service(key)` creates the exact opaque service handle. The
148
+ `system.service(key)` creates the exact opaque service handle. The
158
149
  handler exposes its authored `name`, `enabled()`, `waitReady()`, lifecycle
159
150
  subscriptions, and channel; it does not expose its Program or Endpoint as
160
151
  separate fields. Services are runtime bindings explicitly enabled by their
@@ -0,0 +1,17 @@
1
+ import { type Appearance } from "@phreshos/core";
2
+ import Events from "./events.js";
3
+ /** System Appearance authority reached explicitly through the Server boundary. */
4
+ export default class ServerAppearance extends Events {
5
+ constructor();
6
+ snapshot(): Promise<Readonly<{
7
+ background: import("@phreshos/core").ThemedValue<string, string>;
8
+ foreground: import("@phreshos/core").ThemedValue<string, string>;
9
+ accent: import("@phreshos/core").ThemedValue<string, string>;
10
+ spacing: import("@phreshos/core").ThemedValue<number>;
11
+ radius: import("@phreshos/core").ThemedValue<number>;
12
+ surface: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceSurface, import("@phreshos/core").AppearanceSurface>;
13
+ signInWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
14
+ desktopWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
15
+ }>>;
16
+ readonly update: (appearance: Appearance) => Promise<void>;
17
+ }
@@ -0,0 +1,21 @@
1
+ import { createAppearanceSnapshot } from "@phreshos/core";
2
+ import Events from "./events.js";
3
+ import wire from "./wire.js";
4
+ /** System Appearance authority reached explicitly through the Server boundary. */
5
+ export default class ServerAppearance extends Events {
6
+ constructor() {
7
+ super((event, listener, impossible) => wire.on("host-appearance", event, value => {
8
+ listener(createAppearanceSnapshot(value));
9
+ }, null, impossible), observer => wire.onAll("host-appearance", (event, value) => {
10
+ if (typeof event === "string")
11
+ observer(event, createAppearanceSnapshot(value));
12
+ }));
13
+ }
14
+ async snapshot() {
15
+ const [appearance] = await wire.request(["appearance"]);
16
+ return createAppearanceSnapshot(appearance);
17
+ }
18
+ update = async (appearance) => {
19
+ await wire.request(["update-appearance", appearance]);
20
+ };
21
+ }
package/dist/channel.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- import type { Channel as CoreChannel, ChannelMessage, Cleanup } from "@phreshos/core";
1
+ import type { Channel as CoreChannel, ChannelCapture as CoreChannelCapture, ChannelEvents as CoreChannelEvents, ChannelMessage as CoreChannelMessage, Cleanup } from "@phreshos/core";
2
2
  import { type Endpoint } from "./domain.js";
3
+ /** One value addressed to the current Server, with a server-visible sender. */
4
+ export type ChannelMessage<Payload = unknown> = CoreChannelMessage<Payload, Endpoint | null>;
3
5
  /** Handles one question addressed to the current Server. */
4
6
  export type Answerer<Payload = unknown, Result = undefined> = (message: ChannelMessage<Payload>) => Result | Promise<Result>;
7
+ /** Applies the server-visible sender envelope to known Channel events. */
8
+ export type ChannelEvents<Events extends object> = CoreChannelEvents<Events, Endpoint | null>;
9
+ /** Every event observable through the current Server's Channel. */
10
+ export type ChannelCapture<Events extends object = {}> = CoreChannelCapture<Events, Endpoint | null>;
5
11
  /** Events and questions explicitly accepted by the current Server. */
6
- export interface Channel<Events extends object = {}> extends CoreChannel<Events, Endpoint> {
12
+ export interface Channel<Events extends object = {}> extends CoreChannel<Events, Endpoint | null> {
7
13
  /** Registers one answerer; omitting its return produces `undefined`. */
8
14
  answer<Payload = unknown, Result = undefined>(event: string, answerer: Answerer<Payload, Result>): Cleanup;
9
15
  }
package/dist/channel.js CHANGED
@@ -20,6 +20,6 @@ class ServerChannel extends Events {
20
20
  }
21
21
  function message(value) {
22
22
  const raw = value;
23
- return { from: endpoint(raw.from), payload: raw.payload };
23
+ return { from: raw.from ? endpoint(raw.from) : null, payload: raw.payload };
24
24
  }
25
25
  export const channel = new ServerChannel();
package/dist/domain.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface HandleAddress {
7
7
  identity: string;
8
8
  reference: string;
9
9
  }
10
- /** Client-safe Program data transported by the authoritative host. */
10
+ /** Client-safe Program data transported by the authoritative system. */
11
11
  export interface EndpointDeclarationRecord {
12
12
  start: boolean;
13
13
  }
package/dist/domain.js CHANGED
@@ -91,7 +91,7 @@ class ProgramHandle extends ProgramBase {
91
91
  function programCommandChunk(value) {
92
92
  const chunk = value;
93
93
  if (!chunk || (chunk.stream !== "stdout" && chunk.stream !== "stderr") || typeof chunk.text !== "string") {
94
- throw new Error("The host returned an invalid Program command chunk");
94
+ throw new Error("The system returned an invalid Program command chunk");
95
95
  }
96
96
  return Object.freeze({ stream: chunk.stream, text: chunk.text });
97
97
  }
@@ -415,7 +415,7 @@ export function lifecycleEndpoint(record, kind) {
415
415
  return owner.server;
416
416
  if (kind === "client")
417
417
  return owner.client;
418
- throw new Error("The host returned an invalid Endpoint lifecycle event");
418
+ throw new Error("The system returned an invalid Endpoint lifecycle event");
419
419
  }
420
420
  export function exit(code, signal) {
421
421
  const namedSignal = stringOrNull(signal);
package/dist/main.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export { host, type Host, type HostProcess, type HostProgram, type ClientDescription, type ProcessHostEvents, type ProcessHostExit, type ProgramHostEvents, type ProgramHostUninstall, type ProgramDescription, type ServerDescription } from "./host.js";
1
+ export { system, type System, type SystemProcess, type SystemProgram, type ClientDescription, type SystemProcessEvents, type SystemProcessExit, type SystemProgramEvents, type SystemProgramUninstall, type ProgramDescription, type ServerDescription } from "./system.js";
2
2
  export { current, type Current, type CurrentClient } from "./current.js";
3
- export { type Answerer, type Channel } from "./channel.js";
3
+ export { type Answerer, type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
4
4
  export { type ProgramStartup } from "./startup.js";
5
5
  export { type Storage } from "./storage.js";
6
6
  export { ClientServiceHandler, ServerServiceHandler } from "./service.js";
7
7
  export { ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
8
8
  export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess } from "./domain.js";
9
- 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, ProgramPermission, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, ServerTraffic, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TrafficCapture, TrafficEvents, TrafficMessage, Value, Theme, ThemeEvents, ThemeProperties, WallpaperLaunch, FileWallpaper, DesktopWallpaper, WritableTheme, WindowEvents, WindowGeometry, WindowLayer, WindowState } from "@phreshos/core";
9
+ export type { AnswerCapture, AnswerMessage, AnswerObserver, Askable, AskCapture, AskMessage, AskObserver, Capture, Captures, 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, ProgramPermission, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, ServerTraffic, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TrafficCapture, TrafficEvents, TrafficMessage, Value, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, ThemedValue, WritableAppearance, WindowEvents, WindowGeometry, WindowLayer, WindowState } from "@phreshos/core";
package/dist/main.js CHANGED
@@ -1,4 +1,4 @@
1
- export { host } from "./host.js";
1
+ export { system } from "./system.js";
2
2
  export { current } from "./current.js";
3
3
  export {} from "./channel.js";
4
4
  export {} from "./startup.js";
package/dist/storage.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface Storage extends CoreStorage {
7
7
  /** Server-local implementation of one Program-owned filesystem area. */
8
8
  export declare function area(program: HandleAddress, which: "data" | "cache"): Storage;
9
9
  /** Server-local access to the native home directory supplied by the System. */
10
- export declare function hostStorage(): Storage;
10
+ export declare function systemStorage(): Storage;
11
11
  export declare function store(program: HandleAddress): ProgramStore;
12
12
  export declare function sql(kind: "database" | "logs", program: HandleAddress): ProgramSql;
13
13
  export declare function content(value: unknown): {
package/dist/storage.js CHANGED
@@ -13,7 +13,7 @@ export function area(program, which) {
13
13
  }, `this Program's ${which}`);
14
14
  }
15
15
  /** Server-local access to the native home directory supplied by the System. */
16
- export function hostStorage() {
16
+ export function systemStorage() {
17
17
  return createStorage(async function () {
18
18
  const answer = await wire.request(["host-storage", "path"]);
19
19
  return answer[0];
@@ -25,7 +25,7 @@ function createStorage(root, label) {
25
25
  if (!resolvedRoot) {
26
26
  const resolving = root().then(value => {
27
27
  if (!isAbsolute(value))
28
- throw new Error("The host returned an invalid Storage directory");
28
+ throw new Error("The system returned an invalid Storage directory");
29
29
  return value;
30
30
  });
31
31
  const retained = resolving.catch(error => {
@@ -1,4 +1,4 @@
1
- import type { DesktopWallpaper, Exit, FileWallpaper, Layer, Position, ServedFile, ServiceKey, Size, Subscribable, ThemeProperties, WritableTheme } from "@phreshos/core";
1
+ import type { Exit, Layer, Position, ServiceKey, Size, Subscribable, SystemUploads, WritableAppearance } from "@phreshos/core";
2
2
  import type { ClientServiceHandler, ServerServiceHandler } from "./service.js";
3
3
  import { type Client, type Process, type Program, type Server } from "./domain.js";
4
4
  import { type Storage } from "./storage.js";
@@ -59,19 +59,19 @@ export type ProgramDescription = Description & (Readonly<{
59
59
  client: ClientDescription;
60
60
  }>);
61
61
  /** An uninstall reported with the affected Program and removal scope. */
62
- export type ProgramHostUninstall = Readonly<{
62
+ export type SystemProgramUninstall = Readonly<{
63
63
  /** Program that left the installed state. */
64
64
  program: Program;
65
65
  /** Whether all installed resources, including storage, were removed. */
66
66
  everythingRemoved: boolean;
67
67
  }>;
68
68
  /** A Process exit reported with the Process that ended. */
69
- export type ProcessHostExit = Exit & Readonly<{
69
+ export type SystemProcessExit = Exit & Readonly<{
70
70
  /** Process that ended. */
71
71
  process: Process;
72
72
  }>;
73
- /** Authoritative lifecycle events visible to the Server host. */
74
- export type ProgramHostEvents = {
73
+ /** Authoritative lifecycle events visible to the Server system. */
74
+ export type SystemProgramEvents = {
75
75
  /** A Program entered the runtime registry. */
76
76
  create: Program;
77
77
  /** A Program left the runtime registry. */
@@ -79,10 +79,10 @@ export type ProgramHostEvents = {
79
79
  /** A Program entered the installed state. */
80
80
  install: Program;
81
81
  /** A Program left the installed state. */
82
- uninstall: ProgramHostUninstall;
82
+ uninstall: SystemProgramUninstall;
83
83
  };
84
- /** Authoritative Process lifecycle events visible to the Server host. */
85
- export type ProcessHostEvents = {
84
+ /** Authoritative Process lifecycle events visible to the Server system. */
85
+ export type SystemProcessEvents = {
86
86
  /** One Process Endpoint entered a new live incarnation. */
87
87
  endpointStart: Server | Client;
88
88
  /** One Process Endpoint incarnation ended. */
@@ -90,31 +90,29 @@ export type ProcessHostEvents = {
90
90
  /** A Process entered the runtime set. */
91
91
  create: Process;
92
92
  /** A Process left the runtime set. */
93
- exit: ProcessHostExit;
93
+ exit: SystemProcessExit;
94
94
  };
95
95
  /** Authoritative Program registry available to a Server endpoint. */
96
- export interface HostProgram extends Subscribable<ProgramHostEvents, never> {
96
+ export interface SystemProgram extends Subscribable<SystemProgramEvents, never> {
97
97
  list(onlyInstalled?: boolean): Promise<Program[]>;
98
98
  find(identity: string): Promise<Program | null>;
99
99
  create(source: ProgramDescription | string): Promise<Program>;
100
100
  }
101
101
  /** Authoritative Process registry available to a Server endpoint. */
102
- export interface HostProcess extends Subscribable<ProcessHostEvents, never> {
102
+ export interface SystemProcess extends Subscribable<SystemProcessEvents, never> {
103
103
  list(): Promise<Process[]>;
104
104
  find(identity: string): Promise<Process | null>;
105
105
  }
106
106
  /** Authoritative system capabilities available to a Server endpoint. */
107
- export interface Host {
107
+ export interface System {
108
108
  /** Native operating-system home storage available to Server endpoints. */
109
109
  readonly storage: Storage;
110
- /** Observable system Theme authority. */
111
- readonly theme: WritableTheme<ThemeProperties>;
112
- /** Authoritative wallpaper visible before authentication. */
113
- readonly signInWallpaper: FileWallpaper;
114
- /** Authoritative wallpaper visible within authenticated desktops. */
115
- readonly desktopWallpaper: DesktopWallpaper;
116
- readonly program: HostProgram;
117
- readonly process: HostProcess;
110
+ /** Complete unresolved Appearance authority owned by the System. */
111
+ readonly appearance: WritableAppearance;
112
+ readonly program: SystemProgram;
113
+ readonly process: SystemProcess;
114
+ /** Flat System-owned public uploads capability. */
115
+ readonly uploads: SystemUploads;
118
116
  /** Returns a stable handle for one exact Service identity. */
119
117
  service<ServiceEvents extends object = {}>(key: ServiceKey & {
120
118
  endpoint: "server";
@@ -122,9 +120,7 @@ export interface Host {
122
120
  service<ServiceEvents extends object = {}>(key: ServiceKey & {
123
121
  endpoint: "client";
124
122
  }): ClientServiceHandler<ServiceEvents>;
125
- /** Publishes a value through the host and returns its public file metadata. */
126
- serve(value: unknown): Promise<ServedFile>;
127
123
  }
128
124
  /** Authoritative system capabilities for the currently executing Server. */
129
- export declare const host: Host;
125
+ export declare const system: System;
130
126
  export {};
@@ -1,26 +1,23 @@
1
1
  import Events from "./events.js";
2
2
  import { exit, lifecycleEndpoint, process, program } from "./domain.js";
3
- import serve from "./served.js";
4
- import ServerTheme from "./theme.js";
5
- import { ServerDesktopWallpaper, ServerSignInWallpaper } from "./wallpaper.js";
3
+ import { uploads } from "./uploads.js";
4
+ import ServerAppearance from "./appearance.js";
6
5
  import wire from "./wire.js";
7
6
  import { prepareService } from "./service.js";
8
- import { hostStorage } from "./storage.js";
9
- class ServerHost {
10
- storage = hostStorage();
11
- theme = new ServerTheme();
12
- signInWallpaper = new ServerSignInWallpaper();
13
- desktopWallpaper = new ServerDesktopWallpaper();
14
- program = new ServerHostProgram();
15
- process = new ServerHostProcess();
7
+ import { systemStorage } from "./storage.js";
8
+ class ServerSystem {
9
+ storage = systemStorage();
10
+ appearance = new ServerAppearance();
11
+ program = new ServerSystemProgram();
12
+ process = new ServerSystemProcess();
13
+ uploads = uploads;
16
14
  service(key) { return prepareService(key); }
17
- serve(value) { return serve(value); }
18
15
  }
19
- class ServerHostProgram extends Events {
16
+ class ServerSystemProgram extends Events {
20
17
  constructor() {
21
- super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(hostProgramEvent(event, values)), null, impossible), observer => wire.onAll("host-program", (event, ...values) => {
18
+ super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(systemProgramEvent(event, values)), null, impossible), observer => wire.onAll("host-program", (event, ...values) => {
22
19
  if (typeof event === "string")
23
- observer(event, hostProgramEvent(event, values));
20
+ observer(event, systemProgramEvent(event, values));
24
21
  }));
25
22
  }
26
23
  async list(onlyInstalled = false) {
@@ -36,11 +33,11 @@ class ServerHostProgram extends Events {
36
33
  return program(answer[0]);
37
34
  }
38
35
  }
39
- class ServerHostProcess extends Events {
36
+ class ServerSystemProcess extends Events {
40
37
  constructor() {
41
- super((event, listener, impossible) => wire.on("host-process", event, (...values) => listener(hostProcessEvent(event, values)), null, impossible), observer => wire.onAll("host-process", (event, ...values) => {
38
+ super((event, listener, impossible) => wire.on("host-process", event, (...values) => listener(systemProcessEvent(event, values)), null, impossible), observer => wire.onAll("host-process", (event, ...values) => {
42
39
  if (typeof event === "string")
43
- observer(event, hostProcessEvent(event, values));
40
+ observer(event, systemProcessEvent(event, values));
44
41
  }));
45
42
  }
46
43
  async list() {
@@ -52,7 +49,7 @@ class ServerHostProcess extends Events {
52
49
  return answer[0] ? process(answer[0]) : null;
53
50
  }
54
51
  }
55
- function hostProcessEvent(event, values) {
52
+ function systemProcessEvent(event, values) {
56
53
  if (event === "endpointStart" || event === "endpointStop")
57
54
  return lifecycleEndpoint(values[1], values[2]);
58
55
  if (event === "create") {
@@ -63,7 +60,7 @@ function hostProcessEvent(event, values) {
63
60
  }
64
61
  return values[0];
65
62
  }
66
- function hostProgramEvent(event, values) {
63
+ function systemProgramEvent(event, values) {
67
64
  if (event === "create" || event === "forget" || event === "install") {
68
65
  return program(values[1]);
69
66
  }
@@ -73,4 +70,4 @@ function hostProgramEvent(event, values) {
73
70
  return values[0];
74
71
  }
75
72
  /** Authoritative system capabilities for the currently executing Server. */
76
- export const host = new ServerHost();
73
+ export const system = new ServerSystem();
@@ -0,0 +1,14 @@
1
+ import { type SystemUploads, type Upload } from "@phreshos/core";
2
+ /** Flat upload access performed locally by a Server Endpoint. */
3
+ declare class ServerUploads implements SystemUploads {
4
+ private accessPromise;
5
+ write(value: unknown): Promise<Upload>;
6
+ stream(file: string): Promise<ReadableStream<Uint8Array>>;
7
+ bytes(file: string): Promise<Uint8Array<ArrayBuffer>>;
8
+ text(file: string): Promise<string>;
9
+ json<Value>(file: string): Promise<Value>;
10
+ stat(file: string): Promise<Upload | null>;
11
+ private access;
12
+ }
13
+ export declare const uploads: ServerUploads;
14
+ export {};
@@ -0,0 +1,84 @@
1
+ import { isUploadFile } from "@phreshos/core";
2
+ import { randomUUID } from "node:crypto";
3
+ import { createReadStream, createWriteStream, mkdirSync } from "node:fs";
4
+ import { rename, rm } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
8
+ import { content } from "./storage.js";
9
+ import wire from "./wire.js";
10
+ /** Flat upload access performed locally by a Server Endpoint. */
11
+ class ServerUploads {
12
+ accessPromise = null;
13
+ async write(value) {
14
+ const access = await this.access();
15
+ const source = content(value);
16
+ const identity = randomUUID();
17
+ const file = `${identity}.${source.extension}`;
18
+ const temporary = join(access.root, `.${identity}.uploading`);
19
+ const destination = join(access.root, file);
20
+ let size = 0;
21
+ mkdirSync(access.root, { recursive: true });
22
+ try {
23
+ await pipeline(Readable.fromWeb(source.stream), async function* (chunks) {
24
+ for await (const chunk of chunks) {
25
+ size += chunk.byteLength;
26
+ if (size > access.limit)
27
+ throw new Error(`The upload exceeds ${access.limit / 1024 / 1024 / 1024} GB`);
28
+ yield chunk;
29
+ }
30
+ }, createWriteStream(temporary, { flags: "wx" }));
31
+ await rename(temporary, destination);
32
+ }
33
+ catch (error) {
34
+ await rm(temporary, { force: true }).catch(() => undefined);
35
+ throw error;
36
+ }
37
+ const upload = await this.stat(file);
38
+ if (!upload)
39
+ throw new Error("The completed upload could not be described");
40
+ return upload;
41
+ }
42
+ async stream(file) {
43
+ requireFile(file);
44
+ const { root } = await this.access();
45
+ return Readable.toWeb(createReadStream(join(root, file)));
46
+ }
47
+ async bytes(file) {
48
+ return new Uint8Array(await new Response(await this.stream(file)).arrayBuffer());
49
+ }
50
+ async text(file) {
51
+ return new Response(await this.stream(file)).text();
52
+ }
53
+ async json(file) {
54
+ return JSON.parse(await this.text(file));
55
+ }
56
+ async stat(file) {
57
+ requireFile(file);
58
+ const answer = await wire.request(["uploads", "stat", file]);
59
+ return answer[0];
60
+ }
61
+ access() {
62
+ if (!this.accessPromise) {
63
+ const resolving = wire.request(["uploads", "access"]).then(answer => {
64
+ const [root, limit] = answer;
65
+ if (typeof root !== "string" || !root || typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
66
+ throw new Error("The System returned invalid upload access");
67
+ }
68
+ return { root, limit };
69
+ });
70
+ const retained = resolving.catch(error => {
71
+ if (this.accessPromise === retained)
72
+ this.accessPromise = null;
73
+ throw error;
74
+ });
75
+ this.accessPromise = retained;
76
+ }
77
+ return this.accessPromise;
78
+ }
79
+ }
80
+ function requireFile(file) {
81
+ if (!isUploadFile(file))
82
+ throw new Error("That is not an upload file");
83
+ }
84
+ export const uploads = new ServerUploads();
package/dist/wire.d.ts CHANGED
@@ -18,7 +18,7 @@ declare class Wire {
18
18
  send(route: string, ...values: unknown[]): void;
19
19
  request(values: unknown[], timeout?: number): Promise<unknown>;
20
20
  requestWithin(values: unknown[], deadline: Deadline): Promise<unknown>;
21
- /** Opens one long-running host operation and yields its ordered values. */
21
+ /** Opens one long-running system operation and yields its ordered values. */
22
22
  stream(values: unknown[], timeout?: number): AsyncIterableIterator<unknown>;
23
23
  /** Resolves this endpoint's Process address only for operations that need it. */
24
24
  identity(): Promise<{
package/dist/wire.js CHANGED
@@ -71,7 +71,7 @@ class Wire {
71
71
  this.send("end-host", "wait", question, ...values);
72
72
  });
73
73
  }
74
- /** Opens one long-running host operation and yields its ordered values. */
74
+ /** Opens one long-running system operation and yields its ordered values. */
75
75
  stream(values, timeout = defaultTimeout) {
76
76
  const wire = this;
77
77
  return (async function* () {
@@ -117,7 +117,7 @@ class Wire {
117
117
  const resolving = this.request(["current-process"]).then(value => {
118
118
  const [record] = value;
119
119
  if (typeof record?.identity !== "string" || typeof record.reference !== "string") {
120
- throw new Error("The host returned an invalid Process identity");
120
+ throw new Error("The system returned an invalid Process identity");
121
121
  }
122
122
  return { process: record.identity, reference: record.reference };
123
123
  });
@@ -315,7 +315,7 @@ class Wire {
315
315
  }
316
316
  else if (operation === "data") {
317
317
  if (stream.queue.length >= maximumStreamQueue) {
318
- stream.failure = new Error(`Host stream queue exceeded its capacity of ${maximumStreamQueue}`);
318
+ stream.failure = new Error(`System stream queue exceeded its capacity of ${maximumStreamQueue}`);
319
319
  }
320
320
  else {
321
321
  stream.queue.push(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/server",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "The SDK used by a Program's server endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -46,13 +46,13 @@
46
46
  "prepack": "node --run build"
47
47
  },
48
48
  "peerDependencies": {
49
- "@phreshos/core": "^0.1.14"
49
+ "@phreshos/core": "^0.1.17"
50
50
  },
51
51
  "dependencies": {
52
52
  "@msgpack/msgpack": "^3.1.3"
53
53
  },
54
54
  "devDependencies": {
55
- "@phreshos/core": "^0.1.14",
55
+ "@phreshos/core": "^0.1.18",
56
56
  "@types/node": "^26.2.0",
57
57
  "typescript": "^6.0.3"
58
58
  }
package/dist/served.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { ServedFile } from "@phreshos/core";
2
- /** Writes one public value directly into the host-managed served-file area. */
3
- export default function serve(value: unknown): Promise<ServedFile>;
package/dist/served.js DELETED
@@ -1,41 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { createWriteStream, mkdirSync, statSync } from "node:fs";
3
- import { rename, rm } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { Readable } from "node:stream";
6
- import { pipeline } from "node:stream/promises";
7
- import { content } from "./storage.js";
8
- import wire from "./wire.js";
9
- /** Writes one public value directly into the host-managed served-file area. */
10
- export default async function serve(value) {
11
- const answer = await wire.request(["serve"]);
12
- const [root, limit] = answer;
13
- const source = content(value);
14
- const identity = randomUUID();
15
- const file = `${identity}.${source.extension}`;
16
- const temporary = join(root, `.${identity}.serving`);
17
- const destination = join(root, file);
18
- let size = 0;
19
- mkdirSync(root, { recursive: true });
20
- try {
21
- await pipeline(Readable.fromWeb(source.stream), async function* (chunks) {
22
- for await (const chunk of chunks) {
23
- size += chunk.byteLength;
24
- if (size > limit)
25
- throw new Error(`The served value exceeds ${limit / 1024 / 1024 / 1024} GB`);
26
- yield chunk;
27
- }
28
- }, createWriteStream(temporary, { flags: "wx" }));
29
- await rename(temporary, destination);
30
- }
31
- catch (error) {
32
- await rm(temporary, { force: true }).catch(() => undefined);
33
- throw error;
34
- }
35
- return {
36
- file,
37
- type: source.type,
38
- size,
39
- time: Math.round(statSync(destination).mtimeMs)
40
- };
41
- }
package/dist/theme.d.ts DELETED
@@ -1,15 +0,0 @@
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
- surface: import("@phreshos/core").ThemeSurface;
13
- }>>;
14
- readonly update: (theme: ThemeProperties) => Promise<void>;
15
- }
package/dist/theme.js DELETED
@@ -1,21 +0,0 @@
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
- listener(createThemeSnapshot(value));
9
- }, null, impossible), observer => wire.onAll("host-theme", (event, value) => {
10
- if (typeof event === "string")
11
- observer(event, createThemeSnapshot(value));
12
- }));
13
- }
14
- async snapshot() {
15
- const [theme] = await wire.request(["theme"]);
16
- return createThemeSnapshot(theme);
17
- }
18
- update = async (theme) => {
19
- await wire.request(["update-theme", theme]);
20
- };
21
- }
@@ -1,18 +0,0 @@
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 {};
package/dist/wallpaper.js DELETED
@@ -1,25 +0,0 @@
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
- }