@phreshos/client 0.1.19 → 0.1.21

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
@@ -15,26 +15,31 @@ provide its runtime environment.
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 server-side
18
- capabilities, or contain host and transport implementations.
19
-
20
- Its `Host` contract exposes only desktop-session capabilities: the public
21
- system Theme, desktop and pointer state, publicly served values, and
22
- unrestricted server-side Fetch. `host.theme.snapshot()` explicitly and
23
- asynchronously reads the current snapshot retained by the desktop host.
24
- `host.theme.subscribe("change", listener)` is an ordinary live subscription:
25
- it receives only complete replacements published after registration, with no
26
- initial value or replay. The Client cannot write the authoritative value. The
27
- Host does not expose system-wide Program or Process discovery.
18
+ capabilities, or contain System and transport implementations.
19
+
20
+ Its `System` contract exposes only desktop-session capabilities: read-only
21
+ System Appearance, the mutable Theme local to this Desktop, desktop and pointer
22
+ state, flat public uploads, and unrestricted server-side Fetch.
23
+ `system.appearance.snapshot()` reads complete unresolved authoritative state.
24
+ `system.theme.snapshot()` reads only the effective `"light" | "dark"` mode;
25
+ `update("default")` resumes following the native environment. Both expose
26
+ ordinary live-only `change` subscriptions with no initial replay. A Client
27
+ cannot write authoritative Appearance or discover system-wide Programs and
28
+ Processes.
29
+
30
+ `system.uploads` has the same flat contract as the Server SDK. It writes a
31
+ value, then reads or describes it using exactly one opaque generated file key;
32
+ it exposes no filesystem path, path segments, listing, deletion, or clearing.
28
33
 
29
34
  Desktop and pointer access are independent objects rather than events merged
30
- into Host:
35
+ into System:
31
36
 
32
37
  ```ts
33
- const desktop = await host.desktop.size()
34
- const stopDesktop = host.desktop.subscribe("resize", next => undefined)
38
+ const desktop = await system.desktop.size()
39
+ const stopDesktop = system.desktop.subscribe("resize", next => undefined)
35
40
 
36
- const position = await host.pointer.position()
37
- const stopPointer = host.pointer.subscribe("move", next => undefined)
41
+ const position = await system.pointer.position()
42
+ const stopPointer = system.pointer.subscribe("move", next => undefined)
38
43
  ```
39
44
 
40
45
  The desktop size is the complete desktop area containing this Client,
@@ -44,7 +49,7 @@ movement both require the `pointer` permission. Reads are asynchronous
44
49
  requests; subscriptions receive only future publications and never replay a
45
50
  retained value.
46
51
 
47
- Permission decisions belong to the current Program, not to the desktop Host.
52
+ Permission decisions belong to the current Program, not to the desktop System.
48
53
  They are available from a Program handle and flattened through `current` as the
49
54
  same canonical capability:
50
55
 
@@ -61,13 +66,13 @@ current.permission === program.permission
61
66
  `granted()` reads the effective decision without prompting. `request()` asks
62
67
  only when no known decision can answer immediately and returns `null` if its
63
68
  deadline expires. The desktop remains the internal enforcement boundary, but
64
- permission ownership does not alter the public shape of `host`.
69
+ permission ownership does not alter the public shape of `system`.
65
70
 
66
- The Client Host exposes only direct, exact Service handles. Creating a handle
71
+ The Client System exposes only direct, exact Service handles. Creating a handle
67
72
  does not read or start the Service and exposes no Service registry:
68
73
 
69
74
  ```ts
70
- const service = host.service({
75
+ const service = system.service({
71
76
  program: "counter",
72
77
  endpoint: "server",
73
78
  name: "state"
@@ -115,7 +120,7 @@ absence or incarnation loss rejects without turning the boundary into a waiter.
115
120
  The package provides two contextual runtime entry points:
116
121
 
117
122
  ```ts
118
- import { host, current } from "@phreshos/client"
123
+ import { system, current } from "@phreshos/client"
119
124
  ```
120
125
 
121
126
  It also re-exports the shared Core runtime classes—`Program`, `Process`,
@@ -137,12 +142,12 @@ reserved for directed publications, questions, and answers.
137
142
 
138
143
  Messages sent by an Endpoint in the same Program contain that real Endpoint in
139
144
  `from`. If the sender belongs to another Program, `from` is `null`; the foreign
140
- identity is removed by the server host before the message reaches the desktop.
145
+ identity is removed by the authoritative System before the message reaches the desktop.
141
146
  The same rule applies to Client-visible traffic destinations.
142
147
 
143
148
  Client-visible Program handles can operate only within their own Program.
144
149
  They deliberately omit `install()` and `fork()`, and their `Storage` values never
145
- expose host filesystem paths. Every Client-side Window handle exposes the same
150
+ expose native filesystem paths. Every Client-side Window handle exposes the same
146
151
  authoritative, subscribable Window capability and its `window.local` physical
147
152
  representation on this desktop. Local reads and updates have no events and do
148
153
  not change Server state. `current.window` is only convenient access to the
@@ -177,7 +182,7 @@ them through one authoritative request and produces one `geometry` event.
177
182
  Calling `move()` and `resize()` sequentially or through `Promise.all()` remains
178
183
  two independent operations and can expose an intermediate state remotely.
179
184
 
180
- An `under` or `over` representation may request one local host-rendered Surface:
185
+ An `under` or `over` representation may request one local system-rendered Surface:
181
186
 
182
187
  ```ts
183
188
  await current.window.local.surface.set({ duration: 240, easing: "ease-out", wait: true })
@@ -190,13 +195,13 @@ representation. Reloading or destroying it resets the representation from
190
195
  authoritative truth, while other desktops remain unaffected. Program code may
191
196
  synchronize desired presence through its Server and explicitly apply it
192
197
  again. `set()` and `remove()` accept only a required `VisibilityTransition`;
193
- Programs cannot configure the host Surface's material, opacity, or radius.
198
+ Programs cannot configure the System Surface's material, opacity, or radius.
194
199
  The transition uses milliseconds and a stable named or cubic Bézier easing;
195
200
  the desktop performs the motion, honors reduced motion, and removes the Surface
196
201
  only after its exit transition completes. `wait: true` makes the request settle
197
202
  with that transition. A new iframe representation restores nothing. The
198
203
  container follows the iframe geometry while the independently rounded Surface
199
- neither clips nor masks Client content. `window` and `wallpaper` layers reject
204
+ neither clips nor masks Client content. The ordinary `window` layer rejects
200
205
  the capability.
201
206
 
202
207
  `program.icon()` requests the current Program's guaranteed PNG `Blob` on
@@ -0,0 +1,15 @@
1
+ import Events from "./events.js";
2
+ /** Read-only System Appearance reached through the Desktop boundary. */
3
+ export default class ClientAppearance extends Events {
4
+ constructor();
5
+ snapshot(): Promise<Readonly<{
6
+ background: import("@phreshos/core").ThemedValue<string, string>;
7
+ foreground: import("@phreshos/core").ThemedValue<string, string>;
8
+ accent: import("@phreshos/core").ThemedValue<string, string>;
9
+ spacing: import("@phreshos/core").ThemedValue<number>;
10
+ radius: import("@phreshos/core").ThemedValue<number>;
11
+ surface: import("@phreshos/core").ThemedValue<import("@phreshos/core").AppearanceSurface, import("@phreshos/core").AppearanceSurface>;
12
+ signInWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
13
+ desktopWallpaper: import("@phreshos/core").ThemedValue<string | null, string | null>;
14
+ }>>;
15
+ }
@@ -0,0 +1,18 @@
1
+ import { createAppearanceSnapshot } from "@phreshos/core";
2
+ import Events from "./events.js";
3
+ import wire from "./wire.js";
4
+ /** Read-only System Appearance reached through the Desktop boundary. */
5
+ export default class ClientAppearance 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
+ }
@@ -0,0 +1,2 @@
1
+ /** Wrap a transferred byte stream so cancellation and completion release its transport. */
2
+ export default function controlledStream(body: ReadableStream<Uint8Array>, abort: () => void, close: () => void): ReadableStream<Uint8Array<ArrayBufferLike>>;
@@ -0,0 +1,26 @@
1
+ /** Wrap a transferred byte stream so cancellation and completion release its transport. */
2
+ export default function controlledStream(body, abort, close) {
3
+ const reader = body.getReader();
4
+ return new ReadableStream({
5
+ async pull(controller) {
6
+ try {
7
+ const next = await reader.read();
8
+ if (next.done) {
9
+ close();
10
+ controller.close();
11
+ }
12
+ else
13
+ controller.enqueue(next.value);
14
+ }
15
+ catch (error) {
16
+ close();
17
+ controller.error(error);
18
+ }
19
+ },
20
+ async cancel(reason) {
21
+ abort();
22
+ close();
23
+ await reader.cancel(reason);
24
+ }
25
+ });
26
+ }
package/dist/desktop.d.ts CHANGED
@@ -11,7 +11,7 @@ export type DesktopEvents = {
11
11
  resize: DesktopSize;
12
12
  };
13
13
  /** Explicit desktop size reads and future resizes. */
14
- export interface HostDesktop extends Subscribable<DesktopEvents, never> {
14
+ export interface SystemDesktop extends Subscribable<DesktopEvents, never> {
15
15
  /** Reads the complete current desktop area in CSS pixels. */
16
16
  size(): Promise<DesktopSize>;
17
17
  }
package/dist/desktop.js CHANGED
@@ -17,7 +17,7 @@ export default class ClientDesktop extends Events {
17
17
  const answer = await wire.request(["desktop"]);
18
18
  const size = createSize(answer[0]);
19
19
  if (!size)
20
- throw new Error("The host returned an invalid desktop size");
20
+ throw new Error("The system returned an invalid desktop size");
21
21
  return size;
22
22
  }
23
23
  }
package/dist/domain.js CHANGED
@@ -410,7 +410,7 @@ function lifecycleEndpoint(record, kind) {
410
410
  return owner.server;
411
411
  if (kind === "client")
412
412
  return owner.client;
413
- throw new Error("The host returned an invalid Endpoint lifecycle event");
413
+ throw new Error("The system returned an invalid Endpoint lifecycle event");
414
414
  }
415
415
  export function exit(code, signal) {
416
416
  const namedSignal = stringOrNull(signal);
package/dist/main.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { host, type Host } from "./host.js";
2
- export { type HostPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
3
- export { type DesktopEvents, type DesktopSize, type HostDesktop } from "./desktop.js";
1
+ export { system, type System } from "./system.js";
2
+ export { type SystemPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
3
+ export { type DesktopEvents, type DesktopSize, type SystemDesktop } from "./desktop.js";
4
4
  export { current, type Current, type CurrentServer } from "./current.js";
5
5
  export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
6
6
  export { ClientServiceHandler, ServerServiceHandler, ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
7
7
  export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess, type AnswerCapture, type AnswerMessage, type AnswerObserver, type AskCapture, type AskMessage, type AskObserver, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
8
- export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Theme, ThemeEvents, ThemeProperties, Value, WritableTheme, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } from "@phreshos/core";
8
+ export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, SystemTheme, ThemedValue, Theme, ThemeEvents, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } 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 {} from "./pointer.js";
3
3
  export {} from "./desktop.js";
4
4
  export { current } from "./current.js";
package/dist/pointer.d.ts CHANGED
@@ -13,7 +13,7 @@ export type PointerEvents = {
13
13
  move: PointerPosition;
14
14
  };
15
15
  /** Permission-guarded pointer positions and future movement. */
16
- export interface HostPointer extends Subscribable<PointerEvents, never> {
16
+ export interface SystemPointer extends Subscribable<PointerEvents, never> {
17
17
  /**
18
18
  * Reads the current desktop pointer position, or `null` before one is known.
19
19
  * Rejects unless the `pointer` permission is currently granted.
package/dist/storage.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { content } from "./content.js";
2
+ import controlledStream from "./controlled-stream.js";
2
3
  import wire from "./wire.js";
3
4
  /** Client-side Program storage, structurally scoped by the iframe boundary. */
4
5
  export function area(which) {
@@ -18,7 +19,7 @@ export function area(which) {
18
19
  }
19
20
  if (!(answer[0] instanceof ReadableStream))
20
21
  throw new Error("The storage response has no byte stream");
21
- return controlled(answer[0], abort, close);
22
+ return controlledStream(answer[0], abort, close);
22
23
  }
23
24
  catch (error) {
24
25
  abort();
@@ -69,28 +70,3 @@ export function sql(kind) {
69
70
  }
70
71
  };
71
72
  }
72
- function controlled(body, abort, close) {
73
- const reader = body.getReader();
74
- return new ReadableStream({
75
- async pull(controller) {
76
- try {
77
- const next = await reader.read();
78
- if (next.done) {
79
- close();
80
- controller.close();
81
- }
82
- else
83
- controller.enqueue(next.value);
84
- }
85
- catch (error) {
86
- close();
87
- controller.error(error);
88
- }
89
- },
90
- async cancel(reason) {
91
- abort();
92
- close();
93
- await reader.cancel(reason);
94
- }
95
- });
96
- }
@@ -1,21 +1,23 @@
1
- import type { ClientServiceHandler, ServedFile, ServerServiceHandler, ServiceKey, Theme, ThemeProperties } from "@phreshos/core";
2
- import { type HostPointer } from "./pointer.js";
3
- import { type HostDesktop } from "./desktop.js";
1
+ import type { AppearanceSource, ClientServiceHandler, ServerServiceHandler, ServiceKey, SystemUploads, SystemTheme } from "@phreshos/core";
2
+ import { type SystemPointer } from "./pointer.js";
3
+ import { type SystemDesktop } from "./desktop.js";
4
4
  type ServiceEndpoint = ServiceKey["endpoint"];
5
5
  type ServiceAddress<Endpoint extends ServiceEndpoint> = Omit<ServiceKey, "endpoint"> & Readonly<{
6
6
  endpoint: Endpoint;
7
7
  }>;
8
8
  type ServiceHandle<Endpoint extends ServiceEndpoint, Events extends object> = Endpoint extends "server" ? ServerServiceHandler<Events> : ClientServiceHandler<Events>;
9
9
  /** Desktop capabilities structurally available to a Client endpoint. */
10
- export interface Host {
11
- /** Read-only system Theme explicitly read from and observed through the desktop host. */
12
- readonly theme: Theme<ThemeProperties>;
10
+ export interface System {
11
+ /** Complete unresolved Appearance read from the System authority. */
12
+ readonly appearance: AppearanceSource;
13
+ /** Mutable effective Theme local to this Desktop. */
14
+ readonly theme: SystemTheme;
13
15
  /** Layer-independent desktop size reads and live updates. */
14
- readonly desktop: HostDesktop;
16
+ readonly desktop: SystemDesktop;
15
17
  /** Permission-guarded desktop pointer reads and live movement. */
16
- readonly pointer: HostPointer;
17
- /** Stores one value as a publicly reachable file. */
18
- serve(value: unknown): Promise<ServedFile>;
18
+ readonly pointer: SystemPointer;
19
+ /** Flat System-owned public uploads capability. */
20
+ readonly uploads: SystemUploads;
19
21
  /** Performs an unrestricted server-side fetch on behalf of this Client. */
20
22
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
21
23
  /** Returns a precisely typed stable handle for either service endpoint. */
@@ -26,5 +28,5 @@ export interface Host {
26
28
  service<ServiceEvents extends object>(key: ServiceAddress<"client">): ClientServiceHandler<ServiceEvents>;
27
29
  }
28
30
  /** Desktop capabilities available to this Client. */
29
- export declare const host: Host;
31
+ export declare const system: System;
30
32
  export {};
@@ -1,29 +1,18 @@
1
- import { content } from "./content.js";
1
+ import ClientAppearance from "./appearance.js";
2
2
  import ClientTheme from "./theme.js";
3
3
  import wire from "./wire.js";
4
4
  import ClientPointer, {} from "./pointer.js";
5
5
  import ClientDesktop, {} from "./desktop.js";
6
6
  import { prepareService } from "./service.js";
7
- class ClientHost {
7
+ import { uploads } from "./uploads.js";
8
+ import controlledStream from "./controlled-stream.js";
9
+ class ClientSystem {
10
+ appearance = new ClientAppearance();
8
11
  theme = new ClientTheme();
9
12
  desktop = new ClientDesktop();
10
13
  pointer = new ClientPointer();
14
+ uploads = uploads;
11
15
  service(key) { return prepareService(key); }
12
- async serve(value) {
13
- const source = content(value);
14
- const channel = new MessageChannel();
15
- const abort = () => channel.port1.postMessage("abort");
16
- try {
17
- const answer = await wire.request(["serve", source.body, { extension: source.extension, type: source.type }, channel.port2], undefined, source.body instanceof ReadableStream ? [source.body, channel.port2] : [channel.port2]);
18
- channel.port1.close();
19
- return answer[0];
20
- }
21
- catch (error) {
22
- abort();
23
- channel.port1.close();
24
- throw error;
25
- }
26
- }
27
16
  async fetch(input, init) {
28
17
  const request = new Request(input, {
29
18
  ...init,
@@ -63,7 +52,7 @@ class ClientHost {
63
52
  throw request.signal.aborted ? request.signal.reason : error;
64
53
  }
65
54
  const responseBody = result.body
66
- ? controlled(result.body, abort, () => closeControl(request.signal, control.port1, abort))
55
+ ? controlledStream(result.body, abort, () => closeControl(request.signal, control.port1, abort))
67
56
  : null;
68
57
  if (!responseBody)
69
58
  closeControl(request.signal, control.port1, abort);
@@ -91,34 +80,9 @@ function requestHeaders(normalized, supplied) {
91
80
  const names = new Set(explicit.map(([name]) => name.toLowerCase()));
92
81
  return [...normalized.entries()].filter(([name]) => !names.has(name.toLowerCase())).concat(explicit);
93
82
  }
94
- function controlled(body, abort, close) {
95
- const reader = body.getReader();
96
- return new ReadableStream({
97
- async pull(controller) {
98
- try {
99
- const next = await reader.read();
100
- if (next.done) {
101
- close();
102
- controller.close();
103
- }
104
- else
105
- controller.enqueue(next.value);
106
- }
107
- catch (error) {
108
- close();
109
- controller.error(error);
110
- }
111
- },
112
- async cancel(reason) {
113
- abort();
114
- close();
115
- await reader.cancel(reason);
116
- }
117
- });
118
- }
119
83
  function closeControl(signal, port, abort) {
120
84
  signal.removeEventListener("abort", abort);
121
85
  port.close();
122
86
  }
123
87
  /** Desktop capabilities available to this Client. */
124
- export const host = new ClientHost();
88
+ export const system = new ClientSystem();
package/dist/theme.d.ts CHANGED
@@ -1,13 +1,8 @@
1
+ import { type Theme, type ThemePreference } from "@phreshos/core";
1
2
  import Events from "./events.js";
2
- /** Read-only system Theme reached explicitly through the desktop boundary. */
3
+ /** Mutable Theme local to the current Desktop. */
3
4
  export default class ClientTheme extends Events {
4
5
  constructor();
5
- snapshot(): Promise<Readonly<{
6
- background: string;
7
- foreground: string;
8
- accent: string;
9
- spacing: number;
10
- radius: number;
11
- surface: import("@phreshos/core").ThemeSurface;
12
- }>>;
6
+ snapshot(): Promise<Theme>;
7
+ update(theme: ThemePreference): Promise<void>;
13
8
  }
package/dist/theme.js CHANGED
@@ -1,18 +1,19 @@
1
- import { createThemeSnapshot } from "@phreshos/core";
1
+ import {} from "@phreshos/core";
2
2
  import Events from "./events.js";
3
3
  import wire from "./wire.js";
4
- /** Read-only system Theme reached explicitly through the desktop boundary. */
4
+ /** Mutable Theme local to the current Desktop. */
5
5
  export default class ClientTheme extends Events {
6
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) => {
7
+ super((event, listener, impossible) => wire.on("host-theme", event, value => listener(value), null, impossible), observer => wire.onAll("host-theme", (event, value) => {
10
8
  if (typeof event === "string")
11
- observer(event, createThemeSnapshot(value));
9
+ observer(event, value);
12
10
  }));
13
11
  }
14
12
  async snapshot() {
15
13
  const [theme] = await wire.request(["theme"]);
16
- return createThemeSnapshot(theme);
14
+ return theme;
15
+ }
16
+ async update(theme) {
17
+ await wire.request(["update-theme", theme]);
17
18
  }
18
19
  }
@@ -0,0 +1,12 @@
1
+ import { type SystemUploads, type Upload } from "@phreshos/core";
2
+ /** Flat upload access transported through the Client's Desktop boundary. */
3
+ declare class ClientUploads implements SystemUploads {
4
+ write(value: unknown): Promise<Upload>;
5
+ stream(file: string): Promise<ReadableStream<Uint8Array>>;
6
+ bytes(file: string): Promise<Uint8Array<ArrayBuffer>>;
7
+ text(file: string): Promise<string>;
8
+ json<Value>(file: string): Promise<Value>;
9
+ stat(file: string): Promise<Upload | null>;
10
+ }
11
+ export declare const uploads: ClientUploads;
12
+ export {};
@@ -0,0 +1,58 @@
1
+ import { isUploadFile } from "@phreshos/core";
2
+ import { content } from "./content.js";
3
+ import controlledStream from "./controlled-stream.js";
4
+ import wire from "./wire.js";
5
+ /** Flat upload access transported through the Client's Desktop boundary. */
6
+ class ClientUploads {
7
+ async write(value) {
8
+ const source = content(value);
9
+ const channel = new MessageChannel();
10
+ const abort = () => channel.port1.postMessage("abort");
11
+ try {
12
+ const answer = await wire.request(["uploads", "write", source.body, { extension: source.extension, type: source.type }, channel.port2], undefined, source.body instanceof ReadableStream ? [source.body, channel.port2] : [channel.port2]);
13
+ channel.port1.close();
14
+ return answer[0];
15
+ }
16
+ catch (error) {
17
+ abort();
18
+ channel.port1.close();
19
+ throw error;
20
+ }
21
+ }
22
+ async stream(file) {
23
+ requireFile(file);
24
+ const channel = new MessageChannel();
25
+ const abort = () => channel.port1.postMessage("abort");
26
+ const close = () => channel.port1.close();
27
+ try {
28
+ const answer = await wire.request(["uploads", "stream", file, channel.port2], undefined, [channel.port2]);
29
+ if (!(answer[0] instanceof ReadableStream))
30
+ throw new Error("The upload response has no byte stream");
31
+ return controlledStream(answer[0], abort, close);
32
+ }
33
+ catch (error) {
34
+ abort();
35
+ close();
36
+ throw error;
37
+ }
38
+ }
39
+ async bytes(file) {
40
+ return new Uint8Array(await new Response(await this.stream(file)).arrayBuffer());
41
+ }
42
+ async text(file) {
43
+ return new Response(await this.stream(file)).text();
44
+ }
45
+ async json(file) {
46
+ return JSON.parse(await this.text(file));
47
+ }
48
+ async stat(file) {
49
+ requireFile(file);
50
+ const answer = await wire.request(["uploads", "stat", file]);
51
+ return answer[0];
52
+ }
53
+ }
54
+ function requireFile(file) {
55
+ if (!isUploadFile(file))
56
+ throw new Error("That is not an upload file");
57
+ }
58
+ export const uploads = new ClientUploads();
package/dist/wire.js CHANGED
@@ -319,7 +319,7 @@ class Wire {
319
319
  stream.failure = new Error("The boundary produced a stream value before opening the stream");
320
320
  else if (operation === "data") {
321
321
  if (stream.queue.length >= maximumStreamQueue)
322
- stream.failure = new Error(`Host stream queue exceeded its capacity of ${maximumStreamQueue}`);
322
+ stream.failure = new Error(`System stream queue exceeded its capacity of ${maximumStreamQueue}`);
323
323
  else
324
324
  stream.queue.push(value);
325
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,13 +47,13 @@
47
47
  "prepack": "node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.15"
50
+ "@phreshos/core": "^0.1.17"
51
51
  },
52
52
  "dependencies": {
53
53
  "@msgpack/msgpack": "^3.1.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@phreshos/core": "^0.1.15",
56
+ "@phreshos/core": "^0.1.18",
57
57
  "typescript": "^6.0.3"
58
58
  }
59
59
  }