@meshagent/meshagent 0.35.6 → 0.35.8

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/containers-client.d.ts +79 -2
  3. package/dist/browser/containers-client.js +341 -19
  4. package/dist/browser/database-client.d.ts +95 -24
  5. package/dist/browser/database-client.js +150 -49
  6. package/dist/browser/messaging-client.d.ts +33 -52
  7. package/dist/browser/messaging-client.js +180 -184
  8. package/dist/browser/participant.d.ts +5 -3
  9. package/dist/browser/participant.js +9 -1
  10. package/dist/browser/room-client.js +2 -0
  11. package/dist/browser/room-event.d.ts +6 -2
  12. package/dist/browser/room-event.js +4 -2
  13. package/dist/browser/secrets-client.d.ts +86 -16
  14. package/dist/browser/secrets-client.js +243 -44
  15. package/dist/browser/storage-client.d.ts +17 -4
  16. package/dist/browser/storage-client.js +104 -16
  17. package/dist/esm/containers-client.d.ts +79 -2
  18. package/dist/esm/containers-client.js +341 -19
  19. package/dist/esm/database-client.d.ts +95 -24
  20. package/dist/esm/database-client.js +150 -49
  21. package/dist/esm/messaging-client.d.ts +33 -52
  22. package/dist/esm/messaging-client.js +179 -180
  23. package/dist/esm/participant.d.ts +5 -3
  24. package/dist/esm/participant.js +9 -1
  25. package/dist/esm/room-client.js +2 -0
  26. package/dist/esm/room-event.d.ts +6 -2
  27. package/dist/esm/room-event.js +4 -2
  28. package/dist/esm/secrets-client.d.ts +86 -16
  29. package/dist/esm/secrets-client.js +243 -44
  30. package/dist/esm/storage-client.d.ts +17 -4
  31. package/dist/esm/storage-client.js +103 -16
  32. package/dist/node/containers-client.d.ts +79 -2
  33. package/dist/node/containers-client.js +341 -19
  34. package/dist/node/database-client.d.ts +95 -24
  35. package/dist/node/database-client.js +150 -49
  36. package/dist/node/messaging-client.d.ts +33 -52
  37. package/dist/node/messaging-client.js +180 -184
  38. package/dist/node/participant.d.ts +5 -3
  39. package/dist/node/participant.js +9 -1
  40. package/dist/node/room-client.js +2 -0
  41. package/dist/node/room-event.d.ts +6 -2
  42. package/dist/node/room-event.js +4 -2
  43. package/dist/node/secrets-client.d.ts +86 -16
  44. package/dist/node/secrets-client.js +243 -44
  45. package/dist/node/storage-client.d.ts +17 -4
  46. package/dist/node/storage-client.js +104 -16
  47. package/package.json +1 -1
@@ -1,82 +1,281 @@
1
1
  import { BinaryContent, EmptyContent, FileContent, JsonContent } from "./response";
2
+ import { RoomServerException } from "./room-server-client";
3
+ import { unpackMessage } from "./utils";
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
2
7
  export class SecretsClient {
3
- constructor({ room }) {
8
+ constructor({ room, oauthTokenRequestHandler, secretRequestHandler, }) {
9
+ this._oauthRequestHandler = this._handleClientOAuthTokenRequest.bind(this);
10
+ this._secretRequestHandler = this._handleClientSecretRequest.bind(this);
4
11
  this.client = room;
12
+ this.oauthTokenRequestHandler = oauthTokenRequestHandler;
13
+ this.secretRequestHandler = secretRequestHandler;
14
+ this.client.protocol.addHandler("secrets.request_oauth_token", this._oauthRequestHandler);
15
+ this.client.protocol.addHandler("secrets.request_secret", this._secretRequestHandler);
5
16
  }
6
17
  unexpectedResponse(operation) {
7
- return new Error(`unexpected return type from secrets.${operation}`);
18
+ return new RoomServerException(`unexpected return type from secrets.${operation}`);
8
19
  }
9
- async setSecret({ secretId, data, mimeType, name, delegatedTo, forIdentity, }) {
10
- const response = await this.client.invoke({
20
+ async invoke(operation, input) {
21
+ return await this.client.invoke({
11
22
  toolkit: "secrets",
12
- tool: "set_secret",
13
- input: new BinaryContent({
14
- data,
15
- headers: {
16
- secret_id: secretId,
17
- type: mimeType ?? null,
18
- name: name ?? null,
19
- delegated_to: delegatedTo ?? null,
20
- for_identity: forIdentity ?? null,
21
- has_data: true,
22
- },
23
- }),
23
+ tool: operation,
24
+ input,
24
25
  });
25
- if (response instanceof EmptyContent || response instanceof JsonContent) {
26
- return;
26
+ }
27
+ serializeConnectorRef(connector) {
28
+ if (connector == null) {
29
+ return null;
27
30
  }
28
- throw this.unexpectedResponse("set_secret");
31
+ return {
32
+ openai_connector_id: connector.openaiConnectorId ?? null,
33
+ server_url: connector.serverUrl ?? null,
34
+ client_secret_id: connector.clientSecretId ?? null,
35
+ };
29
36
  }
30
- async getSecret({ secretId, delegatedTo, }) {
31
- const req = {
32
- secret_id: secretId,
33
- type: null,
34
- name: null,
35
- delegated_to: delegatedTo ?? null,
37
+ serializeOAuthConfig(oauth) {
38
+ if (oauth == null) {
39
+ return null;
40
+ }
41
+ return {
42
+ client_id: oauth.client_id,
43
+ client_secret: oauth.client_secret ?? null,
44
+ authorization_endpoint: oauth.authorization_endpoint,
45
+ token_endpoint: oauth.token_endpoint,
46
+ no_pkce: oauth.no_pkce ?? null,
47
+ scopes: oauth.scopes ?? null,
36
48
  };
37
- const response = await this.client.invoke({ toolkit: "secrets", tool: "get_secret", input: req });
38
- if (response instanceof EmptyContent) {
49
+ }
50
+ parseAccessToken(operation, response) {
51
+ if (!(response instanceof JsonContent)) {
52
+ throw this.unexpectedResponse(operation);
53
+ }
54
+ const token = response.json["access_token"];
55
+ if (typeof token !== "string" || token.length === 0) {
39
56
  return null;
40
57
  }
41
- if (response instanceof FileContent) {
42
- return response;
58
+ return token;
59
+ }
60
+ parseSecretInfo(value) {
61
+ if (!isRecord(value) || typeof value["id"] !== "string" || typeof value["name"] !== "string" || typeof value["type"] !== "string") {
62
+ throw this.unexpectedResponse("list_secrets");
43
63
  }
44
- throw this.unexpectedResponse("get_secret");
64
+ const delegatedTo = value["delegated_to"];
65
+ if (delegatedTo !== undefined && delegatedTo !== null && typeof delegatedTo !== "string") {
66
+ throw this.unexpectedResponse("list_secrets");
67
+ }
68
+ return {
69
+ id: value["id"],
70
+ name: value["name"],
71
+ type: value["type"],
72
+ delegatedTo: delegatedTo,
73
+ };
74
+ }
75
+ async _handleClientOAuthTokenRequest(protocol, messageId, type, bytes) {
76
+ void protocol;
77
+ void messageId;
78
+ void type;
79
+ if (bytes == null) {
80
+ throw new RoomServerException("invalid secrets.request_oauth_token payload");
81
+ }
82
+ if (this.oauthTokenRequestHandler == null) {
83
+ throw new RoomServerException("No oauth token handler registered");
84
+ }
85
+ const [request] = unpackMessage(bytes);
86
+ const requestId = request["request_id"];
87
+ const challenge = request["challenge"];
88
+ const requestPayload = request["request"];
89
+ if (typeof requestId !== "string" || !isRecord(requestPayload)) {
90
+ throw new RoomServerException("invalid secrets.request_oauth_token payload");
91
+ }
92
+ const oauth = requestPayload["oauth"];
93
+ if (!isRecord(oauth) || typeof oauth["authorization_endpoint"] !== "string" || typeof oauth["token_endpoint"] !== "string") {
94
+ throw new RoomServerException("invalid secrets.request_oauth_token payload");
95
+ }
96
+ const scopes = oauth["scopes"];
97
+ if (scopes !== undefined && scopes !== null && (!Array.isArray(scopes) || scopes.some((scope) => typeof scope !== "string"))) {
98
+ throw new RoomServerException("invalid secrets.request_oauth_token payload");
99
+ }
100
+ Promise.resolve(this.oauthTokenRequestHandler({
101
+ requestId,
102
+ authorizationEndpoint: oauth["authorization_endpoint"],
103
+ tokenEndpoint: oauth["token_endpoint"],
104
+ challenge: typeof challenge === "string" ? challenge : null,
105
+ scopes: scopes ?? null,
106
+ clientId: typeof oauth["client_id"] === "string" ? oauth["client_id"] : null,
107
+ })).catch((error) => {
108
+ console.warn("OAuth token request handler threw", error);
109
+ });
110
+ }
111
+ async _handleClientSecretRequest(protocol, messageId, type, bytes) {
112
+ void protocol;
113
+ void messageId;
114
+ void type;
115
+ if (bytes == null) {
116
+ throw new RoomServerException("invalid secrets.request_secret payload");
117
+ }
118
+ if (this.secretRequestHandler == null) {
119
+ throw new RoomServerException("No secret handler registered");
120
+ }
121
+ const [request] = unpackMessage(bytes);
122
+ const requestId = request["request_id"];
123
+ const requestPayload = request["request"];
124
+ if (typeof requestId !== "string" || !isRecord(requestPayload) || typeof requestPayload["url"] !== "string" || typeof requestPayload["type"] !== "string") {
125
+ throw new RoomServerException("invalid secrets.request_secret payload");
126
+ }
127
+ Promise.resolve(this.secretRequestHandler({
128
+ requestId,
129
+ url: requestPayload["url"],
130
+ type: requestPayload["type"],
131
+ delegateTo: typeof requestPayload["delegate_to"] === "string" ? requestPayload["delegate_to"] : null,
132
+ })).catch((error) => {
133
+ console.warn("Secret request handler threw", error);
134
+ });
135
+ }
136
+ async provideOAuthAuthorization({ requestId, code, }) {
137
+ const response = await this.invoke("provide_oauth_authorization", {
138
+ request_id: requestId,
139
+ code,
140
+ error: null,
141
+ });
142
+ if (response instanceof EmptyContent || response instanceof JsonContent) {
143
+ return;
144
+ }
145
+ throw this.unexpectedResponse("provide_oauth_authorization");
146
+ }
147
+ async rejectOAuthAuthorization({ requestId, error, }) {
148
+ const response = await this.invoke("provide_oauth_authorization", {
149
+ request_id: requestId,
150
+ code: null,
151
+ error,
152
+ });
153
+ if (response instanceof EmptyContent || response instanceof JsonContent) {
154
+ return;
155
+ }
156
+ throw this.unexpectedResponse("provide_oauth_authorization");
157
+ }
158
+ async provideSecret({ requestId, data, }) {
159
+ const response = await this.invoke("provide_secret", new BinaryContent({
160
+ data,
161
+ headers: {
162
+ request_id: requestId,
163
+ error: null,
164
+ },
165
+ }));
166
+ if (response instanceof EmptyContent || response instanceof JsonContent) {
167
+ return;
168
+ }
169
+ throw this.unexpectedResponse("provide_secret");
170
+ }
171
+ async rejectSecret({ requestId, error, }) {
172
+ const response = await this.invoke("provide_secret", new BinaryContent({
173
+ data: new Uint8Array(0),
174
+ headers: {
175
+ request_id: requestId,
176
+ error,
177
+ },
178
+ }));
179
+ if (response instanceof EmptyContent || response instanceof JsonContent) {
180
+ return;
181
+ }
182
+ throw this.unexpectedResponse("provide_secret");
183
+ }
184
+ async getOfflineOAuthToken({ connector, oauth, delegatedTo, delegatedBy, }) {
185
+ const response = await this.invoke("get_offline_oauth_token", {
186
+ connector: this.serializeConnectorRef(connector),
187
+ oauth: this.serializeOAuthConfig(oauth),
188
+ delegated_to: delegatedTo ?? null,
189
+ delegated_by: delegatedBy ?? null,
190
+ });
191
+ return this.parseAccessToken("get_offline_oauth_token", response);
192
+ }
193
+ async requestOAuthToken({ connector, oauth, timeout = 60 * 5, fromParticipantId, redirectUri, delegateTo, }) {
194
+ const response = await this.invoke("request_oauth_token", {
195
+ connector: this.serializeConnectorRef(connector),
196
+ oauth: this.serializeOAuthConfig(oauth),
197
+ redirect_uri: typeof redirectUri === "string" ? redirectUri : redirectUri.toString(),
198
+ timeout,
199
+ participant_id: fromParticipantId,
200
+ delegate_to: delegateTo ?? null,
201
+ });
202
+ return this.parseAccessToken("request_oauth_token", response);
45
203
  }
46
204
  async listSecrets() {
47
- const response = await this.client.invoke({ toolkit: "secrets", tool: "list_secrets", input: {} });
205
+ const response = await this.invoke("list_secrets", {});
48
206
  if (!(response instanceof JsonContent)) {
49
207
  throw this.unexpectedResponse("list_secrets");
50
208
  }
51
- const secrets = Array.isArray(response.json?.secrets) ? response.json.secrets : [];
52
- return secrets.map((item) => ({
53
- id: item.id,
54
- name: item.name,
55
- type: item.type,
56
- delegatedTo: item.delegated_to,
57
- }));
209
+ const secrets = response.json["secrets"];
210
+ if (!Array.isArray(secrets)) {
211
+ throw this.unexpectedResponse("list_secrets");
212
+ }
213
+ return secrets.map((item) => this.parseSecretInfo(item));
58
214
  }
59
215
  async deleteSecret({ secretId, delegatedTo, }) {
60
- const req = {
216
+ const response = await this.invoke("delete_secret", {
61
217
  id: secretId,
62
218
  delegated_to: delegatedTo ?? null,
63
- };
64
- const response = await this.client.invoke({ toolkit: "secrets", tool: "delete_secret", input: req });
219
+ });
65
220
  if (response instanceof EmptyContent || response instanceof JsonContent) {
66
221
  return;
67
222
  }
68
223
  throw this.unexpectedResponse("delete_secret");
69
224
  }
70
225
  async deleteRequestedSecret({ url, type, delegatedTo, }) {
71
- const req = {
226
+ const response = await this.invoke("delete_requested_secret", {
72
227
  url,
73
228
  type,
74
229
  delegated_to: delegatedTo ?? null,
75
- };
76
- const response = await this.client.invoke({ toolkit: "secrets", tool: "delete_requested_secret", input: req });
230
+ });
77
231
  if (response instanceof EmptyContent || response instanceof JsonContent) {
78
232
  return;
79
233
  }
80
234
  throw this.unexpectedResponse("delete_requested_secret");
81
235
  }
236
+ async requestSecret({ fromParticipantId, url, type, timeout = 60 * 5, delegateTo, }) {
237
+ const response = await this.invoke("request_secret", {
238
+ url,
239
+ type,
240
+ participant_id: fromParticipantId,
241
+ timeout,
242
+ delegate_to: delegateTo ?? null,
243
+ });
244
+ if (response instanceof FileContent) {
245
+ return response.data;
246
+ }
247
+ throw this.unexpectedResponse("request_secret");
248
+ }
249
+ async setSecret({ secretId, type, mimeType, name, delegatedTo, forIdentity, data, }) {
250
+ const response = await this.invoke("set_secret", new BinaryContent({
251
+ data,
252
+ headers: {
253
+ secret_id: secretId ?? null,
254
+ type: type ?? mimeType ?? null,
255
+ name: name ?? null,
256
+ delegated_to: delegatedTo ?? null,
257
+ for_identity: forIdentity ?? null,
258
+ has_data: true,
259
+ },
260
+ }));
261
+ if (response instanceof EmptyContent || response instanceof JsonContent) {
262
+ return;
263
+ }
264
+ throw this.unexpectedResponse("set_secret");
265
+ }
266
+ async getSecret({ secretId, type, name, delegatedTo, }) {
267
+ const response = await this.invoke("get_secret", {
268
+ secret_id: secretId ?? null,
269
+ type: type ?? null,
270
+ name: name ?? null,
271
+ delegated_to: delegatedTo ?? null,
272
+ });
273
+ if (response instanceof EmptyContent) {
274
+ return null;
275
+ }
276
+ if (response instanceof FileContent) {
277
+ return response;
278
+ }
279
+ throw this.unexpectedResponse("get_secret");
280
+ }
82
281
  }
@@ -1,37 +1,50 @@
1
1
  import { RoomClient } from "./room-client";
2
+ import { Protocol } from "./protocol";
2
3
  import { RoomEvent } from "./room-event";
3
4
  import { BinaryContent, FileContent } from "./response";
4
5
  import { EventEmitter } from "./event-emitter";
6
+ type StorageClientRoom = Pick<RoomClient, "invoke" | "invokeStream" | "emit"> & {
7
+ protocol: Pick<Protocol, "addHandler">;
8
+ };
5
9
  export declare class FileHandle {
6
10
  id: number;
7
11
  constructor({ id }: {
8
12
  id: number;
9
13
  });
10
14
  }
11
- declare class StorageEntry {
15
+ export declare class StorageEntry {
12
16
  name: string;
13
17
  isFolder: boolean;
14
18
  size: number | null;
15
- constructor({ name, isFolder, size }: {
19
+ createdAt: Date | null;
20
+ updatedAt: Date | null;
21
+ constructor({ name, isFolder, size, createdAt, updatedAt }: {
16
22
  name: string;
17
23
  isFolder: boolean;
18
24
  size?: number | null;
25
+ createdAt?: Date | null;
26
+ updatedAt?: Date | null;
19
27
  });
20
28
  nameWithoutExtension(): string;
21
29
  }
22
30
  export declare class StorageClient extends EventEmitter<RoomEvent> {
23
31
  private client;
24
32
  constructor({ room }: {
25
- room: RoomClient;
33
+ room: StorageClientRoom;
26
34
  });
27
35
  private _handleFileUpdated;
28
36
  private _handleFileDeleted;
29
37
  private _unexpectedResponseError;
38
+ private _storageEntry;
30
39
  private _invoke;
31
40
  list(path: string): Promise<StorageEntry[]>;
32
- delete(path: string): Promise<void>;
41
+ stat(path: string): Promise<StorageEntry | null>;
42
+ delete(path: string, { recursive, }?: {
43
+ recursive?: boolean | null;
44
+ }): Promise<void>;
33
45
  exists(path: string): Promise<boolean>;
34
46
  private _defaultUploadName;
47
+ private _defaultUploadMimeType;
35
48
  upload(path: string, bytes: Uint8Array, { overwrite, name, mimeType, }?: {
36
49
  overwrite?: boolean;
37
50
  name?: string | null;
@@ -3,16 +3,68 @@ import { BinaryContent, ControlContent, ErrorContent, JsonContent, FileContent }
3
3
  import { unpackMessage } from "./utils";
4
4
  import { EventEmitter } from "./event-emitter";
5
5
  import { RoomServerException } from "./room-server-client";
6
+ const _DEFAULT_UPLOAD_MIME_TYPE = "application/octet-stream";
7
+ const _UPLOAD_MIME_TYPES_BY_SUFFIX = new Map([
8
+ [".tar.gz", "application/x-tar"],
9
+ [".tgz", "application/x-tar"],
10
+ ]);
11
+ const _UPLOAD_MIME_TYPES_BY_EXTENSION = new Map([
12
+ [".bin", "application/octet-stream"],
13
+ [".css", "text/css"],
14
+ [".csv", "text/csv"],
15
+ [".gif", "image/gif"],
16
+ [".gz", "application/gzip"],
17
+ [".htm", "text/html"],
18
+ [".html", "text/html"],
19
+ [".jpeg", "image/jpeg"],
20
+ [".jpg", "image/jpeg"],
21
+ [".js", "text/javascript"],
22
+ [".json", "application/json"],
23
+ [".md", "text/markdown"],
24
+ [".mp3", "audio/mpeg"],
25
+ [".mp4", "video/mp4"],
26
+ [".pdf", "application/pdf"],
27
+ [".png", "image/png"],
28
+ [".svg", "image/svg+xml"],
29
+ [".tar", "application/x-tar"],
30
+ [".txt", "text/plain"],
31
+ [".ts", "text/typescript"],
32
+ [".tsx", "text/tsx"],
33
+ [".wasm", "application/wasm"],
34
+ [".webp", "image/webp"],
35
+ [".xml", "application/xml"],
36
+ [".yaml", "application/yaml"],
37
+ [".yml", "application/yaml"],
38
+ [".zip", "application/zip"],
39
+ ]);
40
+ function _unexpectedStorageResponseError(operation) {
41
+ return new RoomServerException(`unexpected return type from storage.${operation}`);
42
+ }
43
+ function _parseStorageTimestamp(value, operation) {
44
+ if (value == null) {
45
+ return null;
46
+ }
47
+ if (typeof value !== "string") {
48
+ throw _unexpectedStorageResponseError(operation);
49
+ }
50
+ const timestamp = new Date(value);
51
+ if (Number.isNaN(timestamp.getTime())) {
52
+ throw _unexpectedStorageResponseError(operation);
53
+ }
54
+ return timestamp;
55
+ }
6
56
  export class FileHandle {
7
57
  constructor({ id }) {
8
58
  this.id = id;
9
59
  }
10
60
  }
11
- class StorageEntry {
12
- constructor({ name, isFolder, size = null }) {
61
+ export class StorageEntry {
62
+ constructor({ name, isFolder, size = null, createdAt = null, updatedAt = null }) {
13
63
  this.name = name;
14
64
  this.isFolder = isFolder;
15
65
  this.size = size;
66
+ this.createdAt = createdAt;
67
+ this.updatedAt = updatedAt;
16
68
  }
17
69
  nameWithoutExtension() {
18
70
  const segments = this.name
@@ -32,18 +84,32 @@ export class StorageClient extends EventEmitter {
32
84
  }
33
85
  async _handleFileUpdated(protocol, messageId, type, bytes) {
34
86
  const [data, _] = unpackMessage(bytes || new Uint8Array());
35
- const event = new FileUpdatedEvent({ path: data["path"] });
87
+ const event = new FileUpdatedEvent({ path: data["path"], participantId: data["participant_id"] });
36
88
  this.client.emit(event);
37
89
  this.emit('file.updated', event);
38
90
  }
39
91
  async _handleFileDeleted(protocol, messageId, type, bytes) {
40
92
  const [data, _] = unpackMessage(bytes || new Uint8Array());
41
- const event = new FileDeletedEvent({ path: data["path"] });
93
+ const event = new FileDeletedEvent({ path: data["path"], participantId: data["participant_id"] });
42
94
  this.client.emit(event);
43
95
  this.emit('file.deleted', event);
44
96
  }
45
97
  _unexpectedResponseError(operation) {
46
- return new RoomServerException(`unexpected return type from storage.${operation}`);
98
+ return _unexpectedStorageResponseError(operation);
99
+ }
100
+ _storageEntry(operation, value) {
101
+ if (typeof value["name"] !== "string" ||
102
+ typeof value["is_folder"] !== "boolean" ||
103
+ (value["size"] != null && typeof value["size"] !== "number")) {
104
+ throw this._unexpectedResponseError(operation);
105
+ }
106
+ return new StorageEntry({
107
+ name: value["name"],
108
+ isFolder: value["is_folder"],
109
+ size: typeof value["size"] === "number" ? value["size"] : null,
110
+ createdAt: _parseStorageTimestamp(value["created_at"], operation),
111
+ updatedAt: _parseStorageTimestamp(value["updated_at"], operation),
112
+ });
47
113
  }
48
114
  async _invoke(operation, input, callerContext) {
49
115
  return await this.client.invoke({
@@ -55,22 +121,26 @@ export class StorageClient extends EventEmitter {
55
121
  }
56
122
  async list(path) {
57
123
  const response = await this._invoke("list", { path });
58
- if (!(response instanceof JsonContent)) {
124
+ if (!(response instanceof JsonContent) || !Array.isArray(response.json["files"])) {
59
125
  throw this._unexpectedResponseError("list");
60
126
  }
61
127
  const files = response.json["files"];
62
- const entries = files.map((f) => {
63
- return new StorageEntry({
64
- name: f["name"],
65
- isFolder: f["is_folder"],
66
- size: typeof f["size"] === "number" ? f["size"] : null,
67
- });
68
- });
128
+ const entries = files.map((f) => this._storageEntry("list", f));
69
129
  entries.sort((a, b) => a.name.localeCompare(b.name));
70
130
  return entries;
71
131
  }
72
- async delete(path) {
73
- await this._invoke("delete", { path, recursive: null });
132
+ async stat(path) {
133
+ const response = await this._invoke("stat", { path });
134
+ if (!(response instanceof JsonContent) || typeof response.json["exists"] !== "boolean") {
135
+ throw this._unexpectedResponseError("stat");
136
+ }
137
+ if (!response.json["exists"]) {
138
+ return null;
139
+ }
140
+ return this._storageEntry("stat", response.json);
141
+ }
142
+ async delete(path, { recursive = null, } = {}) {
143
+ await this._invoke("delete", { path, recursive });
74
144
  }
75
145
  async exists(path) {
76
146
  const result = await this._invoke("exists", { path });
@@ -87,6 +157,22 @@ export class StorageClient extends EventEmitter {
87
157
  const lastSegment = segments.length > 0 ? segments[segments.length - 1] : undefined;
88
158
  return lastSegment ?? path;
89
159
  }
160
+ _defaultUploadMimeType(name, mimeType) {
161
+ if (typeof mimeType === "string" && mimeType.length > 0) {
162
+ return mimeType;
163
+ }
164
+ const lowerName = name.toLowerCase();
165
+ for (const [suffix, contentType] of _UPLOAD_MIME_TYPES_BY_SUFFIX.entries()) {
166
+ if (lowerName.endsWith(suffix)) {
167
+ return contentType;
168
+ }
169
+ }
170
+ const lastDot = lowerName.lastIndexOf(".");
171
+ if (lastDot >= 0) {
172
+ return _UPLOAD_MIME_TYPES_BY_EXTENSION.get(lowerName.slice(lastDot)) ?? _DEFAULT_UPLOAD_MIME_TYPE;
173
+ }
174
+ return _DEFAULT_UPLOAD_MIME_TYPE;
175
+ }
90
176
  async upload(path, bytes, { overwrite = false, name, mimeType = null, } = {}) {
91
177
  async function* singleChunk() {
92
178
  yield bytes;
@@ -100,6 +186,7 @@ export class StorageClient extends EventEmitter {
100
186
  }
101
187
  async uploadStream(path, chunks, { overwrite = false, chunkSize = 64 * 1024, size = null, name, mimeType = null, } = {}) {
102
188
  const resolvedName = this._defaultUploadName(path, name);
189
+ const resolvedMimeType = this._defaultUploadMimeType(resolvedName, mimeType);
103
190
  const input = new _StorageUploadInputStream({
104
191
  path,
105
192
  overwrite,
@@ -107,7 +194,7 @@ export class StorageClient extends EventEmitter {
107
194
  chunkSize,
108
195
  size,
109
196
  name: resolvedName,
110
- mimeType,
197
+ mimeType: resolvedMimeType,
111
198
  });
112
199
  const response = await this.client.invokeStream({
113
200
  toolkit: "storage",