@hoststack.dev/sdk 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,28 +1,49 @@
1
1
  // src/errors.ts
2
2
  var HostStackError = class extends Error {
3
3
  statusCode;
4
- constructor(statusCode, message) {
4
+ /** Raw response body, if it parsed as JSON. Useful for surfacing field-level validation errors. */
5
+ body;
6
+ constructor(statusCode, message, body) {
5
7
  super(message);
6
8
  this.name = "HostStackError";
7
9
  this.statusCode = statusCode;
10
+ this.body = body;
8
11
  }
9
12
  };
10
13
  var AuthenticationError = class extends HostStackError {
11
- constructor(message = "Authentication failed. Check your API key.") {
12
- super(401, message);
14
+ constructor(message = "Authentication failed \u2014 check your API key.", body) {
15
+ super(401, message, body);
13
16
  this.name = "AuthenticationError";
14
17
  }
15
18
  };
19
+ var ForbiddenError = class extends HostStackError {
20
+ constructor(message = "Permission denied \u2014 API key lacks the required scope.", body) {
21
+ super(403, message, body);
22
+ this.name = "ForbiddenError";
23
+ }
24
+ };
16
25
  var NotFoundError = class extends HostStackError {
17
- constructor(message = "Resource not found.") {
18
- super(404, message);
26
+ constructor(message = "Resource not found.", body) {
27
+ super(404, message, body);
19
28
  this.name = "NotFoundError";
20
29
  }
21
30
  };
31
+ var ConflictError = class extends HostStackError {
32
+ constructor(message = "Conflict \u2014 the resource state prevents this operation.", body) {
33
+ super(409, message, body);
34
+ this.name = "ConflictError";
35
+ }
36
+ };
22
37
  var RateLimitError = class extends HostStackError {
23
- constructor(message = "Rate limit exceeded. Try again later.") {
24
- super(429, message);
38
+ /**
39
+ * Seconds to wait before retrying, parsed from the `Retry-After` response
40
+ * header. `undefined` if the server didn't send one.
41
+ */
42
+ retryAfter;
43
+ constructor(message = "Rate limit exceeded \u2014 back off and retry.", retryAfter, body) {
44
+ super(429, message, body);
25
45
  this.name = "RateLimitError";
46
+ this.retryAfter = retryAfter;
26
47
  }
27
48
  };
28
49
 
@@ -67,9 +88,15 @@ var DatabasesResource = class {
67
88
  constructor(client) {
68
89
  this.client = client;
69
90
  }
70
- /** List all databases for a project. */
91
+ /**
92
+ * List databases for the team. Pass `projectId` to scope to one
93
+ * project; omit it to list every database the team owns.
94
+ */
71
95
  async list(teamId, projectId) {
72
96
  const tid = await this.client.resolveId(teamId, { kind: "team" });
97
+ if (projectId === void 0) {
98
+ return this.client.request("GET", `/api/databases/${tid}`);
99
+ }
73
100
  const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
74
101
  return this.client.request("GET", `/api/databases/${tid}?projectId=${pid}`);
75
102
  }
@@ -138,6 +165,34 @@ var DatabasesResource = class {
138
165
  const did = await this.client.resolveId(databaseId, { kind: "database", teamId: tid });
139
166
  return this.client.request("POST", `/api/databases/${tid}/${did}/query`, { sql });
140
167
  }
168
+ /**
169
+ * v89 Phase 4: trigger a single-node → HA migration (Patroni 3-node
170
+ * cluster). The customer-facing database briefly goes read-only during
171
+ * the pg_dump → bootstrap → pg_restore. The standalone container stays
172
+ * running (read-only) for 24 h as a rollback target before being
173
+ * decommissioned automatically.
174
+ *
175
+ * Requires (server-side): PATRONI_ENABLED env on the deployment +
176
+ * `teams.ha_beta=true` on this team. Throws 403 otherwise.
177
+ *
178
+ * Returns 202 — the upgrade is async; poll `get(...)` until
179
+ * `pgEngineType === 'patroni'` + `status === 'available'` to confirm.
180
+ */
181
+ async upgradeToHa(teamId, databaseId) {
182
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
183
+ const did = await this.client.resolveId(databaseId, { kind: "database", teamId: tid });
184
+ return this.client.request("POST", `/api/databases/${tid}/${did}/upgrade-to-ha`);
185
+ }
186
+ /**
187
+ * v89 Phase 5: cluster topology + failover history for a Patroni-managed
188
+ * Postgres database. Returns 400 for standalone databases — call
189
+ * `get(...)` first if you need to branch.
190
+ */
191
+ async getCluster(teamId, databaseId) {
192
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
193
+ const did = await this.client.resolveId(databaseId, { kind: "database", teamId: tid });
194
+ return this.client.request("GET", `/api/databases/${tid}/${did}/cluster`);
195
+ }
141
196
  };
142
197
 
143
198
  // src/resources/deploys.ts
@@ -344,22 +399,24 @@ var EnvVarsResource = class {
344
399
  async update(teamId, serviceId, envVarId, data) {
345
400
  const tid = await this.client.resolveId(teamId, { kind: "team" });
346
401
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
347
- const eid = await this.client.resolveId(envVarId, {
348
- kind: "envVar",
349
- teamId: tid,
350
- serviceId: sid
351
- });
402
+ const eid = typeof envVarId === "number" ? envVarId : Number.parseInt(envVarId, 10);
403
+ if (Number.isNaN(eid)) {
404
+ throw new Error(
405
+ `Invalid envVarId "${envVarId}": expected a numeric id (env vars have no publicId \u2014 read it from list()).`
406
+ );
407
+ }
352
408
  return this.client.request("PATCH", `/api/services/${tid}/${sid}/env/${eid}`, data);
353
409
  }
354
410
  /** Delete an environment variable. */
355
411
  async delete(teamId, serviceId, envVarId) {
356
412
  const tid = await this.client.resolveId(teamId, { kind: "team" });
357
413
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
358
- const eid = await this.client.resolveId(envVarId, {
359
- kind: "envVar",
360
- teamId: tid,
361
- serviceId: sid
362
- });
414
+ const eid = typeof envVarId === "number" ? envVarId : Number.parseInt(envVarId, 10);
415
+ if (Number.isNaN(eid)) {
416
+ throw new Error(
417
+ `Invalid envVarId "${envVarId}": expected a numeric id (env vars have no publicId \u2014 read it from list()).`
418
+ );
419
+ }
363
420
  return this.client.request("DELETE", `/api/services/${tid}/${sid}/env/${eid}`);
364
421
  }
365
422
  /** Bulk set environment variables (create or update). */
@@ -576,7 +633,13 @@ var ServicesResource = class {
576
633
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
577
634
  return this.client.request("POST", `/api/services/${tid}/${sid}/resume`);
578
635
  }
579
- /** Get service metrics. */
636
+ /**
637
+ * Get the latest metrics snapshot for a service.
638
+ *
639
+ * Returns `metrics: null` before the first agent sample lands;
640
+ * `serverOverview: null` when the service is not currently placed
641
+ * (suspended, between deploys, etc).
642
+ */
580
643
  async getMetrics(teamId, serviceId) {
581
644
  const tid = await this.client.resolveId(teamId, { kind: "team" });
582
645
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
@@ -720,8 +783,7 @@ var PREFIX = {
720
783
  database: "db_",
721
784
  domain: "dom_",
722
785
  volume: "vol_",
723
- envVar: "env_",
724
- environment: "environment_",
786
+ environment: "env_",
725
787
  cronExecution: "cjob_"
726
788
  };
727
789
  var HostStack = class {
@@ -792,13 +854,20 @@ var HostStack = class {
792
854
  const message = data.error ?? `HTTP ${res.status}`;
793
855
  switch (res.status) {
794
856
  case 401:
795
- throw new AuthenticationError(message);
857
+ throw new AuthenticationError(message, data);
858
+ case 403:
859
+ throw new ForbiddenError(message, data);
796
860
  case 404:
797
- throw new NotFoundError(message);
798
- case 429:
799
- throw new RateLimitError(message);
861
+ throw new NotFoundError(message, data);
862
+ case 409:
863
+ throw new ConflictError(message, data);
864
+ case 429: {
865
+ const header = res.headers.get("Retry-After");
866
+ const retryAfter = header && /^\d+$/.test(header) ? Number(header) : void 0;
867
+ throw new RateLimitError(message, retryAfter, data);
868
+ }
800
869
  default:
801
- throw new HostStackError(res.status, message);
870
+ throw new HostStackError(res.status, message, data);
802
871
  }
803
872
  }
804
873
  if (res.status === 204) {
@@ -874,13 +943,6 @@ var HostStack = class {
874
943
  );
875
944
  return r.domains ?? [];
876
945
  }
877
- case "envVar": {
878
- const r = await this.request(
879
- "GET",
880
- `/api/services/${scope.teamId}/${scope.serviceId}/env`
881
- );
882
- return r.envVars ?? [];
883
- }
884
946
  case "volume": {
885
947
  const r = await this.request("GET", `/api/services/${scope.teamId}/${scope.serviceId}/volumes`);
886
948
  return r.volumes ?? [];
@@ -913,7 +975,6 @@ function cacheScope(scope) {
913
975
  return `${scope.kind}:${scope.teamId}`;
914
976
  case "deploy":
915
977
  case "volume":
916
- case "envVar":
917
978
  case "cronExecution":
918
979
  return `${scope.kind}:${scope.teamId}:${scope.serviceId}`;
919
980
  }
@@ -940,6 +1001,6 @@ function wrapArray(items, params) {
940
1001
  };
941
1002
  }
942
1003
 
943
- export { AuthenticationError, HostStack, HostStackError, NotFoundError, RateLimitError, buildPaginationQuery, wrapArray };
1004
+ export { AuthenticationError, ConflictError, ForbiddenError, HostStack, HostStackError, NotFoundError, RateLimitError, buildPaginationQuery, wrapArray };
944
1005
  //# sourceMappingURL=index.js.map
945
1006
  //# sourceMappingURL=index.js.map