@phreshos/client 0.1.37 → 0.1.39
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 +8 -2
- package/dist/appearance.d.ts +8 -13
- package/dist/appearance.js +4 -4
- package/dist/content.d.ts +2 -1
- package/dist/context.d.ts +2 -12
- package/dist/context.js +25 -13
- package/dist/desktop/desktop.js +2 -2
- package/dist/desktop/viewport.d.ts +9 -0
- package/dist/desktop/{surface.js → viewport.js} +6 -6
- package/dist/domain.d.ts +21 -61
- package/dist/domain.js +76 -61
- package/dist/events.d.ts +3 -2
- package/dist/events.js +2 -1
- package/dist/main.d.ts +1 -4
- package/dist/main.js +0 -2
- package/dist/network.d.ts +7 -0
- package/dist/network.js +79 -0
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +2 -2
- package/dist/service.js +26 -21
- package/dist/storage.d.ts +3 -4
- package/dist/storage.js +188 -88
- package/dist/system.js +6 -79
- package/dist/uploads.d.ts +3 -3
- package/package.json +8 -8
- package/dist/desktop/surface.d.ts +0 -9
package/dist/events.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { subscribableDefinition, type Cleanup, type EventOptions, type Subscribable, type SubscribableDefinition } from "@phreshos/core";
|
|
2
2
|
export declare const defaultTimeout = 10000;
|
|
3
3
|
type Failure = (error: Error) => void;
|
|
4
4
|
type Listener = (message: unknown) => unknown;
|
|
@@ -8,9 +8,10 @@ type Register<Message> = (listener: (message: Message) => unknown, impossible?:
|
|
|
8
8
|
export default class Events<Vocabulary extends object = {}, Fallback = unknown> implements Subscribable<Vocabulary, Fallback> {
|
|
9
9
|
private readonly listen;
|
|
10
10
|
private readonly listenAll;
|
|
11
|
+
readonly [subscribableDefinition]?: SubscribableDefinition<Vocabulary, Fallback>;
|
|
11
12
|
constructor(listen: (event: string, listener: Listener, impossible?: Failure) => Cleanup, listenAll: (listener: EveryListener, impossible?: Failure) => Cleanup);
|
|
12
13
|
readonly subscribe: Subscribable<Vocabulary, Fallback>["subscribe"];
|
|
13
|
-
readonly
|
|
14
|
+
readonly wait: Subscribable<Vocabulary, Fallback>["wait"];
|
|
14
15
|
readonly events: Subscribable<Vocabulary, Fallback>["events"];
|
|
15
16
|
}
|
|
16
17
|
/** Converts one persistent registration into a bounded asynchronous iterator. */
|
package/dist/events.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { subscribableDefinition } from "@phreshos/core";
|
|
1
2
|
export const defaultTimeout = 10_000;
|
|
2
3
|
/** SDK-owned waits, queues and callbacks over boundary forwarding state. */
|
|
3
4
|
export default class Events {
|
|
@@ -12,7 +13,7 @@ export default class Events {
|
|
|
12
13
|
return this.listen(eventOrSubscriber, subscriber);
|
|
13
14
|
return this.listenAll((event, message) => eventOrSubscriber({ event, message }));
|
|
14
15
|
});
|
|
15
|
-
|
|
16
|
+
wait = ((event, timeout = defaultTimeout) => {
|
|
16
17
|
return new Promise((resolve, reject) => {
|
|
17
18
|
let settled = false;
|
|
18
19
|
let stop = () => undefined;
|
package/dist/main.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
export { system } from "./system.js";
|
|
2
2
|
export { desktop } from "./desktop/desktop.js";
|
|
3
|
-
export { context
|
|
4
|
-
export { ClientService, ServerService, Service, clientPermissionCatalog, isPermissionName, type ServiceKey, } from "@phreshos/core";
|
|
5
|
-
export { ClientEndpoint, Endpoint, Process, Program, ServerEndpoint, type Window, type ProgramProcess, type ProgramProcessRunEvent, type ProgramProcessRunOptions, type AnswerCapture, type AnswerMessage, type AnswerSubscriber, type AskCapture, type AskMessage, type AskSubscriber, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
|
|
6
|
-
export type { Askable, AnswerOutcome, ClientContext, Capture, Captures, CaptureSubscriber, ClientLaunch, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointLifecycle, EndpointLifecycleEvents, EntryStat, EventMessage, EventName, EventOptions, EventSubscriber, Exit, FileStat, Launch, ServerLaunch, ShellEvent, ShellOptions, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Permission, PermissionDefinition, PermissionDefinitions, PermissionInput, PermissionName, PermissionRequest, PermissionValue, PermissionValueDomain, Permissions, ClientPermissionDeclarations, ClientPermissions, ContextPermissions, ProgramPermissions, TimedContextPermissions, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramStartup, ProgramSql, ProgramStore, ProcessEvents, Publishable, System, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, AnimationsPreference, DesktopPreferences, DesktopPreferencesEvents, DesktopPreferencesSource, DesktopPreferencesUpdate, Desktop, DesktopSize, DesktopSurfaceEvents, DesktopSurfaceSnapshot, DesktopSurfaceSource, WritableDesktopPreferencesSource, ThemedValue, Theme, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowOperations, Transaction } from "@phreshos/core";
|
|
3
|
+
export { context } from "./context.js";
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
export { system } from "./system.js";
|
|
2
2
|
export { desktop } from "./desktop/desktop.js";
|
|
3
3
|
export { context } from "./context.js";
|
|
4
|
-
export { ClientService, ServerService, Service, clientPermissionCatalog, isPermissionName, } from "@phreshos/core";
|
|
5
|
-
export { ClientEndpoint, Endpoint, Process, Program, ServerEndpoint } from "./domain.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Network } from "@phreshos/core";
|
|
2
|
+
declare class ClientNetwork implements Network {
|
|
3
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
4
|
+
websocket(url: string | URL, protocols?: string | string[]): Promise<WebSocket>;
|
|
5
|
+
}
|
|
6
|
+
declare const _default: ClientNetwork;
|
|
7
|
+
export default _default;
|
package/dist/network.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import wire from "./wire.js";
|
|
2
|
+
import controlledStream from "./controlled-stream.js";
|
|
3
|
+
import websocket from "./websocket.js";
|
|
4
|
+
class ClientNetwork {
|
|
5
|
+
async fetch(input, init) {
|
|
6
|
+
const request = new Request(input, {
|
|
7
|
+
...init,
|
|
8
|
+
...init?.body instanceof ReadableStream ? { duplex: "half" } : {}
|
|
9
|
+
});
|
|
10
|
+
const body = request.body;
|
|
11
|
+
const control = new MessageChannel();
|
|
12
|
+
const abort = () => control.port1.postMessage("abort");
|
|
13
|
+
if (request.signal.aborted) {
|
|
14
|
+
control.port1.close();
|
|
15
|
+
control.port2.close();
|
|
16
|
+
throw request.signal.reason;
|
|
17
|
+
}
|
|
18
|
+
request.signal.addEventListener("abort", abort, { once: true });
|
|
19
|
+
const description = {
|
|
20
|
+
body: body !== null,
|
|
21
|
+
cache: request.cache,
|
|
22
|
+
credentials: request.credentials,
|
|
23
|
+
headers: requestHeaders(request.headers, init?.headers),
|
|
24
|
+
integrity: request.integrity,
|
|
25
|
+
keepalive: request.keepalive,
|
|
26
|
+
method: request.method,
|
|
27
|
+
mode: request.mode,
|
|
28
|
+
redirect: request.redirect,
|
|
29
|
+
referrer: request.referrer,
|
|
30
|
+
referrerPolicy: request.referrerPolicy,
|
|
31
|
+
url: request.url
|
|
32
|
+
};
|
|
33
|
+
let result;
|
|
34
|
+
try {
|
|
35
|
+
const answer = await wire.request(["fetch", description, body, control.port2], undefined, body ? [body, control.port2] : [control.port2]);
|
|
36
|
+
result = answer[0];
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
abort();
|
|
40
|
+
closeControl(request.signal, control.port1, abort);
|
|
41
|
+
throw request.signal.aborted ? request.signal.reason : error;
|
|
42
|
+
}
|
|
43
|
+
const responseBody = result.body
|
|
44
|
+
? controlledStream(result.body, abort, () => closeControl(request.signal, control.port1, abort))
|
|
45
|
+
: null;
|
|
46
|
+
if (!responseBody)
|
|
47
|
+
closeControl(request.signal, control.port1, abort);
|
|
48
|
+
const response = new Response(responseBody, {
|
|
49
|
+
headers: result.headers,
|
|
50
|
+
status: result.status,
|
|
51
|
+
statusText: result.statusText
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperties(response, {
|
|
54
|
+
redirected: { configurable: true, enumerable: true, value: result.redirected },
|
|
55
|
+
type: { configurable: true, enumerable: true, value: result.type },
|
|
56
|
+
url: { configurable: true, enumerable: true, value: result.url }
|
|
57
|
+
});
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
websocket(url, protocols) {
|
|
61
|
+
return websocket(url, protocols);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function requestHeaders(normalized, supplied) {
|
|
65
|
+
if (!supplied)
|
|
66
|
+
return [...normalized.entries()];
|
|
67
|
+
const explicit = supplied instanceof Headers
|
|
68
|
+
? [...supplied.entries()]
|
|
69
|
+
: Array.isArray(supplied)
|
|
70
|
+
? supplied.map(([name, value]) => [name, value])
|
|
71
|
+
: Object.entries(supplied);
|
|
72
|
+
const names = new Set(explicit.map(([name]) => name.toLowerCase()));
|
|
73
|
+
return [...normalized.entries()].filter(([name]) => !names.has(name.toLowerCase())).concat(explicit);
|
|
74
|
+
}
|
|
75
|
+
function closeControl(signal, port, abort) {
|
|
76
|
+
signal.removeEventListener("abort", abort);
|
|
77
|
+
port.close();
|
|
78
|
+
}
|
|
79
|
+
export default new ClientNetwork();
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ContextPermissions, ProgramPermissions } from "@phreshos/core";
|
|
2
2
|
import type { HandleAddress } from "./domain.js";
|
|
3
3
|
export declare const defaultPermissionTimeout = 120000;
|
|
4
|
-
/** Bind authoritative
|
|
4
|
+
/** Bind authoritative permission state to one exact Program handle. */
|
|
5
5
|
export declare function programPermissions(program: HandleAddress): ProgramPermissions;
|
|
6
|
-
/**
|
|
6
|
+
/** Permission state and owner requests belonging to the current Client Endpoint. */
|
|
7
7
|
export declare function contextPermissions(): ContextPermissions;
|
package/dist/permissions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parsePermission, parsePermissions } from "@phreshos/core";
|
|
2
2
|
import wire from "./wire.js";
|
|
3
3
|
export const defaultPermissionTimeout = 120_000;
|
|
4
|
-
/** Bind authoritative
|
|
4
|
+
/** Bind authoritative permission state to one exact Program handle. */
|
|
5
5
|
export function programPermissions(program) {
|
|
6
6
|
const operate = (operation, name, permission) => (wire.request(["program-permissions", program, operation, name, permission]));
|
|
7
7
|
return {
|
|
@@ -12,7 +12,7 @@ export function programPermissions(program) {
|
|
|
12
12
|
async delete(name) { await operate("delete", name); }
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Permission state and owner requests belonging to the current Client Endpoint. */
|
|
16
16
|
export function contextPermissions() {
|
|
17
17
|
const timed = (timeout) => ({
|
|
18
18
|
async request(name, permission = true) {
|
package/dist/service.js
CHANGED
|
@@ -4,8 +4,6 @@ import Events from "./events.js";
|
|
|
4
4
|
import HandleRegistry from "./handle-registry.js";
|
|
5
5
|
import wire from "./wire.js";
|
|
6
6
|
const handles = new HandleRegistry();
|
|
7
|
-
const ServerServiceBase = CoreServerService;
|
|
8
|
-
const ClientServiceBase = CoreClientService;
|
|
9
7
|
class ServiceHandle {
|
|
10
8
|
key;
|
|
11
9
|
lifecycle;
|
|
@@ -24,8 +22,11 @@ class ServiceHandle {
|
|
|
24
22
|
await wire.request(["service-wait-ready", this.key, timeout], timeout);
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
|
-
class ServerHandler extends
|
|
25
|
+
class ServerHandler extends CoreServerService {
|
|
28
26
|
key;
|
|
27
|
+
subscribe;
|
|
28
|
+
wait;
|
|
29
|
+
events;
|
|
29
30
|
lifecycle;
|
|
30
31
|
service;
|
|
31
32
|
constructor(key) {
|
|
@@ -33,9 +34,12 @@ class ServerHandler extends ServerServiceBase {
|
|
|
33
34
|
this.key = key;
|
|
34
35
|
this.service = new ServiceHandle(key);
|
|
35
36
|
this.lifecycle = this.service.lifecycle;
|
|
36
|
-
|
|
37
|
+
const events = new Events(...serviceEvents(key, "events"));
|
|
38
|
+
this.subscribe = events.subscribe;
|
|
39
|
+
this.wait = events.wait;
|
|
40
|
+
this.events = events.events;
|
|
37
41
|
}
|
|
38
|
-
publish(event, payload = undefined) { this.service.publish(event, payload); }
|
|
42
|
+
publish = (event, payload = undefined) => { this.service.publish(event, payload); };
|
|
39
43
|
exists() { return this.service.exists(); }
|
|
40
44
|
waitReady(timeout) { return this.service.waitReady(timeout); }
|
|
41
45
|
async ask(event, payload = undefined) {
|
|
@@ -58,31 +62,39 @@ class ServerHandler extends ServerServiceBase {
|
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
|
-
class ClientHandler extends
|
|
65
|
+
class ClientHandler extends CoreClientService {
|
|
66
|
+
subscribe;
|
|
67
|
+
wait;
|
|
68
|
+
events;
|
|
62
69
|
lifecycle;
|
|
63
70
|
service;
|
|
64
71
|
constructor(key) {
|
|
65
72
|
super();
|
|
66
73
|
this.service = new ServiceHandle(key);
|
|
67
74
|
this.lifecycle = this.service.lifecycle;
|
|
68
|
-
|
|
75
|
+
const events = new Events(...serviceEvents(key, "events"));
|
|
76
|
+
this.subscribe = events.subscribe;
|
|
77
|
+
this.wait = events.wait;
|
|
78
|
+
this.events = events.events;
|
|
69
79
|
}
|
|
70
|
-
publish(event, payload = undefined) { this.service.publish(event, payload); }
|
|
80
|
+
publish = (event, payload = undefined) => { this.service.publish(event, payload); };
|
|
71
81
|
exists() { return this.service.exists(); }
|
|
72
82
|
waitReady(timeout) { return this.service.waitReady(timeout); }
|
|
73
83
|
}
|
|
74
84
|
export function prepareService(key) {
|
|
75
85
|
if (!isServiceKey(key))
|
|
76
86
|
throw new Error("A complete service key is required");
|
|
77
|
-
const
|
|
87
|
+
const address = Object.freeze({
|
|
78
88
|
...(key.program === undefined ? {} : { program: key.program }),
|
|
79
|
-
process: key.process
|
|
80
|
-
endpoint: key.endpoint
|
|
89
|
+
process: key.process
|
|
81
90
|
});
|
|
82
91
|
const identity = JSON.stringify([key.program ?? null, key.process, key.endpoint]);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
if (key.endpoint === "server") {
|
|
93
|
+
const normalized = Object.freeze({ ...address, endpoint: "server" });
|
|
94
|
+
return handles.obtain(`service:${identity}`, () => new ServerHandler(normalized));
|
|
95
|
+
}
|
|
96
|
+
const normalized = Object.freeze({ ...address, endpoint: "client" });
|
|
97
|
+
return handles.obtain(`service:${identity}`, () => new ClientHandler(normalized));
|
|
86
98
|
}
|
|
87
99
|
function serviceEvents(key, scope) {
|
|
88
100
|
return [
|
|
@@ -93,10 +105,3 @@ function serviceEvents(key, scope) {
|
|
|
93
105
|
}, impossible)
|
|
94
106
|
];
|
|
95
107
|
}
|
|
96
|
-
function bindEvents(target, events) {
|
|
97
|
-
Object.assign(target, {
|
|
98
|
-
subscribe: events.subscribe.bind(events),
|
|
99
|
-
waitFor: events.waitFor.bind(events),
|
|
100
|
-
events: events.events.bind(events)
|
|
101
|
-
});
|
|
102
|
-
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Storage, type ProgramSql, type ProgramStore } from "@phreshos/core";
|
|
2
2
|
import type { HandleAddress } from "./domain.js";
|
|
3
|
-
export type Storage = CoreStorage;
|
|
4
3
|
/** Client-side access to one exact Program storage area. */
|
|
5
4
|
export declare function area(program: HandleAddress, which: "data" | "cache"): Storage;
|
|
6
|
-
export declare function store(program: HandleAddress): ProgramStore;
|
|
7
|
-
export declare function sql(kind: "database" | "logs", program: HandleAddress): ProgramSql;
|
|
8
5
|
/** Access to the System's native storage through the Client boundary. */
|
|
9
6
|
export declare function systemStorage(): Storage;
|
|
7
|
+
export declare function store(program: HandleAddress): ProgramStore;
|
|
8
|
+
export declare function sql(kind: "database" | "logs", program: HandleAddress): ProgramSql;
|
package/dist/storage.js
CHANGED
|
@@ -1,53 +1,202 @@
|
|
|
1
|
+
import { Storage, StorageFile } from "@phreshos/core";
|
|
1
2
|
import { content } from "./content.js";
|
|
2
3
|
import controlledStream from "./controlled-stream.js";
|
|
3
4
|
import wire from "./wire.js";
|
|
4
5
|
/** Client-side access to one exact Program storage area. */
|
|
5
6
|
export function area(program, which) {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
return new RemoteStorage(new ProgramStorageBoundary(program, which), []);
|
|
8
|
+
}
|
|
9
|
+
/** Access to the System's native storage through the Client boundary. */
|
|
10
|
+
export function systemStorage() {
|
|
11
|
+
return new RemoteStorage(new SystemStorageBoundary(), []);
|
|
12
|
+
}
|
|
13
|
+
class StorageBoundary {
|
|
14
|
+
}
|
|
15
|
+
class ProgramStorageBoundary extends StorageBoundary {
|
|
16
|
+
program;
|
|
17
|
+
area;
|
|
18
|
+
constructor(program, area) {
|
|
19
|
+
super();
|
|
20
|
+
this.program = program;
|
|
21
|
+
this.area = area;
|
|
22
|
+
}
|
|
23
|
+
async ask(operation, path, input) {
|
|
24
|
+
const answer = await wire.request([this.area, this.program, operation, [...path], input]);
|
|
8
25
|
return answer[0];
|
|
9
26
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
abort();
|
|
26
|
-
close();
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
27
|
+
transfer(operation, path, body, input) {
|
|
28
|
+
return transfer([this.area, this.program, operation], path, body, input);
|
|
29
|
+
}
|
|
30
|
+
watch(path, options, signal) {
|
|
31
|
+
return watch(["program", this.program, this.area, [...path], options], signal);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
class SystemStorageBoundary extends StorageBoundary {
|
|
35
|
+
async ask(operation, path, input) {
|
|
36
|
+
const answer = await wire.request(["host-storage", operation, [...path], input]);
|
|
37
|
+
return answer[0];
|
|
38
|
+
}
|
|
39
|
+
transfer(operation, path, body, input) {
|
|
40
|
+
return transfer([`host-storage-${operation}`], path, body, input);
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
watch(path, options, signal) {
|
|
43
|
+
return watch(["system", [...path], options], signal);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
class RemoteStorage extends Storage {
|
|
47
|
+
boundary;
|
|
48
|
+
parts;
|
|
49
|
+
constructor(boundary, parts) {
|
|
50
|
+
super();
|
|
51
|
+
this.boundary = boundary;
|
|
52
|
+
this.parts = parts;
|
|
53
|
+
}
|
|
54
|
+
name() { return this.boundary.ask("name", this.parts); }
|
|
55
|
+
path() { return this.boundary.ask("path", this.parts); }
|
|
56
|
+
navigate(...parts) { return new RemoteStorage(this.boundary, [...this.parts, ...parts]); }
|
|
57
|
+
file(...parts) { return new RemoteStorageFile(this.boundary, [...this.parts, ...parts]); }
|
|
58
|
+
create() { return this.boundary.ask("create", this.parts); }
|
|
59
|
+
stat() { return this.boundary.ask("stat-storage", this.parts); }
|
|
60
|
+
async list(options = {}) {
|
|
61
|
+
const values = await this.boundary.ask("list", this.parts, options);
|
|
62
|
+
if (!Array.isArray(values))
|
|
63
|
+
throw new Error("The System returned an invalid Storage list");
|
|
64
|
+
return values.map(value => {
|
|
65
|
+
const entry = parseEntry(value);
|
|
66
|
+
return entry.kind === "file"
|
|
67
|
+
? new RemoteStorageFile(this.boundary, [...this.parts, ...entry.path])
|
|
68
|
+
: new RemoteStorage(this.boundary, [...this.parts, ...entry.path]);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
copy(destination, options = {}) { return copyStorage(this, destination, options); }
|
|
72
|
+
async move(destination, options = {}) {
|
|
73
|
+
if (await sameLocation(this, destination))
|
|
74
|
+
return;
|
|
75
|
+
await copyStorage(this, destination, options);
|
|
76
|
+
await this.delete();
|
|
77
|
+
}
|
|
78
|
+
delete() { return this.boundary.ask("delete-storage", this.parts); }
|
|
79
|
+
clear() { return this.boundary.ask("clear", this.parts); }
|
|
80
|
+
space() { return this.boundary.ask("space", this.parts); }
|
|
81
|
+
watch(options = {}) {
|
|
82
|
+
return this.boundary.watch(this.parts, { recursive: options.recursive }, options.signal);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
class RemoteStorageFile extends StorageFile {
|
|
86
|
+
boundary;
|
|
87
|
+
parts;
|
|
88
|
+
constructor(boundary, parts) {
|
|
89
|
+
super();
|
|
90
|
+
this.boundary = boundary;
|
|
91
|
+
this.parts = parts;
|
|
92
|
+
}
|
|
93
|
+
name() { return this.boundary.ask("name", this.parts); }
|
|
94
|
+
path() { return this.boundary.ask("path", this.parts); }
|
|
95
|
+
stat() { return this.boundary.ask("stat-file", this.parts); }
|
|
96
|
+
async stream(options = {}) {
|
|
97
|
+
const body = await this.boundary.transfer("stream", this.parts, undefined, options);
|
|
32
98
|
if (!body)
|
|
33
|
-
throw new Error("The
|
|
99
|
+
throw new Error("The Storage response has no byte stream");
|
|
34
100
|
return body;
|
|
35
101
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
102
|
+
async bytes(options) {
|
|
103
|
+
return new Uint8Array(await new Response(await this.stream(options)).arrayBuffer());
|
|
104
|
+
}
|
|
105
|
+
async text(options) {
|
|
106
|
+
return new Response(await this.stream(options)).text();
|
|
107
|
+
}
|
|
108
|
+
async json() { return JSON.parse(await this.text()); }
|
|
109
|
+
async write(value, options = {}) {
|
|
110
|
+
await this.boundary.transfer("write", this.parts, content(value).body, options);
|
|
111
|
+
}
|
|
112
|
+
async append(value) {
|
|
113
|
+
await this.boundary.transfer("append", this.parts, content(value).body);
|
|
114
|
+
}
|
|
115
|
+
async copy(destination, options = {}) {
|
|
116
|
+
if (await sameLocation(this, destination))
|
|
117
|
+
return;
|
|
118
|
+
await destination.write(await this.stream(), { overwrite: options.overwrite ?? false });
|
|
119
|
+
}
|
|
120
|
+
async move(destination, options = {}) {
|
|
121
|
+
if (await sameLocation(this, destination))
|
|
122
|
+
return;
|
|
123
|
+
await this.copy(destination, options);
|
|
124
|
+
await this.delete();
|
|
125
|
+
}
|
|
126
|
+
delete() { return this.boundary.ask("delete-file", this.parts); }
|
|
127
|
+
}
|
|
128
|
+
async function transfer(prefix, path, body, input) {
|
|
129
|
+
const channel = new MessageChannel();
|
|
130
|
+
const abort = () => channel.port1.postMessage("abort");
|
|
131
|
+
const close = () => channel.port1.close();
|
|
132
|
+
try {
|
|
133
|
+
const answer = await wire.request([...prefix, [...path], body ?? null, channel.port2, input], undefined, body instanceof ReadableStream ? [body, channel.port2] : [channel.port2]);
|
|
134
|
+
if (prefix.at(-1) !== "stream") {
|
|
135
|
+
close();
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (!(answer[0] instanceof ReadableStream))
|
|
139
|
+
throw new Error("The Storage response has no byte stream");
|
|
140
|
+
return controlledStream(answer[0], abort, close);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
abort();
|
|
144
|
+
close();
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function watch(values, signal) {
|
|
149
|
+
const operation = wire.stream(["storage-watch", ...values], undefined, signal);
|
|
150
|
+
return (async function* () {
|
|
151
|
+
for await (const value of operation) {
|
|
152
|
+
const change = value;
|
|
153
|
+
if (!change || (change.event !== "change" && change.event !== "rename") || change.path !== null && typeof change.path !== "string") {
|
|
154
|
+
throw new Error("The System returned an invalid Storage change");
|
|
155
|
+
}
|
|
156
|
+
yield Object.freeze({ event: change.event, path: change.path });
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
}
|
|
160
|
+
async function copyStorage(source, destination, options) {
|
|
161
|
+
if (await sameLocation(source, destination))
|
|
162
|
+
return;
|
|
163
|
+
const [sourcePath, destinationPath] = await Promise.all([source.path(), destination.path()]);
|
|
164
|
+
if (descendsFrom(destinationPath, sourcePath))
|
|
165
|
+
throw new Error("A Storage directory cannot be copied inside itself");
|
|
166
|
+
if (!await source.stat())
|
|
167
|
+
throw new Error(`There is no Storage directory at ${sourcePath}`);
|
|
168
|
+
if (await destination.stat()) {
|
|
169
|
+
if (!options.overwrite)
|
|
170
|
+
throw new Error(`A Storage directory already exists at ${destinationPath}`);
|
|
171
|
+
await destination.delete();
|
|
172
|
+
}
|
|
173
|
+
await destination.create();
|
|
174
|
+
for (const entry of await source.list()) {
|
|
175
|
+
const name = await entry.name();
|
|
176
|
+
if (entry instanceof StorageFile)
|
|
177
|
+
await entry.copy(destination.file(name), options);
|
|
178
|
+
else
|
|
179
|
+
await entry.copy(destination.navigate(name), options);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function sameLocation(left, right) {
|
|
183
|
+
const [leftPath, rightPath] = await Promise.all([left.path(), right.path()]);
|
|
184
|
+
return normalizePath(leftPath) === normalizePath(rightPath);
|
|
185
|
+
}
|
|
186
|
+
function descendsFrom(path, parent) {
|
|
187
|
+
const normalized = normalizePath(path);
|
|
188
|
+
const root = normalizePath(parent);
|
|
189
|
+
return normalized.startsWith(`${root}/`);
|
|
190
|
+
}
|
|
191
|
+
function normalizePath(path) {
|
|
192
|
+
return path.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
193
|
+
}
|
|
194
|
+
function parseEntry(value) {
|
|
195
|
+
const entry = value;
|
|
196
|
+
if (!entry || (entry.kind !== "storage" && entry.kind !== "file") || !Array.isArray(entry.path) || entry.path.length === 0 || entry.path.some(part => typeof part !== "string")) {
|
|
197
|
+
throw new Error("The System returned an invalid Storage entry");
|
|
198
|
+
}
|
|
199
|
+
return { kind: entry.kind, path: entry.path };
|
|
51
200
|
}
|
|
52
201
|
export function store(program) {
|
|
53
202
|
async function ask(operation, ...values) {
|
|
@@ -72,52 +221,3 @@ export function sql(kind, program) {
|
|
|
72
221
|
}
|
|
73
222
|
};
|
|
74
223
|
}
|
|
75
|
-
/** Access to the System's native storage through the Client boundary. */
|
|
76
|
-
export function systemStorage() {
|
|
77
|
-
async function ask(operation, ...values) {
|
|
78
|
-
const answer = await wire.request(["host-storage", operation, ...values]);
|
|
79
|
-
return answer[0];
|
|
80
|
-
}
|
|
81
|
-
async function transfer(operation, path, body = null) {
|
|
82
|
-
const channel = new MessageChannel();
|
|
83
|
-
const abort = () => channel.port1.postMessage("abort");
|
|
84
|
-
const close = () => channel.port1.close();
|
|
85
|
-
try {
|
|
86
|
-
const answer = await wire.request([`host-storage-${operation}`, path, body, channel.port2], undefined, body instanceof ReadableStream ? [body, channel.port2] : [channel.port2]);
|
|
87
|
-
if (operation === "write") {
|
|
88
|
-
close();
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
if (!(answer[0] instanceof ReadableStream))
|
|
92
|
-
throw new Error("The storage response has no byte stream");
|
|
93
|
-
return controlledStream(answer[0], abort, close);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
abort();
|
|
97
|
-
close();
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function stream(...path) {
|
|
102
|
-
const body = await transfer("stream", path);
|
|
103
|
-
if (!body)
|
|
104
|
-
throw new Error("The storage response has no body");
|
|
105
|
-
return body;
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
path: () => ask("path"),
|
|
109
|
-
resolve: (...path) => ask("resolve", ...path),
|
|
110
|
-
stream,
|
|
111
|
-
async bytes(...path) { return new Uint8Array(await new Response(await stream(...path)).arrayBuffer()); },
|
|
112
|
-
async text(...path) { return new Response(await stream(...path)).text(); },
|
|
113
|
-
async json(...path) { return JSON.parse(await new Response(await stream(...path)).text()); },
|
|
114
|
-
async write(...args) {
|
|
115
|
-
const path = args.slice(0, -1);
|
|
116
|
-
await transfer("write", path, content(args.at(-1)).body);
|
|
117
|
-
},
|
|
118
|
-
stat: (...path) => ask("stat", ...path),
|
|
119
|
-
list: (...path) => ask("list", ...path),
|
|
120
|
-
delete: (...path) => ask("delete", ...path),
|
|
121
|
-
clear: (...path) => ask("clear", ...path)
|
|
122
|
-
};
|
|
123
|
-
}
|