@hoststack.dev/sdk 0.3.0 → 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.js CHANGED
@@ -182,6 +182,24 @@ var DeploysResource = class {
182
182
  });
183
183
  return this.client.request("POST", `/api/services/${tid}/${sid}/deploys/${did}/rollback`);
184
184
  }
185
+ /**
186
+ * v66 P5: promote a built deploy to a sibling service in another
187
+ * environment. Reuses the source deploy's docker image (no rebuild).
188
+ * If no sibling service exists in the target env yet, the API auto-
189
+ * creates one by cloning the source service's config.
190
+ */
191
+ async promote(teamId, serviceId, deployId, targetEnvironmentId) {
192
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
193
+ const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
194
+ const did = await this.client.resolveId(deployId, {
195
+ kind: "deploy",
196
+ teamId: tid,
197
+ serviceId: sid
198
+ });
199
+ return this.client.request("POST", `/api/services/${tid}/${sid}/deploys/${did}/promote`, {
200
+ targetEnvironmentId
201
+ });
202
+ }
185
203
  /**
186
204
  * Get build logs for a deploy.
187
205
  *
@@ -243,6 +261,50 @@ var DomainsResource = class {
243
261
  }
244
262
  };
245
263
 
264
+ // src/resources/environments.ts
265
+ var EnvironmentsResource = class {
266
+ constructor(client) {
267
+ this.client = client;
268
+ }
269
+ /** List all environments for a project. */
270
+ async list(teamId, projectId) {
271
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
272
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
273
+ return this.client.request("GET", `/api/environments/${tid}/${pid}`);
274
+ }
275
+ /** Get a single environment by id. */
276
+ async get(teamId, projectId, envId) {
277
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
278
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
279
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
280
+ return this.client.request("GET", `/api/environments/${tid}/${pid}/${eid}`);
281
+ }
282
+ /** Create a new environment in the given project. */
283
+ async create(teamId, projectId, data) {
284
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
285
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
286
+ return this.client.request("POST", `/api/environments/${tid}/${pid}`, data);
287
+ }
288
+ /** Update environment metadata (name, default flag, protected flag). */
289
+ async update(teamId, projectId, envId, data) {
290
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
291
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
292
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
293
+ return this.client.request("PATCH", `/api/environments/${tid}/${pid}/${eid}`, data);
294
+ }
295
+ /**
296
+ * Delete an environment. The API blocks delete when the env still
297
+ * has services or databases attached — destroy them first or move
298
+ * them to another env. Cannot delete the project's default env.
299
+ */
300
+ async delete(teamId, projectId, envId) {
301
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
302
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
303
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
304
+ return this.client.request("DELETE", `/api/environments/${tid}/${pid}/${eid}`);
305
+ }
306
+ };
307
+
246
308
  // src/resources/env-vars.ts
247
309
  var EnvVarsResource = class {
248
310
  constructor(client) {
@@ -290,6 +352,65 @@ var EnvVarsResource = class {
290
352
  }
291
353
  };
292
354
 
355
+ // src/resources/notifications.ts
356
+ var NotificationsResource = class {
357
+ constructor(client) {
358
+ this.client = client;
359
+ }
360
+ /**
361
+ * List notification channels for the team. Webhook URLs are
362
+ * server-side masked in the response so this is safe to log.
363
+ */
364
+ async listChannels(teamId) {
365
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
366
+ return this.client.request("GET", `/api/notifications/${tid}/channels`);
367
+ }
368
+ /**
369
+ * Create a Slack/Discord/email notification channel.
370
+ *
371
+ * For type=email, `webhookUrl` is the recipient email address; for
372
+ * type=slack/discord it's the incoming webhook URL.
373
+ *
374
+ * `events` is the explicit subscription list — an empty array means
375
+ * "receive nothing". The platform pre-selects the critical-event
376
+ * set on the dashboard, but SDK callers must pass the list
377
+ * explicitly so behaviour is deterministic.
378
+ */
379
+ async createChannel(teamId, data) {
380
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
381
+ return this.client.request("POST", `/api/notifications/${tid}/channels`, data);
382
+ }
383
+ /**
384
+ * Update a channel's name / active state / event subscriptions. The
385
+ * webhook URL and type are immutable — create a new channel if
386
+ * those need to change.
387
+ */
388
+ async updateChannel(teamId, channelId, data) {
389
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
390
+ return this.client.request(
391
+ "PATCH",
392
+ `/api/notifications/${tid}/channels/${channelId}`,
393
+ data
394
+ );
395
+ }
396
+ /** Delete a notification channel. */
397
+ async deleteChannel(teamId, channelId) {
398
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
399
+ return this.client.request("DELETE", `/api/notifications/${tid}/channels/${channelId}`);
400
+ }
401
+ /**
402
+ * Fire a test event to the channel so the user can confirm the
403
+ * webhook is wired correctly. Returns the dispatch outcome.
404
+ */
405
+ async testChannel(teamId, channelId) {
406
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
407
+ return this.client.request(
408
+ "POST",
409
+ `/api/notifications/${tid}/channels/${channelId}/test`
410
+ );
411
+ }
412
+ };
413
+
293
414
  // src/resources/projects.ts
294
415
  var ProjectsResource = class {
295
416
  constructor(client) {
@@ -333,7 +454,7 @@ async function* streamLogsViaPolling(fetch2, basePath, options = {}) {
333
454
  const buildPath = (lines, since) => {
334
455
  const params = new URLSearchParams();
335
456
  if (options.stream) params.set("stream", options.stream);
336
- if (lines !== void 0) params.set("lines", String(lines));
457
+ if (lines !== void 0) params.set("limit", String(lines));
337
458
  if (since) params.set("since", since);
338
459
  const qs = params.toString();
339
460
  return `${basePath}${qs ? `?${qs}` : ""}`;
@@ -387,10 +508,23 @@ var ServicesResource = class {
387
508
  constructor(client) {
388
509
  this.client = client;
389
510
  }
390
- /** List all services for the active team. */
391
- async list(teamId) {
511
+ /**
512
+ * List services for the active team.
513
+ *
514
+ * Optional filters narrow by project, environment, status, or type.
515
+ * The server treats unknown enum values as no match (returns empty)
516
+ * rather than 400-ing.
517
+ */
518
+ async list(teamId, filters) {
392
519
  const tid = await this.client.resolveId(teamId, { kind: "team" });
393
- return this.client.request("GET", `/api/services/${tid}`);
520
+ const params = new URLSearchParams();
521
+ if (filters?.projectId !== void 0) params.set("projectId", String(filters.projectId));
522
+ if (filters?.environmentId !== void 0)
523
+ params.set("environmentId", String(filters.environmentId));
524
+ if (filters?.status) params.set("status", filters.status);
525
+ if (filters?.type) params.set("type", filters.type);
526
+ const qs = params.toString();
527
+ return this.client.request("GET", `/api/services/${tid}${qs ? `?${qs}` : ""}`);
394
528
  }
395
529
  /** Get a single service by ID. */
396
530
  async get(teamId, serviceId) {
@@ -433,6 +567,25 @@ var ServicesResource = class {
433
567
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
434
568
  return this.client.request("GET", `/api/services/${tid}/${sid}/metrics`);
435
569
  }
570
+ /**
571
+ * Get a metrics time series for a service.
572
+ *
573
+ * `from`/`to` accept ISO-8601 timestamps. Omit both for the trailing
574
+ * hour. Server picks the resolution: raw samples ≤7d, hourly pre-
575
+ * aggregates ≤30d, daily beyond that. Up to ~500 points returned.
576
+ */
577
+ async getMetricsHistory(teamId, serviceId, options) {
578
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
579
+ const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
580
+ const params = new URLSearchParams();
581
+ if (options?.from) params.set("from", options.from);
582
+ if (options?.to) params.set("to", options.to);
583
+ const qs = params.toString();
584
+ return this.client.request(
585
+ "GET",
586
+ `/api/services/${tid}/${sid}/metrics/history${qs ? `?${qs}` : ""}`
587
+ );
588
+ }
436
589
  /** Get service configuration. */
437
590
  async getConfig(teamId, serviceId) {
438
591
  const tid = await this.client.resolveId(teamId, { kind: "team" });
@@ -445,14 +598,30 @@ var ServicesResource = class {
445
598
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
446
599
  return this.client.request("PATCH", `/api/services/${tid}/${sid}/config`, data);
447
600
  }
448
- /** Get runtime logs for a service. */
601
+ /**
602
+ * Get runtime logs for a service.
603
+ *
604
+ * `since`/`until` accept either an ISO-8601 timestamp or a short
605
+ * relative offset like `-5m`, `-1h`, `-2d`.
606
+ *
607
+ * `search` does case-insensitive substring filtering server-side
608
+ * (≤100 chars). `countOnly` returns just `{ count: N }` for cheap
609
+ * polling — useful when you want to know "how many error lines in the
610
+ * last 5 minutes" without paying the bytes.
611
+ */
449
612
  async getRuntimeLogs(teamId, serviceId, options) {
450
613
  const tid = await this.client.resolveId(teamId, { kind: "team" });
451
614
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
452
615
  const params = new URLSearchParams();
453
- if (options?.lines) params.set("lines", String(options.lines));
616
+ const lim = options?.limit ?? options?.lines;
617
+ if (lim != null) params.set("limit", String(lim));
454
618
  if (options?.since) params.set("since", options.since);
619
+ if (options?.until) params.set("until", options.until);
455
620
  if (options?.stream) params.set("stream", options.stream);
621
+ if (options?.level) params.set("level", options.level);
622
+ const grep = options?.grep ?? options?.search;
623
+ if (grep) params.set("search", grep);
624
+ if (options?.countOnly) params.set("count_only", "1");
456
625
  const qs = params.toString();
457
626
  return this.client.request(
458
627
  "GET",
@@ -475,11 +644,7 @@ var ServicesResource = class {
475
644
  const tid = await this.client.resolveId(teamId, { kind: "team" });
476
645
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
477
646
  const basePath = `/api/services/${tid}/${sid}/runtime-logs`;
478
- yield* streamLogsViaPolling(
479
- (path) => this.client.request("GET", path),
480
- basePath,
481
- options
482
- );
647
+ yield* streamLogsViaPolling((path) => this.client.request("GET", path), basePath, options);
483
648
  }
484
649
  };
485
650
 
@@ -541,6 +706,7 @@ var PREFIX = {
541
706
  domain: "dom_",
542
707
  volume: "vol_",
543
708
  envVar: "env_",
709
+ environment: "environment_",
544
710
  cronExecution: "cjob_"
545
711
  };
546
712
  var HostStack = class {
@@ -560,10 +726,18 @@ var HostStack = class {
560
726
  domains;
561
727
  /** Manage service environment variables. */
562
728
  envVars;
729
+ /** v66: manage environments (production/staging/development/preview) per project. */
730
+ environments;
563
731
  /** Manage cron job executions. */
564
732
  cron;
565
733
  /** Manage persistent disks attached to services. */
566
734
  volumes;
735
+ /**
736
+ * Manage notification channels — Slack/Discord webhooks + email
737
+ * recipients with per-channel event filters. Used for deploy
738
+ * failures, restart loops, ACME failures, git auth losses, etc.
739
+ */
740
+ notifications;
567
741
  constructor(options) {
568
742
  if (!options.apiKey) {
569
743
  throw new Error("apiKey is required");
@@ -576,8 +750,10 @@ var HostStack = class {
576
750
  this.databases = new DatabasesResource(this);
577
751
  this.domains = new DomainsResource(this);
578
752
  this.envVars = new EnvVarsResource(this);
753
+ this.environments = new EnvironmentsResource(this);
579
754
  this.cron = new CronResource(this);
580
755
  this.volumes = new VolumesResource(this);
756
+ this.notifications = new NotificationsResource(this);
581
757
  }
582
758
  /**
583
759
  * Make an authenticated request to the HostStack API.
@@ -669,17 +845,11 @@ var HostStack = class {
669
845
  return r.services ?? [];
670
846
  }
671
847
  case "deploy": {
672
- const r = await this.request(
673
- "GET",
674
- `/api/services/${scope.teamId}/${scope.serviceId}/deploys?perPage=100`
675
- );
848
+ const r = await this.request("GET", `/api/services/${scope.teamId}/${scope.serviceId}/deploys?perPage=100`);
676
849
  return r.data ?? [];
677
850
  }
678
851
  case "database": {
679
- const r = await this.request(
680
- "GET",
681
- `/api/databases/${scope.teamId}`
682
- );
852
+ const r = await this.request("GET", `/api/databases/${scope.teamId}`);
683
853
  return r.databases ?? [];
684
854
  }
685
855
  case "domain": {
@@ -704,6 +874,15 @@ var HostStack = class {
704
874
  const r = await this.request("GET", `/api/services/${scope.teamId}/${scope.serviceId}/cron-executions`);
705
875
  return r.executions ?? [];
706
876
  }
877
+ case "environment": {
878
+ const projectsRes = await this.request("GET", `/api/projects/${scope.teamId}`);
879
+ const envs = [];
880
+ for (const p of projectsRes.projects ?? []) {
881
+ const r = await this.request("GET", `/api/environments/${scope.teamId}/${p.id}`);
882
+ envs.push(...r.environments ?? []);
883
+ }
884
+ return envs;
885
+ }
707
886
  }
708
887
  }
709
888
  };
@@ -715,6 +894,7 @@ function cacheScope(scope) {
715
894
  case "service":
716
895
  case "database":
717
896
  case "domain":
897
+ case "environment":
718
898
  return `${scope.kind}:${scope.teamId}`;
719
899
  case "deploy":
720
900
  case "volume":