@sylphx/sdk 0.3.7 → 0.5.0
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/dist/index.d.cts +938 -140
- package/dist/index.d.ts +938 -140
- package/dist/index.js +810 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +791 -269
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.js +44 -20
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/index.mjs +44 -20
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.d.cts +389 -32
- package/dist/react/index.d.ts +389 -32
- package/dist/react/index.js +1610 -1285
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1284 -963
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.cts +355 -18
- package/dist/server/index.d.ts +355 -18
- package/dist/server/index.js +559 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +555 -22
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.js.map +1 -1
- package/dist/web-analytics.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/web-analytics.d.cts +0 -90
- package/dist/web-analytics.d.ts +0 -90
package/dist/index.js
CHANGED
|
@@ -35,20 +35,28 @@ __export(index_exports, {
|
|
|
35
35
|
AuthorizationError: () => AuthorizationError,
|
|
36
36
|
CircuitBreakerOpenError: () => CircuitBreakerOpenError,
|
|
37
37
|
ERROR_CODE_STATUS: () => ERROR_CODE_STATUS,
|
|
38
|
+
InvalidConnectionUrlError: () => InvalidConnectionUrlError,
|
|
38
39
|
NetworkError: () => NetworkError,
|
|
39
40
|
NotFoundError: () => NotFoundError,
|
|
40
41
|
RETRYABLE_CODES: () => RETRYABLE_CODES,
|
|
41
42
|
RateLimitError: () => RateLimitError,
|
|
43
|
+
RunHandle: () => RunHandle,
|
|
44
|
+
RunsClient: () => RunsClient,
|
|
42
45
|
SandboxClient: () => SandboxClient,
|
|
46
|
+
SandboxFiles: () => SandboxFiles,
|
|
47
|
+
SandboxProcesses: () => SandboxProcesses,
|
|
48
|
+
SandboxWatch: () => SandboxWatch,
|
|
43
49
|
StepCompleteSignal: () => StepCompleteSignal,
|
|
44
50
|
StepSleepSignal: () => StepSleepSignal,
|
|
45
51
|
SylphxError: () => SylphxError,
|
|
46
52
|
TimeoutError: () => TimeoutError,
|
|
53
|
+
TriggersClient: () => TriggersClient,
|
|
47
54
|
ValidationError: () => ValidationError,
|
|
48
|
-
WorkerHandle: () =>
|
|
55
|
+
WorkerHandle: () => RunHandle,
|
|
49
56
|
WorkersClient: () => WorkersClient,
|
|
50
57
|
acceptAllConsents: () => acceptAllConsents,
|
|
51
58
|
acceptOrganizationInvitation: () => acceptOrganizationInvitation,
|
|
59
|
+
assignMemberRole: () => assignMemberRole,
|
|
52
60
|
batchIndex: () => batchIndex,
|
|
53
61
|
canDeleteOrganization: () => canDeleteOrganization,
|
|
54
62
|
canManageMembers: () => canManageMembers,
|
|
@@ -63,12 +71,16 @@ __export(index_exports, {
|
|
|
63
71
|
checkFlag: () => checkFlag,
|
|
64
72
|
complete: () => complete,
|
|
65
73
|
createCheckout: () => createCheckout,
|
|
74
|
+
createClient: () => createClient,
|
|
66
75
|
createConfig: () => createConfig,
|
|
67
76
|
createCron: () => createCron,
|
|
68
77
|
createDynamicRestClient: () => createDynamicRestClient,
|
|
69
78
|
createOrganization: () => createOrganization,
|
|
79
|
+
createPermission: () => createPermission,
|
|
70
80
|
createPortalSession: () => createPortalSession,
|
|
71
81
|
createRestClient: () => createRestClient,
|
|
82
|
+
createRole: () => createRole,
|
|
83
|
+
createServerClient: () => createServerClient,
|
|
72
84
|
createServiceWorkerScript: () => createServiceWorkerScript,
|
|
73
85
|
createStepContext: () => createStepContext,
|
|
74
86
|
createTasksHandler: () => createTasksHandler,
|
|
@@ -83,6 +95,8 @@ __export(index_exports, {
|
|
|
83
95
|
deleteEnvVar: () => deleteEnvVar,
|
|
84
96
|
deleteFile: () => deleteFile,
|
|
85
97
|
deleteOrganization: () => deleteOrganization,
|
|
98
|
+
deletePermission: () => deletePermission,
|
|
99
|
+
deleteRole: () => deleteRole,
|
|
86
100
|
deleteUser: () => deleteUser,
|
|
87
101
|
disableDebug: () => disableDebug,
|
|
88
102
|
embed: () => embed,
|
|
@@ -116,6 +130,7 @@ __export(index_exports, {
|
|
|
116
130
|
getFlagPayload: () => getFlagPayload,
|
|
117
131
|
getFlags: () => getFlags,
|
|
118
132
|
getLeaderboard: () => getLeaderboard,
|
|
133
|
+
getMemberPermissions: () => getMemberPermissions,
|
|
119
134
|
getMyReferralCode: () => getMyReferralCode,
|
|
120
135
|
getOrganization: () => getOrganization,
|
|
121
136
|
getOrganizationInvitations: () => getOrganizationInvitations,
|
|
@@ -127,6 +142,7 @@ __export(index_exports, {
|
|
|
127
142
|
getReferralLeaderboard: () => getReferralLeaderboard,
|
|
128
143
|
getReferralStats: () => getReferralStats,
|
|
129
144
|
getRestErrorMessage: () => getRestErrorMessage,
|
|
145
|
+
getRole: () => getRole,
|
|
130
146
|
getScheduledEmail: () => getScheduledEmail,
|
|
131
147
|
getScheduledEmailStats: () => getScheduledEmailStats,
|
|
132
148
|
getSearchStats: () => getSearchStats,
|
|
@@ -146,8 +162,11 @@ __export(index_exports, {
|
|
|
146
162
|
getWebhookDeliveries: () => getWebhookDeliveries,
|
|
147
163
|
getWebhookDelivery: () => getWebhookDelivery,
|
|
148
164
|
getWebhookStats: () => getWebhookStats,
|
|
165
|
+
hasAllPermissions: () => hasAllPermissions,
|
|
166
|
+
hasAnyPermission: () => hasAnyPermission,
|
|
149
167
|
hasConsent: () => hasConsent,
|
|
150
168
|
hasError: () => hasError,
|
|
169
|
+
hasPermission: () => hasPermission,
|
|
151
170
|
hasRole: () => hasRole,
|
|
152
171
|
hasSecret: () => hasSecret,
|
|
153
172
|
identify: () => identify,
|
|
@@ -184,12 +203,13 @@ __export(index_exports, {
|
|
|
184
203
|
leaveOrganization: () => leaveOrganization,
|
|
185
204
|
linkAnonymousConsents: () => linkAnonymousConsents,
|
|
186
205
|
listEnvVars: () => listEnvVars,
|
|
206
|
+
listPermissions: () => listPermissions,
|
|
207
|
+
listRoles: () => listRoles,
|
|
187
208
|
listScheduledEmails: () => listScheduledEmails,
|
|
188
209
|
listSecretKeys: () => listSecretKeys,
|
|
189
210
|
listTasks: () => listTasks,
|
|
190
211
|
listUsers: () => listUsers,
|
|
191
212
|
page: () => page,
|
|
192
|
-
parseKey: () => parseKey,
|
|
193
213
|
pauseCron: () => pauseCron,
|
|
194
214
|
realtimeEmit: () => realtimeEmit,
|
|
195
215
|
recordStreakActivity: () => recordStreakActivity,
|
|
@@ -236,6 +256,7 @@ __export(index_exports, {
|
|
|
236
256
|
updateOrganization: () => updateOrganization,
|
|
237
257
|
updateOrganizationMemberRole: () => updateOrganizationMemberRole,
|
|
238
258
|
updatePushPreferences: () => updatePushPreferences,
|
|
259
|
+
updateRole: () => updateRole,
|
|
239
260
|
updateUser: () => updateUser,
|
|
240
261
|
updateUserMetadata: () => updateUserMetadata,
|
|
241
262
|
updateWebhookConfig: () => updateWebhookConfig,
|
|
@@ -249,10 +270,104 @@ __export(index_exports, {
|
|
|
249
270
|
});
|
|
250
271
|
module.exports = __toCommonJS(index_exports);
|
|
251
272
|
|
|
273
|
+
// src/connection-url.ts
|
|
274
|
+
var SYLPHX_PROTOCOL = "sylphx:";
|
|
275
|
+
var DEFAULT_VERSION = "v1";
|
|
276
|
+
var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}$/;
|
|
277
|
+
var VERSION_REGEX = /^v[0-9]+$/;
|
|
278
|
+
var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
279
|
+
var InvalidConnectionUrlError = class _InvalidConnectionUrlError extends Error {
|
|
280
|
+
code = "INVALID_CONNECTION_URL";
|
|
281
|
+
constructor(message) {
|
|
282
|
+
super(message);
|
|
283
|
+
this.name = "InvalidConnectionUrlError";
|
|
284
|
+
Object.setPrototypeOf(this, _InvalidConnectionUrlError.prototype);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
function fail(reason) {
|
|
288
|
+
throw new InvalidConnectionUrlError(`Invalid Sylphx connection URL: ${reason}`);
|
|
289
|
+
}
|
|
290
|
+
function parseCredential(raw) {
|
|
291
|
+
const match = CREDENTIAL_REGEX.exec(raw);
|
|
292
|
+
if (!match) {
|
|
293
|
+
fail(`credential must match (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}, got "${raw}"`);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
credentialType: match[1],
|
|
297
|
+
env: match[2]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function validateSlug(candidate) {
|
|
301
|
+
if (!candidate || candidate.length > 63 || !SLUG_REGEX.test(candidate)) {
|
|
302
|
+
fail(`slug "${candidate}" is not a valid DNS label (lowercase alnum + hyphens, 1-63 chars)`);
|
|
303
|
+
}
|
|
304
|
+
return candidate;
|
|
305
|
+
}
|
|
306
|
+
function parseConnectionUrl(url) {
|
|
307
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
308
|
+
fail("url must be a non-empty string");
|
|
309
|
+
}
|
|
310
|
+
let parsed;
|
|
311
|
+
try {
|
|
312
|
+
parsed = new URL(url);
|
|
313
|
+
} catch {
|
|
314
|
+
fail(`not a valid URL: "${url}"`);
|
|
315
|
+
}
|
|
316
|
+
if (parsed.protocol !== SYLPHX_PROTOCOL) {
|
|
317
|
+
fail(`protocol must be "sylphx:", got "${parsed.protocol}"`);
|
|
318
|
+
}
|
|
319
|
+
const credential = decodeURIComponent(parsed.username);
|
|
320
|
+
if (!credential) {
|
|
321
|
+
fail("missing credential (expected `sylphx://<credential>@<host>`)");
|
|
322
|
+
}
|
|
323
|
+
if (parsed.password) {
|
|
324
|
+
fail("connection URL must not contain a password component");
|
|
325
|
+
}
|
|
326
|
+
const { credentialType, env } = parseCredential(credential);
|
|
327
|
+
const host = parsed.host;
|
|
328
|
+
if (!host) {
|
|
329
|
+
fail("missing host");
|
|
330
|
+
}
|
|
331
|
+
const hostname = parsed.hostname;
|
|
332
|
+
const firstDot = hostname.indexOf(".");
|
|
333
|
+
if (firstDot <= 0) {
|
|
334
|
+
fail(`host "${hostname}" must contain at least one dot (slug.domain)`);
|
|
335
|
+
}
|
|
336
|
+
const slugCandidate = hostname.slice(0, firstDot);
|
|
337
|
+
const domainSuffix = hostname.slice(firstDot + 1);
|
|
338
|
+
if (!domainSuffix) {
|
|
339
|
+
fail(`host "${hostname}" has empty domain suffix`);
|
|
340
|
+
}
|
|
341
|
+
const slug = validateSlug(slugCandidate);
|
|
342
|
+
const rawPath = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
343
|
+
let version = DEFAULT_VERSION;
|
|
344
|
+
if (rawPath !== "") {
|
|
345
|
+
if (!VERSION_REGEX.test(rawPath)) {
|
|
346
|
+
fail(`path "${parsed.pathname}" must be empty or match /v{N}`);
|
|
347
|
+
}
|
|
348
|
+
version = rawPath;
|
|
349
|
+
}
|
|
350
|
+
if (parsed.search) {
|
|
351
|
+
fail("connection URL must not contain a query string");
|
|
352
|
+
}
|
|
353
|
+
if (parsed.hash) {
|
|
354
|
+
fail("connection URL must not contain a fragment");
|
|
355
|
+
}
|
|
356
|
+
const apiBaseUrl = `https://${host}/${version}`;
|
|
357
|
+
return {
|
|
358
|
+
credential,
|
|
359
|
+
credentialType,
|
|
360
|
+
env,
|
|
361
|
+
slug,
|
|
362
|
+
host,
|
|
363
|
+
apiBaseUrl
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
252
367
|
// src/constants.ts
|
|
253
368
|
var SDK_API_PATH = `/v1`;
|
|
254
369
|
var DEFAULT_SDK_API_HOST = "api.sylphx.com";
|
|
255
|
-
var SDK_VERSION = "0.
|
|
370
|
+
var SDK_VERSION = "0.5.0";
|
|
256
371
|
var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
|
|
257
372
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
258
373
|
var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
@@ -354,6 +469,13 @@ var SylphxError = class _SylphxError extends Error {
|
|
|
354
469
|
static isRateLimited(err) {
|
|
355
470
|
return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS";
|
|
356
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Check if error is an account lockout error (too many failed login attempts).
|
|
474
|
+
* When true, `error.data?.lockoutUntil` contains the ISO 8601 timestamp when the lockout expires.
|
|
475
|
+
*/
|
|
476
|
+
static isAccountLocked(err) {
|
|
477
|
+
return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS" && err.data?.code === "ACCOUNT_LOCKED";
|
|
478
|
+
}
|
|
357
479
|
/**
|
|
358
480
|
* Check if error is a quota exceeded error (plan limit reached)
|
|
359
481
|
*/
|
|
@@ -580,195 +702,153 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
|
|
|
580
702
|
return Math.round(cappedDelay + jitter);
|
|
581
703
|
}
|
|
582
704
|
|
|
583
|
-
// src/
|
|
584
|
-
var
|
|
585
|
-
var
|
|
586
|
-
var
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
};
|
|
592
|
-
function detectKeyIssues(key) {
|
|
593
|
-
const issues = [];
|
|
594
|
-
if (key !== key.trim()) issues.push("whitespace");
|
|
595
|
-
if (key.includes("\n")) issues.push("newline");
|
|
596
|
-
if (key.includes("\r")) issues.push("carriage-return");
|
|
597
|
-
if (key.includes(" ")) issues.push("space");
|
|
598
|
-
if (key !== key.toLowerCase()) issues.push("uppercase-chars");
|
|
599
|
-
return issues;
|
|
600
|
-
}
|
|
601
|
-
function createSanitizationWarning(keyType, issues, envVarName) {
|
|
602
|
-
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
603
|
-
return `[Sylphx] ${keyTypeName} contains ${issues.join(", ")}. This is commonly caused by Vercel CLI's 'env pull' command.
|
|
604
|
-
|
|
605
|
-
To fix permanently:
|
|
606
|
-
1. Go to Vercel Dashboard \u2192 Your Project \u2192 Settings \u2192 Environment Variables
|
|
607
|
-
2. Edit ${envVarName}
|
|
608
|
-
3. Remove any trailing whitespace or newline characters
|
|
609
|
-
4. Redeploy your application
|
|
610
|
-
|
|
611
|
-
The SDK will automatically sanitize the key, but fixing the source is recommended.`;
|
|
612
|
-
}
|
|
613
|
-
function createInvalidKeyError(keyType, key, envVarName) {
|
|
614
|
-
const prefix = keyType === "appId" ? "app" : "sk";
|
|
615
|
-
const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
|
|
616
|
-
const formatHint = `${prefix}_(dev|stg|prod)_[identifier]`;
|
|
617
|
-
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
618
|
-
return `[Sylphx] Invalid ${keyTypeName} format.
|
|
619
|
-
|
|
620
|
-
Expected format: ${formatHint}
|
|
621
|
-
Received: "${maskedKey}"
|
|
622
|
-
|
|
623
|
-
Please check your ${envVarName} environment variable.
|
|
624
|
-
You can find your keys in the Sylphx Console \u2192 API Keys.
|
|
625
|
-
|
|
626
|
-
Common issues:
|
|
627
|
-
\u2022 Key has uppercase characters (must be lowercase)
|
|
628
|
-
\u2022 Key has wrong prefix (App ID: app_, Secret Key: sk_)
|
|
629
|
-
\u2022 Key has invalid environment (must be dev, stg, or prod)
|
|
630
|
-
\u2022 Key was copied with extra whitespace`;
|
|
631
|
-
}
|
|
632
|
-
function extractEnvironment(key) {
|
|
633
|
-
const match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);
|
|
634
|
-
if (!match) return void 0;
|
|
635
|
-
return ENV_PREFIX_MAP[match[1]];
|
|
636
|
-
}
|
|
637
|
-
function validateKeyForType(key, keyType, pattern, envVarName) {
|
|
638
|
-
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
639
|
-
if (!key) {
|
|
640
|
-
return {
|
|
641
|
-
valid: false,
|
|
642
|
-
sanitizedKey: "",
|
|
643
|
-
error: `[Sylphx] ${keyTypeName} is required. Set ${envVarName} in your environment variables.`,
|
|
644
|
-
issues: ["missing"]
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const issues = detectKeyIssues(key);
|
|
648
|
-
if (pattern.test(key)) {
|
|
649
|
-
return {
|
|
650
|
-
valid: true,
|
|
651
|
-
sanitizedKey: key,
|
|
652
|
-
keyType,
|
|
653
|
-
environment: extractEnvironment(key),
|
|
654
|
-
issues: []
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
const sanitized = key.trim().toLowerCase();
|
|
658
|
-
if (pattern.test(sanitized)) {
|
|
659
|
-
return {
|
|
660
|
-
valid: true,
|
|
661
|
-
sanitizedKey: sanitized,
|
|
662
|
-
keyType,
|
|
663
|
-
environment: extractEnvironment(sanitized),
|
|
664
|
-
warning: createSanitizationWarning(keyType, issues, envVarName),
|
|
665
|
-
issues
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
return {
|
|
669
|
-
valid: false,
|
|
670
|
-
sanitizedKey: "",
|
|
671
|
-
error: createInvalidKeyError(keyType, key, envVarName),
|
|
672
|
-
issues: [...issues, "invalid-format"]
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
function validatePublicKey(key) {
|
|
676
|
-
return validateKeyForType(key, "publicKey", PUBLIC_KEY_PATTERN, "NEXT_PUBLIC_SYLPHX_KEY");
|
|
677
|
-
}
|
|
678
|
-
function validateAppId(key) {
|
|
679
|
-
return validateKeyForType(key, "appId", APP_ID_PATTERN, "NEXT_PUBLIC_SYLPHX_APP_ID");
|
|
680
|
-
}
|
|
681
|
-
function validateSecretKey(key) {
|
|
682
|
-
return validateKeyForType(key, "secret", SECRET_KEY_PATTERN, "SYLPHX_SECRET_KEY");
|
|
683
|
-
}
|
|
684
|
-
function validateAndSanitizeSecretKey(key) {
|
|
685
|
-
const result = validateSecretKey(key);
|
|
686
|
-
if (!result.valid) {
|
|
687
|
-
throw new Error(result.error);
|
|
688
|
-
}
|
|
689
|
-
if (result.warning) {
|
|
690
|
-
console.warn(result.warning);
|
|
691
|
-
}
|
|
692
|
-
return result.sanitizedKey;
|
|
693
|
-
}
|
|
694
|
-
function detectKeyType(key) {
|
|
695
|
-
const sanitized = key.trim().toLowerCase();
|
|
696
|
-
if (sanitized.startsWith("pk_")) return "publicKey";
|
|
697
|
-
if (sanitized.startsWith("app_")) return "appId";
|
|
698
|
-
if (sanitized.startsWith("sk_")) return "secret";
|
|
699
|
-
return null;
|
|
700
|
-
}
|
|
701
|
-
function validateKey(key) {
|
|
702
|
-
const keyType = key ? detectKeyType(key) : null;
|
|
703
|
-
if (keyType === "publicKey") {
|
|
704
|
-
return validatePublicKey(key);
|
|
705
|
-
}
|
|
706
|
-
if (keyType === "appId") {
|
|
707
|
-
return validateAppId(key);
|
|
705
|
+
// src/config.ts
|
|
706
|
+
var LEGACY_EMBEDDED_REF_PATTERN = /^(pk|sk)_(dev|stg|prod|prev)_[a-z0-9]{12}_[a-f0-9]+$/;
|
|
707
|
+
var LEGACY_APP_KEY_PATTERN = /^app_(dev|stg|prod|prev)_/;
|
|
708
|
+
var MIGRATION_MESSAGE = "API key format has changed. Use a sylphx:// connection URL instead.\n\nNew format: sylphx://pk_prod_{hex}@your-slug.sylphx.com\n\nGenerate new credentials from the Sylphx Console \u2192 Your App \u2192 Environments.\nSee https://docs.sylphx.com/migration for details.";
|
|
709
|
+
function rejectLegacyKeyFormat(input) {
|
|
710
|
+
const trimmed = input.trim().toLowerCase();
|
|
711
|
+
if (LEGACY_APP_KEY_PATTERN.test(trimmed)) {
|
|
712
|
+
throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
|
|
708
713
|
}
|
|
709
|
-
if (
|
|
710
|
-
|
|
714
|
+
if (LEGACY_EMBEDDED_REF_PATTERN.test(trimmed)) {
|
|
715
|
+
throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
|
|
711
716
|
}
|
|
712
|
-
return {
|
|
713
|
-
valid: false,
|
|
714
|
-
sanitizedKey: "",
|
|
715
|
-
error: key ? `Invalid key format. Keys must start with 'pk_' (publishable), 'app_' (legacy), or 'sk_' (secret), followed by environment (dev/stg/prod). Got: ${key.slice(0, 20)}...` : "API key is required but was not provided.",
|
|
716
|
-
issues: key ? ["invalid_format"] : ["missing"]
|
|
717
|
-
};
|
|
718
717
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
718
|
+
function freezeConfig(opts) {
|
|
719
|
+
return Object.freeze({
|
|
720
|
+
credential: opts.credential,
|
|
721
|
+
credentialType: opts.credentialType,
|
|
722
|
+
env: opts.env,
|
|
723
|
+
slug: opts.slug,
|
|
724
|
+
baseUrl: opts.baseUrl,
|
|
725
|
+
accessToken: opts.accessToken,
|
|
726
|
+
// Backward-compat aliases
|
|
727
|
+
secretKey: opts.credentialType === "sk" ? opts.credential : void 0,
|
|
728
|
+
publicKey: opts.credentialType === "pk" ? opts.credential : void 0,
|
|
729
|
+
ref: opts.slug
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
function createClient(input) {
|
|
733
|
+
if (typeof input === "string") {
|
|
734
|
+
return createConfigFromUrl(input);
|
|
735
|
+
}
|
|
736
|
+
return createConfigFromComponents(input);
|
|
737
|
+
}
|
|
738
|
+
function createServerClient(input) {
|
|
739
|
+
const config = createClient(input);
|
|
740
|
+
if (config.credentialType !== "sk") {
|
|
724
741
|
throw new SylphxError(
|
|
725
|
-
"[Sylphx]
|
|
742
|
+
"[Sylphx] createServerClient() requires a secret key (sk_*). Use a SYLPHX_SECRET_URL with an sk_ credential, or pass { secretKey } in the components object.",
|
|
726
743
|
{ code: "BAD_REQUEST" }
|
|
727
744
|
);
|
|
728
745
|
}
|
|
729
|
-
|
|
730
|
-
|
|
746
|
+
return config;
|
|
747
|
+
}
|
|
748
|
+
function createConfigFromUrl(url) {
|
|
749
|
+
if (!url || typeof url !== "string") {
|
|
731
750
|
throw new SylphxError(
|
|
732
|
-
|
|
751
|
+
"[Sylphx] Connection URL is required. Set SYLPHX_URL or NEXT_PUBLIC_SYLPHX_URL environment variable.\n\nFormat: sylphx://pk_prod_{hex}@your-slug.sylphx.com",
|
|
733
752
|
{ code: "BAD_REQUEST" }
|
|
734
753
|
);
|
|
735
754
|
}
|
|
736
|
-
const
|
|
737
|
-
|
|
755
|
+
const trimmed = url.trim();
|
|
756
|
+
rejectLegacyKeyFormat(trimmed);
|
|
757
|
+
if (!trimmed.startsWith("sylphx://")) {
|
|
758
|
+
if (CREDENTIAL_REGEX.test(trimmed)) {
|
|
759
|
+
throw new SylphxError(
|
|
760
|
+
"[Sylphx] Received a bare credential instead of a connection URL.\n\nWrap it in a connection URL: sylphx://<credential>@<slug>.sylphx.com\nOr use createClient({ slug, publicKey }) for explicit components.",
|
|
761
|
+
{ code: "BAD_REQUEST" }
|
|
762
|
+
);
|
|
763
|
+
}
|
|
738
764
|
throw new SylphxError(
|
|
739
|
-
`[Sylphx] Invalid
|
|
765
|
+
`[Sylphx] Invalid connection URL \u2014 must start with "sylphx://". Got: "${trimmed.slice(0, 30)}..."`,
|
|
740
766
|
{ code: "BAD_REQUEST" }
|
|
741
767
|
);
|
|
742
768
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
769
|
+
let parsed;
|
|
770
|
+
try {
|
|
771
|
+
parsed = parseConnectionUrl(trimmed);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
if (err instanceof InvalidConnectionUrlError) {
|
|
774
|
+
throw new SylphxError(err.message, { code: "BAD_REQUEST", cause: err });
|
|
775
|
+
}
|
|
776
|
+
throw err;
|
|
749
777
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
778
|
+
return freezeConfig({
|
|
779
|
+
credential: parsed.credential,
|
|
780
|
+
credentialType: parsed.credentialType,
|
|
781
|
+
env: parsed.env,
|
|
782
|
+
slug: parsed.slug,
|
|
783
|
+
baseUrl: parsed.apiBaseUrl
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function createConfigFromComponents(input) {
|
|
787
|
+
const credential = input.secretKey || input.publicKey;
|
|
788
|
+
if (!credential) {
|
|
789
|
+
throw new SylphxError("[Sylphx] Either publicKey or secretKey must be provided.", {
|
|
790
|
+
code: "BAD_REQUEST"
|
|
791
|
+
});
|
|
755
792
|
}
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
throw new SylphxError(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
);
|
|
793
|
+
const resolvedSlug = input.slug || input.ref;
|
|
794
|
+
if (!resolvedSlug) {
|
|
795
|
+
throw new SylphxError("[Sylphx] slug is required when using explicit components.", {
|
|
796
|
+
code: "BAD_REQUEST"
|
|
797
|
+
});
|
|
762
798
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
799
|
+
const trimmedCred = credential.trim().toLowerCase();
|
|
800
|
+
if (CREDENTIAL_REGEX.test(trimmedCred)) {
|
|
801
|
+
const match = CREDENTIAL_REGEX.exec(trimmedCred);
|
|
802
|
+
const credentialType = match[1];
|
|
803
|
+
const env = match[2];
|
|
804
|
+
const slug = resolvedSlug.trim().toLowerCase();
|
|
805
|
+
const domain = input.domain?.trim() || "sylphx.com";
|
|
806
|
+
const baseUrl = `https://${slug}.${domain}/v1`;
|
|
807
|
+
return freezeConfig({
|
|
808
|
+
credential: trimmedCred,
|
|
809
|
+
credentialType,
|
|
810
|
+
env,
|
|
811
|
+
slug,
|
|
812
|
+
baseUrl,
|
|
813
|
+
accessToken: input.accessToken
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
const parts = trimmedCred.split("_");
|
|
817
|
+
const prefix = parts[0];
|
|
818
|
+
if ((prefix === "pk" || prefix === "sk") && parts.length >= 3) {
|
|
819
|
+
const envSegment = parts[1];
|
|
820
|
+
const validEnvs = ["dev", "stg", "prod", "prev"];
|
|
821
|
+
const env = validEnvs.includes(envSegment) ? envSegment : "prod";
|
|
822
|
+
const slug = resolvedSlug.trim().toLowerCase();
|
|
823
|
+
let baseUrl;
|
|
824
|
+
if (input.platformUrl) {
|
|
825
|
+
const platform = input.platformUrl.trim().replace(/\/$/, "");
|
|
826
|
+
baseUrl = platform.includes("/v1") ? platform : `${platform}/v1`;
|
|
827
|
+
} else {
|
|
828
|
+
const domain = input.domain?.trim() || "api.sylphx.com";
|
|
829
|
+
baseUrl = `https://${slug}.${domain}/v1`;
|
|
830
|
+
}
|
|
831
|
+
return freezeConfig({
|
|
832
|
+
credential: trimmedCred,
|
|
833
|
+
credentialType: prefix,
|
|
834
|
+
env,
|
|
835
|
+
slug,
|
|
836
|
+
baseUrl,
|
|
837
|
+
accessToken: input.accessToken
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
throw new SylphxError(
|
|
841
|
+
`[Sylphx] Invalid credential format. Expected (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}. Got: "${trimmedCred.slice(0, 30)}..."`,
|
|
842
|
+
{ code: "BAD_REQUEST" }
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
function withToken(config, accessToken) {
|
|
846
|
+
return Object.freeze({
|
|
847
|
+
...config,
|
|
848
|
+
accessToken
|
|
849
|
+
});
|
|
771
850
|
}
|
|
851
|
+
var createConfig = createClient;
|
|
772
852
|
function httpStatusToErrorCode(status) {
|
|
773
853
|
switch (status) {
|
|
774
854
|
case 400:
|
|
@@ -801,79 +881,12 @@ function httpStatusToErrorCode(status) {
|
|
|
801
881
|
return status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST";
|
|
802
882
|
}
|
|
803
883
|
}
|
|
804
|
-
var REF_PATTERN = /^[a-z0-9]{12}$/;
|
|
805
|
-
function createConfig(input) {
|
|
806
|
-
const keyForParsing = input.secretKey || input.publicKey;
|
|
807
|
-
if (!keyForParsing) {
|
|
808
|
-
if (input.ref) {
|
|
809
|
-
const trimmedRef = input.ref.trim();
|
|
810
|
-
if (!REF_PATTERN.test(trimmedRef)) {
|
|
811
|
-
throw new SylphxError(
|
|
812
|
-
`[Sylphx] Invalid project ref format: "${input.ref}". Expected a 12-character lowercase alphanumeric string (e.g. "abc123def456"). Get your ref from Platform Console \u2192 Projects \u2192 Your Project \u2192 Overview.`,
|
|
813
|
-
{ code: "BAD_REQUEST" }
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
const baseUrl2 = `https://${trimmedRef}.${DEFAULT_SDK_API_HOST}${SDK_API_PATH}`;
|
|
817
|
-
console.warn(
|
|
818
|
-
"[Sylphx] Providing only ref without a key is deprecated. Provide secretKey or publicKey \u2014 the ref is now embedded in keys (ADR-021)."
|
|
819
|
-
);
|
|
820
|
-
return Object.freeze({ ref: trimmedRef, baseUrl: baseUrl2, accessToken: input.accessToken });
|
|
821
|
-
}
|
|
822
|
-
throw new SylphxError(
|
|
823
|
-
"[Sylphx] Either publicKey or secretKey must be provided to createConfig().",
|
|
824
|
-
{ code: "BAD_REQUEST" }
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
const parsed = parseKey(keyForParsing);
|
|
828
|
-
const ref = parsed.ref;
|
|
829
|
-
const baseUrl = parsed.baseUrl;
|
|
830
|
-
let secretKey;
|
|
831
|
-
if (input.secretKey) {
|
|
832
|
-
const result = validateKey(input.secretKey);
|
|
833
|
-
if (!result.valid) {
|
|
834
|
-
throw new SylphxError(result.error || "Invalid secret key", {
|
|
835
|
-
code: "BAD_REQUEST",
|
|
836
|
-
data: { issues: result.issues }
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
if (result.warning) console.warn(`[Sylphx] ${result.warning}`);
|
|
840
|
-
secretKey = result.sanitizedKey;
|
|
841
|
-
}
|
|
842
|
-
let publicKey;
|
|
843
|
-
if (input.publicKey) {
|
|
844
|
-
const result = validateKey(input.publicKey);
|
|
845
|
-
if (!result.valid) {
|
|
846
|
-
throw new SylphxError(result.error || "Invalid public key", {
|
|
847
|
-
code: "BAD_REQUEST",
|
|
848
|
-
data: { issues: result.issues }
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
if (result.warning) console.warn(`[Sylphx] ${result.warning}`);
|
|
852
|
-
publicKey = result.sanitizedKey;
|
|
853
|
-
}
|
|
854
|
-
return Object.freeze({
|
|
855
|
-
secretKey,
|
|
856
|
-
publicKey,
|
|
857
|
-
ref,
|
|
858
|
-
baseUrl,
|
|
859
|
-
accessToken: input.accessToken
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
function withToken(config, accessToken) {
|
|
863
|
-
return Object.freeze({
|
|
864
|
-
...config,
|
|
865
|
-
accessToken
|
|
866
|
-
// Preserve baseUrl and ref from original config
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
884
|
function buildHeaders(config) {
|
|
870
885
|
const headers = {
|
|
871
886
|
"Content-Type": "application/json"
|
|
872
887
|
};
|
|
873
|
-
if (config.
|
|
874
|
-
headers["x-app-secret"] = config.
|
|
875
|
-
} else if (config.publicKey) {
|
|
876
|
-
headers["x-app-secret"] = config.publicKey;
|
|
888
|
+
if (config.credential) {
|
|
889
|
+
headers["x-app-secret"] = config.credential;
|
|
877
890
|
}
|
|
878
891
|
if (config.accessToken) {
|
|
879
892
|
headers.Authorization = `Bearer ${config.accessToken}`;
|
|
@@ -892,7 +905,8 @@ async function callApi(config, path, options = {}) {
|
|
|
892
905
|
query,
|
|
893
906
|
timeout = DEFAULT_TIMEOUT_MS,
|
|
894
907
|
signal,
|
|
895
|
-
idempotencyKey
|
|
908
|
+
idempotencyKey,
|
|
909
|
+
headers: extraHeaders
|
|
896
910
|
} = options;
|
|
897
911
|
let url = buildApiUrl(config, path);
|
|
898
912
|
if (query) {
|
|
@@ -914,6 +928,11 @@ async function callApi(config, path, options = {}) {
|
|
|
914
928
|
if (idempotencyKey) {
|
|
915
929
|
headers["Idempotency-Key"] = idempotencyKey;
|
|
916
930
|
}
|
|
931
|
+
if (extraHeaders) {
|
|
932
|
+
for (const [k, v] of Object.entries(extraHeaders)) {
|
|
933
|
+
headers[k] = v;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
917
936
|
const fetchOptions = {
|
|
918
937
|
method,
|
|
919
938
|
headers,
|
|
@@ -990,7 +1009,6 @@ async function callApi(config, path, options = {}) {
|
|
|
990
1009
|
code: "PARSE_ERROR",
|
|
991
1010
|
cause: error instanceof Error ? error : void 0,
|
|
992
1011
|
data: { body: text.slice(0, 200) }
|
|
993
|
-
// Include snippet for debugging
|
|
994
1012
|
});
|
|
995
1013
|
}
|
|
996
1014
|
}
|
|
@@ -1100,6 +1118,111 @@ function installGlobalDebugHelpers() {
|
|
|
1100
1118
|
|
|
1101
1119
|
// src/rest-client.ts
|
|
1102
1120
|
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
1121
|
+
|
|
1122
|
+
// src/key-validation.ts
|
|
1123
|
+
var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;
|
|
1124
|
+
var ENV_PREFIX_MAP = {
|
|
1125
|
+
dev: "development",
|
|
1126
|
+
stg: "staging",
|
|
1127
|
+
prod: "production"
|
|
1128
|
+
};
|
|
1129
|
+
function detectKeyIssues(key) {
|
|
1130
|
+
const issues = [];
|
|
1131
|
+
if (key !== key.trim()) issues.push("whitespace");
|
|
1132
|
+
if (key.includes("\n")) issues.push("newline");
|
|
1133
|
+
if (key.includes("\r")) issues.push("carriage-return");
|
|
1134
|
+
if (key.includes(" ")) issues.push("space");
|
|
1135
|
+
if (key !== key.toLowerCase()) issues.push("uppercase-chars");
|
|
1136
|
+
return issues;
|
|
1137
|
+
}
|
|
1138
|
+
function createSanitizationWarning(keyType, issues, envVarName) {
|
|
1139
|
+
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
1140
|
+
return `[Sylphx] ${keyTypeName} contains ${issues.join(", ")}. This is commonly caused by Vercel CLI's 'env pull' command.
|
|
1141
|
+
|
|
1142
|
+
To fix permanently:
|
|
1143
|
+
1. Go to Vercel Dashboard \u2192 Your Project \u2192 Settings \u2192 Environment Variables
|
|
1144
|
+
2. Edit ${envVarName}
|
|
1145
|
+
3. Remove any trailing whitespace or newline characters
|
|
1146
|
+
4. Redeploy your application
|
|
1147
|
+
|
|
1148
|
+
The SDK will automatically sanitize the key, but fixing the source is recommended.`;
|
|
1149
|
+
}
|
|
1150
|
+
function createInvalidKeyError(keyType, key, envVarName) {
|
|
1151
|
+
const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
|
|
1152
|
+
const formatHint = keyType === "appId" ? "pk_(dev|stg|prod)_{ref}_{hex} or app_(dev|stg|prod)_[id]" : "sk_(dev|stg|prod)_{ref}_{hex}";
|
|
1153
|
+
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
1154
|
+
return `[Sylphx] Invalid ${keyTypeName} format.
|
|
1155
|
+
|
|
1156
|
+
Expected format: ${formatHint}
|
|
1157
|
+
Received: "${maskedKey}"
|
|
1158
|
+
|
|
1159
|
+
Please check your ${envVarName} environment variable.
|
|
1160
|
+
You can find your keys in the Sylphx Console \u2192 API Keys.
|
|
1161
|
+
|
|
1162
|
+
Common issues:
|
|
1163
|
+
\u2022 Key has uppercase characters (must be lowercase)
|
|
1164
|
+
\u2022 Key has wrong prefix (App ID: pk_ or app_, Secret Key: sk_)
|
|
1165
|
+
\u2022 Key has invalid environment (must be dev, stg, or prod)
|
|
1166
|
+
\u2022 Key was copied with extra whitespace`;
|
|
1167
|
+
}
|
|
1168
|
+
function extractEnvironment(key) {
|
|
1169
|
+
const match = key.match(/^(?:app|pk|sk)_(dev|stg|prod|prev)_/);
|
|
1170
|
+
if (!match) return void 0;
|
|
1171
|
+
return ENV_PREFIX_MAP[match[1]];
|
|
1172
|
+
}
|
|
1173
|
+
function validateKeyForType(key, keyType, pattern, envVarName) {
|
|
1174
|
+
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
1175
|
+
if (!key) {
|
|
1176
|
+
return {
|
|
1177
|
+
valid: false,
|
|
1178
|
+
sanitizedKey: "",
|
|
1179
|
+
error: `[Sylphx] ${keyTypeName} is required. Set ${envVarName} in your environment variables.`,
|
|
1180
|
+
issues: ["missing"]
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
const issues = detectKeyIssues(key);
|
|
1184
|
+
if (pattern.test(key)) {
|
|
1185
|
+
return {
|
|
1186
|
+
valid: true,
|
|
1187
|
+
sanitizedKey: key,
|
|
1188
|
+
keyType,
|
|
1189
|
+
environment: extractEnvironment(key),
|
|
1190
|
+
issues: []
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const sanitized = key.trim().toLowerCase();
|
|
1194
|
+
if (pattern.test(sanitized)) {
|
|
1195
|
+
return {
|
|
1196
|
+
valid: true,
|
|
1197
|
+
sanitizedKey: sanitized,
|
|
1198
|
+
keyType,
|
|
1199
|
+
environment: extractEnvironment(sanitized),
|
|
1200
|
+
warning: createSanitizationWarning(keyType, issues, envVarName),
|
|
1201
|
+
issues
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
valid: false,
|
|
1206
|
+
sanitizedKey: "",
|
|
1207
|
+
error: createInvalidKeyError(keyType, key, envVarName),
|
|
1208
|
+
issues: [...issues, "invalid-format"]
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function validateSecretKey(key) {
|
|
1212
|
+
return validateKeyForType(key, "secret", SECRET_KEY_PATTERN, "SYLPHX_SECRET_KEY");
|
|
1213
|
+
}
|
|
1214
|
+
function validateAndSanitizeSecretKey(key) {
|
|
1215
|
+
const result = validateSecretKey(key);
|
|
1216
|
+
if (!result.valid) {
|
|
1217
|
+
throw new Error(result.error);
|
|
1218
|
+
}
|
|
1219
|
+
if (result.warning) {
|
|
1220
|
+
console.warn(result.warning);
|
|
1221
|
+
}
|
|
1222
|
+
return result.sanitizedKey;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/rest-client.ts
|
|
1103
1226
|
function createAuthMiddleware(config) {
|
|
1104
1227
|
return {
|
|
1105
1228
|
async onRequest({ request }) {
|
|
@@ -2319,6 +2442,73 @@ async function updatePushPreferences(config, preferences) {
|
|
|
2319
2442
|
});
|
|
2320
2443
|
}
|
|
2321
2444
|
|
|
2445
|
+
// src/lib/triggers/index.ts
|
|
2446
|
+
var TriggersClient = class {
|
|
2447
|
+
/** Create a new trigger (cron or event source, task/run/http target) */
|
|
2448
|
+
static async create(config, options) {
|
|
2449
|
+
return callApi(config, "/triggers", {
|
|
2450
|
+
method: "POST",
|
|
2451
|
+
body: options
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
/** List all triggers for the project */
|
|
2455
|
+
static async list(config) {
|
|
2456
|
+
return callApi(config, "/triggers");
|
|
2457
|
+
}
|
|
2458
|
+
/** Get a trigger by ID */
|
|
2459
|
+
static async get(config, triggerId) {
|
|
2460
|
+
return callApi(config, `/triggers/${triggerId}`);
|
|
2461
|
+
}
|
|
2462
|
+
/** Update a trigger */
|
|
2463
|
+
static async update(config, triggerId, options) {
|
|
2464
|
+
return callApi(config, `/triggers/${triggerId}`, {
|
|
2465
|
+
method: "PATCH",
|
|
2466
|
+
body: options
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
/** Delete a trigger */
|
|
2470
|
+
static async delete(config, triggerId) {
|
|
2471
|
+
return callApi(config, `/triggers/${triggerId}`, { method: "DELETE" });
|
|
2472
|
+
}
|
|
2473
|
+
/** Pause a trigger */
|
|
2474
|
+
static async pause(config, triggerId) {
|
|
2475
|
+
return callApi(config, `/triggers/${triggerId}/pause`, { method: "POST" });
|
|
2476
|
+
}
|
|
2477
|
+
/** Resume a paused trigger */
|
|
2478
|
+
static async resume(config, triggerId) {
|
|
2479
|
+
return callApi(config, `/triggers/${triggerId}/resume`, { method: "POST" });
|
|
2480
|
+
}
|
|
2481
|
+
/** Fire a trigger immediately (one-shot, regardless of schedule) */
|
|
2482
|
+
static async fire(config, triggerId) {
|
|
2483
|
+
return callApi(config, `/triggers/${triggerId}/fire`, {
|
|
2484
|
+
method: "POST"
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Publish an event — dispatches all active event triggers matching the event name.
|
|
2489
|
+
*
|
|
2490
|
+
* @example
|
|
2491
|
+
* ```typescript
|
|
2492
|
+
* await TriggersClient.publishEvent(config, 'user.signup', { userId: '123', plan: 'pro' })
|
|
2493
|
+
* ```
|
|
2494
|
+
*/
|
|
2495
|
+
/**
|
|
2496
|
+
* Publish an event — dispatches all active event triggers matching the event name.
|
|
2497
|
+
* Endpoint: POST /triggers/events
|
|
2498
|
+
*
|
|
2499
|
+
* @example
|
|
2500
|
+
* ```typescript
|
|
2501
|
+
* await TriggersClient.publishEvent(config, 'user.signup', { userId: '123', plan: 'pro' })
|
|
2502
|
+
* ```
|
|
2503
|
+
*/
|
|
2504
|
+
static async publishEvent(config, eventName, payload) {
|
|
2505
|
+
return callApi(config, "/triggers/events", {
|
|
2506
|
+
method: "POST",
|
|
2507
|
+
body: { eventName, payload: payload ?? {} }
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2322
2512
|
// src/lib/tasks/handler.ts
|
|
2323
2513
|
var import_node_crypto = require("crypto");
|
|
2324
2514
|
var StepCompleteSignal = class {
|
|
@@ -2335,6 +2525,14 @@ var StepSleepSignal = class {
|
|
|
2335
2525
|
}
|
|
2336
2526
|
_isStepSleepSignal = true;
|
|
2337
2527
|
};
|
|
2528
|
+
var StepWaitEventSignal = class {
|
|
2529
|
+
constructor(stepName, eventName, options = {}) {
|
|
2530
|
+
this.stepName = stepName;
|
|
2531
|
+
this.eventName = eventName;
|
|
2532
|
+
this.options = options;
|
|
2533
|
+
}
|
|
2534
|
+
_isStepWaitEventSignal = true;
|
|
2535
|
+
};
|
|
2338
2536
|
function createStepContext(completedSteps, resolvedWaits) {
|
|
2339
2537
|
return {
|
|
2340
2538
|
/**
|
|
@@ -2361,6 +2559,32 @@ function createStepContext(completedSteps, resolvedWaits) {
|
|
|
2361
2559
|
return;
|
|
2362
2560
|
}
|
|
2363
2561
|
throw new StepSleepSignal(name, duration);
|
|
2562
|
+
},
|
|
2563
|
+
/**
|
|
2564
|
+
* Pause execution until a named event is published via TriggersClient.publishEvent().
|
|
2565
|
+
*
|
|
2566
|
+
* - If event already arrived (platform re-dispatched with result): return event payload.
|
|
2567
|
+
* - If not yet arrived: throw StepWaitEventSignal to pause execution.
|
|
2568
|
+
*
|
|
2569
|
+
* @param name Step identifier (unique within handler).
|
|
2570
|
+
* @param eventName The event name to listen for (e.g. 'user.approved').
|
|
2571
|
+
* @param options Optional timeout ('24h', '7d') and payload filter.
|
|
2572
|
+
*
|
|
2573
|
+
* @example Human-in-the-loop approval
|
|
2574
|
+
* ```typescript
|
|
2575
|
+
* const approval = await step.waitForEvent('wait-approval', 'order.approved', {
|
|
2576
|
+
* timeout: '48h',
|
|
2577
|
+
* filter: { orderId: payload.orderId },
|
|
2578
|
+
* })
|
|
2579
|
+
* if (!approval) throw new Error('Approval timed out')
|
|
2580
|
+
* await sendConfirmation(approval.approvedBy)
|
|
2581
|
+
* ```
|
|
2582
|
+
*/
|
|
2583
|
+
async waitForEvent(name, eventName, options = {}) {
|
|
2584
|
+
if (resolvedWaits.has(name)) {
|
|
2585
|
+
return resolvedWaits.get(name) ?? null;
|
|
2586
|
+
}
|
|
2587
|
+
throw new StepWaitEventSignal(name, eventName, options);
|
|
2364
2588
|
}
|
|
2365
2589
|
};
|
|
2366
2590
|
}
|
|
@@ -2440,9 +2664,9 @@ function createTasksHandler(taskDefs, options = {}) {
|
|
|
2440
2664
|
for (const step of context?.steps ?? []) {
|
|
2441
2665
|
completedSteps.set(step.name, step.result);
|
|
2442
2666
|
}
|
|
2443
|
-
const resolvedWaits = /* @__PURE__ */ new
|
|
2667
|
+
const resolvedWaits = /* @__PURE__ */ new Map();
|
|
2444
2668
|
for (const wait of context?.waits ?? []) {
|
|
2445
|
-
resolvedWaits.
|
|
2669
|
+
resolvedWaits.set(wait.name, wait.result ?? void 0);
|
|
2446
2670
|
}
|
|
2447
2671
|
const stepCtx = createStepContext(completedSteps, resolvedWaits);
|
|
2448
2672
|
try {
|
|
@@ -2465,6 +2689,16 @@ function createTasksHandler(taskDefs, options = {}) {
|
|
|
2465
2689
|
duration: signal.duration
|
|
2466
2690
|
});
|
|
2467
2691
|
}
|
|
2692
|
+
if (err instanceof StepWaitEventSignal || err?._isStepWaitEventSignal) {
|
|
2693
|
+
const signal = err;
|
|
2694
|
+
return Response.json({
|
|
2695
|
+
status: "step_wait_event",
|
|
2696
|
+
stepName: signal.stepName,
|
|
2697
|
+
eventName: signal.eventName,
|
|
2698
|
+
timeout: signal.options.timeout ?? null,
|
|
2699
|
+
filter: signal.options.filter ?? null
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2468
2702
|
const message = err instanceof Error ? err.message : String(err);
|
|
2469
2703
|
console.error(`[sylphx/tasks] Task "${taskName}" threw an error:`, err);
|
|
2470
2704
|
return Response.json(
|
|
@@ -2936,6 +3170,86 @@ function canDeleteOrganization(membership) {
|
|
|
2936
3170
|
return hasRole(membership, "super_admin");
|
|
2937
3171
|
}
|
|
2938
3172
|
|
|
3173
|
+
// src/permissions.ts
|
|
3174
|
+
async function listPermissions(config) {
|
|
3175
|
+
return callApi(config, "/permissions");
|
|
3176
|
+
}
|
|
3177
|
+
async function createPermission(config, input) {
|
|
3178
|
+
return callApi(config, "/permissions", {
|
|
3179
|
+
method: "POST",
|
|
3180
|
+
body: input
|
|
3181
|
+
});
|
|
3182
|
+
}
|
|
3183
|
+
async function deletePermission(config, permissionKey) {
|
|
3184
|
+
return callApi(config, `/permissions/${permissionKey}`, {
|
|
3185
|
+
method: "DELETE"
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
async function getMemberPermissions(config, orgIdOrSlug, memberId) {
|
|
3189
|
+
return callApi(
|
|
3190
|
+
config,
|
|
3191
|
+
`/orgs/${orgIdOrSlug}/members/${memberId}/permissions`
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
function hasPermission(permissions, required) {
|
|
3195
|
+
return permissions.includes(required);
|
|
3196
|
+
}
|
|
3197
|
+
function hasAnyPermission(permissions, required) {
|
|
3198
|
+
return required.some((perm) => permissions.includes(perm));
|
|
3199
|
+
}
|
|
3200
|
+
function hasAllPermissions(permissions, required) {
|
|
3201
|
+
return required.every((perm) => permissions.includes(perm));
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/roles.ts
|
|
3205
|
+
async function listRoles(config) {
|
|
3206
|
+
return callApi(config, "/roles");
|
|
3207
|
+
}
|
|
3208
|
+
async function getRole(config, roleKey) {
|
|
3209
|
+
return callApi(config, `/roles/${roleKey}`);
|
|
3210
|
+
}
|
|
3211
|
+
async function createRole(config, input) {
|
|
3212
|
+
const body = {
|
|
3213
|
+
key: input.key,
|
|
3214
|
+
name: input.name
|
|
3215
|
+
};
|
|
3216
|
+
if (input.description !== void 0) body.description = input.description;
|
|
3217
|
+
if (input.permissions !== void 0) body.permissionKeys = input.permissions;
|
|
3218
|
+
if (input.isDefault !== void 0) body.isDefault = input.isDefault;
|
|
3219
|
+
if (input.sortOrder !== void 0) body.sortOrder = input.sortOrder;
|
|
3220
|
+
return callApi(config, "/roles", {
|
|
3221
|
+
method: "POST",
|
|
3222
|
+
body
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
async function updateRole(config, roleKey, input) {
|
|
3226
|
+
const body = {};
|
|
3227
|
+
if (input.name !== void 0) body.name = input.name;
|
|
3228
|
+
if (input.description !== void 0) body.description = input.description;
|
|
3229
|
+
if (input.permissions !== void 0) body.permissionKeys = input.permissions;
|
|
3230
|
+
if (input.isDefault !== void 0) body.isDefault = input.isDefault;
|
|
3231
|
+
if (input.sortOrder !== void 0) body.sortOrder = input.sortOrder;
|
|
3232
|
+
return callApi(config, `/roles/${roleKey}`, {
|
|
3233
|
+
method: "PUT",
|
|
3234
|
+
body
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
async function deleteRole(config, roleKey) {
|
|
3238
|
+
return callApi(config, `/roles/${roleKey}`, {
|
|
3239
|
+
method: "DELETE"
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
async function assignMemberRole(config, orgIdOrSlug, memberId, roleKey) {
|
|
3243
|
+
return callApi(
|
|
3244
|
+
config,
|
|
3245
|
+
`/orgs/${orgIdOrSlug}/members/${memberId}/assign-role`,
|
|
3246
|
+
{
|
|
3247
|
+
method: "PUT",
|
|
3248
|
+
body: { roleKey }
|
|
3249
|
+
}
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
|
|
2939
3253
|
// src/secrets.ts
|
|
2940
3254
|
async function getSecret(config, input) {
|
|
2941
3255
|
return callApi(config, "/secrets/get", {
|
|
@@ -3394,6 +3708,143 @@ var SandboxFiles = class {
|
|
|
3394
3708
|
return data.files;
|
|
3395
3709
|
}
|
|
3396
3710
|
};
|
|
3711
|
+
var SandboxProcesses = class {
|
|
3712
|
+
constructor(endpoint, token) {
|
|
3713
|
+
this.endpoint = endpoint;
|
|
3714
|
+
this.token = token;
|
|
3715
|
+
}
|
|
3716
|
+
authHeader() {
|
|
3717
|
+
return { Authorization: `Bearer ${this.token}` };
|
|
3718
|
+
}
|
|
3719
|
+
/** Spawn a new tracked process. Returns processId + pid immediately. */
|
|
3720
|
+
async start(opts) {
|
|
3721
|
+
const res = await fetch(`${this.endpoint}/process/start`, {
|
|
3722
|
+
method: "POST",
|
|
3723
|
+
headers: { ...this.authHeader(), "Content-Type": "application/json" },
|
|
3724
|
+
body: JSON.stringify(opts)
|
|
3725
|
+
});
|
|
3726
|
+
if (!res.ok) throw new Error(`process.start failed: ${await res.text()}`);
|
|
3727
|
+
return await res.json();
|
|
3728
|
+
}
|
|
3729
|
+
/** List all tracked processes. */
|
|
3730
|
+
async list() {
|
|
3731
|
+
const res = await fetch(`${this.endpoint}/process/list`, {
|
|
3732
|
+
headers: this.authHeader()
|
|
3733
|
+
});
|
|
3734
|
+
if (!res.ok) throw new Error(`process.list failed: ${await res.text()}`);
|
|
3735
|
+
return await res.json();
|
|
3736
|
+
}
|
|
3737
|
+
/** Get full process info including buffered output. */
|
|
3738
|
+
async get(processId) {
|
|
3739
|
+
const res = await fetch(`${this.endpoint}/process/${processId}`, {
|
|
3740
|
+
headers: this.authHeader()
|
|
3741
|
+
});
|
|
3742
|
+
if (!res.ok) throw new Error(`process.get failed: ${await res.text()}`);
|
|
3743
|
+
return await res.json();
|
|
3744
|
+
}
|
|
3745
|
+
/** Send a signal to a process. */
|
|
3746
|
+
async kill(processId, signal = "SIGTERM") {
|
|
3747
|
+
const res = await fetch(`${this.endpoint}/process/${processId}/kill`, {
|
|
3748
|
+
method: "POST",
|
|
3749
|
+
headers: { ...this.authHeader(), "Content-Type": "application/json" },
|
|
3750
|
+
body: JSON.stringify({ signal })
|
|
3751
|
+
});
|
|
3752
|
+
if (!res.ok) throw new Error(`process.kill failed: ${await res.text()}`);
|
|
3753
|
+
}
|
|
3754
|
+
/** Write to process stdin. */
|
|
3755
|
+
async writeStdin(processId, data) {
|
|
3756
|
+
const res = await fetch(`${this.endpoint}/process/${processId}/input`, {
|
|
3757
|
+
method: "POST",
|
|
3758
|
+
headers: { ...this.authHeader(), "Content-Type": "application/json" },
|
|
3759
|
+
body: JSON.stringify({ data })
|
|
3760
|
+
});
|
|
3761
|
+
if (!res.ok) throw new Error(`process.writeStdin failed: ${await res.text()}`);
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Wait for a process to complete and return its final info.
|
|
3765
|
+
* Polls every 500ms until status is no longer 'running'.
|
|
3766
|
+
*
|
|
3767
|
+
* For real-time output, use stream() instead.
|
|
3768
|
+
*/
|
|
3769
|
+
async wait(processId, timeoutMs = 3e5) {
|
|
3770
|
+
const deadline = Date.now() + timeoutMs;
|
|
3771
|
+
while (Date.now() < deadline) {
|
|
3772
|
+
const info = await this.get(processId);
|
|
3773
|
+
if (info.status !== "running") return info;
|
|
3774
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3775
|
+
}
|
|
3776
|
+
throw new Error(`Timed out waiting for process ${processId} to complete (${timeoutMs}ms)`);
|
|
3777
|
+
}
|
|
3778
|
+
/** Stream process output as async iterable SSE events. */
|
|
3779
|
+
async *stream(processId) {
|
|
3780
|
+
const res = await fetch(
|
|
3781
|
+
`${this.endpoint}/process/${processId}/stream`,
|
|
3782
|
+
{ headers: this.authHeader() }
|
|
3783
|
+
);
|
|
3784
|
+
if (!res.ok) throw new Error(`process.stream failed: ${await res.text()}`);
|
|
3785
|
+
if (!res.body) throw new Error("process.stream: no response body");
|
|
3786
|
+
const decoder = new TextDecoder();
|
|
3787
|
+
const reader = res.body.getReader();
|
|
3788
|
+
let buffer = "";
|
|
3789
|
+
try {
|
|
3790
|
+
while (true) {
|
|
3791
|
+
const { done, value } = await reader.read();
|
|
3792
|
+
if (done) break;
|
|
3793
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3794
|
+
const lines = buffer.split("\n");
|
|
3795
|
+
buffer = lines.pop() ?? "";
|
|
3796
|
+
for (const line of lines) {
|
|
3797
|
+
if (line.startsWith("data: ")) {
|
|
3798
|
+
try {
|
|
3799
|
+
const event = JSON.parse(line.slice(6));
|
|
3800
|
+
yield event;
|
|
3801
|
+
if (event.type === "exit") return;
|
|
3802
|
+
} catch {
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
} finally {
|
|
3808
|
+
reader.releaseLock();
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
var SandboxWatch = class {
|
|
3813
|
+
constructor(endpoint, token) {
|
|
3814
|
+
this.endpoint = endpoint;
|
|
3815
|
+
this.token = token;
|
|
3816
|
+
}
|
|
3817
|
+
authHeader() {
|
|
3818
|
+
return { Authorization: `Bearer ${this.token}` };
|
|
3819
|
+
}
|
|
3820
|
+
/** Start watching a path. Events delivered via sandbox.events() SSE stream. */
|
|
3821
|
+
async add(opts) {
|
|
3822
|
+
const res = await fetch(`${this.endpoint}/watch`, {
|
|
3823
|
+
method: "POST",
|
|
3824
|
+
headers: { ...this.authHeader(), "Content-Type": "application/json" },
|
|
3825
|
+
body: JSON.stringify(opts)
|
|
3826
|
+
});
|
|
3827
|
+
if (!res.ok) throw new Error(`watch.add failed: ${await res.text()}`);
|
|
3828
|
+
return await res.json();
|
|
3829
|
+
}
|
|
3830
|
+
/** List active watches. */
|
|
3831
|
+
async list() {
|
|
3832
|
+
const res = await fetch(`${this.endpoint}/watch`, {
|
|
3833
|
+
headers: this.authHeader()
|
|
3834
|
+
});
|
|
3835
|
+
if (!res.ok) throw new Error(`watch.list failed: ${await res.text()}`);
|
|
3836
|
+
const data = await res.json();
|
|
3837
|
+
return data.watches;
|
|
3838
|
+
}
|
|
3839
|
+
/** Stop watching a path. */
|
|
3840
|
+
async remove(path) {
|
|
3841
|
+
const res = await fetch(`${this.endpoint}/watch?path=${encodeURIComponent(path)}`, {
|
|
3842
|
+
method: "DELETE",
|
|
3843
|
+
headers: this.authHeader()
|
|
3844
|
+
});
|
|
3845
|
+
if (!res.ok) throw new Error(`watch.remove failed: ${await res.text()}`);
|
|
3846
|
+
}
|
|
3847
|
+
};
|
|
3397
3848
|
var SandboxClient = class _SandboxClient {
|
|
3398
3849
|
id;
|
|
3399
3850
|
config;
|
|
@@ -3403,12 +3854,18 @@ var SandboxClient = class _SandboxClient {
|
|
|
3403
3854
|
token;
|
|
3404
3855
|
/** File operations (direct to exec-server) */
|
|
3405
3856
|
files;
|
|
3857
|
+
/** Concurrent process management (direct to exec-server) */
|
|
3858
|
+
processes;
|
|
3859
|
+
/** Filesystem watch management (direct to exec-server) */
|
|
3860
|
+
watch;
|
|
3406
3861
|
constructor(id, config, endpoint, token) {
|
|
3407
3862
|
this.id = id;
|
|
3408
3863
|
this.config = config;
|
|
3409
3864
|
this.endpoint = endpoint;
|
|
3410
3865
|
this.token = token;
|
|
3411
3866
|
this.files = endpoint && token ? new SandboxFiles(endpoint, token) : null;
|
|
3867
|
+
this.processes = endpoint && token ? new SandboxProcesses(endpoint, token) : null;
|
|
3868
|
+
this.watch = endpoint && token ? new SandboxWatch(endpoint, token) : null;
|
|
3412
3869
|
}
|
|
3413
3870
|
// ---------------------------------------------------------------------------
|
|
3414
3871
|
// Factory
|
|
@@ -3428,7 +3885,8 @@ var SandboxClient = class _SandboxClient {
|
|
|
3428
3885
|
idleTimeoutMs: options?.idleTimeoutMs ?? 3e5,
|
|
3429
3886
|
resources: options?.resources,
|
|
3430
3887
|
env: options?.env,
|
|
3431
|
-
storage: options?.storageGi !== void 0 ? { enabled: true, sizeGi: options.storageGi } : void 0
|
|
3888
|
+
storage: options?.storageGi !== void 0 ? { enabled: true, sizeGi: options.storageGi } : void 0,
|
|
3889
|
+
volumeMounts: options?.volumeMounts
|
|
3432
3890
|
}
|
|
3433
3891
|
});
|
|
3434
3892
|
return new _SandboxClient(record.id, config, record.endpoint, record.token);
|
|
@@ -3456,10 +3914,21 @@ var SandboxClient = class _SandboxClient {
|
|
|
3456
3914
|
// Exec — SSE streaming (primary)
|
|
3457
3915
|
// ---------------------------------------------------------------------------
|
|
3458
3916
|
/**
|
|
3459
|
-
* Execute a command and stream output as async iterable events.
|
|
3917
|
+
* Execute a command and stream output as async iterable SSE events.
|
|
3918
|
+
*
|
|
3919
|
+
* **Stateless mode**: each exec() call runs in an isolated bash invocation.
|
|
3920
|
+
* Shell state (CWD changes, exported env vars, functions) is NOT preserved
|
|
3921
|
+
* between calls.
|
|
3460
3922
|
*
|
|
3461
|
-
*
|
|
3462
|
-
*
|
|
3923
|
+
* For state-preserving execution (CWD, env), use `run()` which runs in the
|
|
3924
|
+
* persistent active shell and returns the result once complete.
|
|
3925
|
+
*
|
|
3926
|
+
* For streaming + state-preserving (advanced), combine `sandbox.events()` with `run()`:
|
|
3927
|
+
* ```typescript
|
|
3928
|
+
* const eventStream = sandbox.events({ type: 'stdout' })
|
|
3929
|
+
* sandbox.run(['npm', 'install']) // don't await yet
|
|
3930
|
+
* for await (const ev of eventStream) { ... }
|
|
3931
|
+
* ```
|
|
3463
3932
|
*
|
|
3464
3933
|
* @example
|
|
3465
3934
|
* ```typescript
|
|
@@ -3471,13 +3940,13 @@ var SandboxClient = class _SandboxClient {
|
|
|
3471
3940
|
*/
|
|
3472
3941
|
async *exec(command, options) {
|
|
3473
3942
|
this.assertDirect();
|
|
3474
|
-
const res = await fetch(`${this.endpoint}/exec
|
|
3943
|
+
const res = await fetch(`${this.endpoint}/exec`, {
|
|
3475
3944
|
method: "POST",
|
|
3476
3945
|
headers: {
|
|
3477
3946
|
Authorization: `Bearer ${this.token}`,
|
|
3478
3947
|
"Content-Type": "application/json"
|
|
3479
3948
|
},
|
|
3480
|
-
body: JSON.stringify({ command, ...options })
|
|
3949
|
+
body: JSON.stringify({ command, ...options, stateless: true, stream: true })
|
|
3481
3950
|
});
|
|
3482
3951
|
if (!res.ok) {
|
|
3483
3952
|
throw new Error(`exec failed (${res.status}): ${await res.text()}`);
|
|
@@ -3530,6 +3999,58 @@ var SandboxClient = class _SandboxClient {
|
|
|
3530
3999
|
return { stdout, stderr, exitCode, durationMs };
|
|
3531
4000
|
}
|
|
3532
4001
|
// ---------------------------------------------------------------------------
|
|
4002
|
+
// Events — Unified SSE stream
|
|
4003
|
+
// ---------------------------------------------------------------------------
|
|
4004
|
+
/**
|
|
4005
|
+
* Subscribe to the unified event stream (SSE).
|
|
4006
|
+
*
|
|
4007
|
+
* Receives all sandbox events: stdout, stderr, exit, port, file, shell, resource.
|
|
4008
|
+
* Filter by type/pid/shellId using query params.
|
|
4009
|
+
*
|
|
4010
|
+
* @example
|
|
4011
|
+
* ```typescript
|
|
4012
|
+
* for await (const event of sandbox.events({ type: 'file' })) {
|
|
4013
|
+
* console.log('File changed:', event.path, event.event)
|
|
4014
|
+
* }
|
|
4015
|
+
* ```
|
|
4016
|
+
*/
|
|
4017
|
+
async *events(filter) {
|
|
4018
|
+
this.assertDirect();
|
|
4019
|
+
const params = new URLSearchParams();
|
|
4020
|
+
if (filter?.type) params.set("type", filter.type);
|
|
4021
|
+
if (filter?.pid !== void 0) params.set("pid", String(filter.pid));
|
|
4022
|
+
if (filter?.shellId) params.set("shellId", filter.shellId);
|
|
4023
|
+
const qs = params.toString();
|
|
4024
|
+
const url = `${this.endpoint}/events${qs ? `?${qs}` : ""}`;
|
|
4025
|
+
const res = await fetch(url, {
|
|
4026
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
4027
|
+
});
|
|
4028
|
+
if (!res.ok) throw new Error(`events failed (${res.status}): ${await res.text()}`);
|
|
4029
|
+
if (!res.body) throw new Error("events: no response body");
|
|
4030
|
+
const decoder = new TextDecoder();
|
|
4031
|
+
const reader = res.body.getReader();
|
|
4032
|
+
let buffer = "";
|
|
4033
|
+
try {
|
|
4034
|
+
while (true) {
|
|
4035
|
+
const { done, value } = await reader.read();
|
|
4036
|
+
if (done) break;
|
|
4037
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4038
|
+
const lines = buffer.split("\n");
|
|
4039
|
+
buffer = lines.pop() ?? "";
|
|
4040
|
+
for (const line of lines) {
|
|
4041
|
+
if (line.startsWith("data: ")) {
|
|
4042
|
+
try {
|
|
4043
|
+
yield JSON.parse(line.slice(6));
|
|
4044
|
+
} catch {
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
} finally {
|
|
4050
|
+
reader.releaseLock();
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
// ---------------------------------------------------------------------------
|
|
3533
4054
|
// PTY — Interactive terminal (WebSocket)
|
|
3534
4055
|
// ---------------------------------------------------------------------------
|
|
3535
4056
|
/**
|
|
@@ -3575,7 +4096,7 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3575
4096
|
]);
|
|
3576
4097
|
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
3577
4098
|
var DEFAULT_WAIT_TIMEOUT_MS = 72e5;
|
|
3578
|
-
var
|
|
4099
|
+
var RunHandle = class {
|
|
3579
4100
|
id;
|
|
3580
4101
|
config;
|
|
3581
4102
|
constructor(id, config) {
|
|
@@ -3599,7 +4120,7 @@ var WorkerHandle = class {
|
|
|
3599
4120
|
*
|
|
3600
4121
|
* @param options.pollIntervalMs - How often to poll in ms (default: 3000)
|
|
3601
4122
|
* @param options.timeoutMs - Max time to wait before throwing (default: 7_200_000 = 2h)
|
|
3602
|
-
* @returns
|
|
4123
|
+
* @returns RunResult with exit code, status, stdout/stderr
|
|
3603
4124
|
* @throws Error if waitTimeout is exceeded
|
|
3604
4125
|
*
|
|
3605
4126
|
* @example
|
|
@@ -3667,7 +4188,7 @@ var WorkerHandle = class {
|
|
|
3667
4188
|
await callApi(this.config, `/workers/${this.id}`, { method: "DELETE" });
|
|
3668
4189
|
}
|
|
3669
4190
|
};
|
|
3670
|
-
var
|
|
4191
|
+
var RunsClient = {
|
|
3671
4192
|
// --------------------------------------------------------------------------
|
|
3672
4193
|
// Run
|
|
3673
4194
|
// --------------------------------------------------------------------------
|
|
@@ -3679,7 +4200,7 @@ var WorkersClient = {
|
|
|
3679
4200
|
*
|
|
3680
4201
|
* @example
|
|
3681
4202
|
* ```typescript
|
|
3682
|
-
* const
|
|
4203
|
+
* const run = await RunsClient.create(config, {
|
|
3683
4204
|
* image: 'registry.sylphx.com/sylphx/trainer:abc123',
|
|
3684
4205
|
* command: ['python', 'train.py', '--fold', '3'],
|
|
3685
4206
|
* resources: { requests: { cpu: '4', memory: '16Gi' } },
|
|
@@ -3689,7 +4210,7 @@ var WorkersClient = {
|
|
|
3689
4210
|
* ```
|
|
3690
4211
|
*/
|
|
3691
4212
|
async run(config, options) {
|
|
3692
|
-
const run = await callApi(config, "/
|
|
4213
|
+
const run = await callApi(config, "/runs", {
|
|
3693
4214
|
method: "POST",
|
|
3694
4215
|
body: {
|
|
3695
4216
|
image: options.image,
|
|
@@ -3700,25 +4221,25 @@ var WorkersClient = {
|
|
|
3700
4221
|
volumeMounts: options.volumeMounts
|
|
3701
4222
|
}
|
|
3702
4223
|
});
|
|
3703
|
-
return new
|
|
4224
|
+
return new RunHandle(run.id, config);
|
|
3704
4225
|
},
|
|
3705
4226
|
// --------------------------------------------------------------------------
|
|
3706
4227
|
// Get
|
|
3707
4228
|
// --------------------------------------------------------------------------
|
|
3708
4229
|
/**
|
|
3709
|
-
* Get a
|
|
4230
|
+
* Get a RunHandle for an existing run by ID.
|
|
3710
4231
|
*
|
|
3711
4232
|
* Useful for resuming monitoring across requests.
|
|
3712
4233
|
*
|
|
3713
4234
|
* @example
|
|
3714
4235
|
* ```typescript
|
|
3715
4236
|
* // Store the worker ID, retrieve later
|
|
3716
|
-
* const handle =
|
|
4237
|
+
* const handle = RunsClient.fromId(config, storedWorkerId)
|
|
3717
4238
|
* const result = await handle.wait()
|
|
3718
4239
|
* ```
|
|
3719
4240
|
*/
|
|
3720
4241
|
fromId(config, workerId) {
|
|
3721
|
-
return new
|
|
4242
|
+
return new RunHandle(workerId, config);
|
|
3722
4243
|
},
|
|
3723
4244
|
// --------------------------------------------------------------------------
|
|
3724
4245
|
// List
|
|
@@ -3728,12 +4249,12 @@ var WorkersClient = {
|
|
|
3728
4249
|
*
|
|
3729
4250
|
* @example
|
|
3730
4251
|
* ```typescript
|
|
3731
|
-
* const { workers } = await
|
|
4252
|
+
* const { workers } = await RunsClient.list(config, { status: 'running' })
|
|
3732
4253
|
* console.log(`${workers.length} workers currently running`)
|
|
3733
4254
|
* ```
|
|
3734
4255
|
*/
|
|
3735
4256
|
async list(config, options) {
|
|
3736
|
-
return callApi(config, "/
|
|
4257
|
+
return callApi(config, "/runs", {
|
|
3737
4258
|
method: "GET",
|
|
3738
4259
|
query: options?.status ? { status: options.status } : void 0
|
|
3739
4260
|
});
|
|
@@ -3744,11 +4265,11 @@ var WorkersClient = {
|
|
|
3744
4265
|
/**
|
|
3745
4266
|
* Spawn a worker and wait for it to complete in one call.
|
|
3746
4267
|
*
|
|
3747
|
-
* Equivalent to `(await
|
|
4268
|
+
* Equivalent to `(await RunsClient.create(config, options)).wait(waitOptions)`.
|
|
3748
4269
|
*
|
|
3749
4270
|
* @example
|
|
3750
4271
|
* ```typescript
|
|
3751
|
-
* const result = await
|
|
4272
|
+
* const result = await RunsClient.runAndWait(config, {
|
|
3752
4273
|
* image: 'registry.sylphx.com/sylphx/process:abc',
|
|
3753
4274
|
* command: ['node', 'dist/process.js'],
|
|
3754
4275
|
* })
|
|
@@ -3756,13 +4277,14 @@ var WorkersClient = {
|
|
|
3756
4277
|
* ```
|
|
3757
4278
|
*/
|
|
3758
4279
|
async runAndWait(config, options, waitOptions) {
|
|
3759
|
-
const handle = await
|
|
4280
|
+
const handle = await RunsClient.run(config, options);
|
|
3760
4281
|
return handle.wait(waitOptions);
|
|
3761
4282
|
}
|
|
3762
4283
|
};
|
|
3763
4284
|
function sleep2(ms) {
|
|
3764
4285
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3765
4286
|
}
|
|
4287
|
+
var WorkersClient = RunsClient;
|
|
3766
4288
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3767
4289
|
0 && (module.exports = {
|
|
3768
4290
|
ACHIEVEMENT_TIER_CONFIG,
|
|
@@ -3770,20 +4292,28 @@ function sleep2(ms) {
|
|
|
3770
4292
|
AuthorizationError,
|
|
3771
4293
|
CircuitBreakerOpenError,
|
|
3772
4294
|
ERROR_CODE_STATUS,
|
|
4295
|
+
InvalidConnectionUrlError,
|
|
3773
4296
|
NetworkError,
|
|
3774
4297
|
NotFoundError,
|
|
3775
4298
|
RETRYABLE_CODES,
|
|
3776
4299
|
RateLimitError,
|
|
4300
|
+
RunHandle,
|
|
4301
|
+
RunsClient,
|
|
3777
4302
|
SandboxClient,
|
|
4303
|
+
SandboxFiles,
|
|
4304
|
+
SandboxProcesses,
|
|
4305
|
+
SandboxWatch,
|
|
3778
4306
|
StepCompleteSignal,
|
|
3779
4307
|
StepSleepSignal,
|
|
3780
4308
|
SylphxError,
|
|
3781
4309
|
TimeoutError,
|
|
4310
|
+
TriggersClient,
|
|
3782
4311
|
ValidationError,
|
|
3783
4312
|
WorkerHandle,
|
|
3784
4313
|
WorkersClient,
|
|
3785
4314
|
acceptAllConsents,
|
|
3786
4315
|
acceptOrganizationInvitation,
|
|
4316
|
+
assignMemberRole,
|
|
3787
4317
|
batchIndex,
|
|
3788
4318
|
canDeleteOrganization,
|
|
3789
4319
|
canManageMembers,
|
|
@@ -3798,12 +4328,16 @@ function sleep2(ms) {
|
|
|
3798
4328
|
checkFlag,
|
|
3799
4329
|
complete,
|
|
3800
4330
|
createCheckout,
|
|
4331
|
+
createClient,
|
|
3801
4332
|
createConfig,
|
|
3802
4333
|
createCron,
|
|
3803
4334
|
createDynamicRestClient,
|
|
3804
4335
|
createOrganization,
|
|
4336
|
+
createPermission,
|
|
3805
4337
|
createPortalSession,
|
|
3806
4338
|
createRestClient,
|
|
4339
|
+
createRole,
|
|
4340
|
+
createServerClient,
|
|
3807
4341
|
createServiceWorkerScript,
|
|
3808
4342
|
createStepContext,
|
|
3809
4343
|
createTasksHandler,
|
|
@@ -3818,6 +4352,8 @@ function sleep2(ms) {
|
|
|
3818
4352
|
deleteEnvVar,
|
|
3819
4353
|
deleteFile,
|
|
3820
4354
|
deleteOrganization,
|
|
4355
|
+
deletePermission,
|
|
4356
|
+
deleteRole,
|
|
3821
4357
|
deleteUser,
|
|
3822
4358
|
disableDebug,
|
|
3823
4359
|
embed,
|
|
@@ -3851,6 +4387,7 @@ function sleep2(ms) {
|
|
|
3851
4387
|
getFlagPayload,
|
|
3852
4388
|
getFlags,
|
|
3853
4389
|
getLeaderboard,
|
|
4390
|
+
getMemberPermissions,
|
|
3854
4391
|
getMyReferralCode,
|
|
3855
4392
|
getOrganization,
|
|
3856
4393
|
getOrganizationInvitations,
|
|
@@ -3862,6 +4399,7 @@ function sleep2(ms) {
|
|
|
3862
4399
|
getReferralLeaderboard,
|
|
3863
4400
|
getReferralStats,
|
|
3864
4401
|
getRestErrorMessage,
|
|
4402
|
+
getRole,
|
|
3865
4403
|
getScheduledEmail,
|
|
3866
4404
|
getScheduledEmailStats,
|
|
3867
4405
|
getSearchStats,
|
|
@@ -3881,8 +4419,11 @@ function sleep2(ms) {
|
|
|
3881
4419
|
getWebhookDeliveries,
|
|
3882
4420
|
getWebhookDelivery,
|
|
3883
4421
|
getWebhookStats,
|
|
4422
|
+
hasAllPermissions,
|
|
4423
|
+
hasAnyPermission,
|
|
3884
4424
|
hasConsent,
|
|
3885
4425
|
hasError,
|
|
4426
|
+
hasPermission,
|
|
3886
4427
|
hasRole,
|
|
3887
4428
|
hasSecret,
|
|
3888
4429
|
identify,
|
|
@@ -3919,12 +4460,13 @@ function sleep2(ms) {
|
|
|
3919
4460
|
leaveOrganization,
|
|
3920
4461
|
linkAnonymousConsents,
|
|
3921
4462
|
listEnvVars,
|
|
4463
|
+
listPermissions,
|
|
4464
|
+
listRoles,
|
|
3922
4465
|
listScheduledEmails,
|
|
3923
4466
|
listSecretKeys,
|
|
3924
4467
|
listTasks,
|
|
3925
4468
|
listUsers,
|
|
3926
4469
|
page,
|
|
3927
|
-
parseKey,
|
|
3928
4470
|
pauseCron,
|
|
3929
4471
|
realtimeEmit,
|
|
3930
4472
|
recordStreakActivity,
|
|
@@ -3971,6 +4513,7 @@ function sleep2(ms) {
|
|
|
3971
4513
|
updateOrganization,
|
|
3972
4514
|
updateOrganizationMemberRole,
|
|
3973
4515
|
updatePushPreferences,
|
|
4516
|
+
updateRole,
|
|
3974
4517
|
updateUser,
|
|
3975
4518
|
updateUserMetadata,
|
|
3976
4519
|
updateWebhookConfig,
|