@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 +34 -43
- package/dist/appearance.d.ts +17 -0
- package/dist/appearance.js +21 -0
- package/dist/channel.d.ts +8 -2
- package/dist/channel.js +1 -1
- package/dist/domain.d.ts +1 -1
- package/dist/domain.js +2 -2
- package/dist/main.d.ts +3 -3
- package/dist/main.js +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +2 -2
- package/dist/{host.d.ts → system.d.ts} +19 -23
- package/dist/{host.js → system.js} +18 -21
- package/dist/uploads.d.ts +14 -0
- package/dist/uploads.js +84 -0
- package/dist/wire.d.ts +1 -1
- package/dist/wire.js +3 -3
- package/package.json +3 -3
- package/dist/served.d.ts +0 -3
- package/dist/served.js +0 -41
- package/dist/theme.d.ts +0 -15
- package/dist/theme.js +0 -21
- package/dist/wallpaper.d.ts +0 -18
- package/dist/wallpaper.js +0 -25
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
|
|
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
|
|
19
|
-
|
|
20
|
-
Its `
|
|
21
|
-
authority, authoritative Program and Process discovery, runtime
|
|
22
|
-
creation, lifecycle events, and
|
|
23
|
-
explicitly
|
|
24
|
-
`subscribe("change", listener)` receives only
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
await
|
|
38
|
-
|
|
39
|
-
await
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
119
|
-
Object descriptions passed to `
|
|
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
|
-
`
|
|
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
|
-
|
|
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 `
|
|
120
|
+
on `system`:
|
|
130
121
|
|
|
131
122
|
```ts
|
|
132
|
-
const programs = await
|
|
133
|
-
const program = await
|
|
134
|
-
const processes = await
|
|
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 =
|
|
137
|
-
const stopProcesses =
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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,
|
|
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
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
74
|
-
export type
|
|
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:
|
|
82
|
+
uninstall: SystemProgramUninstall;
|
|
83
83
|
};
|
|
84
|
-
/** Authoritative Process lifecycle events visible to the Server
|
|
85
|
-
export type
|
|
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:
|
|
93
|
+
exit: SystemProcessExit;
|
|
94
94
|
};
|
|
95
95
|
/** Authoritative Program registry available to a Server endpoint. */
|
|
96
|
-
export interface
|
|
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
|
|
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
|
|
107
|
+
export interface System {
|
|
108
108
|
/** Native operating-system home storage available to Server endpoints. */
|
|
109
109
|
readonly storage: Storage;
|
|
110
|
-
/**
|
|
111
|
-
readonly
|
|
112
|
-
|
|
113
|
-
readonly
|
|
114
|
-
/**
|
|
115
|
-
readonly
|
|
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
|
|
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
|
|
4
|
-
import
|
|
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 {
|
|
9
|
-
class
|
|
10
|
-
storage =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
16
|
+
class ServerSystemProgram extends Events {
|
|
20
17
|
constructor() {
|
|
21
|
-
super((event, listener, impossible) => wire.on("host-program", event, (...values) => listener(
|
|
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,
|
|
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
|
|
36
|
+
class ServerSystemProcess extends Events {
|
|
40
37
|
constructor() {
|
|
41
|
-
super((event, listener, impossible) => wire.on("host-process", event, (...values) => listener(
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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 {};
|
package/dist/uploads.js
ADDED
|
@@ -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
|
|
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
|
|
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
|
|
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(`
|
|
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.
|
|
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.
|
|
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.
|
|
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
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
|
-
}
|
package/dist/wallpaper.d.ts
DELETED
|
@@ -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
|
-
}
|