buckyos 0.7.138 → 0.7.140

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 (73) hide show
  1. package/cli/core/app.ts +4 -0
  2. package/cli/dist/cli.mjs +545 -124
  3. package/cli/dist/cli.mjs.map +1 -1
  4. package/cli/modules/aiws.ts +294 -0
  5. package/cli/modules/executor.ts +98 -0
  6. package/cli/version.ts +1 -1
  7. package/dist/aiworkspace_client.d.ts +91 -0
  8. package/dist/aiworkspace_client.d.ts.map +1 -0
  9. package/dist/app-frame.cjs +1 -1
  10. package/dist/app-frame.cjs.map +1 -1
  11. package/dist/app-frame.d.ts +103 -3
  12. package/dist/app-frame.d.ts.map +1 -1
  13. package/dist/app-frame.mjs +189 -8
  14. package/dist/app-frame.mjs.map +1 -1
  15. package/dist/browser.cjs +1 -1
  16. package/dist/browser.cjs.map +1 -1
  17. package/dist/browser.d.ts +2 -0
  18. package/dist/browser.d.ts.map +1 -1
  19. package/dist/browser.mjs +7 -3
  20. package/dist/browser.mjs.map +1 -1
  21. package/dist/content.cjs +1 -1
  22. package/dist/content.cjs.map +1 -1
  23. package/dist/content.d.ts +18 -0
  24. package/dist/content.d.ts.map +1 -1
  25. package/dist/content.mjs +61 -1
  26. package/dist/content.mjs.map +1 -1
  27. package/dist/executor_client-978a8e1b.js +13 -0
  28. package/dist/executor_client-978a8e1b.js.map +1 -0
  29. package/dist/{ndm_proxy-6b03b9ec.mjs → executor_client-d2c9e854.mjs} +848 -247
  30. package/dist/executor_client-d2c9e854.mjs.map +1 -0
  31. package/dist/executor_client.d.ts +138 -0
  32. package/dist/executor_client.d.ts.map +1 -0
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.mjs +12 -3
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/internal/hash_worker.d.ts +2 -1
  40. package/dist/internal/hash_worker.d.ts.map +1 -1
  41. package/dist/internal/tus_client.d.ts +3 -0
  42. package/dist/internal/tus_client.d.ts.map +1 -1
  43. package/dist/ndm_client.d.ts +56 -7
  44. package/dist/ndm_client.d.ts.map +1 -1
  45. package/dist/{ndn_types-ac9c1e48.mjs → ndn_types-dcb74613.mjs} +3 -3
  46. package/dist/{ndn_types-ac9c1e48.mjs.map → ndn_types-dcb74613.mjs.map} +1 -1
  47. package/dist/nfsp.cjs +1 -1
  48. package/dist/nfsp.cjs.map +1 -1
  49. package/dist/nfsp.d.ts +16 -0
  50. package/dist/nfsp.d.ts.map +1 -1
  51. package/dist/nfsp.mjs +6 -0
  52. package/dist/nfsp.mjs.map +1 -1
  53. package/dist/node.cjs +1 -1
  54. package/dist/node.cjs.map +1 -1
  55. package/dist/node.d.ts +2 -0
  56. package/dist/node.d.ts.map +1 -1
  57. package/dist/node.mjs +7 -3
  58. package/dist/node.mjs.map +1 -1
  59. package/dist/provision.mjs +1 -1
  60. package/dist/runtime.d.ts +2 -0
  61. package/dist/runtime.d.ts.map +1 -1
  62. package/dist/sdk_core.d.ts +4 -0
  63. package/dist/sdk_core.d.ts.map +1 -1
  64. package/dist/{tus_client-19baa342.mjs → tus_client-8fd4d937.mjs} +40 -2
  65. package/dist/tus_client-8fd4d937.mjs.map +1 -0
  66. package/dist/{tus_client-2b915a81.js → tus_client-a2f73d53.js} +4 -4
  67. package/dist/tus_client-a2f73d53.js.map +1 -0
  68. package/package.json +1 -1
  69. package/dist/ndm_proxy-67651066.js +0 -13
  70. package/dist/ndm_proxy-67651066.js.map +0 -1
  71. package/dist/ndm_proxy-6b03b9ec.mjs.map +0 -1
  72. package/dist/tus_client-19baa342.mjs.map +0 -1
  73. package/dist/tus_client-2b915a81.js.map +0 -1
@@ -35,7 +35,7 @@ var __privateMethod = (obj, member, method) => {
35
35
  return method;
36
36
  };
37
37
  var _listener, _listeners, _cancelled, _allowInsecure, _gzip, _headers, _method, _timeout, _url, _body, _bodyType, _creds, _preflight, _process, _retry, _signal, _throttle, _getUrlFunc, _send, send_fn, _statusCode, _statusMessage, _headers2, _body2, _request, _error, _names, _data, _dataLength, _writeData, writeData_fn, _data2, _offset, _bytesRead, _parent, _maxInflation, _incrementBytesRead, incrementBytesRead_fn, _peekBytes, peekBytes_fn, _r, _s, _v, _networkV, _privateKey, _options, _type, _to, _data3, _nonce, _gasLimit, _gasPrice, _maxPriorityFeePerGas, _maxFeePerGas, _value, _chainId, _sig, _accessList, _maxFeePerBlobGas, _blobVersionedHashes, _kzg, _blobs, _auths, _blobWrapperVersion, _getSerialized, getSerialized_fn, _types, _fullTypes, _encoderCache, _getEncoder, getEncoder_fn, _offset2, _tokens, _subTokenString, subTokenString_fn, _walkAsync, walkAsync_fn, _getCoder, getCoder_fn, _errors, _events, _functions, _abiCoder, _getFunction, getFunction_fn, _getEvent, getEvent_fn, _transactions, _logs, _startBlock, _iface, _iface2, _filter, _a, _supports2544, _resolver, _fetch, fetch_fn, _getResolver, getResolver_fn, _url2, _processFunc, _name, _chainId2, _plugins, _provider, _poller, _interval, _blockNumber, _poll, poll_fn, _provider2, _poll2, _running, _tag, _lastBlock, _filter2, _hash, _provider3, _filter3, _poller2, _running2, _blockNumber2, _poll3, poll_fn2, _subs, _plugins2, _pausedState, _destroyed, _networkPromise, _anyNetwork, _performCache, _lastBlockNumber, _nextTimer, _timers, _disableCcipRead, _requestRate, _requestTimes, _options2, _getDelay, getDelay_fn, _perform, perform_fn, _call, call_fn, _checkNetwork, checkNetwork_fn, _getAccountValue, getAccountValue_fn, _getBlock, getBlock_fn, _hasSub, hasSub_fn, _getSub, getSub_fn, _throwUnsupported, throwUnsupported_fn, _provider4, _filterIdPromise, _poller3, _running3, _network, _hault, _poll4, poll_fn3, _teardown, teardown_fn, _event, _options3, _nextId, _payloads, _drainTimer, _notReady, _network2, _pendingDetectNetwork, _scheduleDrain, scheduleDrain_fn, _pollingInterval, _connect, _signingKey, _data4, _checksum, _words, _loadWords, loadWords_fn, _account, account_fn, _fromSeed, fromSeed_fn, _fromAccount, fromAccount_fn;
38
- import { v as DID, w as getBrowserIdentityRaw, x as clearBrowserUserInfo, y as saveBrowserIdentity, z as saveBrowserUserInfo, A as getBrowserUserInfo, B as clearBrowserIdentity, q as parseBuckyOSOwnerDocument, h as hashPassword, C as saveLocalAccountInfo, E as cleanLocalAccountInfo, F as canonicalize, G as signJwtEdDSA, O as ObjId, H as ChunkId, I as FileObject, J as sha256Bytes, K as DirObject, S as SimpleChunkList } from "./ndn_types-ac9c1e48.mjs";
38
+ import { v as DID, w as getBrowserIdentityRaw, x as clearBrowserUserInfo, y as saveBrowserIdentity, z as saveBrowserUserInfo, A as getBrowserUserInfo, B as clearBrowserIdentity, q as parseBuckyOSOwnerDocument, h as hashPassword, C as saveLocalAccountInfo, E as cleanLocalAccountInfo, F as canonicalize, G as signJwtEdDSA, H as ChunkId, I as sha256Bytes, J as FileObject, O as ObjId, K as DirObject, S as SimpleChunkList } from "./ndn_types-dcb74613.mjs";
39
39
  class RPCError extends Error {
40
40
  constructor(message, code, data) {
41
41
  super(message);
@@ -323,6 +323,91 @@ function buildJumpUrl(action, params, jumpBase = DEFAULT_JUMP_BASE_URL) {
323
323
  const fragment = new URLSearchParams(params).toString();
324
324
  return `${base.origin}${path}${fragment ? `#${fragment}` : ""}`;
325
325
  }
326
+ const AIWORKSPACE_SERVICE_NAME = "aiworkspace";
327
+ class AiwsFailure extends Error {
328
+ constructor(error) {
329
+ super(`${error.code}${error.sub_code ? `/${error.sub_code}` : ""}: ${error.detail}`);
330
+ this.name = "AiwsFailure";
331
+ this.code = error.code;
332
+ this.subCode = error.sub_code;
333
+ this.data = error.data;
334
+ }
335
+ }
336
+ class AiWorkspaceClient {
337
+ constructor(rpcClient) {
338
+ this.rpcClient = rpcClient;
339
+ }
340
+ async call(method, params) {
341
+ const raw = await this.rpcClient.call(method, params);
342
+ if (raw && typeof raw === "object" && raw.ok === false)
343
+ throw new AiwsFailure(raw.error);
344
+ return raw;
345
+ }
346
+ // ---- workspace
347
+ list() {
348
+ return this.call("ws.list", {});
349
+ }
350
+ info(workspace_id) {
351
+ return this.call("ws.get_info", { workspace_id });
352
+ }
353
+ getUserState(workspace_id) {
354
+ return this.call("ws.get_user_state", { workspace_id });
355
+ }
356
+ setUserState(workspace_id, entries, expect) {
357
+ return this.call("ws.set_user_state", { workspace_id, entries, ...expect ? { expect } : {} });
358
+ }
359
+ // ---- document
360
+ resolve(workspace_id, reference) {
361
+ return this.call("doc.resolve", { workspace_id, reference });
362
+ }
363
+ resolvePath(workspace_id, path) {
364
+ return this.call("doc.resolve", { workspace_id, path });
365
+ }
366
+ read(workspace_id, entity_id, selector) {
367
+ return this.call("doc.read", { workspace_id, entity_id, ...selector ? { selector } : {} });
368
+ }
369
+ query(workspace_id, params) {
370
+ return this.call("doc.query", { workspace_id, ...params });
371
+ }
372
+ outline(workspace_id) {
373
+ return this.call("doc.outline", { workspace_id });
374
+ }
375
+ prepare(workspace_id, request) {
376
+ return this.rpcClient.call("doc.prepare", { workspace_id, ...request });
377
+ }
378
+ /** The three-state commit result (`status: accepted | conflict | rejected`), never thrown. */
379
+ commit(workspace_id, request) {
380
+ return this.rpcClient.call("doc.commit", { workspace_id, ...request });
381
+ }
382
+ getSubmission(workspace_id, epoch, idempotency_key) {
383
+ return this.call("doc.get_submission", { workspace_id, epoch, idempotency_key });
384
+ }
385
+ // ---- runs (M3 executor protocol)
386
+ runStart(workspace_id, program, params, idempotency_key) {
387
+ return this.call("proc.start", { workspace_id, program, params, idempotency_key });
388
+ }
389
+ runGet(workspace_id, run_id, choices) {
390
+ return this.call("proc.get", { workspace_id, run_id, ...choices ? { choices } : {} });
391
+ }
392
+ runList(workspace_id, wish_id, limit) {
393
+ return this.call("proc.list", { workspace_id, ...wish_id ? { wish_id } : {}, ...limit ? { limit } : {} });
394
+ }
395
+ runClaim(workspace_id, run_id, claim_token, executor) {
396
+ return this.call("proc.claim", { workspace_id, run_id, claim_token, ...executor ? { executor } : {} });
397
+ }
398
+ runHeartbeat(workspace_id, run_id, attempt, progress) {
399
+ return this.call("proc.heartbeat", { workspace_id, run_id, attempt, ...progress ? { progress } : {} });
400
+ }
401
+ runReport(workspace_id, report) {
402
+ return this.call("proc.report", { workspace_id, ...report });
403
+ }
404
+ runApply(workspace_id, run_id, plan_digest, session_id) {
405
+ return this.call("proc.apply", { workspace_id, run_id, plan_digest, ...session_id ? { session_id } : {} });
406
+ }
407
+ runCancel(workspace_id, run_id) {
408
+ return this.call("proc.cancel", { workspace_id, run_id });
409
+ }
410
+ }
326
411
  function serializeAuthTarget(target) {
327
412
  if (!target || typeof target !== "object") {
328
413
  throw new RPCError("verify-hub auth target is required");
@@ -3453,6 +3538,9 @@ class BuckyOSRuntime {
3453
3538
  getAiccClient() {
3454
3539
  return new AiccClient(this.getServiceRpcClient("aicc"));
3455
3540
  }
3541
+ getAiWorkspaceClient() {
3542
+ return new AiWorkspaceClient(this.getServiceRpcClient(AIWORKSPACE_SERVICE_NAME));
3543
+ }
3456
3544
  getMsgQueueClient() {
3457
3545
  return new MsgQueueClient(this.getServiceRpcClient("kmsg"));
3458
3546
  }
@@ -5392,6 +5480,12 @@ class BuckyOSSDK {
5392
5480
  }
5393
5481
  return this.currentRuntime.getAiccClient();
5394
5482
  }
5483
+ getAiWorkspaceClient() {
5484
+ if (this.currentRuntime == null) {
5485
+ throw new Error("BuckyOS WebSDK is not initialized,call initBuckyOS first");
5486
+ }
5487
+ return this.currentRuntime.getAiWorkspaceClient();
5488
+ }
5395
5489
  getMsgQueueClient() {
5396
5490
  if (this.currentRuntime == null) {
5397
5491
  throw new Error("BuckyOS WebSDK is not initialized,call initBuckyOS first");
@@ -5584,6 +5678,7 @@ function createSDKModule(target) {
5584
5678
  getTaskManagerClient: sdk.getTaskManagerClient.bind(sdk),
5585
5679
  getWorkflowClient: sdk.getWorkflowClient.bind(sdk),
5586
5680
  getAiccClient: sdk.getAiccClient.bind(sdk),
5681
+ getAiWorkspaceClient: sdk.getAiWorkspaceClient.bind(sdk),
5587
5682
  getMsgQueueClient: sdk.getMsgQueueClient.bind(sdk),
5588
5683
  getMsgCenterClient: sdk.getMsgCenterClient.bind(sdk),
5589
5684
  getRepoClient: sdk.getRepoClient.bind(sdk)
@@ -22178,7 +22273,7 @@ class AbstractProvider {
22178
22273
  request.body = { data, sender };
22179
22274
  }
22180
22275
  this.emit("debug", { action: "sendCcipReadFetchRequest", request, index: i, urls });
22181
- let errorMessage = "unknown error";
22276
+ let errorMessage2 = "unknown error";
22182
22277
  let resp;
22183
22278
  try {
22184
22279
  resp = await request.send();
@@ -22194,13 +22289,13 @@ class AbstractProvider {
22194
22289
  return result.data;
22195
22290
  }
22196
22291
  if (result.message) {
22197
- errorMessage = result.message;
22292
+ errorMessage2 = result.message;
22198
22293
  }
22199
22294
  this.emit("debug", { action: "receiveCcipReadFetchError", request, result });
22200
22295
  } catch (error) {
22201
22296
  }
22202
- assert(resp.statusCode < 400 || resp.statusCode >= 500, `response not found during CCIP fetch: ${errorMessage}`, "OFFCHAIN_FAULT", { reason: "404_MISSING_RESOURCE", transaction: tx, info: { url, errorMessage } });
22203
- errorMessages.push(errorMessage);
22297
+ assert(resp.statusCode < 400 || resp.statusCode >= 500, `response not found during CCIP fetch: ${errorMessage2}`, "OFFCHAIN_FAULT", { reason: "404_MISSING_RESOURCE", transaction: tx, info: { url, errorMessage: errorMessage2 } });
22298
+ errorMessages.push(errorMessage2);
22204
22299
  }
22205
22300
  assert(false, `error encountered during CCIP fetch: ${errorMessages.map((m) => JSON.stringify(m)).join(", ")}`, "OFFCHAIN_FAULT", {
22206
22301
  reason: "500_SERVER_ERROR",
@@ -27581,7 +27676,9 @@ function getWorkerBlobUrl() {
27581
27676
  function isHashWorkerAvailable() {
27582
27677
  return typeof Worker !== "undefined" && typeof Blob !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined";
27583
27678
  }
27584
- function hashFileInWorker(file, chunkSize) {
27679
+ function hashFileInWorker(file, chunkSize, signal) {
27680
+ let cancel = () => {
27681
+ };
27585
27682
  let progressCb = null;
27586
27683
  let cumulativeBytes = 0;
27587
27684
  const chunks = [];
@@ -27593,6 +27690,16 @@ function hashFileInWorker(file, chunkSize) {
27593
27690
  reject(new Error("Failed to create hash worker"));
27594
27691
  return;
27595
27692
  }
27693
+ cancel = () => {
27694
+ worker.terminate();
27695
+ signal == null ? void 0 : signal.removeEventListener("abort", cancel);
27696
+ reject(new Error("Hash cancelled"));
27697
+ };
27698
+ if (signal == null ? void 0 : signal.aborted) {
27699
+ cancel();
27700
+ return;
27701
+ }
27702
+ signal == null ? void 0 : signal.addEventListener("abort", cancel, { once: true });
27596
27703
  worker.onmessage = (e) => {
27597
27704
  const msg = e.data;
27598
27705
  if (msg.type === "chunk") {
@@ -27607,14 +27714,17 @@ function hashFileInWorker(file, chunkSize) {
27607
27714
  if (progressCb)
27608
27715
  progressCb(cumulativeBytes);
27609
27716
  } else if (msg.type === "done") {
27717
+ signal == null ? void 0 : signal.removeEventListener("abort", cancel);
27610
27718
  worker.terminate();
27611
27719
  resolve(chunks);
27612
27720
  } else if (msg.type === "error") {
27721
+ signal == null ? void 0 : signal.removeEventListener("abort", cancel);
27613
27722
  worker.terminate();
27614
27723
  reject(new Error(msg.message ?? "Worker hash error"));
27615
27724
  }
27616
27725
  };
27617
27726
  worker.onerror = (err) => {
27727
+ signal == null ? void 0 : signal.removeEventListener("abort", cancel);
27618
27728
  worker.terminate();
27619
27729
  reject(new Error(err.message ?? "Worker error"));
27620
27730
  };
@@ -27622,6 +27732,7 @@ function hashFileInWorker(file, chunkSize) {
27622
27732
  });
27623
27733
  return {
27624
27734
  result,
27735
+ cancel: () => cancel(),
27625
27736
  onProgress(cb) {
27626
27737
  progressCb = cb;
27627
27738
  }
@@ -27664,6 +27775,7 @@ const browserProvider = {
27664
27775
  },
27665
27776
  pickFiles(options) {
27666
27777
  return new Promise((resolve, reject) => {
27778
+ var _a2, _b;
27667
27779
  const input = document.createElement("input");
27668
27780
  input.type = "file";
27669
27781
  if (options.mode === "single_dir") {
@@ -27679,27 +27791,44 @@ const browserProvider = {
27679
27791
  input.accept = options.accept.join(",");
27680
27792
  }
27681
27793
  let settled = false;
27682
- input.addEventListener("change", () => {
27794
+ const cleanup = () => {
27795
+ var _a3;
27796
+ window.removeEventListener("focus", onFocus);
27797
+ (_a3 = options.signal) == null ? void 0 : _a3.removeEventListener("abort", onAbort);
27798
+ };
27799
+ const fail2 = (error) => {
27683
27800
  if (settled)
27684
27801
  return;
27685
27802
  settled = true;
27803
+ cleanup();
27804
+ reject(error);
27805
+ };
27806
+ const onAbort = () => fail2(new NdmError("USER_CANCELLED", "Import cancelled"));
27807
+ const onFocus = () => {
27808
+ setTimeout(() => {
27809
+ if (!settled)
27810
+ fail2(new NdmError("USER_CANCELLED", "User cancelled file selection"));
27811
+ }, 500);
27812
+ };
27813
+ input.addEventListener("change", () => {
27814
+ if (settled)
27815
+ return;
27686
27816
  const files = input.files;
27687
27817
  if (!files || files.length === 0) {
27688
- reject(new NdmError("USER_CANCELLED", "No files selected"));
27818
+ fail2(new NdmError(options.mode === "single_dir" ? "EMPTY_DIRECTORY_UNAVAILABLE" : "USER_CANCELLED", options.mode === "single_dir" ? "FileList cannot represent an empty directory; use a directory-tree provider" : "No files selected"));
27689
27819
  return;
27690
27820
  }
27821
+ settled = true;
27822
+ cleanup();
27691
27823
  resolve(Array.from(files));
27692
27824
  });
27693
- const onFocus = () => {
27694
- setTimeout(() => {
27695
- if (!settled) {
27696
- settled = true;
27697
- reject(new NdmError("USER_CANCELLED", "User cancelled file selection"));
27698
- }
27699
- window.removeEventListener("focus", onFocus);
27700
- }, 500);
27701
- };
27825
+ input.addEventListener("cancel", onAbort);
27702
27826
  window.addEventListener("focus", onFocus);
27827
+ if ((_a2 = options.signal) == null ? void 0 : _a2.aborted) {
27828
+ onAbort();
27829
+ return;
27830
+ }
27831
+ (_b = options.signal) == null ? void 0 : _b.addEventListener("abort", onAbort, { once: true });
27703
27832
  input.click();
27704
27833
  });
27705
27834
  }
@@ -27772,7 +27901,8 @@ async function callStoreApi(methodName, requestBody, options) {
27772
27901
  method: "POST",
27773
27902
  headers,
27774
27903
  body: JSON.stringify(requestBody),
27775
- credentials: (options == null ? void 0 : options.credentials) ?? "include"
27904
+ credentials: (options == null ? void 0 : options.credentials) ?? "include",
27905
+ signal: options == null ? void 0 : options.signal
27776
27906
  });
27777
27907
  if (response.status === 204) {
27778
27908
  await ((_a2 = response.body) == null ? void 0 : _a2.cancel());
@@ -27806,7 +27936,8 @@ async function getZoneGatewayJson(pathWithQuery, options) {
27806
27936
  const response = await fetcher(`${endpoint}${pathWithQuery}`, {
27807
27937
  method: "GET",
27808
27938
  headers,
27809
- credentials: (options == null ? void 0 : options.credentials) ?? "include"
27939
+ credentials: (options == null ? void 0 : options.credentials) ?? "include",
27940
+ signal: options == null ? void 0 : options.signal
27810
27941
  });
27811
27942
  const contentType = response.headers.get("content-type") ?? "";
27812
27943
  const isJsonResponse = contentType.includes("application/json");
@@ -27911,7 +28042,8 @@ async function debugDumpExpandState$1(request, options) {
27911
28042
  async function anchorState$1(request, options) {
27912
28043
  return callStoreApi("anchor_state", request, options);
27913
28044
  }
27914
- async function materializeFile(file, chunkSize = DEFAULT_CHUNK_SIZE) {
28045
+ async function materializeFile(file, chunkSize = DEFAULT_CHUNK_SIZE, signal) {
28046
+ throwIfCancelled(signal);
27915
28047
  const fileSize = file.size;
27916
28048
  const chunks = [];
27917
28049
  if (fileSize <= chunkSize) {
@@ -27921,12 +28053,14 @@ async function materializeFile(file, chunkSize = DEFAULT_CHUNK_SIZE) {
27921
28053
  const chunkIdStr = chunkId.toString();
27922
28054
  chunks.push({ chunkId: chunkIdStr, offset: 0, length: buf.length, uploaded: false });
27923
28055
  const fileObj2 = new FileObject(file.name, fileSize, chunkIdStr);
27924
- const [objId2] = fileObj2.genObjId();
27925
- return { objectId: objId2.toString(), chunks, fileObject: fileObj2.toJSON() };
28056
+ const [objId2, objData2] = fileObj2.genObjId();
28057
+ throwIfCancelled(signal);
28058
+ return { objectId: objId2.toString(), chunks, fileObject: fileObj2.toJSON(), objects: [{ obj_id: objId2.toString(), obj_data: objData2 }] };
27926
28059
  }
27927
28060
  const chunkList = new SimpleChunkList();
27928
28061
  let offset = 0;
27929
28062
  while (offset < fileSize) {
28063
+ throwIfCancelled(signal);
27930
28064
  const end = Math.min(offset + chunkSize, fileSize);
27931
28065
  const slice = new Uint8Array(await file.slice(offset, end).arrayBuffer());
27932
28066
  const hash2 = sha256Bytes(slice);
@@ -27935,29 +28069,42 @@ async function materializeFile(file, chunkSize = DEFAULT_CHUNK_SIZE) {
27935
28069
  chunks.push({ chunkId: chunkId.toString(), offset, length: slice.length, uploaded: false });
27936
28070
  offset = end;
27937
28071
  }
27938
- const [chunkListObjId] = chunkList.genObjId();
28072
+ const [chunkListObjId, chunkListData] = chunkList.genObjId();
27939
28073
  const fileObj = new FileObject(file.name, fileSize, chunkListObjId.toString());
27940
- const [objId] = fileObj.genObjId();
27941
- return { objectId: objId.toString(), chunks, fileObject: fileObj.toJSON() };
27942
- }
27943
- async function materializeFileViaWorker(file, onHashProgress, chunkSize = DEFAULT_CHUNK_SIZE) {
27944
- if (!isHashWorkerAvailable()) {
27945
- return materializeFile(file, chunkSize);
28074
+ const [objId, objData] = fileObj.genObjId();
28075
+ throwIfCancelled(signal);
28076
+ return { objectId: objId.toString(), chunks, fileObject: fileObj.toJSON(), objects: [
28077
+ { obj_id: chunkListObjId.toString(), obj_data: chunkListData },
28078
+ { obj_id: objId.toString(), obj_data: objData }
28079
+ ] };
28080
+ }
28081
+ async function materializeFileViaWorker(file, onHashProgress, chunkSize = DEFAULT_CHUNK_SIZE, signal) {
28082
+ if (!isHashWorkerAvailable() || file.size === 0) {
28083
+ const result = await materializeFile(file, chunkSize, signal);
28084
+ onHashProgress == null ? void 0 : onHashProgress(file.size);
28085
+ return result;
27946
28086
  }
27947
28087
  let hashResults;
28088
+ let timer;
28089
+ const worker = hashFileInWorker(file, chunkSize, signal);
27948
28090
  try {
27949
- const session = hashFileInWorker(file, chunkSize);
27950
- if (onHashProgress) {
27951
- session.onProgress(onHashProgress);
27952
- }
27953
- const WORKER_TIMEOUT_MS = 5 * 60 * 1e3;
28091
+ if (onHashProgress)
28092
+ worker.onProgress(onHashProgress);
27954
28093
  const timeout = new Promise((_, reject) => {
27955
- setTimeout(() => reject(new Error("hash worker timeout")), WORKER_TIMEOUT_MS);
28094
+ timer = setTimeout(() => {
28095
+ worker.cancel();
28096
+ reject(new Error("hash worker timeout"));
28097
+ }, 5 * 60 * 1e3);
27956
28098
  });
27957
- hashResults = await Promise.race([session.result, timeout]);
28099
+ hashResults = await Promise.race([worker.result, timeout]);
27958
28100
  } catch {
27959
- return materializeFile(file, chunkSize);
28101
+ throwIfCancelled(signal);
28102
+ return materializeFile(file, chunkSize, signal);
28103
+ } finally {
28104
+ if (timer)
28105
+ clearTimeout(timer);
27960
28106
  }
28107
+ throwIfCancelled(signal);
27961
28108
  const fileSize = file.size;
27962
28109
  const chunks = [];
27963
28110
  if (hashResults.length === 1) {
@@ -27966,8 +28113,9 @@ async function materializeFileViaWorker(file, onHashProgress, chunkSize = DEFAUL
27966
28113
  const chunkIdStr = chunkId.toString();
27967
28114
  chunks.push({ chunkId: chunkIdStr, offset: 0, length: r.length, uploaded: false });
27968
28115
  const fileObj2 = new FileObject(file.name, fileSize, chunkIdStr);
27969
- const [objId2] = fileObj2.genObjId();
27970
- return { objectId: objId2.toString(), chunks, fileObject: fileObj2.toJSON() };
28116
+ const [objId2, objData2] = fileObj2.genObjId();
28117
+ throwIfCancelled(signal);
28118
+ return { objectId: objId2.toString(), chunks, fileObject: fileObj2.toJSON(), objects: [{ obj_id: objId2.toString(), obj_data: objData2 }] };
27971
28119
  }
27972
28120
  const chunkList = new SimpleChunkList();
27973
28121
  for (const r of hashResults) {
@@ -27975,10 +28123,14 @@ async function materializeFileViaWorker(file, onHashProgress, chunkSize = DEFAUL
27975
28123
  chunkList.appendChunk(chunkId);
27976
28124
  chunks.push({ chunkId: chunkId.toString(), offset: r.offset, length: r.length, uploaded: false });
27977
28125
  }
27978
- const [chunkListObjId] = chunkList.genObjId();
28126
+ const [chunkListObjId, chunkListData] = chunkList.genObjId();
27979
28127
  const fileObj = new FileObject(file.name, fileSize, chunkListObjId.toString());
27980
- const [objId] = fileObj.genObjId();
27981
- return { objectId: objId.toString(), chunks, fileObject: fileObj.toJSON() };
28128
+ const [objId, objData] = fileObj.genObjId();
28129
+ throwIfCancelled(signal);
28130
+ return { objectId: objId.toString(), chunks, fileObject: fileObj.toJSON(), objects: [
28131
+ { obj_id: chunkListObjId.toString(), obj_data: chunkListData },
28132
+ { obj_id: objId.toString(), obj_data: objData }
28133
+ ] };
27982
28134
  }
27983
28135
  function buildFileObjectFromContentId(name, size, contentId) {
27984
28136
  const fileObj = new FileObject(name, size, contentId);
@@ -28000,27 +28152,21 @@ function resolveImportLookupEndpoint() {
28000
28152
  return void 0;
28001
28153
  }
28002
28154
  async function tryCalculateQcidFromFile(file) {
28003
- const fileSize = file.size;
28004
- if (fileSize < MIN_QCID_FILE_SIZE) {
28005
- return null;
28006
- }
28007
- const beginBytes = new Uint8Array(await file.slice(0, QCID_HASH_PIECE_SIZE).arrayBuffer());
28008
- const midOffset = Math.floor(fileSize / 2);
28009
- const midBytes = new Uint8Array(
28010
- await file.slice(midOffset, midOffset + QCID_HASH_PIECE_SIZE).arrayBuffer()
28011
- );
28012
- const combined = new Uint8Array(beginBytes.length + midBytes.length);
28013
- combined.set(beginBytes, 0);
28014
- combined.set(midBytes, beginBytes.length);
28015
- const hash2 = sha256Bytes(combined);
28016
- return ChunkId.fromMixHashResult(fileSize, hash2, "qcid").toString();
28155
+ return file.size < MIN_QCID_FILE_SIZE ? null : calculateQcidFromFile(file);
28017
28156
  }
28018
28157
  async function calculateQcidFromFile(file) {
28019
- const qcid = await tryCalculateQcidFromFile(file);
28020
- if (!qcid) {
28021
- throw new Error(`QCID requires file size >= ${MIN_QCID_FILE_SIZE} bytes`);
28158
+ const fileSize = file.size;
28159
+ let sampled;
28160
+ if (fileSize < MIN_QCID_FILE_SIZE) {
28161
+ sampled = new Uint8Array(await file.arrayBuffer());
28162
+ } else {
28163
+ sampled = new Uint8Array(QCID_HASH_PIECE_SIZE * 3);
28164
+ const offsets = [0, Math.floor((fileSize - QCID_HASH_PIECE_SIZE) / 2), fileSize - QCID_HASH_PIECE_SIZE];
28165
+ for (let index = 0; index < offsets.length; index++) {
28166
+ sampled.set(new Uint8Array(await file.slice(offsets[index], offsets[index] + QCID_HASH_PIECE_SIZE).arrayBuffer()), index * QCID_HASH_PIECE_SIZE);
28167
+ }
28022
28168
  }
28023
- return qcid;
28169
+ return ChunkId.fromMixHashResult(fileSize, sha256Bytes(sampled), "qcid").toString();
28024
28170
  }
28025
28171
  function extractLookupContentId(response, quickHash) {
28026
28172
  const body = response;
@@ -28046,81 +28192,75 @@ function extractLookupContentId(response, quickHash) {
28046
28192
  }
28047
28193
  return void 0;
28048
28194
  }
28049
- async function lookupFileByQcid(file) {
28050
- const endpoint = resolveImportLookupEndpoint();
28051
- if (!endpoint) {
28195
+ async function lookupFileByQcid(file, options, warnings) {
28196
+ const endpoint = options.endpoint ?? resolveImportLookupEndpoint();
28197
+ if (!endpoint)
28052
28198
  return null;
28053
- }
28054
28199
  const qcid = await tryCalculateQcidFromFile(file);
28055
- if (!qcid) {
28200
+ throwIfCancelled(options.signal);
28201
+ if (!qcid)
28056
28202
  return null;
28057
- }
28058
- for (const scope of ["app", "global"]) {
28059
- try {
28060
- const response = await lookupObject(
28061
- { scope, quick_hash: qcid },
28062
- {
28063
- endpoint,
28064
- sessionToken: getActiveRuntimeType() === RuntimeType.Browser ? null : void 0
28065
- }
28066
- );
28067
- const contentId = extractLookupContentId(response, qcid);
28068
- if (!contentId) {
28069
- continue;
28070
- }
28071
- return {
28072
- ...buildFileObjectFromContentId(file.name, file.size, contentId),
28073
- locality: "store"
28074
- };
28075
- } catch (error) {
28076
- if (error instanceof NdmStoreApiError && error.status === 404) {
28077
- continue;
28078
- }
28203
+ try {
28204
+ const response = await lookupObject({ scope: "app", quick_hash: qcid }, { ...options, endpoint });
28205
+ if (response.reusable !== true || !("state" in response) || !["completed", "same_as", "local_link"].includes(response.state))
28206
+ return null;
28207
+ if (response.chunk_size !== file.size)
28208
+ return null;
28209
+ const contentId = extractLookupContentId(response, qcid);
28210
+ if (!contentId || !(ObjId.fromString(contentId).isChunk() || ObjId.fromString(contentId).isChunkList()) || contentId.startsWith("qcid:"))
28079
28211
  return null;
28212
+ return { ...buildFileObjectFromContentId(file.name, file.size, contentId), locality: "store", qcid };
28213
+ } catch (error) {
28214
+ throwIfCancelled(options.signal);
28215
+ if (!(error instanceof NdmStoreApiError && error.status === 404)) {
28216
+ warnings.push(`QCID lookup for ${file.name}: ${errorMessage(error)}`);
28080
28217
  }
28218
+ return null;
28081
28219
  }
28082
- return null;
28220
+ }
28221
+ function validateName(name) {
28222
+ if (!name || name === "." || name === ".." || /[\\/\x00-\x1f]/.test(name) || /^[a-z]:/i.test(name)) {
28223
+ throw new NdmError("INVALID_IMPORT_PATH", `Invalid file or directory name: ${name}`);
28224
+ }
28225
+ }
28226
+ function validatePath(path) {
28227
+ const parts = path.split("/");
28228
+ parts.forEach(validateName);
28229
+ return parts;
28083
28230
  }
28084
28231
  function buildDirTree(files, fileObjects) {
28085
- const firstPath = files[0].webkitRelativePath;
28086
- const rootName = firstPath ? firstPath.split("/")[0] : "directory";
28087
- const root = {
28088
- kind: "dir",
28089
- objectId: "",
28090
- // will be computed later
28091
- name: rootName,
28092
- children: []
28093
- };
28094
- const dirMap = /* @__PURE__ */ new Map();
28095
- dirMap.set("", root);
28096
- for (const fileObj of fileObjects) {
28097
- const relPath = fileObj.relativePath ?? fileObj.name;
28098
- const parts = relPath.split("/");
28099
- const pathParts = parts.length > 1 ? parts.slice(1) : parts;
28100
- let currentDir = root;
28101
- for (let i = 0; i < pathParts.length - 1; i++) {
28102
- const dirName = pathParts[i];
28103
- const dirPath = pathParts.slice(0, i + 1).join("/");
28104
- let dir = dirMap.get(dirPath);
28105
- if (!dir) {
28106
- dir = {
28107
- kind: "dir",
28108
- objectId: "",
28109
- name: dirName,
28110
- relativePath: dirPath,
28111
- children: []
28112
- };
28113
- dirMap.set(dirPath, dir);
28114
- currentDir.children.push(dir);
28232
+ if (files.length === 0)
28233
+ throw new NdmError("EMPTY_DIRECTORY_UNAVAILABLE", "FileList cannot represent an empty directory; use importDirectory with directory nodes");
28234
+ const paths = files.map((file) => validatePath(file.webkitRelativePath || file.name));
28235
+ if (paths.some((parts) => parts.length < 2 || parts[0] !== paths[0][0])) {
28236
+ throw new NdmError("INVALID_IMPORT_PATH", "Folder selection must contain one root directory in webkitRelativePath");
28237
+ }
28238
+ const root = { kind: "dir", objectId: "", name: paths[0][0], children: [] };
28239
+ for (let index = 0; index < paths.length; index++) {
28240
+ const parts = paths[index].slice(1);
28241
+ let dir = root;
28242
+ for (let i = 0; i < parts.length; i++) {
28243
+ const name = parts[i];
28244
+ const existing = dir.children.find((child) => child.name === name);
28245
+ if (i === parts.length - 1) {
28246
+ if (existing)
28247
+ throw new NdmError("IMPORT_PATH_CONFLICT", `Duplicate file or directory: ${paths[index].join("/")}`);
28248
+ if (name !== fileObjects[index].name)
28249
+ throw new NdmError("INVALID_IMPORT_PATH", "Relative path basename must match File.name");
28250
+ dir.children.push(fileObjects[index]);
28251
+ } else {
28252
+ if ((existing == null ? void 0 : existing.kind) === "file")
28253
+ throw new NdmError("IMPORT_PATH_CONFLICT", `File used as directory: ${name}`);
28254
+ const child = existing ?? { kind: "dir", objectId: "", name, relativePath: parts.slice(0, i + 1).join("/"), children: [] };
28255
+ if (!existing)
28256
+ dir.children.push(child);
28257
+ dir = child;
28115
28258
  }
28116
- currentDir = dir;
28117
28259
  }
28118
- currentDir.children.push(fileObj);
28119
28260
  }
28120
- computeDirObjectIds(root);
28121
28261
  return root;
28122
28262
  }
28123
- function computeDirObjectIds(dir) {
28263
+ function computeDirObjectIds(dir, manifest) {
28124
28264
  const ndnDir = new DirObject(dir.name);
28125
28265
  if (dir.children) {
28126
28266
  for (const child of dir.children) {
@@ -28130,13 +28270,14 @@ function computeDirObjectIds(dir) {
28130
28270
  }
28131
28271
  ndnDir.addFile(child.name, child._ndnFileObject, child.size);
28132
28272
  } else {
28133
- const childState = computeDirObjectIds(child);
28273
+ const childState = computeDirObjectIds(child, manifest);
28134
28274
  ndnDir.addDirectory(child.name, ObjId.fromString(childState.objectId), childState.totalSize);
28135
28275
  }
28136
28276
  }
28137
28277
  }
28138
- const [objId] = ndnDir.genObjId();
28278
+ const [objId, objData] = ndnDir.genObjId();
28139
28279
  dir.objectId = objId.toString();
28280
+ manifest.set(dir.objectId, { obj_id: dir.objectId, obj_data: objData });
28140
28281
  return {
28141
28282
  objectId: dir.objectId,
28142
28283
  totalSize: ndnDir.total_size,
@@ -28225,18 +28366,63 @@ function collectSummary(items) {
28225
28366
  return { totalObjects: totalFiles + totalDirs, totalFiles, totalDirs, totalBytes };
28226
28367
  }
28227
28368
  async function pickupAndImport(options) {
28369
+ const provider = options.provider ?? currentProvider;
28370
+ const caps = provider.getCapabilities();
28371
+ if (options.mode === "single_dir" && !caps.canPickDirectory)
28372
+ throw new NdmError("DIRECTORY_NOT_SUPPORTED");
28373
+ if (options.mode === "mixed" && !caps.canPickMixed)
28374
+ throw new NdmError("MODE_NOT_SUPPORTED_IN_RUNTIME");
28375
+ throwIfCancelled(options.signal);
28376
+ const files = await provider.pickFiles(options);
28377
+ return importFiles(files, options);
28378
+ }
28379
+ async function importFiles(selectedFiles, options) {
28380
+ return importSelectedFiles(selectedFiles, options);
28381
+ }
28382
+ async function importDirectory(tree, options = {}) {
28383
+ return importSelectedFiles([], { ...options, mode: "single_dir" }, tree);
28384
+ }
28385
+ async function importSelectedFiles(selectedFiles, options, tree) {
28228
28386
  var _a2;
28229
- const caps = currentProvider.getCapabilities();
28230
- if (options.mode === "single_dir" && !caps.canPickDirectory) {
28231
- throw new NdmError("DIRECTORY_NOT_SUPPORTED", "Current runtime does not support directory selection");
28232
- }
28233
- if (options.mode === "mixed" && !caps.canPickMixed) {
28234
- throw new NdmError("MODE_NOT_SUPPORTED_IN_RUNTIME", "Current runtime does not support mixed file/directory selection");
28235
- }
28236
- const files = await currentProvider.pickFiles(options);
28237
- if (files.length === 0) {
28238
- throw new NdmError("USER_CANCELLED", "No files selected");
28239
- }
28387
+ throwIfCancelled(options.signal);
28388
+ const files = [...selectedFiles];
28389
+ const treePaths = [];
28390
+ if (tree) {
28391
+ const walk = (dir, parent) => {
28392
+ validateName(dir.name);
28393
+ const names = /* @__PURE__ */ new Set();
28394
+ for (const child of dir.children) {
28395
+ const name = child.kind === "file" ? child.file.name : child.name;
28396
+ validateName(name);
28397
+ if (names.has(name))
28398
+ throw new NdmError("IMPORT_PATH_CONFLICT", `Duplicate child: ${parent}/${name}`);
28399
+ names.add(name);
28400
+ if (child.kind === "file") {
28401
+ files.push(child.file);
28402
+ treePaths.push(`${parent}/${name}`);
28403
+ } else
28404
+ walk(child, `${parent}/${child.name}`);
28405
+ }
28406
+ };
28407
+ walk(tree, tree.name);
28408
+ }
28409
+ if (!tree && files.length === 0)
28410
+ throw new NdmError(options.mode === "single_dir" ? "EMPTY_DIRECTORY_UNAVAILABLE" : "USER_CANCELLED", options.mode === "single_dir" ? "FileList cannot represent empty directories; use importDirectory" : "No files selected");
28411
+ if (options.mode === "single_file" && files.length !== 1)
28412
+ throw new NdmError("INVALID_IMPORT_PATH", "single_file requires exactly one file");
28413
+ files.forEach((file) => {
28414
+ validateName(file.name);
28415
+ if (file.webkitRelativePath)
28416
+ validatePath(file.webkitRelativePath);
28417
+ });
28418
+ if (!tree && options.mode === "single_dir")
28419
+ buildDirTree(files, files.map((file) => ({ kind: "file", objectId: "", name: file.name, size: file.size })));
28420
+ const warnings = [];
28421
+ if (!tree && options.mode === "single_dir")
28422
+ warnings.push("FileList selections cannot preserve empty directories");
28423
+ const manifest = /* @__PURE__ */ new Map();
28424
+ const qcidLinks = [];
28425
+ const requestOptions = { endpoint: options.endpoint, sessionToken: options.sessionToken, fetcher: options.fetcher, headers: options.headers, signal: options.signal };
28240
28426
  const fileObjects = [];
28241
28427
  const objectStates = /* @__PURE__ */ new Map();
28242
28428
  let allFilesAlreadyStored = true;
@@ -28244,11 +28430,12 @@ async function pickupAndImport(options) {
28244
28430
  const fileCount = files.length;
28245
28431
  for (let fi = 0; fi < files.length; fi++) {
28246
28432
  const file = files[fi];
28247
- const relativePath = file.webkitRelativePath || void 0;
28433
+ throwIfCancelled(options.signal);
28434
+ const relativePath = treePaths[fi] || file.webkitRelativePath || void 0;
28248
28435
  if (onProgress) {
28249
28436
  onProgress({ phase: "qcid_lookup", fileIndex: fi, fileCount, fileName: file.name, fileTotalBytes: file.size });
28250
28437
  }
28251
- const lookupHit = await lookupFileByQcid(file);
28438
+ const lookupHit = await lookupFileByQcid(file, requestOptions, warnings);
28252
28439
  let materialized = null;
28253
28440
  if (!lookupHit) {
28254
28441
  if (onProgress) {
@@ -28258,12 +28445,25 @@ async function pickupAndImport(options) {
28258
28445
  file,
28259
28446
  onProgress ? (bytesHashed) => {
28260
28447
  onProgress({ phase: "materializing", fileIndex: fi, fileCount, fileName: file.name, bytesProcessed: bytesHashed, fileTotalBytes: file.size });
28261
- } : void 0
28448
+ } : void 0,
28449
+ DEFAULT_CHUNK_SIZE,
28450
+ options.signal
28262
28451
  );
28263
28452
  }
28264
28453
  const objectId = (lookupHit == null ? void 0 : lookupHit.objectId) ?? materialized.objectId;
28265
28454
  const fileObject = (lookupHit == null ? void 0 : lookupHit.fileObject) ?? materialized.fileObject;
28266
28455
  const chunks = lookupHit ? [] : materialized.chunks;
28456
+ if (materialized)
28457
+ for (const object2 of materialized.objects)
28458
+ manifest.set(object2.obj_id, object2);
28459
+ else {
28460
+ const [, objData] = new FileObject(file.name, file.size, fileObject.content).genObjId();
28461
+ manifest.set(objectId, { obj_id: objectId, obj_data: objData });
28462
+ }
28463
+ const qcid = (lookupHit == null ? void 0 : lookupHit.qcid) ?? await tryCalculateQcidFromFile(file);
28464
+ if (qcid)
28465
+ qcidLinks.push({ qcid, content_id: fileObject.content, size: file.size });
28466
+ throwIfCancelled(options.signal);
28267
28467
  const imported = {
28268
28468
  kind: "file",
28269
28469
  objectId,
@@ -28306,7 +28506,10 @@ async function pickupAndImport(options) {
28306
28506
  let items;
28307
28507
  let selection;
28308
28508
  if (options.mode === "single_dir") {
28309
- const dirObj = buildDirTree(files, fileObjects);
28509
+ let index = 0;
28510
+ const rebuild = (node) => ({ kind: "dir", name: node.name, objectId: "", children: node.children.map((child) => child.kind === "file" ? fileObjects[index++] : rebuild(child)) });
28511
+ const dirObj = tree ? rebuild(tree) : buildDirTree(files, fileObjects);
28512
+ computeDirObjectIds(dirObj, manifest);
28310
28513
  items = [dirObj];
28311
28514
  selection = dirObj;
28312
28515
  } else {
@@ -28319,32 +28522,35 @@ async function pickupAndImport(options) {
28319
28522
  }
28320
28523
  const summary = collectSummary(items);
28321
28524
  const sessionId = generateSessionId();
28322
- let materializationStatus = "ok";
28323
- if (allFilesAlreadyStored || caps.canUseNDMStore) {
28324
- materializationStatus = "all_in_store";
28325
- } else if (caps.canUseNDMCache) {
28326
- materializationStatus = "on_cache";
28327
- }
28328
- const uploadStatus = materializationStatus === "all_in_store" ? "not_required" : "not_started";
28525
+ const materializationStatus = allFilesAlreadyStored ? "all_in_store" : "ok";
28526
+ const uploadStatus = "not_started";
28329
28527
  const session = {
28330
28528
  sessionId,
28331
28529
  items,
28332
28530
  materializationStatus,
28333
28531
  uploadStatus,
28334
28532
  summary,
28335
- objectStates
28533
+ objectStates,
28534
+ objectStatus: "pending",
28535
+ warnings,
28536
+ manifest,
28537
+ qcidLinks,
28538
+ requestOptions,
28539
+ transmittedBytes: 0
28336
28540
  };
28337
28541
  sessionRegistry.set(sessionId, session);
28338
28542
  const snapshot = {
28339
28543
  sessionId,
28340
28544
  selection,
28341
28545
  items,
28546
+ objectStatus: "pending",
28547
+ warnings,
28342
28548
  materializationStatus,
28343
28549
  uploadStatus,
28344
28550
  summary
28345
28551
  };
28346
28552
  if (options.autoStartUpload && uploadStatus === "not_started") {
28347
- startUpload(sessionId).catch(() => {
28553
+ startUpload(sessionId, { endpoint: options.endpoint, sessionToken: options.sessionToken, fetcher: options.fetcher, headers: options.headers, signal: options.signal }).catch(() => {
28348
28554
  });
28349
28555
  snapshot.uploadStatus = "uploading";
28350
28556
  }
@@ -28358,21 +28564,27 @@ async function getImportSessionStatus(sessionId) {
28358
28564
  const perObjectProgress = {};
28359
28565
  let uploadedBytes = 0;
28360
28566
  let uploadedObjects = 0;
28361
- for (const [id2, state] of session.objectStates) {
28362
- perObjectProgress[id2] = {
28363
- objectId: state.objectId,
28364
- uploadedBytes: state.uploadedBytes,
28365
- totalBytes: state.size,
28366
- state: state.state
28367
- };
28368
- uploadedBytes += state.uploadedBytes;
28369
- if (state.state === "completed")
28370
- uploadedObjects++;
28371
- }
28567
+ const collect = (items) => {
28568
+ for (const item of items) {
28569
+ if (item.kind === "dir") {
28570
+ collect(item.children ?? []);
28571
+ continue;
28572
+ }
28573
+ const state = session.objectStates.get(item.objectId);
28574
+ perObjectProgress[item.objectId] = { objectId: item.objectId, uploadedBytes: state.uploadedBytes, totalBytes: state.size, state: state.state };
28575
+ uploadedBytes += state.uploadedBytes;
28576
+ if (state.state === "completed")
28577
+ uploadedObjects++;
28578
+ }
28579
+ };
28580
+ collect(session.items);
28372
28581
  return {
28373
28582
  sessionId,
28374
28583
  materializationStatus: session.materializationStatus,
28375
28584
  uploadStatus: session.uploadStatus,
28585
+ objectStatus: session.objectStatus,
28586
+ error: session.error,
28587
+ warnings: [...session.warnings],
28376
28588
  summary: session.summary,
28377
28589
  progress: {
28378
28590
  uploadedBytes,
@@ -28388,6 +28600,10 @@ async function getUploadProgress(sessionId) {
28388
28600
  const result = {
28389
28601
  sessionId,
28390
28602
  uploadStatus: status.uploadStatus,
28603
+ objectStatus: status.objectStatus,
28604
+ error: status.error,
28605
+ warnings: status.warnings,
28606
+ transmittedBytes: requireSession(sessionId).transmittedBytes,
28391
28607
  totalBytes: status.progress.totalBytes,
28392
28608
  uploadedBytes: status.progress.uploadedBytes,
28393
28609
  totalObjects: status.progress.totalObjects,
@@ -28405,13 +28621,13 @@ async function getUploadProgress(sessionId) {
28405
28621
  }
28406
28622
  return result;
28407
28623
  }
28408
- async function uploadChunkViaTus(endpoint, file, chunkInfo, chunkIndex, appId, fileHash, sessionToken, onProgress, signal) {
28624
+ async function uploadChunkViaTus(endpoint, file, chunkInfo, chunkIndex, fileHash, sessionToken, extraHeaders, onProgress, signal) {
28409
28625
  const slice = file.slice(chunkInfo.offset, chunkInfo.offset + chunkInfo.length);
28410
28626
  const chunkData = new Uint8Array(await slice.arrayBuffer());
28411
- const logicalPath = `${appId}/${chunkInfo.chunkId}`;
28627
+ const logicalPath = chunkInfo.chunkId;
28412
28628
  let tusModule;
28413
28629
  try {
28414
- tusModule = await import("./tus_client-19baa342.mjs");
28630
+ tusModule = await import("./tus_client-8fd4d937.mjs");
28415
28631
  } catch (error) {
28416
28632
  const message = error instanceof Error ? error.message : String(error);
28417
28633
  throw new NdmError("UPLOAD_FAILED", `Failed to load tus-js-client: ${message}`);
@@ -28424,12 +28640,12 @@ async function uploadChunkViaTus(endpoint, file, chunkInfo, chunkIndex, appId, f
28424
28640
  const blob = new Blob([chunkData]);
28425
28641
  const upload = new tusModule.Upload(blob, {
28426
28642
  endpoint: `${endpoint}/ndm/v1/uploads`,
28427
- headers: sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {},
28643
+ ...typeof XMLHttpRequest === "undefined" ? { httpStack: tusModule.fetchHttpStack } : {},
28644
+ headers: { ...extraHeaders, ...sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {} },
28428
28645
  chunkSize: chunkData.length,
28429
28646
  retryDelays: [0, 1e3, 3e3, 5e3],
28430
28647
  storeFingerprintForResuming: false,
28431
28648
  metadata: {
28432
- app_id: appId,
28433
28649
  logical_path: logicalPath,
28434
28650
  file_name: file.name,
28435
28651
  file_size: String(file.size),
@@ -28443,109 +28659,209 @@ async function uploadChunkViaTus(endpoint, file, chunkInfo, chunkIndex, appId, f
28443
28659
  },
28444
28660
  onSuccess: () => {
28445
28661
  onProgress(chunkData.length);
28662
+ signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
28446
28663
  resolve(chunkInfo.chunkId);
28447
28664
  },
28448
28665
  onError: (error) => {
28666
+ signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
28449
28667
  reject(new NdmError("UPLOAD_FAILED", error.message));
28450
28668
  }
28451
28669
  });
28452
- if (signal) {
28453
- signal.addEventListener("abort", () => {
28454
- upload.abort(true);
28455
- reject(new NdmError("UPLOAD_FAILED", "Upload aborted"));
28456
- });
28457
- }
28670
+ const onAbort = () => {
28671
+ void Promise.resolve(upload.abort(true)).catch(() => void 0);
28672
+ reject(new NdmError("USER_CANCELLED", "Upload cancelled"));
28673
+ };
28674
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
28458
28675
  upload.start();
28459
28676
  });
28460
28677
  }
28461
- async function startUpload(sessionId, options) {
28678
+ function errorMessage(error) {
28679
+ return error instanceof Error ? error.message : String(error);
28680
+ }
28681
+ function throwIfCancelled(signal) {
28682
+ if (signal == null ? void 0 : signal.aborted)
28683
+ throw new NdmError("USER_CANCELLED", "Import cancelled");
28684
+ }
28685
+ function requireSession(sessionId) {
28462
28686
  const session = sessionRegistry.get(sessionId);
28463
- if (!session) {
28687
+ if (!session)
28464
28688
  throw new NdmError("SESSION_NOT_FOUND", `Session ${sessionId} not found`);
28465
- }
28466
- if (session.uploadStatus === "completed" || session.uploadStatus === "not_required") {
28467
- return getImportSessionStatus(sessionId);
28468
- }
28469
- if (session.uploadStatus === "uploading") {
28470
- return getImportSessionStatus(sessionId);
28471
- }
28472
- if (session.materializationStatus === "all_in_store") {
28473
- session.uploadStatus = "not_required";
28689
+ return session;
28690
+ }
28691
+ async function startUpload(sessionId, options = {}) {
28692
+ var _a2;
28693
+ const session = requireSession(sessionId);
28694
+ if (session.objectStatus === "ready" || session.uploadPromise)
28474
28695
  return getImportSessionStatus(sessionId);
28475
- }
28696
+ const concurrency = options.concurrency ?? 3;
28697
+ if (!Number.isInteger(concurrency) || concurrency < 1)
28698
+ throw new NdmError("UPLOAD_FAILED", "concurrency must be a positive integer");
28699
+ throwIfCancelled(options.signal);
28700
+ session.requestOptions = { ...session.requestOptions, ...Object.fromEntries(Object.entries({ endpoint: options.endpoint, sessionToken: options.sessionToken, fetcher: options.fetcher, headers: options.headers }).filter(([, value]) => value !== void 0)) };
28701
+ const endpoint = session.requestOptions.endpoint ?? resolveImportLookupEndpoint();
28702
+ if (!endpoint)
28703
+ throw new NdmError("STORE_API_ENDPOINT_REQUIRED", "Pass endpoint or initialize the SDK before uploading");
28704
+ session.requestOptions.endpoint = normalizeEndpoint$1(endpoint);
28705
+ session.abortController = new AbortController();
28706
+ const cancel = () => {
28707
+ var _a3;
28708
+ return (_a3 = session.abortController) == null ? void 0 : _a3.abort();
28709
+ };
28710
+ (_a2 = options.signal) == null ? void 0 : _a2.addEventListener("abort", cancel, { once: true });
28711
+ session.requestOptions.signal = session.abortController.signal;
28476
28712
  session.uploadStatus = "uploading";
28713
+ session.objectStatus = "pending";
28714
+ session.error = void 0;
28715
+ session.onProgress = options.onProgress;
28477
28716
  session.uploadStartTime = Date.now();
28478
- session.abortController = new AbortController();
28479
- const concurrency = (options == null ? void 0 : options.concurrency) ?? 3;
28480
- let endpoint;
28481
- if (options == null ? void 0 : options.endpoint) {
28482
- endpoint = options.endpoint.replace(/\/+$/, "");
28483
- } else {
28484
- endpoint = typeof window !== "undefined" ? window.location.origin : "";
28485
- }
28486
- const sessionToken = (options == null ? void 0 : options.sessionToken) !== void 0 ? async () => options.sessionToken ?? null : async () => getActiveSessionToken().catch(() => null);
28487
- doUpload(session, endpoint, concurrency, sessionToken).catch(() => {
28717
+ session.uploadPromise = doUpload(session, concurrency).catch((error) => {
28718
+ var _a3;
28719
+ session.error = errorMessage(error);
28720
+ const cancelled = (_a3 = session.abortController) == null ? void 0 : _a3.signal.aborted;
28721
+ session.uploadStatus = cancelled ? "cancelled" : "failed";
28722
+ session.objectStatus = cancelled ? "cancelled" : "failed";
28723
+ throw error;
28724
+ }).finally(() => {
28725
+ var _a3;
28726
+ session.uploadPromise = void 0;
28727
+ (_a3 = options.signal) == null ? void 0 : _a3.removeEventListener("abort", cancel);
28728
+ emitProgress(session);
28488
28729
  });
28730
+ void session.uploadPromise.catch(() => void 0);
28489
28731
  return getImportSessionStatus(sessionId);
28490
28732
  }
28491
- async function doUpload(session, endpoint, concurrency, sessionToken) {
28492
- const states = Array.from(session.objectStates.values()).filter((s) => s.state !== "completed");
28493
- let running = 0;
28494
- let idx = 0;
28495
- let hasError = false;
28496
- await new Promise((resolve, reject) => {
28497
- function next() {
28498
- if (hasError)
28499
- return;
28500
- if (idx >= states.length && running === 0) {
28501
- const allCompleted = Array.from(session.objectStates.values()).every((s) => s.state === "completed");
28502
- session.uploadStatus = allCompleted ? "completed" : "failed";
28503
- resolve();
28504
- return;
28505
- }
28506
- while (running < concurrency && idx < states.length) {
28507
- const state = states[idx++];
28508
- running++;
28509
- uploadSingleObject(session, endpoint, state, sessionToken).then(() => {
28510
- running--;
28511
- next();
28512
- }).catch(() => {
28513
- running--;
28514
- if (!hasError) {
28515
- hasError = true;
28516
- session.uploadStatus = "failed";
28517
- reject(new NdmError("UPLOAD_FAILED", `Upload of ${state.name} failed`));
28518
- }
28519
- });
28520
- }
28733
+ async function ensureUploaded(sessionId, options = {}) {
28734
+ await startUpload(sessionId, options);
28735
+ const session = requireSession(sessionId);
28736
+ await session.uploadPromise;
28737
+ if (session.objectStatus !== "ready")
28738
+ throw new NdmError("OBJECT_NOT_READY", session.error ?? "Objects are not ready");
28739
+ return getImportSessionStatus(sessionId);
28740
+ }
28741
+ function cancelImportSession(sessionId) {
28742
+ var _a2;
28743
+ const session = requireSession(sessionId);
28744
+ if (session.objectStatus === "ready")
28745
+ return;
28746
+ (_a2 = session.abortController) == null ? void 0 : _a2.abort();
28747
+ session.uploadStatus = "cancelled";
28748
+ session.objectStatus = "cancelled";
28749
+ }
28750
+ function releaseImportSession(sessionId) {
28751
+ const session = requireSession(sessionId);
28752
+ cancelImportSession(sessionId);
28753
+ sessionRegistry.delete(sessionId);
28754
+ const release = (items) => items.forEach((item) => {
28755
+ if (item.kind === "dir")
28756
+ release(item.children ?? []);
28757
+ else {
28758
+ delete item._file;
28759
+ delete item._ndnFileObject;
28521
28760
  }
28522
- next();
28523
28761
  });
28762
+ release(session.items);
28524
28763
  }
28525
- async function uploadSingleObject(session, endpoint, state, sessionToken) {
28526
- var _a2;
28527
- state.state = "uploading";
28528
- for (const [chunkIndex, chunk] of state.chunks.entries()) {
28529
- if (chunk.uploaded)
28530
- continue;
28531
- await uploadChunkViaTus(
28532
- endpoint,
28533
- state.file,
28534
- chunk,
28535
- chunkIndex,
28536
- "default",
28537
- state.objectId,
28538
- await sessionToken(),
28539
- (uploaded) => {
28540
- const prevChunkBytes = state.chunks.filter((c) => c !== chunk && c.uploaded).reduce((sum, c) => sum + c.length, 0);
28541
- state.uploadedBytes = prevChunkBytes + uploaded;
28542
- },
28543
- (_a2 = session.abortController) == null ? void 0 : _a2.signal
28544
- );
28545
- chunk.uploaded = true;
28764
+ function emitProgress(session) {
28765
+ if (session.onProgress && sessionRegistry.has(session.sessionId)) {
28766
+ void getUploadProgress(session.sessionId).then((progress) => {
28767
+ var _a2;
28768
+ return (_a2 = session.onProgress) == null ? void 0 : _a2.call(session, progress);
28769
+ }).catch(() => void 0);
28546
28770
  }
28547
- state.uploadedBytes = state.size;
28548
- state.state = "completed";
28771
+ }
28772
+ async function doUpload(session, concurrency) {
28773
+ var _a2;
28774
+ const states = [...session.objectStates.values()];
28775
+ const chunkTasks = /* @__PURE__ */ new Map();
28776
+ let index = 0;
28777
+ let failure;
28778
+ const workers = Array.from({ length: Math.min(concurrency, states.length) }, async () => {
28779
+ var _a3, _b;
28780
+ while (index < states.length && !failure) {
28781
+ const state = states[index++];
28782
+ try {
28783
+ throwIfCancelled((_a3 = session.abortController) == null ? void 0 : _a3.signal);
28784
+ if (state.state === "completed")
28785
+ continue;
28786
+ state.state = "uploading";
28787
+ for (const [chunkIndex, chunk] of state.chunks.entries()) {
28788
+ throwIfCancelled((_b = session.abortController) == null ? void 0 : _b.signal);
28789
+ if (chunk.uploaded)
28790
+ continue;
28791
+ let task = chunkTasks.get(chunk.chunkId);
28792
+ if (!task) {
28793
+ task = uploadMissingChunk(session, state, chunk, chunkIndex);
28794
+ chunkTasks.set(chunk.chunkId, task);
28795
+ }
28796
+ await task;
28797
+ chunk.uploaded = true;
28798
+ state.uploadedBytes = state.chunks.filter((c) => c.uploaded).reduce((sum, c) => sum + c.length, 0);
28799
+ emitProgress(session);
28800
+ }
28801
+ state.uploadedBytes = state.size;
28802
+ state.state = "completed";
28803
+ } catch (error) {
28804
+ state.state = "failed";
28805
+ failure = error;
28806
+ }
28807
+ }
28808
+ });
28809
+ await Promise.all(workers);
28810
+ if (failure)
28811
+ throw failure;
28812
+ throwIfCancelled((_a2 = session.abortController) == null ? void 0 : _a2.signal);
28813
+ session.objectStatus = "finalizing";
28814
+ emitProgress(session);
28815
+ const options = session.requestOptions;
28816
+ const token = options.sessionToken !== void 0 ? options.sessionToken : await getActiveSessionToken();
28817
+ const response = await (options.fetcher ?? defaultStoreFetcher)(`${options.endpoint}/ndm/v1/import/finalize`, {
28818
+ method: "POST",
28819
+ credentials: "include",
28820
+ signal: options.signal,
28821
+ headers: { ...options.headers, "Content-Type": "application/json", ...token ? { Authorization: `Bearer ${token}` } : {} },
28822
+ body: JSON.stringify({ objects: [...session.manifest.values()], roots: session.items.map((item) => item.objectId), qcid_links: session.qcidLinks })
28823
+ });
28824
+ const body = await response.json();
28825
+ if (!response.ok)
28826
+ throw new NdmStoreApiError(response.status, body.error, body.message ?? "Object registration failed", body);
28827
+ throwIfCancelled(options.signal);
28828
+ if (Array.isArray(body.warnings))
28829
+ session.warnings.push(...body.warnings.map(String));
28830
+ if (body.ready !== true || !Array.isArray(body.roots) || session.items.some((item) => !body.roots.some((root) => root.obj_id === item.objectId && root.ready === true))) {
28831
+ throw new NdmError("OBJECT_NOT_READY", "NDM did not confirm all root objects and their dependencies are ready");
28832
+ }
28833
+ session.objectStatus = "ready";
28834
+ session.uploadStatus = session.transmittedBytes === 0 ? "not_required" : "completed";
28835
+ session.materializationStatus = "all_in_store";
28836
+ emitProgress(session);
28837
+ }
28838
+ async function uploadMissingChunk(session, state, chunk, chunkIndex) {
28839
+ var _a2, _b;
28840
+ try {
28841
+ const found = await lookupObject({ scope: "app", quick_hash: chunk.chunkId }, session.requestOptions);
28842
+ if (found.reusable === true && "state" in found && ["completed", "same_as", "local_link"].includes(found.state) && found.chunk_size === chunk.length)
28843
+ return;
28844
+ } catch (error) {
28845
+ throwIfCancelled((_a2 = session.abortController) == null ? void 0 : _a2.signal);
28846
+ if (!(error instanceof NdmStoreApiError && error.status === 404))
28847
+ session.warnings.push(`Chunk lookup: ${errorMessage(error)}`);
28848
+ }
28849
+ const token = session.requestOptions.sessionToken !== void 0 ? session.requestOptions.sessionToken : await getActiveSessionToken();
28850
+ await uploadChunkViaTus(
28851
+ session.requestOptions.endpoint,
28852
+ state.file,
28853
+ chunk,
28854
+ chunkIndex,
28855
+ state.objectId,
28856
+ token,
28857
+ session.requestOptions.headers,
28858
+ (uploaded) => {
28859
+ state.uploadedBytes = state.chunks.filter((c) => c.uploaded).reduce((sum, c) => sum + c.length, 0) + uploaded;
28860
+ emitProgress(session);
28861
+ },
28862
+ (_b = session.abortController) == null ? void 0 : _b.signal
28863
+ );
28864
+ session.transmittedBytes += chunk.length;
28549
28865
  }
28550
28866
  const ndm_client = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
28551
28867
  __proto__: null,
@@ -28555,7 +28871,9 @@ const ndm_client = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
28555
28871
  anchorState: anchorState$1,
28556
28872
  applyEdge: applyEdge$1,
28557
28873
  calculateQcidFromFile,
28874
+ cancelImportSession,
28558
28875
  debugDumpExpandState: debugDumpExpandState$1,
28876
+ ensureUploaded,
28559
28877
  forcedGcUntil: forcedGcUntil$1,
28560
28878
  fsAcquire: fsAcquire$1,
28561
28879
  fsAnchorState: fsAnchorState$1,
@@ -28567,6 +28885,8 @@ const ndm_client = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
28567
28885
  getObject: getObject$1,
28568
28886
  getUploadProgress,
28569
28887
  haveChunk: haveChunk$1,
28888
+ importDirectory,
28889
+ importFiles,
28570
28890
  isObjectExist: isObjectExist$1,
28571
28891
  isObjectStored: isObjectStored$1,
28572
28892
  lookupObject,
@@ -28577,6 +28897,7 @@ const ndm_client = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
28577
28897
  putObject: putObject$1,
28578
28898
  queryChunkState: queryChunkState$1,
28579
28899
  queryObjectById: queryObjectById$1,
28900
+ releaseImportSession,
28580
28901
  removeChunk: removeChunk$1,
28581
28902
  removeObject: removeObject$1,
28582
28903
  setImportProvider,
@@ -29134,6 +29455,279 @@ const ndm_proxy = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
29134
29455
  unpin,
29135
29456
  unpinOwner
29136
29457
  }, Symbol.toStringTag, { value: "Module" }));
29458
+ class ExecutorClient {
29459
+ constructor(serviceUrl, token = null, options = {}) {
29460
+ this.rpc = new kRPCClient(serviceUrl, token, null, options);
29461
+ }
29462
+ syncSessionToken(token) {
29463
+ this.rpc.setSessionToken(token);
29464
+ }
29465
+ createDevice(input) {
29466
+ return this.rpc.call("executor.device.create", input);
29467
+ }
29468
+ listDevices() {
29469
+ return this.rpc.call("executor.device.list", {});
29470
+ }
29471
+ issueAccess(executor_id, expires_at_ms) {
29472
+ return this.rpc.call("executor.device.issue", { executor_id, expires_at_ms });
29473
+ }
29474
+ revokeAccess(executor_id) {
29475
+ return this.rpc.call("executor.device.revoke", { executor_id });
29476
+ }
29477
+ deleteDevice(executor_id) {
29478
+ return this.rpc.call("executor.device.delete", { executor_id });
29479
+ }
29480
+ listOnline() {
29481
+ return this.rpc.call("executor.online.list", {});
29482
+ }
29483
+ async *watchOnline(signal, intervalMs = 5e3) {
29484
+ if (intervalMs < 250)
29485
+ throw new Error("Watch interval must be at least 250 ms");
29486
+ let previous = "";
29487
+ while (!(signal == null ? void 0 : signal.aborted)) {
29488
+ const { executors } = await this.listOnline();
29489
+ const next = JSON.stringify(executors);
29490
+ if (next !== previous) {
29491
+ previous = next;
29492
+ yield executors;
29493
+ }
29494
+ await new Promise((resolve) => {
29495
+ const done = () => {
29496
+ clearTimeout(timer);
29497
+ signal == null ? void 0 : signal.removeEventListener("abort", done);
29498
+ resolve();
29499
+ };
29500
+ const timer = setTimeout(done, intervalMs);
29501
+ signal == null ? void 0 : signal.addEventListener("abort", done, { once: true });
29502
+ if (signal == null ? void 0 : signal.aborted)
29503
+ done();
29504
+ });
29505
+ }
29506
+ }
29507
+ openRoute(target) {
29508
+ return this.rpc.call("executor.stream.open", {
29509
+ executor_id: target.executor_id,
29510
+ generation: target.generation,
29511
+ profile_id: target.profile_id
29512
+ });
29513
+ }
29514
+ async connect(target, sessionToken, runtimeId, runId) {
29515
+ const route = await this.openRoute(target);
29516
+ return ExecutorCommandStream.connect(route, target, sessionToken, runtimeId, runId);
29517
+ }
29518
+ }
29519
+ class ExecutorStreamError extends Error {
29520
+ constructor(message, code = "unavailable", effectUnknown = false) {
29521
+ super(message);
29522
+ this.code = code;
29523
+ this.effectUnknown = effectUnknown;
29524
+ this.name = "ExecutorStreamError";
29525
+ }
29526
+ }
29527
+ function encodeExecutorControl(message) {
29528
+ const payload = new TextEncoder().encode(JSON.stringify(message));
29529
+ if (payload.length > 1024 * 1024)
29530
+ throw new Error("Executor control frame exceeds 1 MiB");
29531
+ const frame = new Uint8Array(5 + payload.length);
29532
+ frame[0] = 1;
29533
+ new DataView(frame.buffer).setUint32(1, payload.length);
29534
+ frame.set(payload, 5);
29535
+ return frame;
29536
+ }
29537
+ class ExecutorCommandStream {
29538
+ constructor(socket, target, runId) {
29539
+ this.socket = socket;
29540
+ this.target = target;
29541
+ this.runId = runId;
29542
+ this.buffer = new Uint8Array();
29543
+ this.sequence = 0;
29544
+ this.pending = /* @__PURE__ */ new Map();
29545
+ this.closed = false;
29546
+ socket.binaryType = "arraybuffer";
29547
+ socket.onmessage = (event) => {
29548
+ if (!(event.data instanceof ArrayBuffer)) {
29549
+ this.fail(new ExecutorStreamError("Expected binary Command Stream"));
29550
+ return;
29551
+ }
29552
+ try {
29553
+ this.receive(new Uint8Array(event.data));
29554
+ } catch {
29555
+ this.fail(new ExecutorStreamError("Invalid Command Stream frame", "protocol"));
29556
+ }
29557
+ };
29558
+ socket.onclose = () => this.fail(new ExecutorStreamError("Executor disconnected; query the persisted operation locator before taking further action", "unavailable", true));
29559
+ socket.onerror = () => this.fail(new ExecutorStreamError("Executor transport failed", "unavailable", true));
29560
+ }
29561
+ static async connect(route, target, token, runtimeId, runId) {
29562
+ if (route.executor_id !== target.executor_id || route.generation !== target.generation || route.profile_id !== target.profile_id || route.expires_at_ms <= Date.now()) {
29563
+ throw new ExecutorStreamError("Route does not match the requested active executor", "stale_target");
29564
+ }
29565
+ const url = new URL(route.ws_url);
29566
+ if (url.protocol !== "wss:" && !(url.protocol === "ws:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
29567
+ throw new ExecutorStreamError("Executor routes require WSS");
29568
+ }
29569
+ if (url.username || url.password || url.hash)
29570
+ throw new ExecutorStreamError("Invalid executor route URL");
29571
+ const socket = new WebSocket(url);
29572
+ await new Promise((resolve, reject) => {
29573
+ const timer = setTimeout(() => {
29574
+ socket.close();
29575
+ reject(new ExecutorStreamError("Route authentication timed out"));
29576
+ }, 1e4);
29577
+ socket.onopen = () => socket.send(JSON.stringify({ ticket: route.ticket }));
29578
+ socket.onmessage = (event) => {
29579
+ try {
29580
+ if (typeof event.data !== "string" || JSON.parse(event.data).ready !== true)
29581
+ throw new Error();
29582
+ clearTimeout(timer);
29583
+ resolve();
29584
+ } catch {
29585
+ clearTimeout(timer);
29586
+ socket.close();
29587
+ reject(new ExecutorStreamError("Route authentication failed"));
29588
+ }
29589
+ };
29590
+ socket.onerror = socket.onclose = () => {
29591
+ clearTimeout(timer);
29592
+ reject(new ExecutorStreamError("Unable to connect executor route"));
29593
+ };
29594
+ });
29595
+ const client = new ExecutorCommandStream(socket, target, runId);
29596
+ try {
29597
+ const login = await client.request("login", { v: 1, token, profile: target.profile_id, client: { runtime_id: runtimeId, run_id: runId } });
29598
+ if (login.type !== "login_ok" || login.store_id !== target.store_id)
29599
+ throw new ExecutorStreamError("Executor ledger changed", "store_mismatch");
29600
+ return client;
29601
+ } catch (error) {
29602
+ client.close();
29603
+ throw error;
29604
+ }
29605
+ }
29606
+ receive(bytes2) {
29607
+ const merged = new Uint8Array(this.buffer.length + bytes2.length);
29608
+ merged.set(this.buffer);
29609
+ merged.set(bytes2, this.buffer.length);
29610
+ this.buffer = merged;
29611
+ while (this.buffer.length >= 5) {
29612
+ const length = new DataView(this.buffer.buffer, this.buffer.byteOffset).getUint32(1);
29613
+ if (length > 1024 * 1024 || ![1, 2].includes(this.buffer[0]))
29614
+ throw new Error();
29615
+ if (this.buffer.length < length + 5)
29616
+ return;
29617
+ const kind = this.buffer[0];
29618
+ const payload = this.buffer.slice(5, length + 5);
29619
+ this.buffer = this.buffer.slice(length + 5);
29620
+ if (kind !== 1)
29621
+ continue;
29622
+ const reply = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload));
29623
+ if (reply.type === "closing") {
29624
+ this.fail(new ExecutorStreamError("Executor stopped accepting requests"));
29625
+ return;
29626
+ }
29627
+ const pending = reply.request_id && this.pending.get(reply.request_id);
29628
+ if (!pending)
29629
+ continue;
29630
+ this.pending.delete(reply.request_id);
29631
+ clearTimeout(pending.timer);
29632
+ if (reply.error)
29633
+ pending.reject(new ExecutorStreamError(reply.error.message, reply.error.code, reply.error.effect_unknown));
29634
+ else
29635
+ pending.resolve(reply);
29636
+ }
29637
+ }
29638
+ fail(error) {
29639
+ if (this.closed)
29640
+ return;
29641
+ this.closed = true;
29642
+ for (const item of this.pending.values()) {
29643
+ clearTimeout(item.timer);
29644
+ item.reject(error);
29645
+ }
29646
+ this.pending.clear();
29647
+ this.socket.close();
29648
+ }
29649
+ close() {
29650
+ this.fail(new ExecutorStreamError("Executor stream closed", "unavailable", true));
29651
+ }
29652
+ request(type, params = {}, timeoutMs = 4e4) {
29653
+ if (this.closed)
29654
+ return Promise.reject(new ExecutorStreamError("Executor stream is closed"));
29655
+ const request_id = `q${++this.sequence}`;
29656
+ return new Promise((resolve, reject) => {
29657
+ const timer = setTimeout(() => {
29658
+ this.pending.delete(request_id);
29659
+ reject(new ExecutorStreamError("Executor response timed out; query operation before retrying", "unavailable", true));
29660
+ }, timeoutMs);
29661
+ this.pending.set(request_id, { resolve, reject, timer });
29662
+ try {
29663
+ this.socket.send(encodeExecutorControl({ ...params, type, request_id }));
29664
+ } catch {
29665
+ clearTimeout(timer);
29666
+ this.pending.delete(request_id);
29667
+ reject(new ExecutorStreamError("Unable to send request", "unavailable", true));
29668
+ }
29669
+ });
29670
+ }
29671
+ operation(locator) {
29672
+ if (locator.executor_id !== this.target.executor_id || locator.store_id !== this.target.store_id || locator.profile_id !== this.target.profile_id) {
29673
+ throw new ExecutorStreamError("Operation locator belongs to another executor, profile or ledger", "store_mismatch");
29674
+ }
29675
+ return { store_id: locator.store_id, op_id: locator.op_id, run_id: locator.run_id, call_id: locator.call_id };
29676
+ }
29677
+ fresh(locator) {
29678
+ if (locator.generation !== this.target.generation)
29679
+ throw new ExecutorStreamError("Cannot dispatch an old operation to a new connection generation", "stale_target");
29680
+ return this.operation(locator);
29681
+ }
29682
+ async describe(workdir) {
29683
+ const reply = await this.request("describe", { workdir });
29684
+ const described = reply.target;
29685
+ if (reply.type !== "describe_ok" || (described == null ? void 0 : described.executor_id) !== this.target.executor_id || (described == null ? void 0 : described.node_did) || (described == null ? void 0 : described.profile_id) !== this.target.profile_id || (described == null ? void 0 : described.store_id) !== this.target.store_id) {
29686
+ throw new ExecutorStreamError("Executor describe target changed", "stale_target");
29687
+ }
29688
+ return reply;
29689
+ }
29690
+ exec(locator, cmd, cwd, env_overrides = {}, execution_timeout_ms = 6e4) {
29691
+ return this.request("exec", { ...this.fresh(locator), cmd, cwd, env_overrides, execution_timeout_ms });
29692
+ }
29693
+ read(locator, path) {
29694
+ return this.request("read", { ...this.fresh(locator), path, offset: 0 });
29695
+ }
29696
+ edit(locator, path, edits) {
29697
+ return this.request("edit", { ...this.fresh(locator), path, edits });
29698
+ }
29699
+ async write(locator, path, content, mode = "replace") {
29700
+ const operation = this.fresh(locator);
29701
+ const id2 = new TextEncoder().encode(locator.op_id);
29702
+ if (id2.length === 0 || id2.length > 128)
29703
+ throw new Error("Invalid operation ID");
29704
+ const hash2 = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", content))).map((value) => value.toString(16).padStart(2, "0")).join("");
29705
+ const accepted = this.request("write", { ...operation, path, mode, content_length: content.length, content_hash: `sha256:${hash2}` });
29706
+ for (let offset = 0; offset < content.length; offset += 16384) {
29707
+ const chunk = content.subarray(offset, offset + 16384);
29708
+ const frame = new Uint8Array(16 + id2.length + chunk.length);
29709
+ const view = new DataView(frame.buffer);
29710
+ frame[0] = 2;
29711
+ view.setUint32(1, frame.length - 5);
29712
+ frame[5] = 4;
29713
+ view.setBigUint64(6, BigInt(offset));
29714
+ view.setUint16(14, id2.length);
29715
+ frame.set(id2, 16);
29716
+ frame.set(chunk, 16 + id2.length);
29717
+ this.socket.send(frame);
29718
+ }
29719
+ this.socket.send(encodeExecutorControl({ type: "upload_end", request_id: `end${++this.sequence}`, op_id: locator.op_id }));
29720
+ return accepted;
29721
+ }
29722
+ query(locator, channel, offset = 0, wait_ms = 0) {
29723
+ this.operation(locator);
29724
+ return this.request("query", { store_id: locator.store_id, op_id: locator.op_id, channel, offset, max_bytes: 262144, wait_ms });
29725
+ }
29726
+ cancel(locator) {
29727
+ this.operation(locator);
29728
+ return this.request("cancel", { store_id: locator.store_id, op_id: locator.op_id });
29729
+ }
29730
+ }
29137
29731
  export {
29138
29732
  CONTACT_TAG_HOMESTATION_FOLLOWING as $,
29139
29733
  NOTIFY_ACTION_DISMISS as A,
@@ -29302,6 +29896,13 @@ export {
29302
29896
  BnsTxExecutorError as c5,
29303
29897
  walletUserHasSnAccount as c6,
29304
29898
  BnsTxExecutor as c7,
29899
+ ExecutorClient as c8,
29900
+ ExecutorStreamError as c9,
29901
+ encodeExecutorControl as ca,
29902
+ ExecutorCommandStream as cb,
29903
+ AIWORKSPACE_SERVICE_NAME as cc,
29904
+ AiwsFailure as cd,
29905
+ AiWorkspaceClient as ce,
29305
29906
  BS_SERVICE_TASK_MANAGER as d,
29306
29907
  getActiveRuntimeType as e,
29307
29908
  getActiveZoneGatewayOrigin as f,
@@ -29326,4 +29927,4 @@ export {
29326
29927
  NOTIFY_TEXT_CONTROL_ID as y,
29327
29928
  NOTIFY_ACTION_ANSWER as z
29328
29929
  };
29329
- //# sourceMappingURL=ndm_proxy-6b03b9ec.mjs.map
29930
+ //# sourceMappingURL=executor_client-d2c9e854.mjs.map