@phreshos/server 0.1.18 → 0.1.20

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
@@ -19,7 +19,7 @@ capabilities, or contain System and transport implementations.
19
19
 
20
20
  Its `System` contract exposes the complete unresolved Appearance with
21
21
  replacement authority, authoritative Program and Process discovery, runtime
22
- Program creation, lifecycle events, and publicly served values.
22
+ Program creation, lifecycle events, and flat public uploads.
23
23
  `system.appearance.snapshot()` explicitly reads current authoritative state;
24
24
  `subscribe("change", listener)` receives only replacements published after
25
25
  registration, and `update()` validates and persists a complete replacement.
@@ -27,16 +27,19 @@ Wallpaper filenames are ordinary Appearance values rather than separate SDK
27
27
  capabilities:
28
28
 
29
29
  ```ts
30
- const served = await system.serve(file)
30
+ const upload = await system.uploads.write(file)
31
+ const bytes = await system.uploads.bytes(upload.file)
31
32
  const appearance = await system.appearance.snapshot()
32
33
 
33
34
  await system.appearance.update({
34
35
  ...appearance,
35
36
  desktopWallpaper: {
36
37
  ...appearance.desktopWallpaper,
37
- light: served.file
38
+ light: upload.file
38
39
  }
39
40
  })
41
+
42
+ void bytes
40
43
  ```
41
44
 
42
45
  Its JavaScript entry point adapts the Process IPC boundary to these contracts.
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/main.d.ts CHANGED
@@ -1,9 +1,9 @@
1
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, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, ThemedValue, WritableAppearance, 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/system.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Exit, Layer, Position, ServedFile, ServiceKey, Size, Subscribable, WritableAppearance } 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";
@@ -111,6 +111,8 @@ export interface System {
111
111
  readonly appearance: WritableAppearance;
112
112
  readonly program: SystemProgram;
113
113
  readonly process: SystemProcess;
114
+ /** Flat System-owned public uploads capability. */
115
+ readonly uploads: SystemUploads;
114
116
  /** Returns a stable handle for one exact Service identity. */
115
117
  service<ServiceEvents extends object = {}>(key: ServiceKey & {
116
118
  endpoint: "server";
@@ -118,8 +120,6 @@ export interface System {
118
120
  service<ServiceEvents extends object = {}>(key: ServiceKey & {
119
121
  endpoint: "client";
120
122
  }): ClientServiceHandler<ServiceEvents>;
121
- /** Publishes a value through the system and returns its public file metadata. */
122
- serve(value: unknown): Promise<ServedFile>;
123
123
  }
124
124
  /** Authoritative system capabilities for the currently executing Server. */
125
125
  export declare const system: System;
package/dist/system.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Events from "./events.js";
2
2
  import { exit, lifecycleEndpoint, process, program } from "./domain.js";
3
- import serve from "./served.js";
3
+ import { uploads } from "./uploads.js";
4
4
  import ServerAppearance from "./appearance.js";
5
5
  import wire from "./wire.js";
6
6
  import { prepareService } from "./service.js";
@@ -10,8 +10,8 @@ class ServerSystem {
10
10
  appearance = new ServerAppearance();
11
11
  program = new ServerSystemProgram();
12
12
  process = new ServerSystemProcess();
13
+ uploads = uploads;
13
14
  service(key) { return prepareService(key); }
14
- serve(value) { return serve(value); }
15
15
  }
16
16
  class ServerSystemProgram extends Events {
17
17
  constructor() {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/server",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
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.17"
49
+ "@phreshos/core": "^0.1.19"
50
50
  },
51
51
  "dependencies": {
52
52
  "@msgpack/msgpack": "^3.1.3"
53
53
  },
54
54
  "devDependencies": {
55
- "@phreshos/core": "^0.1.17",
55
+ "@phreshos/core": "^0.1.19",
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 system-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 system-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
- }