@daytona/sdk 0.198.0-alpha.1 → 0.199.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/Daytona.d.ts +2 -27
- package/cjs/Daytona.js +7 -64
- package/cjs/Daytona.js.map +1 -1
- package/cjs/Sandbox.d.ts +48 -149
- package/cjs/Sandbox.js +237 -521
- package/cjs/Sandbox.js.map +1 -1
- package/cjs/Volume.d.ts +91 -4
- package/cjs/Volume.js +78 -5
- package/cjs/Volume.js.map +1 -1
- package/cjs/index.d.ts +3 -1
- package/cjs/index.js +3 -1
- package/cjs/index.js.map +1 -1
- package/esm/Daytona.d.ts +2 -27
- package/esm/Daytona.js +7 -64
- package/esm/Daytona.js.map +1 -1
- package/esm/Sandbox.d.ts +48 -149
- package/esm/Sandbox.js +238 -522
- package/esm/Sandbox.js.map +1 -1
- package/esm/Volume.d.ts +91 -4
- package/esm/Volume.js +77 -4
- package/esm/Volume.js.map +1 -1
- package/esm/index.d.ts +3 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/utils/Import.js +1 -1
- package/package.json +3 -5
- package/cjs/utils/EventDispatcher.d.ts +0 -85
- package/cjs/utils/EventDispatcher.js +0 -398
- package/cjs/utils/EventDispatcher.js.map +0 -1
- package/cjs/utils/EventSubscriptionManager.d.ts +0 -14
- package/cjs/utils/EventSubscriptionManager.js +0 -98
- package/cjs/utils/EventSubscriptionManager.js.map +0 -1
- package/esm/utils/EventDispatcher.d.ts +0 -85
- package/esm/utils/EventDispatcher.js +0 -394
- package/esm/utils/EventDispatcher.js.map +0 -1
- package/esm/utils/EventSubscriptionManager.d.ts +0 -14
- package/esm/utils/EventSubscriptionManager.js +0 -94
- package/esm/utils/EventSubscriptionManager.js.map +0 -1
package/esm/Sandbox.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { __esDecorate, __runInitializers } from "tslib";
|
|
6
6
|
import { SandboxState } from '@daytona/api-client';
|
|
7
|
-
import { TelemetryApi as AnalyticsTelemetryApi, Configuration as AnalyticsConfiguration, } from '@daytona/analytics-api-client';
|
|
8
7
|
import { Daytona } from './Daytona.js';
|
|
9
|
-
import { FileSystemApi, GitApi, ProcessApi, LspApi, InfoApi,
|
|
8
|
+
import { FileSystemApi, GitApi, ProcessApi, LspApi, InfoApi, ComputerUseApi, InterpreterApi, ServerApi, } from '@daytona/toolbox-api-client';
|
|
10
9
|
import { FileSystem } from './FileSystem.js';
|
|
11
10
|
import { Git } from './Git.js';
|
|
12
11
|
import { Process } from './Process.js';
|
|
@@ -16,12 +15,35 @@ import { CODE_TOOLBOX_LANGUAGE_LABEL } from './Daytona.js';
|
|
|
16
15
|
import { ComputerUse } from './ComputerUse.js';
|
|
17
16
|
import { CodeInterpreter } from './CodeInterpreter.js';
|
|
18
17
|
import { WithInstrumentation } from './utils/otel.decorator.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
import { VolumeType } from '@daytona/api-client';
|
|
19
|
+
/**
|
|
20
|
+
* Builds the shell command that bootstraps the hotmount agent and mounts the volume inside a
|
|
21
|
+
* Sandbox. It exports the SEAWEED_* environment contract and runs the region's `init.sh`, using
|
|
22
|
+
* passwordless sudo when not already root (sudo strips env, so the vars are passed via `env`).
|
|
23
|
+
*/
|
|
24
|
+
function buildHotmountMountCommand(mountToken, mountPath) {
|
|
25
|
+
const envVars = {
|
|
26
|
+
SEAWEED_TOKEN: mountToken.token,
|
|
27
|
+
SEAWEED_GATEWAY_GRPC: mountToken.gatewayGrpc,
|
|
28
|
+
SEAWEED_GATEWAY_HTTP: mountToken.gatewayHttp,
|
|
29
|
+
SEAWEED_BINARIES_URL: mountToken.binariesUrl,
|
|
30
|
+
SEAWEED_MOUNT_DIR: mountPath,
|
|
31
|
+
SEAWEED_VERSION: mountToken.version,
|
|
24
32
|
};
|
|
33
|
+
const envAssignments = Object.entries(envVars)
|
|
34
|
+
.filter(([, value]) => value !== undefined && value !== '')
|
|
35
|
+
.map(([key, value]) => `${key}='${value}'`)
|
|
36
|
+
.join(' ');
|
|
37
|
+
// The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the
|
|
38
|
+
// download fails, rather than letting a broken `curl … | bash` pipe exit 0 and mount nothing.
|
|
39
|
+
const inner = [
|
|
40
|
+
'set -e',
|
|
41
|
+
'if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi',
|
|
42
|
+
'mkdir -p "$SEAWEED_MOUNT_DIR"',
|
|
43
|
+
'init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"',
|
|
44
|
+
'printf %s "$init_script" | bash',
|
|
45
|
+
].join('; ');
|
|
46
|
+
return (`if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; ` + `$SUDO env ${envAssignments} bash -c '${inner}'`);
|
|
25
47
|
}
|
|
26
48
|
/**
|
|
27
49
|
* Represents a Daytona Sandbox.
|
|
@@ -52,10 +74,8 @@ function withEvents(target, _context) {
|
|
|
52
74
|
* @property {string} [backupCreatedAt] - When the backup was created (not returned by list results;
|
|
53
75
|
* call `refreshData()` on each item to populate)
|
|
54
76
|
* @property {number} [autoStopInterval] - Auto-stop interval in minutes
|
|
55
|
-
* @property {number} [autoPauseInterval] - Auto-pause interval in minutes
|
|
56
77
|
* @property {number} [autoArchiveInterval] - Auto-archive interval in minutes
|
|
57
78
|
* @property {number} [autoDeleteInterval] - Auto-delete interval in minutes
|
|
58
|
-
* @property {string} [autoDestroyAt] - When the Sandbox will be automatically destroyed (only set when a TTL is configured)
|
|
59
79
|
* @property {Array<SandboxVolume>} [volumes] - Volumes attached to the Sandbox (not returned by
|
|
60
80
|
* list results; call `refreshData()` on each item to populate)
|
|
61
81
|
* @property {BuildInfo} [buildInfo] - Build information for the Sandbox if it was created from dynamic build
|
|
@@ -77,14 +97,13 @@ function withEvents(target, _context) {
|
|
|
77
97
|
let Sandbox = (() => {
|
|
78
98
|
let _instanceExtraInitializers = [];
|
|
79
99
|
let _getUserHomeDir_decorators;
|
|
80
|
-
let _getMetricsLatest_decorators;
|
|
81
|
-
let _getMetrics_decorators;
|
|
82
100
|
let _getUserRootDir_decorators;
|
|
83
101
|
let _getWorkDir_decorators;
|
|
102
|
+
let _mountVolume_decorators;
|
|
103
|
+
let _pullVolumes_decorators;
|
|
84
104
|
let _createLspServer_decorators;
|
|
85
105
|
let _setLabels_decorators;
|
|
86
106
|
let _start_decorators;
|
|
87
|
-
let _recover_decorators;
|
|
88
107
|
let _stop_decorators;
|
|
89
108
|
let __experimental_fork_decorators;
|
|
90
109
|
let __experimental_createSnapshot_decorators;
|
|
@@ -93,18 +112,13 @@ let Sandbox = (() => {
|
|
|
93
112
|
let _waitUntilStarted_decorators;
|
|
94
113
|
let _waitUntilStopped_decorators;
|
|
95
114
|
let _refreshData_decorators;
|
|
96
|
-
let _refreshActivity_decorators;
|
|
97
115
|
let _setAutostopInterval_decorators;
|
|
98
|
-
let _setAutoPauseInterval_decorators;
|
|
99
|
-
let _setTtl_decorators;
|
|
100
116
|
let _setAutoArchiveInterval_decorators;
|
|
101
117
|
let _setAutoDeleteInterval_decorators;
|
|
102
118
|
let _updateNetworkSettings_decorators;
|
|
103
119
|
let _updateSecrets_decorators;
|
|
104
120
|
let _updateEnv_decorators;
|
|
105
121
|
let _getPreviewLink_decorators;
|
|
106
|
-
let _getSignedPreviewUrl_decorators;
|
|
107
|
-
let _expireSignedPreviewUrl_decorators;
|
|
108
122
|
let _archive_decorators;
|
|
109
123
|
let _resize_decorators;
|
|
110
124
|
let _waitForResizeComplete_decorators;
|
|
@@ -114,50 +128,43 @@ let Sandbox = (() => {
|
|
|
114
128
|
return class Sandbox {
|
|
115
129
|
static {
|
|
116
130
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
117
|
-
_getUserHomeDir_decorators = [WithInstrumentation()
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
_createLspServer_decorators = [WithInstrumentation()
|
|
123
|
-
_setLabels_decorators = [WithInstrumentation()
|
|
124
|
-
_start_decorators = [WithInstrumentation()
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
_setAutoPauseInterval_decorators = [WithInstrumentation()];
|
|
137
|
-
_setTtl_decorators = [WithInstrumentation()];
|
|
138
|
-
_setAutoArchiveInterval_decorators = [WithInstrumentation(), withEvents];
|
|
139
|
-
_setAutoDeleteInterval_decorators = [WithInstrumentation(), withEvents];
|
|
131
|
+
_getUserHomeDir_decorators = [WithInstrumentation()];
|
|
132
|
+
_getUserRootDir_decorators = [WithInstrumentation()];
|
|
133
|
+
_getWorkDir_decorators = [WithInstrumentation()];
|
|
134
|
+
_mountVolume_decorators = [WithInstrumentation()];
|
|
135
|
+
_pullVolumes_decorators = [WithInstrumentation()];
|
|
136
|
+
_createLspServer_decorators = [WithInstrumentation()];
|
|
137
|
+
_setLabels_decorators = [WithInstrumentation()];
|
|
138
|
+
_start_decorators = [WithInstrumentation()];
|
|
139
|
+
_stop_decorators = [WithInstrumentation()];
|
|
140
|
+
__experimental_fork_decorators = [WithInstrumentation()];
|
|
141
|
+
__experimental_createSnapshot_decorators = [WithInstrumentation()];
|
|
142
|
+
_pause_decorators = [WithInstrumentation()];
|
|
143
|
+
_delete_decorators = [WithInstrumentation()];
|
|
144
|
+
_waitUntilStarted_decorators = [WithInstrumentation()];
|
|
145
|
+
_waitUntilStopped_decorators = [WithInstrumentation()];
|
|
146
|
+
_refreshData_decorators = [WithInstrumentation()];
|
|
147
|
+
_setAutostopInterval_decorators = [WithInstrumentation()];
|
|
148
|
+
_setAutoArchiveInterval_decorators = [WithInstrumentation()];
|
|
149
|
+
_setAutoDeleteInterval_decorators = [WithInstrumentation()];
|
|
140
150
|
_updateNetworkSettings_decorators = [WithInstrumentation()];
|
|
141
151
|
_updateSecrets_decorators = [WithInstrumentation()];
|
|
142
152
|
_updateEnv_decorators = [WithInstrumentation()];
|
|
143
|
-
_getPreviewLink_decorators = [WithInstrumentation()
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
_revokeSshAccess_decorators = [WithInstrumentation(), withEvents];
|
|
151
|
-
_validateSshAccess_decorators = [WithInstrumentation(), withEvents];
|
|
153
|
+
_getPreviewLink_decorators = [WithInstrumentation()];
|
|
154
|
+
_archive_decorators = [WithInstrumentation()];
|
|
155
|
+
_resize_decorators = [WithInstrumentation()];
|
|
156
|
+
_waitForResizeComplete_decorators = [WithInstrumentation()];
|
|
157
|
+
_createSshAccess_decorators = [WithInstrumentation()];
|
|
158
|
+
_revokeSshAccess_decorators = [WithInstrumentation()];
|
|
159
|
+
_validateSshAccess_decorators = [WithInstrumentation()];
|
|
152
160
|
__esDecorate(this, null, _getUserHomeDir_decorators, { kind: "method", name: "getUserHomeDir", static: false, private: false, access: { has: obj => "getUserHomeDir" in obj, get: obj => obj.getUserHomeDir }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
153
|
-
__esDecorate(this, null, _getMetricsLatest_decorators, { kind: "method", name: "getMetricsLatest", static: false, private: false, access: { has: obj => "getMetricsLatest" in obj, get: obj => obj.getMetricsLatest }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
154
|
-
__esDecorate(this, null, _getMetrics_decorators, { kind: "method", name: "getMetrics", static: false, private: false, access: { has: obj => "getMetrics" in obj, get: obj => obj.getMetrics }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
155
161
|
__esDecorate(this, null, _getUserRootDir_decorators, { kind: "method", name: "getUserRootDir", static: false, private: false, access: { has: obj => "getUserRootDir" in obj, get: obj => obj.getUserRootDir }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
156
162
|
__esDecorate(this, null, _getWorkDir_decorators, { kind: "method", name: "getWorkDir", static: false, private: false, access: { has: obj => "getWorkDir" in obj, get: obj => obj.getWorkDir }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
163
|
+
__esDecorate(this, null, _mountVolume_decorators, { kind: "method", name: "mountVolume", static: false, private: false, access: { has: obj => "mountVolume" in obj, get: obj => obj.mountVolume }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
164
|
+
__esDecorate(this, null, _pullVolumes_decorators, { kind: "method", name: "pullVolumes", static: false, private: false, access: { has: obj => "pullVolumes" in obj, get: obj => obj.pullVolumes }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
157
165
|
__esDecorate(this, null, _createLspServer_decorators, { kind: "method", name: "createLspServer", static: false, private: false, access: { has: obj => "createLspServer" in obj, get: obj => obj.createLspServer }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
158
166
|
__esDecorate(this, null, _setLabels_decorators, { kind: "method", name: "setLabels", static: false, private: false, access: { has: obj => "setLabels" in obj, get: obj => obj.setLabels }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
159
167
|
__esDecorate(this, null, _start_decorators, { kind: "method", name: "start", static: false, private: false, access: { has: obj => "start" in obj, get: obj => obj.start }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
160
|
-
__esDecorate(this, null, _recover_decorators, { kind: "method", name: "recover", static: false, private: false, access: { has: obj => "recover" in obj, get: obj => obj.recover }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
161
168
|
__esDecorate(this, null, _stop_decorators, { kind: "method", name: "stop", static: false, private: false, access: { has: obj => "stop" in obj, get: obj => obj.stop }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
162
169
|
__esDecorate(this, null, __experimental_fork_decorators, { kind: "method", name: "_experimental_fork", static: false, private: false, access: { has: obj => "_experimental_fork" in obj, get: obj => obj._experimental_fork }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
163
170
|
__esDecorate(this, null, __experimental_createSnapshot_decorators, { kind: "method", name: "_experimental_createSnapshot", static: false, private: false, access: { has: obj => "_experimental_createSnapshot" in obj, get: obj => obj._experimental_createSnapshot }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -166,18 +173,13 @@ let Sandbox = (() => {
|
|
|
166
173
|
__esDecorate(this, null, _waitUntilStarted_decorators, { kind: "method", name: "waitUntilStarted", static: false, private: false, access: { has: obj => "waitUntilStarted" in obj, get: obj => obj.waitUntilStarted }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
167
174
|
__esDecorate(this, null, _waitUntilStopped_decorators, { kind: "method", name: "waitUntilStopped", static: false, private: false, access: { has: obj => "waitUntilStopped" in obj, get: obj => obj.waitUntilStopped }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
168
175
|
__esDecorate(this, null, _refreshData_decorators, { kind: "method", name: "refreshData", static: false, private: false, access: { has: obj => "refreshData" in obj, get: obj => obj.refreshData }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
169
|
-
__esDecorate(this, null, _refreshActivity_decorators, { kind: "method", name: "refreshActivity", static: false, private: false, access: { has: obj => "refreshActivity" in obj, get: obj => obj.refreshActivity }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
170
176
|
__esDecorate(this, null, _setAutostopInterval_decorators, { kind: "method", name: "setAutostopInterval", static: false, private: false, access: { has: obj => "setAutostopInterval" in obj, get: obj => obj.setAutostopInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
171
|
-
__esDecorate(this, null, _setAutoPauseInterval_decorators, { kind: "method", name: "setAutoPauseInterval", static: false, private: false, access: { has: obj => "setAutoPauseInterval" in obj, get: obj => obj.setAutoPauseInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
172
|
-
__esDecorate(this, null, _setTtl_decorators, { kind: "method", name: "setTtl", static: false, private: false, access: { has: obj => "setTtl" in obj, get: obj => obj.setTtl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
173
177
|
__esDecorate(this, null, _setAutoArchiveInterval_decorators, { kind: "method", name: "setAutoArchiveInterval", static: false, private: false, access: { has: obj => "setAutoArchiveInterval" in obj, get: obj => obj.setAutoArchiveInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
174
178
|
__esDecorate(this, null, _setAutoDeleteInterval_decorators, { kind: "method", name: "setAutoDeleteInterval", static: false, private: false, access: { has: obj => "setAutoDeleteInterval" in obj, get: obj => obj.setAutoDeleteInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
175
179
|
__esDecorate(this, null, _updateNetworkSettings_decorators, { kind: "method", name: "updateNetworkSettings", static: false, private: false, access: { has: obj => "updateNetworkSettings" in obj, get: obj => obj.updateNetworkSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
176
180
|
__esDecorate(this, null, _updateSecrets_decorators, { kind: "method", name: "updateSecrets", static: false, private: false, access: { has: obj => "updateSecrets" in obj, get: obj => obj.updateSecrets }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
177
181
|
__esDecorate(this, null, _updateEnv_decorators, { kind: "method", name: "updateEnv", static: false, private: false, access: { has: obj => "updateEnv" in obj, get: obj => obj.updateEnv }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
178
182
|
__esDecorate(this, null, _getPreviewLink_decorators, { kind: "method", name: "getPreviewLink", static: false, private: false, access: { has: obj => "getPreviewLink" in obj, get: obj => obj.getPreviewLink }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
179
|
-
__esDecorate(this, null, _getSignedPreviewUrl_decorators, { kind: "method", name: "getSignedPreviewUrl", static: false, private: false, access: { has: obj => "getSignedPreviewUrl" in obj, get: obj => obj.getSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
180
|
-
__esDecorate(this, null, _expireSignedPreviewUrl_decorators, { kind: "method", name: "expireSignedPreviewUrl", static: false, private: false, access: { has: obj => "expireSignedPreviewUrl" in obj, get: obj => obj.expireSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
181
183
|
__esDecorate(this, null, _archive_decorators, { kind: "method", name: "archive", static: false, private: false, access: { has: obj => "archive" in obj, get: obj => obj.archive }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
182
184
|
__esDecorate(this, null, _resize_decorators, { kind: "method", name: "resize", static: false, private: false, access: { has: obj => "resize" in obj, get: obj => obj.resize }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
183
185
|
__esDecorate(this, null, _waitForResizeComplete_decorators, { kind: "method", name: "waitForResizeComplete", static: false, private: false, access: { has: obj => "waitForResizeComplete" in obj, get: obj => obj.waitForResizeComplete }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -189,8 +191,7 @@ let Sandbox = (() => {
|
|
|
189
191
|
clientConfig = __runInitializers(this, _instanceExtraInitializers);
|
|
190
192
|
axiosInstance;
|
|
191
193
|
sandboxApi;
|
|
192
|
-
|
|
193
|
-
subscriptionManager;
|
|
194
|
+
volumeService;
|
|
194
195
|
fs;
|
|
195
196
|
git;
|
|
196
197
|
process;
|
|
@@ -215,10 +216,8 @@ let Sandbox = (() => {
|
|
|
215
216
|
backupState;
|
|
216
217
|
backupCreatedAt;
|
|
217
218
|
autoStopInterval;
|
|
218
|
-
autoPauseInterval;
|
|
219
219
|
autoArchiveInterval;
|
|
220
220
|
autoDeleteInterval;
|
|
221
|
-
autoDestroyAt;
|
|
222
221
|
volumes;
|
|
223
222
|
buildInfo;
|
|
224
223
|
createdAt;
|
|
@@ -231,28 +230,25 @@ let Sandbox = (() => {
|
|
|
231
230
|
toolboxProxyUrl;
|
|
232
231
|
infoApi;
|
|
233
232
|
serverApi;
|
|
234
|
-
systemApi;
|
|
235
|
-
stateWaiters = [];
|
|
236
|
-
stateWaiterErrorMessageFns = new WeakMap();
|
|
237
|
-
subId;
|
|
238
233
|
/**
|
|
239
|
-
* Creates a new Sandbox instance
|
|
240
|
-
*
|
|
241
|
-
* Internal: obtain sandboxes via {@link Daytona.create}, {@link Daytona.get}, or
|
|
242
|
-
* {@link Daytona.list} rather than constructing directly.
|
|
234
|
+
* Creates a new Sandbox instance
|
|
243
235
|
*
|
|
244
236
|
* @param {SandboxDto} sandboxDto - The API Sandbox instance
|
|
245
|
-
* @param {SandboxApi} sandboxApi - API client for Sandbox operations
|
|
246
|
-
* @param {InfoApi} infoApi - API client for info operations
|
|
247
|
-
* @param {EventSubscriptionManager} subscriptionManager - Event subscription manager for real-time updates
|
|
248
237
|
*/
|
|
249
|
-
constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi,
|
|
238
|
+
constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi, volumeService) {
|
|
250
239
|
this.clientConfig = clientConfig;
|
|
251
240
|
this.axiosInstance = axiosInstance;
|
|
252
241
|
this.sandboxApi = sandboxApi;
|
|
253
|
-
this.
|
|
254
|
-
this.subscriptionManager = subscriptionManager;
|
|
242
|
+
this.volumeService = volumeService;
|
|
255
243
|
this.processSandboxDto(sandboxDto);
|
|
244
|
+
// Set the toolbox base URL
|
|
245
|
+
let baseUrl = this.toolboxProxyUrl;
|
|
246
|
+
if (!baseUrl.endsWith('/')) {
|
|
247
|
+
baseUrl += '/';
|
|
248
|
+
}
|
|
249
|
+
this.axiosInstance.defaults.baseURL = baseUrl + this.id;
|
|
250
|
+
this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
|
|
251
|
+
// Initialize Services
|
|
256
252
|
const getPreviewToken = async () => (await this.getPreviewLink(1)).token;
|
|
257
253
|
this.fs = new FileSystem(this.clientConfig, new FileSystemApi(this.clientConfig, '', this.axiosInstance));
|
|
258
254
|
this.git = new Git(new GitApi(this.clientConfig, '', this.axiosInstance));
|
|
@@ -262,8 +258,6 @@ let Sandbox = (() => {
|
|
|
262
258
|
this.computerUse = new ComputerUse(new ComputerUseApi(this.clientConfig, '', this.axiosInstance));
|
|
263
259
|
this.infoApi = new InfoApi(this.clientConfig, '', this.axiosInstance);
|
|
264
260
|
this.serverApi = new ServerApi(this.clientConfig, '', this.axiosInstance);
|
|
265
|
-
this.systemApi = new SystemApi(this.clientConfig, '', this.axiosInstance);
|
|
266
|
-
this.subscribeToEvents();
|
|
267
261
|
}
|
|
268
262
|
/**
|
|
269
263
|
* Gets the user's home directory path for the logged in user inside the Sandbox.
|
|
@@ -278,53 +272,6 @@ let Sandbox = (() => {
|
|
|
278
272
|
const response = await this.infoApi.getUserHomeDir();
|
|
279
273
|
return response.data.dir;
|
|
280
274
|
}
|
|
281
|
-
/**
|
|
282
|
-
* Gets the most recent resource usage sample directly from the sandbox daemon.
|
|
283
|
-
*
|
|
284
|
-
* Unlike {@link getMetrics}, which returns aggregated historical samples, this returns the
|
|
285
|
-
* single current reading without going through the telemetry backend.
|
|
286
|
-
*
|
|
287
|
-
* @returns The current resource usage sample for the sandbox.
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* const m = await sandbox.getMetricsLatest()
|
|
291
|
-
* console.log(`CPU: ${m.cpuUsedPct}%, mem: ${m.memUsed}/${m.memTotal}`)
|
|
292
|
-
*/
|
|
293
|
-
async getMetricsLatest() {
|
|
294
|
-
const response = await this.systemApi.getSystemMetrics();
|
|
295
|
-
return sandboxMetricsFromSystemMetrics(response.data);
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Gets historical time-series resource usage metrics for the Sandbox.
|
|
299
|
-
*
|
|
300
|
-
* @param {Date} [start] - Start of the time range. Defaults to the Sandbox creation time.
|
|
301
|
-
* @param {Date} [end] - End of the time range. Defaults to the current time.
|
|
302
|
-
* @returns Time-ordered usage samples over the requested range.
|
|
303
|
-
*
|
|
304
|
-
* @example
|
|
305
|
-
* const samples = await sandbox.getMetrics()
|
|
306
|
-
* for (const s of samples) {
|
|
307
|
-
* console.log(`${s.timestamp.toISOString()} CPU: ${s.cpuUsedPct}% mem: ${s.memUsed}/${s.memTotal}`)
|
|
308
|
-
* }
|
|
309
|
-
*/
|
|
310
|
-
async getMetrics(start, end) {
|
|
311
|
-
const to = end ?? new Date();
|
|
312
|
-
const from = start ?? (this.createdAt ? new Date(this.createdAt) : to);
|
|
313
|
-
const analyticsApiUrl = await this.getAnalyticsApiUrl();
|
|
314
|
-
if (analyticsApiUrl) {
|
|
315
|
-
const response = await this.buildAnalyticsTelemetryApi(analyticsApiUrl).organizationOrganizationIdSandboxSandboxIdTelemetryMetricsGet(this.organizationId, this.id, from.toISOString(), to.toISOString(), SANDBOX_METRIC_NAMES.join(','));
|
|
316
|
-
return pivotSandboxMetricPoints(response.data);
|
|
317
|
-
}
|
|
318
|
-
const response = await this.sandboxApi.getSandboxMetrics(this.id, from, to, undefined, SANDBOX_METRIC_NAMES);
|
|
319
|
-
return pivotSandboxMetrics(response.data.series);
|
|
320
|
-
}
|
|
321
|
-
buildAnalyticsTelemetryApi(analyticsApiUrl) {
|
|
322
|
-
const analyticsConfig = new AnalyticsConfiguration({
|
|
323
|
-
basePath: analyticsApiUrl,
|
|
324
|
-
apiKey: this.clientConfig.baseOptions?.headers?.Authorization,
|
|
325
|
-
});
|
|
326
|
-
return new AnalyticsTelemetryApi(analyticsConfig, undefined, Daytona.createAxiosInstance());
|
|
327
|
-
}
|
|
328
275
|
/**
|
|
329
276
|
* @deprecated Use `getUserHomeDir` instead. This method will be removed in a future version.
|
|
330
277
|
*/
|
|
@@ -345,6 +292,71 @@ let Sandbox = (() => {
|
|
|
345
292
|
const response = await this.infoApi.getWorkDir();
|
|
346
293
|
return response.data.dir;
|
|
347
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Mounts a hotmount Volume into the running Sandbox on the fly.
|
|
297
|
+
*
|
|
298
|
+
* Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount
|
|
299
|
+
* volumes are mounted at runtime: this method requests a short-lived mount token from the API
|
|
300
|
+
* and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's
|
|
301
|
+
* `init.sh`), mounting the filesystem at the given path.
|
|
302
|
+
*
|
|
303
|
+
* The Sandbox must have `/dev/fuse` available, outbound access to the region gateway (ports
|
|
304
|
+
* 443 and 18443) and binaries bucket, and passwordless sudo (or run as root).
|
|
305
|
+
*
|
|
306
|
+
* @param {Volume} volume - The hotmount Volume to mount. Must be of type `VolumeType.HOTMOUNT`.
|
|
307
|
+
* @param {string} mountPath - The absolute path inside the Sandbox to mount the Volume at
|
|
308
|
+
* @returns {Promise<void>}
|
|
309
|
+
* @throws {DaytonaError} If the Volume is not a hotmount volume, or if mounting fails
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* const volume = await daytona.volume.get("shared-fs");
|
|
313
|
+
* await sandbox.mountVolume(volume, "/mnt/shared");
|
|
314
|
+
*/
|
|
315
|
+
async mountVolume(volume, mountPath) {
|
|
316
|
+
if (volume.type !== VolumeType.HOTMOUNT) {
|
|
317
|
+
throw new DaytonaValidationError(`Only hotmount volumes can be mounted on the fly. Volume '${volume.name}' is of type '${volume.type}'.`);
|
|
318
|
+
}
|
|
319
|
+
if (!this.volumeService) {
|
|
320
|
+
throw new DaytonaError('Volume service is not available for this Sandbox instance.');
|
|
321
|
+
}
|
|
322
|
+
const mountToken = await this.volumeService.getMountToken(volume);
|
|
323
|
+
const command = buildHotmountMountCommand(mountToken, mountPath);
|
|
324
|
+
const response = await this.process.executeCommand(command);
|
|
325
|
+
if (response.exitCode !== 0) {
|
|
326
|
+
throw new DaytonaError(`Failed to mount hotmount volume '${volume.name}': ${response.result}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox.
|
|
331
|
+
*
|
|
332
|
+
* Blockmount volumes reconcile in the background: each sandbox writes to a private scratch
|
|
333
|
+
* that is committed to the shared store periodically, but other sandboxes' commits only
|
|
334
|
+
* appear locally on re-materialize. This method makes them appear immediately, without
|
|
335
|
+
* stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the
|
|
336
|
+
* merge), then applies the volume's latest merged state in place. Files modified locally
|
|
337
|
+
* after another sandbox's change keep the local version (last-change-wins by mtime).
|
|
338
|
+
*
|
|
339
|
+
* @param {Volume} [volume] - The blockmount Volume to pull. Omit to pull every blockmount
|
|
340
|
+
* Volume attached to the Sandbox.
|
|
341
|
+
* @returns {Promise<VolumePullResult[]>} Per-volume pull results
|
|
342
|
+
* @throws {DaytonaError} If the Sandbox has no matching mounted blockmount Volume
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* // sandbox B picks up what sandbox A committed, while both keep running
|
|
346
|
+
* const results = await sandbox.pullVolumes();
|
|
347
|
+
* console.log(results); // [{ volumeId, upToDate, filesWritten, deleted, ... }]
|
|
348
|
+
*/
|
|
349
|
+
async pullVolumes(volume) {
|
|
350
|
+
if (volume && volume.type !== VolumeType.BLOCKMOUNT) {
|
|
351
|
+
throw new DaytonaValidationError(`Only blockmount volumes can be pulled. Volume '${volume.name}' is of type '${volume.type}'.`);
|
|
352
|
+
}
|
|
353
|
+
// Raw toolbox call (the daemon's generated client has no pull op — the runner intercepts
|
|
354
|
+
// this path), so replicate what the generated clients do: resolve against this sandbox's
|
|
355
|
+
// clientConfig.basePath (the shared axios instance's default baseURL is mutated by every
|
|
356
|
+
// Sandbox construction and may point at another sandbox) and inject the auth headers.
|
|
357
|
+
const response = await this.axiosInstance.post(`${this.clientConfig.basePath}/volumes/pull`, volume ? { volumeId: volume.id } : {}, { headers: this.clientConfig.baseOptions?.headers });
|
|
358
|
+
return response.data.results ?? [];
|
|
359
|
+
}
|
|
348
360
|
/**
|
|
349
361
|
* Creates a new Language Server Protocol (LSP) server instance.
|
|
350
362
|
*
|
|
@@ -482,16 +494,9 @@ let Sandbox = (() => {
|
|
|
482
494
|
timeout: timeout * 1000,
|
|
483
495
|
});
|
|
484
496
|
const sandboxDto = response.data;
|
|
485
|
-
const
|
|
486
|
-
? sandboxDto
|
|
487
|
-
: {
|
|
488
|
-
...sandboxDto,
|
|
489
|
-
toolboxProxyUrl: (await this.sandboxApi.getToolboxProxyUrl(sandboxDto.id)).data.url,
|
|
490
|
-
};
|
|
491
|
-
const forkedSandbox = new Sandbox(sandboxWithProxyUrl, structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi, this.getAnalyticsApiUrl, this.subscriptionManager);
|
|
497
|
+
const forkedSandbox = new Sandbox(sandboxDto, structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi);
|
|
492
498
|
const timeElapsed = Date.now() - startTime;
|
|
493
|
-
|
|
494
|
-
await forkedSandbox.waitUntilStarted(remainingTimeout);
|
|
499
|
+
await forkedSandbox.waitUntilStarted(timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout);
|
|
495
500
|
return forkedSandbox;
|
|
496
501
|
}
|
|
497
502
|
/**
|
|
@@ -519,19 +524,34 @@ let Sandbox = (() => {
|
|
|
519
524
|
}
|
|
520
525
|
const startTime = Date.now();
|
|
521
526
|
const req = { name };
|
|
522
|
-
|
|
527
|
+
await this.sandboxApi.createSandboxSnapshot(this.id, req, undefined, {
|
|
523
528
|
timeout: timeout * 1000,
|
|
524
529
|
});
|
|
525
|
-
this.
|
|
530
|
+
await this.refreshData();
|
|
526
531
|
const timeElapsed = Date.now() - startTime;
|
|
527
532
|
const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
|
|
528
533
|
await this.waitForSnapshotComplete(remainingTimeout);
|
|
529
534
|
}
|
|
530
535
|
async waitForSnapshotComplete(timeout) {
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
536
|
+
let checkInterval = 100;
|
|
537
|
+
const startTime = Date.now();
|
|
538
|
+
while (this.state === SandboxState.SNAPSHOTTING) {
|
|
539
|
+
await this.refreshData();
|
|
540
|
+
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
541
|
+
if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
|
|
542
|
+
throw new DaytonaError(`Sandbox ${this.id} snapshot failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
543
|
+
}
|
|
544
|
+
if (this.state !== SandboxState.SNAPSHOTTING) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
548
|
+
throw new DaytonaTimeoutError('Sandbox snapshot did not complete within the timeout period');
|
|
549
|
+
}
|
|
550
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
551
|
+
if (Date.now() - startTime > 5000) {
|
|
552
|
+
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
535
555
|
}
|
|
536
556
|
/**
|
|
537
557
|
* Pauses the Sandbox, freezing all running processes.
|
|
@@ -562,48 +582,32 @@ let Sandbox = (() => {
|
|
|
562
582
|
await this.refreshData();
|
|
563
583
|
const timeElapsed = Date.now() - startTime;
|
|
564
584
|
const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
|
|
565
|
-
|
|
566
|
-
// (paused, stopped, archived, ...), not only on exactly PAUSED.
|
|
567
|
-
const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
|
|
568
|
-
const excludeStates = new Set([SandboxState.PAUSING, ...errorStates]);
|
|
569
|
-
const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
|
|
570
|
-
await this.waitForState(targetStates, errorStates, remainingTimeout, 'Sandbox failed to become paused within the timeout period', (state) => `Sandbox ${this.id} pause failed with state: ${state}, error reason: ${this.errorReason}`);
|
|
585
|
+
await this.waitForPauseComplete(remainingTimeout);
|
|
571
586
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
*
|
|
575
|
-
* By default this returns as soon as the deletion request is accepted (matching
|
|
576
|
-
* historical behavior). Pass `wait = true` to block until the Sandbox reaches
|
|
577
|
-
* the 'destroyed' state.
|
|
578
|
-
*
|
|
579
|
-
* @param {number} [timeout] - Timeout in seconds for the request — and, when
|
|
580
|
-
* `wait` is true, for reaching 'destroyed'. 0 means no timeout.
|
|
581
|
-
* Defaults to 60-second timeout.
|
|
582
|
-
* @param {boolean} [wait] - If true, wait until the Sandbox is destroyed. Defaults to false.
|
|
583
|
-
* @returns {Promise<void>}
|
|
584
|
-
*/
|
|
585
|
-
async delete(timeout = 60, wait = false) {
|
|
586
|
-
if (timeout < 0) {
|
|
587
|
-
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
588
|
-
}
|
|
587
|
+
async waitForPauseComplete(timeout) {
|
|
588
|
+
let checkInterval = 100;
|
|
589
589
|
const startTime = Date.now();
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
this.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (wait && this.state !== SandboxState.DESTROYED) {
|
|
596
|
-
const timeElapsed = Date.now() - startTime;
|
|
597
|
-
await this.waitForState([SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout, 'Sandbox failed to be destroyed within the timeout period', (state) => `Sandbox ${this.id} failed to delete with state: ${state}, error reason: ${this.errorReason}`, true);
|
|
590
|
+
while (this.state === SandboxState.PAUSING) {
|
|
591
|
+
await this.refreshData();
|
|
592
|
+
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
593
|
+
if (this.state === SandboxState.ERROR) {
|
|
594
|
+
throw new DaytonaError(`Sandbox ${this.id} pause failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
598
595
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (this.subId) {
|
|
602
|
-
this.subscriptionManager.unsubscribe(this.subId);
|
|
603
|
-
this.subId = undefined;
|
|
596
|
+
if (timeout > 0 && (Date.now() - startTime) / 1000 >= timeout) {
|
|
597
|
+
throw new DaytonaError(`Sandbox ${this.id} failed to pause within ${timeout} seconds`);
|
|
604
598
|
}
|
|
599
|
+
await new Promise((resolve) => globalThis.setTimeout(resolve, checkInterval));
|
|
600
|
+
checkInterval = Math.min(checkInterval * 1.5, 1000);
|
|
605
601
|
}
|
|
606
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Deletes the Sandbox.
|
|
605
|
+
* @returns {Promise<void>}
|
|
606
|
+
*/
|
|
607
|
+
async delete(timeout = 60) {
|
|
608
|
+
await this.sandboxApi.deleteSandbox(this.id, undefined, { timeout: timeout * 1000 });
|
|
609
|
+
this.refreshDataSafe();
|
|
610
|
+
}
|
|
607
611
|
/**
|
|
608
612
|
* Waits for the Sandbox to reach the 'started' state.
|
|
609
613
|
*
|
|
@@ -619,10 +623,26 @@ let Sandbox = (() => {
|
|
|
619
623
|
if (timeout < 0) {
|
|
620
624
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
621
625
|
}
|
|
622
|
-
|
|
623
|
-
|
|
626
|
+
let checkInterval = 100;
|
|
627
|
+
const startTime = Date.now();
|
|
628
|
+
while (this.state !== 'started') {
|
|
629
|
+
await this.refreshData();
|
|
630
|
+
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
631
|
+
if (this.state === 'started') {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (this.state === 'error') {
|
|
635
|
+
const errMsg = `Sandbox ${this.id} failed to start with status: ${this.state}, error reason: ${this.errorReason}`;
|
|
636
|
+
throw new DaytonaError(errMsg);
|
|
637
|
+
}
|
|
638
|
+
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
639
|
+
throw new DaytonaTimeoutError('Sandbox failed to become ready within the timeout period');
|
|
640
|
+
}
|
|
641
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
642
|
+
if (Date.now() - startTime > 5000) {
|
|
643
|
+
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
644
|
+
}
|
|
624
645
|
}
|
|
625
|
-
return this.waitForState([SandboxState.STARTED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become ready within the timeout period', (state) => `Sandbox ${this.id} failed to start with status: ${state}, error reason: ${this.errorReason}`);
|
|
626
646
|
}
|
|
627
647
|
/**
|
|
628
648
|
* Wait for Sandbox to reach 'stopped' state.
|
|
@@ -639,11 +659,27 @@ let Sandbox = (() => {
|
|
|
639
659
|
if (timeout < 0) {
|
|
640
660
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
641
661
|
}
|
|
662
|
+
let checkInterval = 100;
|
|
663
|
+
const startTime = Date.now();
|
|
642
664
|
// Treat destroyed as stopped to cover ephemeral sandboxes that are automatically deleted after stopping
|
|
643
|
-
|
|
644
|
-
|
|
665
|
+
while (this.state !== 'stopped' && this.state !== 'destroyed') {
|
|
666
|
+
this.refreshDataSafe();
|
|
667
|
+
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
668
|
+
if (this.state === 'stopped' || this.state === 'destroyed') {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (this.state === 'error') {
|
|
672
|
+
const errMsg = `Sandbox failed to stop with status: ${this.state}, error reason: ${this.errorReason}`;
|
|
673
|
+
throw new DaytonaError(errMsg);
|
|
674
|
+
}
|
|
675
|
+
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
676
|
+
throw new DaytonaTimeoutError('Sandbox failed to become stopped within the timeout period');
|
|
677
|
+
}
|
|
678
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
679
|
+
if (Date.now() - startTime > 5000) {
|
|
680
|
+
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
681
|
+
}
|
|
645
682
|
}
|
|
646
|
-
return this.waitForState([SandboxState.STOPPED, SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become stopped within the timeout period', (state) => `Sandbox failed to stop with status: ${state}, error reason: ${this.errorReason}`, true);
|
|
647
683
|
}
|
|
648
684
|
/**
|
|
649
685
|
* Refreshes the Sandbox data from the API.
|
|
@@ -700,60 +736,6 @@ let Sandbox = (() => {
|
|
|
700
736
|
await this.sandboxApi.setAutostopInterval(this.id, interval);
|
|
701
737
|
this.autoStopInterval = interval;
|
|
702
738
|
}
|
|
703
|
-
/**
|
|
704
|
-
* Set the auto-pause interval for the Sandbox.
|
|
705
|
-
*
|
|
706
|
-
* The Sandbox will automatically pause after being idle (no new events) for the specified interval.
|
|
707
|
-
* Events include any state changes or interactions with the Sandbox through the sdk.
|
|
708
|
-
* Interactions using Sandbox Previews are not included.
|
|
709
|
-
*
|
|
710
|
-
* Only supported for sandbox classes that support pausing. At most one of the auto-stop
|
|
711
|
-
* and auto-pause intervals may be non-zero, so disable auto-stop first by setting its
|
|
712
|
-
* interval to 0.
|
|
713
|
-
*
|
|
714
|
-
* @param {number} interval - Number of minutes of inactivity before auto-pausing.
|
|
715
|
-
* Set to 0 to disable auto-pause. For pause-supporting sandbox
|
|
716
|
-
* classes, creation defaults to 60 minutes when neither interval is provided.
|
|
717
|
-
* @returns {Promise<void>}
|
|
718
|
-
* @throws {DaytonaError} - `DaytonaError` - If interval is not a non-negative integer
|
|
719
|
-
*
|
|
720
|
-
* @example
|
|
721
|
-
* // Auto-pause after 1 hour
|
|
722
|
-
* await sandbox.setAutoPauseInterval(60);
|
|
723
|
-
* // Or disable auto-pause
|
|
724
|
-
* await sandbox.setAutoPauseInterval(0);
|
|
725
|
-
*/
|
|
726
|
-
async setAutoPauseInterval(interval) {
|
|
727
|
-
if (!Number.isInteger(interval) || interval < 0) {
|
|
728
|
-
throw new DaytonaValidationError('autoPauseInterval must be a non-negative integer');
|
|
729
|
-
}
|
|
730
|
-
await this.sandboxApi.setAutoPauseInterval(this.id, interval);
|
|
731
|
-
this.autoPauseInterval = interval;
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Set the TTL (maximum time to live) for the Sandbox.
|
|
735
|
-
*
|
|
736
|
-
* The Sandbox will be destroyed once the TTL elapses, counted as wall-clock time regardless of the
|
|
737
|
-
* Sandbox state - even if it is stopped, paused, or archived. Calling this method re-anchors the
|
|
738
|
-
* deadline from the current time. Call `refreshData()` afterwards to read the updated `autoDestroyAt`.
|
|
739
|
-
*
|
|
740
|
-
* @param {number} ttlMinutes - Number of minutes from now after which the Sandbox will be destroyed.
|
|
741
|
-
* Set to 0 to disable the TTL.
|
|
742
|
-
* @returns {Promise<void>}
|
|
743
|
-
* @throws {DaytonaError} - `DaytonaError` - If ttlMinutes is not a non-negative integer
|
|
744
|
-
*
|
|
745
|
-
* @example
|
|
746
|
-
* // Destroy the Sandbox 1 hour from now
|
|
747
|
-
* await sandbox.setTtl(60);
|
|
748
|
-
* // Or disable the TTL
|
|
749
|
-
* await sandbox.setTtl(0);
|
|
750
|
-
*/
|
|
751
|
-
async setTtl(ttlMinutes) {
|
|
752
|
-
if (!Number.isInteger(ttlMinutes) || ttlMinutes < 0) {
|
|
753
|
-
throw new DaytonaValidationError('ttlMinutes must be a non-negative integer');
|
|
754
|
-
}
|
|
755
|
-
await this.sandboxApi.setTtl(this.id, ttlMinutes);
|
|
756
|
-
}
|
|
757
739
|
/**
|
|
758
740
|
* Set the auto-archive interval for the Sandbox.
|
|
759
741
|
*
|
|
@@ -976,13 +958,25 @@ let Sandbox = (() => {
|
|
|
976
958
|
if (timeout < 0) {
|
|
977
959
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
978
960
|
}
|
|
979
|
-
|
|
980
|
-
|
|
961
|
+
let checkInterval = 100;
|
|
962
|
+
const startTime = Date.now();
|
|
963
|
+
while (this.state === SandboxState.RESIZING) {
|
|
964
|
+
await this.refreshData();
|
|
965
|
+
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
966
|
+
if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
|
|
967
|
+
throw new DaytonaError(`Sandbox ${this.id} resize failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
968
|
+
}
|
|
969
|
+
if (this.state !== SandboxState.RESIZING) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
973
|
+
throw new DaytonaTimeoutError('Sandbox resize did not complete within the timeout period');
|
|
974
|
+
}
|
|
975
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
976
|
+
if (Date.now() - startTime > 5000) {
|
|
977
|
+
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
978
|
+
}
|
|
981
979
|
}
|
|
982
|
-
const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
|
|
983
|
-
const excludeStates = new Set([SandboxState.RESIZING, ...errorStates]);
|
|
984
|
-
const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
|
|
985
|
-
return this.waitForState(targetStates, errorStates, timeout, 'Sandbox resize did not complete within the timeout period', (state) => `Sandbox ${this.id} resize failed with state: ${state}, error reason: ${this.errorReason}`);
|
|
986
980
|
}
|
|
987
981
|
/**
|
|
988
982
|
* Creates an SSH access token for the sandbox.
|
|
@@ -1011,172 +1005,6 @@ let Sandbox = (() => {
|
|
|
1011
1005
|
async validateSshAccess(token) {
|
|
1012
1006
|
return (await this.sandboxApi.validateSshAccess(token)).data;
|
|
1013
1007
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Subscribes to real-time events for this sandbox.
|
|
1016
|
-
* Auto-updates sandbox metadata on every event.
|
|
1017
|
-
*/
|
|
1018
|
-
subscribeToEvents() {
|
|
1019
|
-
if (this.subId) {
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
this.subId = this.subscriptionManager.subscribe(this.id, this.handleEvent.bind(this), [
|
|
1023
|
-
'sandbox.state.updated',
|
|
1024
|
-
'sandbox.created',
|
|
1025
|
-
]);
|
|
1026
|
-
}
|
|
1027
|
-
ensureSubscribed() {
|
|
1028
|
-
if (this.subId) {
|
|
1029
|
-
if (this.subscriptionManager.refresh(this.subId)) {
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
|
-
this.subId = undefined;
|
|
1033
|
-
}
|
|
1034
|
-
this.subscribeToEvents();
|
|
1035
|
-
}
|
|
1036
|
-
handleEvent(eventName, data) {
|
|
1037
|
-
if (!data || typeof data !== 'object')
|
|
1038
|
-
return;
|
|
1039
|
-
const raw = data.sandbox ?? data;
|
|
1040
|
-
if (!raw || typeof raw !== 'object')
|
|
1041
|
-
return;
|
|
1042
|
-
if (eventName === 'sandbox.created') {
|
|
1043
|
-
this.processSandboxDto(raw);
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
const newState = raw.state ?? data.newState;
|
|
1047
|
-
if (newState) {
|
|
1048
|
-
this.applyState(newState);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Waits for the sandbox to reach one of the target states.
|
|
1053
|
-
* Throws on error states or timeout.
|
|
1054
|
-
*
|
|
1055
|
-
* @param targetStates - States that indicate success.
|
|
1056
|
-
* @param errorStates - States that indicate failure.
|
|
1057
|
-
* @param timeout - Maximum time to wait in seconds. 0 means no timeout.
|
|
1058
|
-
* @param timeoutMessage - Error message when timeout is reached.
|
|
1059
|
-
* @param errorMessageFn - Function that produces an error message from the current state.
|
|
1060
|
-
* @param safeRefresh - If true, use refreshDataSafe() for polling (for delete operations where 404 is expected).
|
|
1061
|
-
*/
|
|
1062
|
-
waitForState(targetStates, errorStates, timeout, timeoutMessage, errorMessageFn, safeRefresh = false) {
|
|
1063
|
-
this.ensureSubscribed();
|
|
1064
|
-
return new Promise((resolve, reject) => {
|
|
1065
|
-
let timeoutTimer = null;
|
|
1066
|
-
let pollTimer = null;
|
|
1067
|
-
let settled = false;
|
|
1068
|
-
const waiter = {
|
|
1069
|
-
targetStates: new Set(targetStates),
|
|
1070
|
-
errorStates: new Set(errorStates),
|
|
1071
|
-
resolve: (_) => {
|
|
1072
|
-
if (settled)
|
|
1073
|
-
return;
|
|
1074
|
-
cleanup();
|
|
1075
|
-
resolve();
|
|
1076
|
-
},
|
|
1077
|
-
reject: (err) => {
|
|
1078
|
-
if (settled)
|
|
1079
|
-
return;
|
|
1080
|
-
cleanup();
|
|
1081
|
-
reject(err);
|
|
1082
|
-
},
|
|
1083
|
-
};
|
|
1084
|
-
this.stateWaiters.push(waiter);
|
|
1085
|
-
this.stateWaiterErrorMessageFns.set(waiter, errorMessageFn);
|
|
1086
|
-
const cleanup = () => {
|
|
1087
|
-
if (settled)
|
|
1088
|
-
return;
|
|
1089
|
-
settled = true;
|
|
1090
|
-
if (timeoutTimer)
|
|
1091
|
-
clearTimeout(timeoutTimer);
|
|
1092
|
-
if (pollTimer)
|
|
1093
|
-
clearTimeout(pollTimer);
|
|
1094
|
-
this.removeStateWaiter(waiter);
|
|
1095
|
-
};
|
|
1096
|
-
// Fast-path only on cached *target* states (parity with main's pre-check).
|
|
1097
|
-
// Cached error states are deliberately NOT evaluated here — main always
|
|
1098
|
-
// refreshed before failing, so a stale ERROR must survive one refresh.
|
|
1099
|
-
if (this.state && waiter.targetStates.has(this.state)) {
|
|
1100
|
-
waiter.resolve(this.state);
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
if (timeout !== 0) {
|
|
1104
|
-
timeoutTimer = setTimeout(() => {
|
|
1105
|
-
void (async () => {
|
|
1106
|
-
if (settled)
|
|
1107
|
-
return;
|
|
1108
|
-
// Parity with main: complete one final refresh-then-evaluate before
|
|
1109
|
-
// rejecting, so a clamped/short timeout still observes the latest state.
|
|
1110
|
-
let refreshed = false;
|
|
1111
|
-
try {
|
|
1112
|
-
if (safeRefresh) {
|
|
1113
|
-
refreshed = await this.refreshDataSafe();
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
await this.refreshData();
|
|
1117
|
-
refreshed = true;
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
catch {
|
|
1121
|
-
// fall through to the timeout rejection below
|
|
1122
|
-
}
|
|
1123
|
-
if (!settled) {
|
|
1124
|
-
// Explicit evaluation: applyState() no-ops on unchanged state, so a
|
|
1125
|
-
// persistent error state would otherwise time out generically here.
|
|
1126
|
-
// Only evaluate when the refresh succeeded — a failed refresh leaves
|
|
1127
|
-
// the cached state stale and must not be treated as authoritative.
|
|
1128
|
-
if (refreshed && this.checkStateWaiter(waiter, this.state)) {
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
-
cleanup();
|
|
1132
|
-
reject(new DaytonaTimeoutError(timeoutMessage));
|
|
1133
|
-
}
|
|
1134
|
-
})();
|
|
1135
|
-
}, timeout * 1000);
|
|
1136
|
-
}
|
|
1137
|
-
// Poll as a safety net for missed state changes. With an active event subscription
|
|
1138
|
-
// a sparse 1s cadence suffices; without events (polling mode) replicate main's
|
|
1139
|
-
// cadence exactly: 100ms steady for the first 5s, then exponential backoff capped at 1s.
|
|
1140
|
-
const streaming = !!this.subId;
|
|
1141
|
-
const pollStart = Date.now();
|
|
1142
|
-
let pollInterval = streaming ? 1000 : 100;
|
|
1143
|
-
const doPoll = async () => {
|
|
1144
|
-
if (settled)
|
|
1145
|
-
return;
|
|
1146
|
-
let refreshed = false;
|
|
1147
|
-
try {
|
|
1148
|
-
if (safeRefresh) {
|
|
1149
|
-
refreshed = await this.refreshDataSafe();
|
|
1150
|
-
}
|
|
1151
|
-
else {
|
|
1152
|
-
await this.refreshData();
|
|
1153
|
-
refreshed = true;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
catch (error) {
|
|
1157
|
-
waiter.reject(error instanceof Error ? error : new DaytonaError(String(error)));
|
|
1158
|
-
return;
|
|
1159
|
-
}
|
|
1160
|
-
if (!settled) {
|
|
1161
|
-
// Evaluate the refreshed state explicitly: applyState() no-ops when the
|
|
1162
|
-
// state is unchanged, so a persistent error state would otherwise never
|
|
1163
|
-
// reject the waiter. Only evaluate when the refresh succeeded — a failed
|
|
1164
|
-
// safe refresh leaves the cached state stale, so keep polling instead.
|
|
1165
|
-
if (refreshed && this.checkStateWaiter(waiter, this.state)) {
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
if (!streaming && Date.now() - pollStart > 5000) {
|
|
1169
|
-
pollInterval = Math.min(pollInterval * 1.1, 1000);
|
|
1170
|
-
}
|
|
1171
|
-
pollTimer = setTimeout(() => {
|
|
1172
|
-
void doPoll();
|
|
1173
|
-
}, pollInterval);
|
|
1174
|
-
}
|
|
1175
|
-
};
|
|
1176
|
-
// First poll runs immediately (main refreshed before any state evaluation).
|
|
1177
|
-
void doPoll();
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
1008
|
/**
|
|
1181
1009
|
* Assigns the API sandbox data to the Sandbox object.
|
|
1182
1010
|
*
|
|
@@ -1184,7 +1012,6 @@ let Sandbox = (() => {
|
|
|
1184
1012
|
* @returns {void}
|
|
1185
1013
|
*/
|
|
1186
1014
|
processSandboxDto(sandboxDto) {
|
|
1187
|
-
const newState = sandboxDto.state;
|
|
1188
1015
|
this.id = sandboxDto.id;
|
|
1189
1016
|
this.name = sandboxDto.name;
|
|
1190
1017
|
this.organizationId = sandboxDto.organizationId;
|
|
@@ -1197,17 +1024,17 @@ let Sandbox = (() => {
|
|
|
1197
1024
|
this.gpu = sandboxDto.gpu;
|
|
1198
1025
|
this.memory = sandboxDto.memory;
|
|
1199
1026
|
this.disk = sandboxDto.disk;
|
|
1027
|
+
this.state = sandboxDto.state;
|
|
1200
1028
|
this.errorReason = sandboxDto.errorReason;
|
|
1201
1029
|
this.recoverable = sandboxDto.recoverable;
|
|
1202
1030
|
this.backupState = sandboxDto.backupState;
|
|
1203
1031
|
this.autoStopInterval = sandboxDto.autoStopInterval;
|
|
1204
|
-
this.autoPauseInterval = sandboxDto.autoPauseInterval;
|
|
1205
1032
|
this.autoArchiveInterval = sandboxDto.autoArchiveInterval;
|
|
1206
1033
|
this.autoDeleteInterval = sandboxDto.autoDeleteInterval;
|
|
1207
|
-
this.autoDestroyAt = sandboxDto.autoDestroyAt;
|
|
1208
1034
|
this.createdAt = sandboxDto.createdAt;
|
|
1209
1035
|
this.updatedAt = sandboxDto.updatedAt;
|
|
1210
1036
|
this.lastActivityAt = sandboxDto.lastActivityAt;
|
|
1037
|
+
this.toolboxProxyUrl = sandboxDto.toolboxProxyUrl;
|
|
1211
1038
|
// Fields only present in the full SandboxDto (not returned by list endpoint)
|
|
1212
1039
|
if ('env' in sandboxDto) {
|
|
1213
1040
|
this.env = sandboxDto.env;
|
|
@@ -1219,21 +1046,6 @@ let Sandbox = (() => {
|
|
|
1219
1046
|
this.buildInfo = sandboxDto.buildInfo;
|
|
1220
1047
|
this.backupCreatedAt = sandboxDto.backupCreatedAt;
|
|
1221
1048
|
}
|
|
1222
|
-
const newProxyUrl = sandboxDto.toolboxProxyUrl;
|
|
1223
|
-
if (newProxyUrl && newProxyUrl !== this.toolboxProxyUrl && this.axiosInstance) {
|
|
1224
|
-
let baseUrl = newProxyUrl;
|
|
1225
|
-
if (!baseUrl.endsWith('/')) {
|
|
1226
|
-
baseUrl += '/';
|
|
1227
|
-
}
|
|
1228
|
-
this.axiosInstance.defaults.baseURL = baseUrl + this.id;
|
|
1229
|
-
this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
|
|
1230
|
-
}
|
|
1231
|
-
if (newProxyUrl) {
|
|
1232
|
-
this.toolboxProxyUrl = newProxyUrl;
|
|
1233
|
-
}
|
|
1234
|
-
if (newState) {
|
|
1235
|
-
this.applyState(newState);
|
|
1236
|
-
}
|
|
1237
1049
|
}
|
|
1238
1050
|
/**
|
|
1239
1051
|
* Refreshes the Sandbox data from the API, but does not throw an error if the sandbox has been deleted.
|
|
@@ -1241,113 +1053,17 @@ let Sandbox = (() => {
|
|
|
1241
1053
|
*
|
|
1242
1054
|
* @returns {Promise<void>}
|
|
1243
1055
|
*/
|
|
1244
|
-
/**
|
|
1245
|
-
* @returns true when the refresh produced authoritative state (success, or 404
|
|
1246
|
-
* mapped to DESTROYED); false when a transient error was swallowed and
|
|
1247
|
-
* the cached state is stale.
|
|
1248
|
-
*/
|
|
1249
1056
|
async refreshDataSafe() {
|
|
1250
1057
|
try {
|
|
1251
1058
|
await this.refreshData();
|
|
1252
|
-
return true;
|
|
1253
1059
|
}
|
|
1254
1060
|
catch (error) {
|
|
1255
1061
|
if (error instanceof DaytonaNotFoundError) {
|
|
1256
|
-
this.
|
|
1257
|
-
return true;
|
|
1062
|
+
this.state = SandboxState.DESTROYED;
|
|
1258
1063
|
}
|
|
1259
|
-
// Other errors are deliberately swallowed (parity with main): transient
|
|
1260
|
-
// failures (e.g. 502) mid-poll must not abort stop()/delete() waits.
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
applyState(newState) {
|
|
1265
|
-
if (newState === this.state) {
|
|
1266
|
-
return;
|
|
1267
|
-
}
|
|
1268
|
-
this.state = newState;
|
|
1269
|
-
for (const waiter of [...this.stateWaiters]) {
|
|
1270
|
-
this.checkStateWaiter(waiter, this.state);
|
|
1271
1064
|
}
|
|
1272
1065
|
}
|
|
1273
|
-
checkStateWaiter(waiter, state) {
|
|
1274
|
-
if (!state) {
|
|
1275
|
-
return false;
|
|
1276
|
-
}
|
|
1277
|
-
if (waiter.targetStates.has(state)) {
|
|
1278
|
-
waiter.resolve(state);
|
|
1279
|
-
return true;
|
|
1280
|
-
}
|
|
1281
|
-
if (waiter.errorStates.has(state)) {
|
|
1282
|
-
const errorMessageFn = this.stateWaiterErrorMessageFns.get(waiter);
|
|
1283
|
-
waiter.reject(new DaytonaError(errorMessageFn ? errorMessageFn(state) : `Sandbox ${this.id} failed with state: ${state}`));
|
|
1284
|
-
return true;
|
|
1285
|
-
}
|
|
1286
|
-
return false;
|
|
1287
|
-
}
|
|
1288
|
-
removeStateWaiter(waiter) {
|
|
1289
|
-
const index = this.stateWaiters.indexOf(waiter);
|
|
1290
|
-
if (index !== -1) {
|
|
1291
|
-
this.stateWaiters.splice(index, 1);
|
|
1292
|
-
}
|
|
1293
|
-
this.stateWaiterErrorMessageFns.delete(waiter);
|
|
1294
|
-
}
|
|
1295
1066
|
};
|
|
1296
1067
|
})();
|
|
1297
1068
|
export { Sandbox };
|
|
1298
|
-
const SANDBOX_METRIC_FIELD_BY_NAME = {
|
|
1299
|
-
'daytona.sandbox.cpu.utilization': 'cpuUsedPct',
|
|
1300
|
-
'daytona.sandbox.cpu.limit': 'cpuCount',
|
|
1301
|
-
'daytona.sandbox.memory.usage': 'memUsed',
|
|
1302
|
-
'daytona.sandbox.memory.limit': 'memTotal',
|
|
1303
|
-
'daytona.sandbox.memory.cache': 'memCache',
|
|
1304
|
-
'daytona.sandbox.filesystem.usage': 'diskUsed',
|
|
1305
|
-
'daytona.sandbox.filesystem.total': 'diskTotal',
|
|
1306
|
-
};
|
|
1307
|
-
const SANDBOX_METRIC_NAMES = Object.keys(SANDBOX_METRIC_FIELD_BY_NAME);
|
|
1308
|
-
function sandboxMetricsFromSystemMetrics(m) {
|
|
1309
|
-
return {
|
|
1310
|
-
cpuCount: m.cpuCount ?? 0,
|
|
1311
|
-
cpuUsedPct: m.cpuUsedPct ?? 0,
|
|
1312
|
-
diskTotal: m.diskTotal ?? 0,
|
|
1313
|
-
diskUsed: m.diskUsed ?? 0,
|
|
1314
|
-
memTotal: m.memTotal ?? 0,
|
|
1315
|
-
memUsed: m.memUsed ?? 0,
|
|
1316
|
-
memCache: m.memCache ?? 0,
|
|
1317
|
-
timestamp: m.timestamp ? new Date(m.timestamp) : new Date(),
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
function buildSandboxMetrics(buckets) {
|
|
1321
|
-
return [...buckets.keys()].sort().map((ts) => {
|
|
1322
|
-
const v = buckets.get(ts);
|
|
1323
|
-
return {
|
|
1324
|
-
cpuCount: v.cpuCount ?? 0,
|
|
1325
|
-
cpuUsedPct: v.cpuUsedPct ?? 0,
|
|
1326
|
-
diskTotal: v.diskTotal ?? 0,
|
|
1327
|
-
diskUsed: v.diskUsed ?? 0,
|
|
1328
|
-
memTotal: v.memTotal ?? 0,
|
|
1329
|
-
memUsed: v.memUsed ?? 0,
|
|
1330
|
-
memCache: v.memCache ?? 0,
|
|
1331
|
-
timestamp: new Date(ts),
|
|
1332
|
-
};
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
function pivotMetricTriples(triples) {
|
|
1336
|
-
const buckets = new Map();
|
|
1337
|
-
for (const [name, timestamp, value] of triples) {
|
|
1338
|
-
const field = name ? SANDBOX_METRIC_FIELD_BY_NAME[name] : undefined;
|
|
1339
|
-
if (!field || timestamp === undefined || value === undefined)
|
|
1340
|
-
continue;
|
|
1341
|
-
const bucket = buckets.get(timestamp) ?? {};
|
|
1342
|
-
bucket[field] = value;
|
|
1343
|
-
buckets.set(timestamp, bucket);
|
|
1344
|
-
}
|
|
1345
|
-
return buildSandboxMetrics(buckets);
|
|
1346
|
-
}
|
|
1347
|
-
function pivotSandboxMetrics(series) {
|
|
1348
|
-
return pivotMetricTriples((series ?? []).flatMap((s) => (s.dataPoints ?? []).map((p) => [s.metricName, p.timestamp, p.value])));
|
|
1349
|
-
}
|
|
1350
|
-
function pivotSandboxMetricPoints(points) {
|
|
1351
|
-
return pivotMetricTriples((points ?? []).map((p) => [p.metricName, p.timestamp, p.value]));
|
|
1352
|
-
}
|
|
1353
1069
|
//# sourceMappingURL=Sandbox.js.map
|