@meshagent/meshagent 0.35.5 → 0.35.7

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 +16 -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 +141 -30
  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 +140 -30
  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 +141 -30
  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",
@@ -131,7 +218,10 @@ export class StorageClient extends EventEmitter {
131
218
  if (chunk.headers["kind"] !== "pull") {
132
219
  throw this._unexpectedResponseError("upload");
133
220
  }
134
- input.requestNext();
221
+ const rawChunkSize = chunk.headers["chunk_size"];
222
+ input.requestNext(typeof rawChunkSize === "number" && rawChunkSize > 0
223
+ ? rawChunkSize
224
+ : null);
135
225
  }
136
226
  }
137
227
  finally {
@@ -321,7 +411,7 @@ class _StorageDownloadInputStream {
321
411
  class _StorageUploadInputStream {
322
412
  constructor({ path, overwrite, chunks, chunkSize, size, name, mimeType, }) {
323
413
  this.closed = false;
324
- this.pendingPulls = 0;
414
+ this.pendingPulls = [];
325
415
  this.waitingResolver = null;
326
416
  this.pendingChunk = new Uint8Array(0);
327
417
  this.pendingOffset = 0;
@@ -334,11 +424,11 @@ class _StorageUploadInputStream {
334
424
  this.name = name;
335
425
  this.mimeType = mimeType;
336
426
  }
337
- requestNext() {
427
+ requestNext(chunkSize = null) {
338
428
  if (this.closed) {
339
429
  return;
340
430
  }
341
- this.pendingPulls += 1;
431
+ this.pendingPulls.push(chunkSize);
342
432
  if (this.waitingResolver) {
343
433
  const resolver = this.waitingResolver;
344
434
  this.waitingResolver = null;
@@ -356,21 +446,26 @@ class _StorageUploadInputStream {
356
446
  resolver();
357
447
  }
358
448
  }
359
- async nextChunk() {
360
- while (true) {
449
+ async nextChunk(requestedChunkSize) {
450
+ const parts = [];
451
+ let totalLength = 0;
452
+ while (totalLength < requestedChunkSize) {
361
453
  if (this.pendingOffset < this.pendingChunk.length) {
362
454
  const start = this.pendingOffset;
363
- const end = Math.min(start + this.chunkSize, this.pendingChunk.length);
455
+ const end = Math.min(start + (requestedChunkSize - totalLength), this.pendingChunk.length);
456
+ const part = this.pendingChunk.slice(start, end);
364
457
  this.pendingOffset = end;
365
- return this.pendingChunk.slice(start, end);
458
+ parts.push(part);
459
+ totalLength += part.length;
460
+ continue;
366
461
  }
367
462
  if (this.sourceExhausted) {
368
- return null;
463
+ break;
369
464
  }
370
465
  const next = await this.source.next();
371
466
  if (next.done) {
372
467
  this.sourceExhausted = true;
373
- return null;
468
+ break;
374
469
  }
375
470
  if (next.value.length === 0) {
376
471
  continue;
@@ -378,6 +473,19 @@ class _StorageUploadInputStream {
378
473
  this.pendingChunk = next.value;
379
474
  this.pendingOffset = 0;
380
475
  }
476
+ if (totalLength === 0) {
477
+ return null;
478
+ }
479
+ if (parts.length === 1) {
480
+ return parts[0];
481
+ }
482
+ const combined = new Uint8Array(totalLength);
483
+ let offset = 0;
484
+ for (const part of parts) {
485
+ combined.set(part, offset);
486
+ offset += part.length;
487
+ }
488
+ return combined;
381
489
  }
382
490
  async *stream() {
383
491
  yield new BinaryContent({
@@ -392,7 +500,7 @@ class _StorageUploadInputStream {
392
500
  },
393
501
  });
394
502
  while (!this.closed) {
395
- if (this.pendingPulls === 0) {
503
+ if (this.pendingPulls.length === 0) {
396
504
  await new Promise((resolve) => {
397
505
  this.waitingResolver = resolve;
398
506
  });
@@ -400,11 +508,13 @@ class _StorageUploadInputStream {
400
508
  if (this.closed) {
401
509
  return;
402
510
  }
403
- if (this.pendingPulls === 0) {
511
+ if (this.pendingPulls.length === 0) {
404
512
  continue;
405
513
  }
406
- this.pendingPulls -= 1;
407
- const chunk = await this.nextChunk();
514
+ const requestedChunkSize = this.pendingPulls.shift();
515
+ const chunk = await this.nextChunk(typeof requestedChunkSize === "number" && requestedChunkSize > 0
516
+ ? requestedChunkSize
517
+ : this.chunkSize);
408
518
  if (chunk == null) {
409
519
  return;
410
520
  }