@sandbox-kit/sandbox 0.1.0 → 0.1.2

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.
@@ -1,390 +1,15 @@
1
- // Client and sandbox runtime behavior is handwritten. Portable client data
2
- // contracts come from standard protobuf generation.
3
- import { create, } from "@bufbuild/protobuf";
4
- import { pathToString } from "@bufbuild/protobuf/reflect";
5
- import { durationFromMs } from "@bufbuild/protobuf/wkt";
6
- import { createValidator } from "@bufbuild/protovalidate";
7
- import { ConfigSchema, } from "@sandbox-kit/contracts/client";
8
- import { CreateOptionsSchema, ErrorKind as ProtoErrorKind, } from "@sandbox-kit/contracts/sandbox";
9
- import { SandboxState, } from "#contracts/sandbox_data_contracts.js";
10
- import { materializeDurationFields, } from "#internal/duration_ms.js";
11
- import { metadataObjectFromMap } from "#metadata/metadata_object.js";
12
- import { protoEnum } from "#internal/proto_enum.js";
1
+ // Client runtime behavior is handwritten. Portable request and response data
2
+ // remain generated from protobuf. This module owns construction and create /
3
+ // attach / get / close; validation, errors, and the Sandbox handle live beside it.
13
4
  import { InfoFieldId } from "#contracts/field_paths.js";
14
- import { ConfigFieldProvider, ConfigFieldTimeout, } from "#contracts/config_paths.js";
15
- const contractValidator = createValidator({ failFast: true });
16
- const stableRuleIDs = {
17
- "enum.defined_only": "known_enum",
18
- "duration.gte": "minimum",
19
- "oneof.required": "exactly_one",
20
- auth_credentials_required: "exactly_one",
21
- cpu_limit: "limit",
22
- memory_limit: "limit",
23
- cpu_requires_positive: "requires_positive",
24
- memory_requires_positive: "requires_positive",
25
- restricted_requires_allowlist: "requires_any",
26
- block_all_forbids_network: "forbids",
27
- allow_all_forbids_allowlists: "forbids",
28
- after_requires_duration: "requires_any",
29
- after_forbids_duration: "forbids",
30
- };
31
- // Ergonomic PascalCase names over protobuf enum values; not a second contract.
32
- export const ErrorKind = protoEnum(ProtoErrorKind);
33
- export class SandboxError extends Error {
34
- kind;
35
- provider;
36
- operation;
37
- field;
38
- rule;
39
- statusCode;
40
- providerCode;
41
- providerSource;
42
- constructor(info, options) {
43
- super(info.message, options);
44
- this.name = "SandboxError";
45
- this.kind = info.kind;
46
- this.provider = info.provider;
47
- this.operation = info.operation;
48
- this.field = info.field;
49
- this.rule = info.rule;
50
- this.statusCode = info.statusCode;
51
- this.providerCode = info.providerCode;
52
- this.providerSource = info.providerSource;
53
- }
54
- }
55
- export function sandboxError(info, cause) {
56
- return new SandboxError(info, cause === undefined ? undefined : { cause });
57
- }
58
- function validationError(provider, operation, field, rule, message) {
59
- throw sandboxError({
60
- kind: ErrorKind.InvalidArgument,
61
- provider,
62
- operation,
63
- field,
64
- rule,
65
- message: "sandbox-kit: " + message,
66
- });
67
- }
68
- function validSecureEndpoint(value) {
69
- try {
70
- const url = new URL(value);
71
- if (!url.hostname || url.username || url.password || url.hash)
72
- return false;
73
- if (url.protocol === "https:")
74
- return true;
75
- if (url.protocol !== "http:")
76
- return false;
77
- const host = url.hostname.toLowerCase();
78
- return (host === "localhost" ||
79
- host === "[::1]" ||
80
- host === "::1" ||
81
- /^127(?:\.[0-9]{1,3}){3}$/.test(host));
82
- }
83
- catch {
84
- return false;
85
- }
86
- }
87
- /**
88
- * Rejects public {@link CreateOptions} the shared contract cannot represent.
89
- * Materializes wire form privately for Protovalidate. Provider-specific checks
90
- * remain in each provider adapter.
91
- */
92
- export function validateCreateOptions(value, provider = "", operation = "create") {
93
- const message = materializeCreateOptions(value);
94
- const result = contractValidator.validate(CreateOptionsSchema, message);
95
- if (result.kind === "valid") {
96
- return;
97
- }
98
- if (result.kind === "error") {
99
- throw sandboxError({
100
- kind: ErrorKind.InvalidArgument,
101
- provider,
102
- operation,
103
- field: "",
104
- message: "sandbox-kit: invalid configuration",
105
- }, result.error);
106
- }
107
- const violation = result.violations[0];
108
- const ruleID = violation.ruleId;
109
- let field = pathToString(violation.field);
110
- let rule = stableRuleIDs[ruleID] ?? ruleID;
111
- if (!field) {
112
- field = messageFieldHint(ruleID);
113
- }
114
- validationError(provider, operation, field, rule, violation.message || "invalid configuration");
115
- }
116
- function messageFieldHint(ruleID) {
117
- switch (ruleID) {
118
- case "cpu_limit":
119
- case "cpu_requires_positive":
120
- return "resources.cpu_limit_cores";
121
- case "memory_limit":
122
- case "memory_requires_positive":
123
- return "resources.memory_limit_mib";
124
- case "restricted_requires_allowlist":
125
- return "network.outbound_cidrs,outbound_domains";
126
- case "block_all_forbids_network":
127
- return "network.outbound_cidrs,outbound_domains,private_network,linked_sandbox,inbound_cidrs,ports";
128
- case "allow_all_forbids_allowlists":
129
- return "network.outbound_cidrs,outbound_domains";
130
- case "after_requires_duration":
131
- case "after_forbids_duration":
132
- return "after";
133
- case "oneof.required":
134
- return "tcp_port,command";
135
- case "auth_credentials_required":
136
- return "api_key,token_pair,bearer_token,oauth_refresh";
137
- case "exactly_one":
138
- return "client_secret,jwt_key";
139
- default:
140
- return "";
141
- }
142
- }
143
- export function validateConfig(value, provider = "", operation = "create") {
144
- const message = materializeClientConfig(value);
145
- const result = contractValidator.validate(ConfigSchema, message);
146
- if (result.kind === "error") {
147
- throw sandboxError({
148
- kind: ErrorKind.InvalidArgument,
149
- provider,
150
- operation,
151
- field: "",
152
- message: "sandbox-kit: invalid configuration",
153
- }, result.error);
154
- }
155
- if (result.kind === "invalid") {
156
- const violation = result.violations[0];
157
- const ruleID = violation.ruleId;
158
- let field = pathToString(violation.field);
159
- const rule = stableRuleIDs[ruleID] ?? ruleID;
160
- if (!field) {
161
- field = messageFieldHint(ruleID);
162
- }
163
- validationError(provider, operation, field, rule, violation.message || "invalid configuration");
164
- }
165
- if (message.endpoint !== undefined &&
166
- !validSecureEndpoint(message.endpoint)) {
167
- validationError(provider, operation, "endpoint", "secure_endpoint", "must use HTTPS except for loopback HTTP development endpoints");
168
- }
169
- }
170
- function clone(value) {
171
- return structuredClone(value);
172
- }
173
- function operationSignal(options, inheritedTimeoutMs) {
174
- const timeout = options.timeout ?? inheritedTimeoutMs;
175
- if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
176
- throw sandboxError({
177
- kind: ErrorKind.InvalidArgument,
178
- provider: "",
179
- operation: "",
180
- field: ConfigFieldTimeout,
181
- message: "sandbox-kit: timeout must be a finite non-negative number",
182
- });
183
- }
184
- if ((timeout === undefined || timeout === 0) && options.signal === undefined)
185
- return { dispose() { } };
186
- const controller = new AbortController();
187
- const abort = () => controller.abort(options.signal?.reason);
188
- if (options.signal?.aborted)
189
- abort();
190
- else
191
- options.signal?.addEventListener("abort", abort, { once: true });
192
- const timer = timeout !== undefined && timeout > 0
193
- ? setTimeout(() => controller.abort(new DOMException("The operation timed out", "TimeoutError")), timeout)
194
- : undefined;
195
- return {
196
- signal: controller.signal,
197
- dispose() {
198
- if (timer !== undefined)
199
- clearTimeout(timer);
200
- options.signal?.removeEventListener("abort", abort);
201
- },
202
- };
203
- }
204
- function operationOptions(options, signal) {
205
- return {
206
- ...(options.timeout === undefined ? {} : { timeout: options.timeout }),
207
- ...(signal === undefined ? {} : { signal }),
208
- };
209
- }
210
- function abortReason(signal) {
211
- const reason = signal.reason;
212
- return reason instanceof DOMException &&
213
- (reason.name === "AbortError" || reason.name === "TimeoutError")
214
- ? reason
215
- : new DOMException("The operation was aborted", "AbortError");
216
- }
217
- async function awaitWithSignal(operation, signal) {
218
- if (signal?.aborted)
219
- throw abortReason(signal);
220
- const pending = operation();
221
- if (signal === undefined)
222
- return pending;
223
- return new Promise((resolve, reject) => {
224
- const aborted = () => reject(abortReason(signal));
225
- const cleanup = () => signal.removeEventListener("abort", aborted);
226
- signal.addEventListener("abort", aborted, { once: true });
227
- pending.then((value) => {
228
- cleanup();
229
- resolve(value);
230
- }, (error) => {
231
- cleanup();
232
- reject(error);
233
- });
234
- });
235
- }
236
- const sandboxConstructorToken = Symbol("sandbox-kit.Sandbox");
237
- export class Sandbox {
238
- #backend;
239
- #owner;
240
- #timeout;
241
- #info;
242
- #closed = false;
243
- #tail = Promise.resolve();
244
- constructor(token, backend, owner, info, timeout) {
245
- if (token !== sandboxConstructorToken)
246
- throw new TypeError("Sandbox handles are created by Client");
247
- this.#backend = backend;
248
- this.#owner = owner;
249
- this.#info = clone(info);
250
- this.#timeout = timeout;
251
- }
252
- get id() {
253
- return this.#info.id;
254
- }
255
- get providerName() {
256
- return this.#info.provider;
257
- }
258
- info() {
259
- return clone(this.#info);
260
- }
261
- capabilities() {
262
- if (this.#closed || this.#owner.isClosed() || this.#info.terminal)
263
- return [];
264
- return this.#backend.capabilities?.(clone(this.#info)) ?? [];
265
- }
266
- supports(capability) {
267
- return this.capabilities().includes(capability);
268
- }
269
- async refresh(options = {}) {
270
- return this.#serialized("refresh", options, this.#timeout, async (signal) => {
271
- const method = this.#backend.refresh;
272
- if (!method)
273
- unsupported(this.providerName, "refresh");
274
- const info = await awaitWithSignal(() => method.call(this.#backend, this.id, operationOptions(options, signal)), signal);
275
- this.#info = clone({ ...info, provider: this.providerName });
276
- return this.info();
277
- });
278
- }
279
- async start(options = {}) {
280
- return this.#update("start", this.#backend.start, wireTimeoutOptions(options), options);
281
- }
282
- async stop(options = {}) {
283
- const { force, ...call } = options;
284
- return this.#update("stop", this.#backend.stop, {
285
- ...wireTimeoutOptions(call),
286
- ...(force === undefined ? {} : { force }),
287
- }, call);
288
- }
289
- async pause(options = {}) {
290
- return this.#update("pause", this.#backend.pause, wireTimeoutOptions(options), options);
291
- }
292
- async terminate(options = {}) {
293
- const { wait, ...call } = options;
294
- const wire = {
295
- ...wireTimeoutOptions(call),
296
- ...(wait === undefined ? {} : { wait }),
297
- };
298
- return this.#serialized("terminate", call, call.timeout ?? this.#timeout, async (signal) => {
299
- const method = this.#backend.terminate;
300
- if (!method)
301
- unsupported(this.providerName, "terminate");
302
- const result = await awaitWithSignal(() => method.call(this.#backend, this.id, clone(wire), operationOptions(call, signal)), signal);
303
- this.#info = {
304
- ...this.#info,
305
- state: SandboxState.Terminated,
306
- terminal: true,
307
- };
308
- return result;
309
- });
310
- }
311
- async archive(options = {}) {
312
- return this.#update("archive", this.#backend.archive, wireTimeoutOptions(options), options);
313
- }
314
- async delete(options = {}) {
315
- const { wait, ...call } = options;
316
- return this.#update("delete", this.#backend.delete, { ...wireTimeoutOptions(call), ...(wait === undefined ? {} : { wait }) }, call);
317
- }
318
- async close(options = {}) {
319
- if (this.#closed)
320
- return;
321
- await this.#serialized("close", options, this.#timeout, async (signal) => {
322
- const close = this.#backend.closeSandbox;
323
- if (close)
324
- await awaitWithSignal(() => close.call(this.#backend, this.id, operationOptions(options, signal)), signal);
325
- this.#closed = true;
326
- });
327
- }
328
- async [Symbol.asyncDispose]() {
329
- await this.close();
330
- }
331
- async #update(operation, method, wire, options) {
332
- return this.#serialized(operation, options, options.timeout ?? this.#timeout, async (signal) => {
333
- if (!method)
334
- unsupported(this.providerName, operation);
335
- const info = await awaitWithSignal(() => method.call(this.#backend, this.id, clone(wire), operationOptions(options, signal)), signal);
336
- this.#info = clone({ ...info, provider: this.providerName });
337
- return this.info();
338
- });
339
- }
340
- async #serialized(operation, options, timeout, body) {
341
- this.#ensureOpen(operation);
342
- const releaseClient = this.#owner.begin(operation);
343
- const previous = this.#tail;
344
- let unlock;
345
- this.#tail = new Promise((resolve) => {
346
- unlock = resolve;
347
- });
348
- let acquired = false;
349
- let scoped;
350
- try {
351
- scoped = operationSignal(options, timeout);
352
- await awaitWithSignal(() => previous, scoped.signal);
353
- acquired = true;
354
- this.#ensureOpen(operation);
355
- return await body(scoped.signal);
356
- }
357
- catch (error) {
358
- throw withContext(error, this.providerName, operation);
359
- }
360
- finally {
361
- scoped?.dispose();
362
- if (acquired)
363
- unlock();
364
- else
365
- void previous.then(unlock, unlock);
366
- releaseClient();
367
- }
368
- }
369
- #ensureOpen(operation) {
370
- if (this.#closed || this.#owner.isClosed())
371
- throw sandboxError({
372
- kind: ErrorKind.FailedPrecondition,
373
- provider: this.providerName,
374
- operation,
375
- field: "sandbox",
376
- message: "sandbox-kit: sandbox handle is closed",
377
- });
378
- if (operation !== "close" && this.#info.terminal)
379
- throw sandboxError({
380
- kind: ErrorKind.FailedPrecondition,
381
- provider: this.providerName,
382
- operation,
383
- field: "sandbox",
384
- message: "sandbox-kit: sandbox lifecycle is terminal",
385
- });
386
- }
387
- }
5
+ import { ConfigFieldProvider } from "#contracts/config_paths.js";
6
+ import { ErrorKind, sandboxError, unsupported, withContext, } from "#client/errors.js";
7
+ import { awaitWithSignal, operationOptions, operationSignal, } from "#client/operation.js";
8
+ import { Sandbox, sandboxConstructorToken, } from "#client/sandbox.js";
9
+ import { validateConfig, validateCreateOptions } from "#client/validation.js";
10
+ export { ErrorKind, SandboxError, sandboxError } from "#client/errors.js";
11
+ export { Sandbox } from "#client/sandbox.js";
12
+ export { validateConfig, validateCreateOptions } from "#client/validation.js";
388
13
  export class Client {
389
14
  #backend;
390
15
  #providerName;
@@ -442,33 +67,21 @@ export class Client {
442
67
  return this.#providerName;
443
68
  }
444
69
  async attach(id, options = {}) {
445
- const release = this.#begin("attach");
446
- let scoped;
447
- try {
448
- scoped = operationSignal(options, this.#timeout);
449
- if (!id.trim())
450
- throw sandboxError({
451
- kind: ErrorKind.InvalidArgument,
452
- provider: this.providerName,
453
- operation: "attach",
454
- field: InfoFieldId,
455
- message: "sandbox-kit: sandbox ID must not be blank",
456
- });
70
+ return this.#openById("attach", id, options, (fetchOptions) => {
457
71
  if (!this.#backend.attach)
458
72
  unsupported(this.providerName, "attach");
459
- const info = await awaitWithSignal(() => this.#backend.attach(id, operationOptions(options, scoped?.signal)), scoped.signal);
460
- return new Sandbox(sandboxConstructorToken, this.#backend, this.#lifecycle, { ...info, id, provider: this.providerName }, this.#timeout);
461
- }
462
- catch (error) {
463
- throw withContext(error, this.providerName, "attach");
464
- }
465
- finally {
466
- scoped?.dispose();
467
- release();
468
- }
73
+ return this.#backend.attach(id, fetchOptions);
74
+ });
469
75
  }
470
76
  async get(id, options = {}) {
471
- const release = this.#begin("get");
77
+ return this.#openById("get", id, options, (fetchOptions) => {
78
+ if (!this.#backend.refresh)
79
+ unsupported(this.providerName, "get");
80
+ return this.#backend.refresh(id, fetchOptions);
81
+ });
82
+ }
83
+ async #openById(operation, id, options, fetch) {
84
+ const release = this.#begin(operation);
472
85
  let scoped;
473
86
  try {
474
87
  scoped = operationSignal(options, this.#timeout);
@@ -476,17 +89,20 @@ export class Client {
476
89
  throw sandboxError({
477
90
  kind: ErrorKind.InvalidArgument,
478
91
  provider: this.providerName,
479
- operation: "get",
92
+ operation,
480
93
  field: InfoFieldId,
481
94
  message: "sandbox-kit: sandbox ID must not be blank",
482
95
  });
483
- if (!this.#backend.refresh)
484
- unsupported(this.providerName, "get");
485
- const info = await awaitWithSignal(() => this.#backend.refresh(id, operationOptions(options, scoped?.signal)), scoped.signal);
486
- return new Sandbox(sandboxConstructorToken, this.#backend, this.#lifecycle, { ...info, id, provider: this.providerName }, this.#timeout);
96
+ const info = await awaitWithSignal(() => fetch(operationOptions(options, scoped?.signal)), scoped.signal);
97
+ return new Sandbox(sandboxConstructorToken, {
98
+ backend: this.#backend,
99
+ owner: this.#lifecycle,
100
+ info: { ...info, id, provider: this.providerName },
101
+ ...(this.#timeout === undefined ? {} : { timeout: this.#timeout }),
102
+ });
487
103
  }
488
104
  catch (error) {
489
- throw withContext(error, this.providerName, "get");
105
+ throw withContext(error, this.providerName, operation);
490
106
  }
491
107
  finally {
492
108
  scoped?.dispose();
@@ -533,7 +149,12 @@ export class Client {
533
149
  field: ConfigFieldProvider,
534
150
  message: "sandbox-kit: response provider differs from selected provider",
535
151
  });
536
- return new Sandbox(sandboxConstructorToken, this.#backend, this.#lifecycle, { ...result.sandbox, provider: this.providerName }, this.#timeout);
152
+ return new Sandbox(sandboxConstructorToken, {
153
+ backend: this.#backend,
154
+ owner: this.#lifecycle,
155
+ info: { ...result.sandbox, provider: this.providerName },
156
+ ...(this.#timeout === undefined ? {} : { timeout: this.#timeout }),
157
+ });
537
158
  }
538
159
  catch (error) {
539
160
  throw withContext(error, this.providerName, "create");
@@ -599,151 +220,3 @@ export class Client {
599
220
  };
600
221
  }
601
222
  }
602
- /** Materializes a plain client config into the generated protobuf message. */
603
- function materializeClientConfig(input) {
604
- const { auth, timeout, ...rest } = input;
605
- return create(ConfigSchema, {
606
- ...rest,
607
- ...(timeout === undefined ? {} : { timeout: durationFromMs(timeout) }),
608
- ...(auth === undefined
609
- ? {}
610
- : { auth: { credentials: authCredentials(auth) } }),
611
- });
612
- }
613
- function wireTimeoutOptions(options) {
614
- return options.timeout === undefined
615
- ? {}
616
- : { timeout: durationFromMs(options.timeout) };
617
- }
618
- /** Materializes plain create options into the generated protobuf message. */
619
- function materializeCreateOptions(input) {
620
- const { source, readiness, providerOptions, network, ...rest } = input;
621
- const withDurations = materializeDurationFields(rest);
622
- return create(CreateOptionsSchema, {
623
- ...withDurations,
624
- ...(network === undefined
625
- ? {}
626
- : { network: materializeNetworkConfig(network) }),
627
- ...(source === undefined ? {} : { source: { kind: sourceKind(source) } }),
628
- ...(readiness === undefined
629
- ? {}
630
- : { readiness: { check: readinessCheck(readiness) } }),
631
- ...(providerOptions === undefined
632
- ? {}
633
- : { providerOptions: metadataObjectFromMap(providerOptions) }),
634
- });
635
- }
636
- /** Wrap public string-array allowlists into wire StringAllowlist messages. */
637
- function materializeNetworkConfig(network) {
638
- const { outboundCidrs, outboundDomains, inboundCidrs, ...rest } = network;
639
- return {
640
- ...rest,
641
- ...(outboundCidrs === undefined
642
- ? {}
643
- : { outboundCidrs: { entries: outboundCidrs } }),
644
- ...(outboundDomains === undefined
645
- ? {}
646
- : { outboundDomains: { entries: outboundDomains } }),
647
- ...(inboundCidrs === undefined
648
- ? {}
649
- : { inboundCidrs: { entries: inboundCidrs } }),
650
- };
651
- }
652
- function authCredentials(auth) {
653
- if (auth.apiKey !== undefined)
654
- return { case: "apiKey", value: auth.apiKey };
655
- if (auth.tokenPair !== undefined)
656
- return {
657
- case: "tokenPair",
658
- value: auth.tokenPair,
659
- };
660
- if (auth.bearerToken !== undefined)
661
- return {
662
- case: "bearerToken",
663
- value: auth.bearerToken,
664
- };
665
- if (auth.oauthRefresh !== undefined)
666
- return {
667
- case: "oauthRefresh",
668
- value: auth.oauthRefresh,
669
- };
670
- return { case: undefined };
671
- }
672
- function sourceKind(source) {
673
- if (source.providerDefault !== undefined)
674
- return {
675
- case: "providerDefault",
676
- value: {},
677
- };
678
- if (source.image !== undefined)
679
- return { case: "image", value: source.image };
680
- if (source.snapshot !== undefined)
681
- return { case: "snapshot", value: source.snapshot };
682
- if (source.warmPool !== undefined)
683
- return { case: "warmPool", value: source.warmPool };
684
- return { case: undefined };
685
- }
686
- function readinessCheck(readiness) {
687
- if (readiness.tcpPort !== undefined)
688
- return { case: "tcpPort", value: readiness.tcpPort };
689
- if (readiness.command !== undefined)
690
- return { case: "command", value: readiness.command };
691
- return { case: undefined };
692
- }
693
- function unsupported(provider, operation) {
694
- throw sandboxError({
695
- kind: ErrorKind.Unsupported,
696
- provider,
697
- operation,
698
- field: "",
699
- message: "sandbox-kit: provider does not support " + operation,
700
- });
701
- }
702
- function withContext(error, provider, operation) {
703
- if (error instanceof SandboxError) {
704
- if (error.provider === provider && error.operation === operation)
705
- return error;
706
- return sandboxError({
707
- kind: error.kind,
708
- provider: error.provider || provider,
709
- operation: error.operation || operation,
710
- field: error.field,
711
- message: error.message,
712
- ...(error.rule === undefined ? {} : { rule: error.rule }),
713
- ...(error.statusCode === undefined
714
- ? {}
715
- : { statusCode: error.statusCode }),
716
- ...(error.providerCode === undefined
717
- ? {}
718
- : { providerCode: error.providerCode }),
719
- ...(error.providerSource === undefined
720
- ? {}
721
- : { providerSource: error.providerSource }),
722
- }, error.cause);
723
- }
724
- if (error instanceof DOMException && error.name === "AbortError")
725
- return sandboxError({
726
- kind: ErrorKind.Canceled,
727
- provider,
728
- operation,
729
- field: "",
730
- message: error.message,
731
- }, error);
732
- if (error instanceof DOMException && error.name === "TimeoutError")
733
- return sandboxError({
734
- kind: ErrorKind.Timeout,
735
- provider,
736
- operation,
737
- field: "",
738
- message: error.message,
739
- }, error);
740
- return sandboxError({
741
- kind: ErrorKind.Unknown,
742
- provider,
743
- operation,
744
- field: "",
745
- message: error instanceof Error
746
- ? error.message
747
- : "sandbox-kit: provider operation failed",
748
- }, error);
749
- }
@@ -0,0 +1,35 @@
1
+ import { ErrorKind as ProtoErrorKind } from "@sandbox-kit/contracts/sandbox";
2
+ import type { ErrorInfo } from "#contracts/sandbox_data_contracts.js";
3
+ export declare const ErrorKind: {
4
+ readonly Unknown: ProtoErrorKind.UNKNOWN;
5
+ readonly InvalidArgument: ProtoErrorKind.INVALID_ARGUMENT;
6
+ readonly Unsupported: ProtoErrorKind.UNSUPPORTED;
7
+ readonly Authentication: ProtoErrorKind.AUTHENTICATION;
8
+ readonly PermissionDenied: ProtoErrorKind.PERMISSION_DENIED;
9
+ readonly NotFound: ProtoErrorKind.NOT_FOUND;
10
+ readonly AlreadyExists: ProtoErrorKind.ALREADY_EXISTS;
11
+ readonly Conflict: ProtoErrorKind.CONFLICT;
12
+ readonly RateLimited: ProtoErrorKind.RATE_LIMITED;
13
+ readonly ResourceExhausted: ProtoErrorKind.RESOURCE_EXHAUSTED;
14
+ readonly Timeout: ProtoErrorKind.TIMEOUT;
15
+ readonly Canceled: ProtoErrorKind.CANCELED;
16
+ readonly Unavailable: ProtoErrorKind.UNAVAILABLE;
17
+ readonly InvalidResponse: ProtoErrorKind.INVALID_RESPONSE;
18
+ readonly Internal: ProtoErrorKind.INTERNAL;
19
+ readonly FailedPrecondition: ProtoErrorKind.FAILED_PRECONDITION;
20
+ };
21
+ export type ErrorKind = (typeof ErrorKind)[keyof typeof ErrorKind];
22
+ export declare class SandboxError extends Error {
23
+ readonly kind: ErrorInfo["kind"];
24
+ readonly provider: string;
25
+ readonly operation: string;
26
+ readonly field: string;
27
+ readonly rule: string | undefined;
28
+ readonly statusCode: number | undefined;
29
+ readonly providerCode: string | undefined;
30
+ readonly providerSource: string | undefined;
31
+ constructor(info: ErrorInfo, options?: ErrorOptions);
32
+ }
33
+ export declare function sandboxError(info: ErrorInfo, cause?: unknown): SandboxError;
34
+ export declare function unsupported(provider: string, operation: string): never;
35
+ export declare function withContext(error: unknown, provider: string, operation: string): SandboxError;