@distilled.cloud/stackit 0.0.0-placeholder → 1.0.0-rc.13

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.
Files changed (298) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +34 -0
  3. package/lib/credentials.d.ts +29 -0
  4. package/lib/credentials.d.ts.map +1 -0
  5. package/lib/credentials.js +49 -0
  6. package/lib/credentials.js.map +1 -0
  7. package/lib/errors.d.ts +36 -0
  8. package/lib/errors.d.ts.map +1 -0
  9. package/lib/errors.js +26 -0
  10. package/lib/errors.js.map +1 -0
  11. package/lib/index.d.ts +30 -0
  12. package/lib/index.d.ts.map +1 -0
  13. package/lib/index.js +30 -0
  14. package/lib/index.js.map +1 -0
  15. package/lib/pagination.d.ts +9 -0
  16. package/lib/pagination.d.ts.map +1 -0
  17. package/lib/pagination.js +48 -0
  18. package/lib/pagination.js.map +1 -0
  19. package/lib/protocol.d.ts +32 -0
  20. package/lib/protocol.d.ts.map +1 -0
  21. package/lib/protocol.js +148 -0
  22. package/lib/protocol.js.map +1 -0
  23. package/lib/response-validation.test.d.ts +4 -0
  24. package/lib/response-validation.test.d.ts.map +1 -0
  25. package/lib/response-validation.test.js +31 -0
  26. package/lib/response-validation.test.js.map +1 -0
  27. package/lib/retry.d.ts +31 -0
  28. package/lib/retry.d.ts.map +1 -0
  29. package/lib/retry.js +31 -0
  30. package/lib/retry.js.map +1 -0
  31. package/lib/services/alb.d.ts +603 -0
  32. package/lib/services/alb.d.ts.map +1 -0
  33. package/lib/services/alb.js +562 -0
  34. package/lib/services/alb.js.map +1 -0
  35. package/lib/services/alb_waf.d.ts +526 -0
  36. package/lib/services/alb_waf.d.ts.map +1 -0
  37. package/lib/services/alb_waf.js +545 -0
  38. package/lib/services/alb_waf.js.map +1 -0
  39. package/lib/services/authorization.d.ts +251 -0
  40. package/lib/services/authorization.d.ts.map +1 -0
  41. package/lib/services/authorization.js +353 -0
  42. package/lib/services/authorization.js.map +1 -0
  43. package/lib/services/automation.d.ts +358 -0
  44. package/lib/services/automation.d.ts.map +1 -0
  45. package/lib/services/automation.js +426 -0
  46. package/lib/services/automation.js.map +1 -0
  47. package/lib/services/cdn.d.ts +1239 -0
  48. package/lib/services/cdn.d.ts.map +1 -0
  49. package/lib/services/cdn.js +992 -0
  50. package/lib/services/cdn.js.map +1 -0
  51. package/lib/services/certificates.d.ts +201 -0
  52. package/lib/services/certificates.d.ts.map +1 -0
  53. package/lib/services/certificates.js +197 -0
  54. package/lib/services/certificates.js.map +1 -0
  55. package/lib/services/cost.d.ts +222 -0
  56. package/lib/services/cost.d.ts.map +1 -0
  57. package/lib/services/cost.js +188 -0
  58. package/lib/services/cost.js.map +1 -0
  59. package/lib/services/dns.d.ts +875 -0
  60. package/lib/services/dns.d.ts.map +1 -0
  61. package/lib/services/dns.js +854 -0
  62. package/lib/services/dns.js.map +1 -0
  63. package/lib/services/dremio.d.ts +358 -0
  64. package/lib/services/dremio.d.ts.map +1 -0
  65. package/lib/services/dremio.js +339 -0
  66. package/lib/services/dremio.js.map +1 -0
  67. package/lib/services/edge.d.ts +312 -0
  68. package/lib/services/edge.d.ts.map +1 -0
  69. package/lib/services/edge.js +375 -0
  70. package/lib/services/edge.js.map +1 -0
  71. package/lib/services/functions.d.ts +409 -0
  72. package/lib/services/functions.d.ts.map +1 -0
  73. package/lib/services/functions.js +423 -0
  74. package/lib/services/functions.js.map +1 -0
  75. package/lib/services/git.d.ts +497 -0
  76. package/lib/services/git.d.ts.map +1 -0
  77. package/lib/services/git.js +552 -0
  78. package/lib/services/git.js.map +1 -0
  79. package/lib/services/iaas.d.ts +2965 -0
  80. package/lib/services/iaas.d.ts.map +1 -0
  81. package/lib/services/iaas.js +3768 -0
  82. package/lib/services/iaas.js.map +1 -0
  83. package/lib/services/index.d.ts +51 -0
  84. package/lib/services/index.d.ts.map +1 -0
  85. package/lib/services/index.js +52 -0
  86. package/lib/services/index.js.map +1 -0
  87. package/lib/services/intake.d.ts +555 -0
  88. package/lib/services/intake.d.ts.map +1 -0
  89. package/lib/services/intake.js +513 -0
  90. package/lib/services/intake.js.map +1 -0
  91. package/lib/services/kms.d.ts +654 -0
  92. package/lib/services/kms.d.ts.map +1 -0
  93. package/lib/services/kms.js +703 -0
  94. package/lib/services/kms.js.map +1 -0
  95. package/lib/services/lb_ip_lists.d.ts +162 -0
  96. package/lib/services/lb_ip_lists.d.ts.map +1 -0
  97. package/lib/services/lb_ip_lists.js +173 -0
  98. package/lib/services/lb_ip_lists.js.map +1 -0
  99. package/lib/services/load_balancer.d.ts +563 -0
  100. package/lib/services/load_balancer.d.ts.map +1 -0
  101. package/lib/services/load_balancer.js +542 -0
  102. package/lib/services/load_balancer.js.map +1 -0
  103. package/lib/services/logme.d.ts +516 -0
  104. package/lib/services/logme.d.ts.map +1 -0
  105. package/lib/services/logme.js +636 -0
  106. package/lib/services/logme.js.map +1 -0
  107. package/lib/services/logs.d.ts +436 -0
  108. package/lib/services/logs.d.ts.map +1 -0
  109. package/lib/services/logs.js +498 -0
  110. package/lib/services/logs.js.map +1 -0
  111. package/lib/services/mariadb.d.ts +479 -0
  112. package/lib/services/mariadb.d.ts.map +1 -0
  113. package/lib/services/mariadb.js +611 -0
  114. package/lib/services/mariadb.js.map +1 -0
  115. package/lib/services/model_experiments.d.ts +306 -0
  116. package/lib/services/model_experiments.d.ts.map +1 -0
  117. package/lib/services/model_experiments.js +356 -0
  118. package/lib/services/model_experiments.js.map +1 -0
  119. package/lib/services/model_serving.d.ts +289 -0
  120. package/lib/services/model_serving.d.ts.map +1 -0
  121. package/lib/services/model_serving.js +319 -0
  122. package/lib/services/model_serving.js.map +1 -0
  123. package/lib/services/mongodb_flex.d.ts +826 -0
  124. package/lib/services/mongodb_flex.d.ts.map +1 -0
  125. package/lib/services/mongodb_flex.js +889 -0
  126. package/lib/services/mongodb_flex.js.map +1 -0
  127. package/lib/services/object_storage.d.ts +446 -0
  128. package/lib/services/object_storage.d.ts.map +1 -0
  129. package/lib/services/object_storage.js +577 -0
  130. package/lib/services/object_storage.js.map +1 -0
  131. package/lib/services/observability.d.ts +2750 -0
  132. package/lib/services/observability.d.ts.map +1 -0
  133. package/lib/services/observability.js +3407 -0
  134. package/lib/services/observability.js.map +1 -0
  135. package/lib/services/opensearch.d.ts +505 -0
  136. package/lib/services/opensearch.d.ts.map +1 -0
  137. package/lib/services/opensearch.js +623 -0
  138. package/lib/services/opensearch.js.map +1 -0
  139. package/lib/services/pim.d.ts +997 -0
  140. package/lib/services/pim.d.ts.map +1 -0
  141. package/lib/services/pim.js +789 -0
  142. package/lib/services/pim.js.map +1 -0
  143. package/lib/services/postgres_flex.d.ts +854 -0
  144. package/lib/services/postgres_flex.d.ts.map +1 -0
  145. package/lib/services/postgres_flex.js +1018 -0
  146. package/lib/services/postgres_flex.js.map +1 -0
  147. package/lib/services/pubsub.d.ts +489 -0
  148. package/lib/services/pubsub.d.ts.map +1 -0
  149. package/lib/services/pubsub.js +614 -0
  150. package/lib/services/pubsub.js.map +1 -0
  151. package/lib/services/rabbitmq.d.ts +523 -0
  152. package/lib/services/rabbitmq.d.ts.map +1 -0
  153. package/lib/services/rabbitmq.js +636 -0
  154. package/lib/services/rabbitmq.js.map +1 -0
  155. package/lib/services/registry.d.ts +212 -0
  156. package/lib/services/registry.d.ts.map +1 -0
  157. package/lib/services/registry.js +221 -0
  158. package/lib/services/registry.js.map +1 -0
  159. package/lib/services/resource_manager.d.ts +458 -0
  160. package/lib/services/resource_manager.d.ts.map +1 -0
  161. package/lib/services/resource_manager.js +503 -0
  162. package/lib/services/resource_manager.js.map +1 -0
  163. package/lib/services/run_command.d.ts +175 -0
  164. package/lib/services/run_command.d.ts.map +1 -0
  165. package/lib/services/run_command.js +203 -0
  166. package/lib/services/run_command.js.map +1 -0
  167. package/lib/services/sca.d.ts +541 -0
  168. package/lib/services/sca.d.ts.map +1 -0
  169. package/lib/services/sca.js +478 -0
  170. package/lib/services/sca.js.map +1 -0
  171. package/lib/services/scf.d.ts +498 -0
  172. package/lib/services/scf.d.ts.map +1 -0
  173. package/lib/services/scf.js +718 -0
  174. package/lib/services/scf.js.map +1 -0
  175. package/lib/services/secrets_manager.d.ts +505 -0
  176. package/lib/services/secrets_manager.d.ts.map +1 -0
  177. package/lib/services/secrets_manager.js +659 -0
  178. package/lib/services/secrets_manager.js.map +1 -0
  179. package/lib/services/server_backup.d.ts +380 -0
  180. package/lib/services/server_backup.d.ts.map +1 -0
  181. package/lib/services/server_backup.js +474 -0
  182. package/lib/services/server_backup.js.map +1 -0
  183. package/lib/services/server_update.d.ts +292 -0
  184. package/lib/services/server_update.d.ts.map +1 -0
  185. package/lib/services/server_update.js +364 -0
  186. package/lib/services/server_update.js.map +1 -0
  187. package/lib/services/service_account.d.ts +465 -0
  188. package/lib/services/service_account.d.ts.map +1 -0
  189. package/lib/services/service_account.js +523 -0
  190. package/lib/services/service_account.js.map +1 -0
  191. package/lib/services/service_enablement.d.ts +149 -0
  192. package/lib/services/service_enablement.d.ts.map +1 -0
  193. package/lib/services/service_enablement.js +159 -0
  194. package/lib/services/service_enablement.js.map +1 -0
  195. package/lib/services/sfs.d.ts +721 -0
  196. package/lib/services/sfs.d.ts.map +1 -0
  197. package/lib/services/sfs.js +845 -0
  198. package/lib/services/sfs.js.map +1 -0
  199. package/lib/services/ske.d.ts +536 -0
  200. package/lib/services/ske.d.ts.map +1 -0
  201. package/lib/services/ske.js +598 -0
  202. package/lib/services/ske.js.map +1 -0
  203. package/lib/services/sqlserverflex.d.ts +956 -0
  204. package/lib/services/sqlserverflex.d.ts.map +1 -0
  205. package/lib/services/sqlserverflex.js +1087 -0
  206. package/lib/services/sqlserverflex.js.map +1 -0
  207. package/lib/services/stackit_marketplace.d.ts +288 -0
  208. package/lib/services/stackit_marketplace.d.ts.map +1 -0
  209. package/lib/services/stackit_marketplace.js +393 -0
  210. package/lib/services/stackit_marketplace.js.map +1 -0
  211. package/lib/services/telemetry_link.d.ts +443 -0
  212. package/lib/services/telemetry_link.d.ts.map +1 -0
  213. package/lib/services/telemetry_link.js +550 -0
  214. package/lib/services/telemetry_link.js.map +1 -0
  215. package/lib/services/telemetry_router.d.ts +493 -0
  216. package/lib/services/telemetry_router.d.ts.map +1 -0
  217. package/lib/services/telemetry_router.js +523 -0
  218. package/lib/services/telemetry_router.js.map +1 -0
  219. package/lib/services/ufw.d.ts +295 -0
  220. package/lib/services/ufw.d.ts.map +1 -0
  221. package/lib/services/ufw.js +301 -0
  222. package/lib/services/ufw.js.map +1 -0
  223. package/lib/services/valkey.d.ts +505 -0
  224. package/lib/services/valkey.d.ts.map +1 -0
  225. package/lib/services/valkey.js +627 -0
  226. package/lib/services/valkey.js.map +1 -0
  227. package/lib/services/vpn.d.ts +1024 -0
  228. package/lib/services/vpn.d.ts.map +1 -0
  229. package/lib/services/vpn.js +1013 -0
  230. package/lib/services/vpn.js.map +1 -0
  231. package/lib/services/workflows.d.ts +600 -0
  232. package/lib/services/workflows.d.ts.map +1 -0
  233. package/lib/services/workflows.js +556 -0
  234. package/lib/services/workflows.js.map +1 -0
  235. package/lib/traits.d.ts +24 -0
  236. package/lib/traits.d.ts.map +1 -0
  237. package/lib/traits.js +15 -0
  238. package/lib/traits.js.map +1 -0
  239. package/package.json +78 -7
  240. package/src/credentials.ts +90 -0
  241. package/src/errors.ts +50 -0
  242. package/src/index.ts +34 -0
  243. package/src/pagination.ts +73 -0
  244. package/src/protocol.ts +243 -0
  245. package/src/response-validation.test.ts +45 -0
  246. package/src/retry.ts +53 -0
  247. package/src/services/alb.ts +1409 -0
  248. package/src/services/alb_waf.ts +1347 -0
  249. package/src/services/authorization.ts +740 -0
  250. package/src/services/automation.ts +966 -0
  251. package/src/services/cdn.ts +2736 -0
  252. package/src/services/certificates.ts +450 -0
  253. package/src/services/cost.ts +484 -0
  254. package/src/services/dns.ts +2271 -0
  255. package/src/services/dremio.ts +803 -0
  256. package/src/services/edge.ts +839 -0
  257. package/src/services/functions.ts +1010 -0
  258. package/src/services/git.ts +1275 -0
  259. package/src/services/iaas.ts +8670 -0
  260. package/src/services/index.ts +51 -0
  261. package/src/services/intake.ts +1233 -0
  262. package/src/services/kms.ts +1638 -0
  263. package/src/services/lb_ip_lists.ts +377 -0
  264. package/src/services/load_balancer.ts +1342 -0
  265. package/src/services/logme.ts +1379 -0
  266. package/src/services/logs.ts +1150 -0
  267. package/src/services/mariadb.ts +1293 -0
  268. package/src/services/model_experiments.ts +797 -0
  269. package/src/services/model_serving.ts +697 -0
  270. package/src/services/mongodb_flex.ts +2078 -0
  271. package/src/services/object_storage.ts +1248 -0
  272. package/src/services/observability.ts +7856 -0
  273. package/src/services/opensearch.ts +1354 -0
  274. package/src/services/pim.ts +2090 -0
  275. package/src/services/postgres_flex.ts +2326 -0
  276. package/src/services/pubsub.ts +1336 -0
  277. package/src/services/rabbitmq.ts +1402 -0
  278. package/src/services/registry.ts +494 -0
  279. package/src/services/resource_manager.ts +1155 -0
  280. package/src/services/run_command.ts +441 -0
  281. package/src/services/sca.ts +1222 -0
  282. package/src/services/scf.ts +1537 -0
  283. package/src/services/secrets_manager.ts +1399 -0
  284. package/src/services/server_backup.ts +1032 -0
  285. package/src/services/server_update.ts +767 -0
  286. package/src/services/service_account.ts +1208 -0
  287. package/src/services/service_enablement.ts +359 -0
  288. package/src/services/sfs.ts +1913 -0
  289. package/src/services/ske.ts +1408 -0
  290. package/src/services/sqlserverflex.ts +2543 -0
  291. package/src/services/stackit_marketplace.ts +853 -0
  292. package/src/services/telemetry_link.ts +1245 -0
  293. package/src/services/telemetry_router.ts +1205 -0
  294. package/src/services/ufw.ts +704 -0
  295. package/src/services/valkey.ts +1357 -0
  296. package/src/services/vpn.ts +2554 -0
  297. package/src/services/workflows.ts +1361 -0
  298. package/src/traits.ts +65 -0
package/package.json CHANGED
@@ -1,15 +1,86 @@
1
1
  {
2
2
  "name": "@distilled.cloud/stackit",
3
- "version": "0.0.0-placeholder",
4
- "description": "Placeholder package for initial npm registration.",
5
- "license": "Apache-2.0",
3
+ "version": "1.0.0-rc.13",
6
4
  "repository": {
7
5
  "type": "git",
8
6
  "url": "https://github.com/alchemy-run/distilled",
9
7
  "directory": "packages/stackit"
10
8
  },
11
- "publishConfig": {
12
- "access": "public",
13
- "tag": "placeholder"
9
+ "license": "Apache-2.0",
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "module": "src/index.ts",
13
+ "files": [
14
+ "lib",
15
+ "src"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./lib/index.d.ts",
20
+ "bun": "./src/index.ts",
21
+ "worker": "./src/index.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./Credentials": {
25
+ "types": "./lib/credentials.d.ts",
26
+ "bun": "./src/credentials.ts",
27
+ "worker": "./src/credentials.ts",
28
+ "default": "./lib/credentials.js"
29
+ },
30
+ "./Errors": {
31
+ "types": "./lib/errors.d.ts",
32
+ "bun": "./src/errors.ts",
33
+ "worker": "./src/errors.ts",
34
+ "default": "./lib/errors.js"
35
+ },
36
+ "./Pagination": {
37
+ "types": "./lib/pagination.d.ts",
38
+ "bun": "./src/pagination.ts",
39
+ "worker": "./src/pagination.ts",
40
+ "default": "./lib/pagination.js"
41
+ },
42
+ "./Protocol": {
43
+ "types": "./lib/protocol.d.ts",
44
+ "bun": "./src/protocol.ts",
45
+ "worker": "./src/protocol.ts",
46
+ "default": "./lib/protocol.js"
47
+ },
48
+ "./Retry": {
49
+ "types": "./lib/retry.d.ts",
50
+ "bun": "./src/retry.ts",
51
+ "worker": "./src/retry.ts",
52
+ "default": "./lib/retry.js"
53
+ },
54
+ "./Traits": {
55
+ "types": "./lib/traits.d.ts",
56
+ "bun": "./src/traits.ts",
57
+ "worker": "./src/traits.ts",
58
+ "default": "./lib/traits.js"
59
+ },
60
+ "./*": {
61
+ "types": "./lib/services/*.d.ts",
62
+ "bun": "./src/services/*.ts",
63
+ "worker": "./src/services/*.ts",
64
+ "default": "./lib/services/*.js"
65
+ }
66
+ },
67
+ "dependencies": {
68
+ "@distilled.cloud/core": "1.0.0-rc.13"
69
+ },
70
+ "devDependencies": {
71
+ "@types/bun": "1.3.14",
72
+ "@types/node": "26.2.0",
73
+ "@effect/platform-bun": "^4.0.0"
74
+ },
75
+ "peerDependencies": {
76
+ "effect": "^4.0.0"
77
+ },
78
+ "scripts": {
79
+ "build": "tsc -b",
80
+ "convert": "bun scripts/convert.ts",
81
+ "generate": "bun scripts/generate.ts",
82
+ "typecheck": "tsc -p tsconfig.json",
83
+ "specs:fetch": "git submodule update --force --init --depth=1 specs/spec-mirror-stackit",
84
+ "specs:update": "git submodule update --remote --force --depth=1 specs/spec-mirror-stackit"
14
85
  }
15
- }
86
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * STACKIT credentials — hand-written.
3
+ *
4
+ * The `Credentials` service resolves `{ token, region }` per request; the
5
+ * protocol layer formats the `Authorization: Bearer <token>` header from it
6
+ * and rewrites each operation's spec `servers` URL for the region. The token
7
+ * is a STACKIT service-account access token — the same value the official
8
+ * Go SDK, CLI and Terraform provider read from
9
+ * `STACKIT_SERVICE_ACCOUNT_TOKEN`. Key-file exchange
10
+ * (`STACKIT_SERVICE_ACCOUNT_KEY_PATH`) is not modelled: mint the token
11
+ * outside the SDK.
12
+ *
13
+ * Each STACKIT product has its own host (`iaas.api.eu01.stackit.cloud`,
14
+ * `ske.api.eu01.stackit.cloud`, …). The per-operation host is baked into
15
+ * the generated `T.Http` trait from the OpenAPI `servers` entry; `region`
16
+ * (default `eu01`) is substituted at request time. An explicit `apiBaseUrl`
17
+ * overrides that host for every call — a proxy, not a per-product map.
18
+ */
19
+ import * as EffectConfig from "effect/Config";
20
+ import * as Context from "effect/Context";
21
+ import * as Effect from "effect/Effect";
22
+ import * as Layer from "effect/Layer";
23
+ import * as Redacted from "effect/Redacted";
24
+ import { ConfigError } from "@distilled.cloud/core/errors";
25
+
26
+ /** STACKIT's primary public region (Schwarz IT / Germany). */
27
+ export const DEFAULT_REGION = "eu01";
28
+
29
+ export interface Config {
30
+ readonly token: Redacted.Redacted<string>;
31
+ /**
32
+ * Region inserted into product hosts (`eu01` →
33
+ * `https://iaas.api.eu01.stackit.cloud`). `"global"` leaves the spec URL
34
+ * unchanged.
35
+ */
36
+ readonly region: string;
37
+ /** When set, every operation uses this host instead of the spec's. */
38
+ readonly apiBaseUrl?: string;
39
+ }
40
+
41
+ export class Credentials extends Context.Service<
42
+ Credentials,
43
+ Effect.Effect<Config>
44
+ >()("StackitCredentials") {}
45
+
46
+ const envConfig = EffectConfig.all({
47
+ // `STACKIT_SERVICE_ACCOUNT_TOKEN` / `STACKIT_REGION` are what the
48
+ // official Go SDK, CLI and Terraform provider read.
49
+ token: EffectConfig.String("STACKIT_SERVICE_ACCOUNT_TOKEN"),
50
+ region: EffectConfig.String("STACKIT_REGION").pipe(
51
+ EffectConfig.withDefault(DEFAULT_REGION),
52
+ ),
53
+ apiBaseUrl: EffectConfig.String("STACKIT_API_BASE_URL").pipe(
54
+ EffectConfig.option,
55
+ ),
56
+ });
57
+
58
+ export const CredentialsFromEnv = Layer.succeed(
59
+ Credentials,
60
+ envConfig.pipe(
61
+ Effect.mapError(
62
+ () =>
63
+ new ConfigError({
64
+ message:
65
+ "STACKIT_SERVICE_ACCOUNT_TOKEN environment variable is required",
66
+ }),
67
+ ),
68
+ Effect.map(({ token, region, apiBaseUrl }) => ({
69
+ token: Redacted.make(token),
70
+ region,
71
+ apiBaseUrl: apiBaseUrl._tag === "Some" ? apiBaseUrl.value : undefined,
72
+ })),
73
+ Effect.orDie,
74
+ ),
75
+ );
76
+
77
+ /** Convenience layer from a plain token + optional region / base URL. */
78
+ export const credentials = (config: {
79
+ readonly token: string;
80
+ readonly region?: string;
81
+ readonly apiBaseUrl?: string;
82
+ }): Layer.Layer<Credentials> =>
83
+ Layer.succeed(
84
+ Credentials,
85
+ Effect.succeed({
86
+ token: Redacted.make(config.token),
87
+ region: config.region ?? DEFAULT_REGION,
88
+ apiBaseUrl: config.apiBaseUrl,
89
+ }),
90
+ );
package/src/errors.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * STACKIT-specific error types.
3
+ *
4
+ * Re-exports the common HTTP errors from core and adds the unknown-error
5
+ * and parse-error wrappers. STACKIT failures carry `{ message, error? }`
6
+ * (the `ErrorMessage` schema in most of the product specs); the protocol
7
+ * reads `message` for the human half and `error` as the machine-readable
8
+ * code on {@link UnknownStackitError}.
9
+ */
10
+ export {
11
+ BadGateway,
12
+ BadRequest,
13
+ Conflict,
14
+ ConfigError,
15
+ Forbidden,
16
+ GatewayTimeout,
17
+ InternalServerError,
18
+ Locked,
19
+ NotFound,
20
+ ServiceUnavailable,
21
+ TooManyRequests,
22
+ Unauthorized,
23
+ UnprocessableEntity,
24
+ HTTP_STATUS_MAP,
25
+ DEFAULT_ERRORS,
26
+ API_ERRORS,
27
+ } from "@distilled.cloud/core/errors";
28
+ export type { DefaultErrors } from "@distilled.cloud/core/errors";
29
+
30
+ import * as Schema from "effect/Schema";
31
+ import * as Category from "@distilled.cloud/core/category";
32
+
33
+ /** Unknown STACKIT error — returned when nothing else matches the failure. */
34
+ export class UnknownStackitError extends Schema.TaggedError<UnknownStackitError>()(
35
+ "UnknownStackitError",
36
+ {
37
+ code: Schema.optional(Schema.String),
38
+ message: Schema.optional(Schema.String),
39
+ body: Schema.Unknown,
40
+ },
41
+ ).pipe(Category.withServerError) {}
42
+
43
+ /** Schema parse error wrapper. */
44
+ export class StackitParseError extends Schema.TaggedError<StackitParseError>()(
45
+ "StackitParseError",
46
+ {
47
+ body: Schema.Unknown,
48
+ cause: Schema.Unknown,
49
+ },
50
+ ).pipe(Category.withParseError) {}
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @distilled.cloud/stackit — STACKIT Cloud API SDK for Effect.
3
+ *
4
+ * `./services` is generated by `scripts/generate.ts` from the Smithy models
5
+ * in `.generated-specs` (written by `scripts/convert.ts` from the OpenAPI
6
+ * documents mirrored at `specs/spec-mirror-stackit/specs/`). Everything else
7
+ * in this folder is hand-written.
8
+ *
9
+ * One module per STACKIT product (IaaS, SKE, DNS, Postgres Flex, …), using
10
+ * that product's latest published OpenAPI version. Auth is a service-account
11
+ * bearer token (`STACKIT_SERVICE_ACCOUNT_TOKEN`); each product's host is
12
+ * rewritten for `STACKIT_REGION` (default `eu01`).
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import * as Stackit from "@distilled.cloud/stackit";
17
+ *
18
+ * const { zones } = yield* Stackit.dns.listZones({
19
+ * projectId: "...",
20
+ * });
21
+ * ```
22
+ */
23
+ export * from "./credentials.ts";
24
+ export * from "./errors.ts";
25
+ export * as T from "./traits.ts";
26
+ export {
27
+ StackitProtocol,
28
+ applyRegion,
29
+ type StackitOpError,
30
+ type StackitOpContext,
31
+ } from "./protocol.ts";
32
+ export { paginateByTotalPages } from "./pagination.ts";
33
+ export * as Retry from "./retry.ts";
34
+ export * from "./services/index.ts";
@@ -0,0 +1,73 @@
1
+ /**
2
+ * STACKIT pagination — hand-written.
3
+ *
4
+ * Several STACKIT list operations page by NUMBER: `?page=N&pageSize=M` in,
5
+ * and a response carrying the collection beside `totalPages` (DNS zones /
6
+ * record sets, and the same envelope on other products). Core's
7
+ * `paginatePageNumber` treats `outputToken` as the NEXT page number, so
8
+ * pointing it at `totalPages` would jump to the last page; this strategy
9
+ * advances by one until `page >= totalPages`.
10
+ */
11
+ import * as Effect from "effect/Effect";
12
+ import * as Stream from "effect/Stream";
13
+ import {
14
+ getPath,
15
+ type PaginationStrategy,
16
+ } from "@distilled.cloud/core/pagination";
17
+
18
+ export type {
19
+ PaginatedTrait,
20
+ PaginationStrategy,
21
+ } from "@distilled.cloud/core/pagination";
22
+
23
+ /**
24
+ * Stream of pages using STACKIT's `page` / `totalPages` envelope: request
25
+ * `page`, `page+1`, … while `page < totalPages` and the collection is
26
+ * non-empty.
27
+ */
28
+ export const paginateByTotalPages: PaginationStrategy = (
29
+ operation,
30
+ input,
31
+ pagination,
32
+ ) => {
33
+ const inputToken = pagination.inputToken;
34
+ const totalPagesPath = pagination.outputToken;
35
+ const itemsPath = pagination.items;
36
+ if (!inputToken || !totalPagesPath) {
37
+ return Stream.die(
38
+ new Error(
39
+ "Total-pages pagination requires inputToken and outputToken (totalPages)",
40
+ ),
41
+ );
42
+ }
43
+
44
+ type State = { page: number; done: boolean };
45
+ const startPage =
46
+ typeof input[inputToken] === "number" ? (input[inputToken] as number) : 1;
47
+
48
+ return Stream.unfold({ page: startPage, done: false } as State, (state) =>
49
+ Effect.gen(function* () {
50
+ if (state.done) return undefined;
51
+
52
+ const requestPayload = {
53
+ ...input,
54
+ [inputToken]: state.page,
55
+ } as typeof input;
56
+ const response = yield* operation(requestPayload);
57
+
58
+ const totalPages = getPath(response, totalPagesPath);
59
+ const items = itemsPath
60
+ ? (getPath(response, itemsPath) as readonly unknown[] | undefined)
61
+ : undefined;
62
+ const exhausted =
63
+ typeof totalPages !== "number" ||
64
+ state.page >= totalPages ||
65
+ (items !== undefined && items.length === 0);
66
+
67
+ return [
68
+ response,
69
+ { page: state.page + 1, done: exhausted } satisfies State,
70
+ ] as const;
71
+ }),
72
+ );
73
+ };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * StackitProtocol — the shared bearer-REST protocol instantiated for STACKIT.
3
+ *
4
+ * STACKIT speaks plain JSON with no success envelope. FAILURES are
5
+ * `{ "message": "...", "error": "..." }` (the `ErrorMessage` schema most
6
+ * product specs share). Each product has its own host, baked into the
7
+ * generated operation's `T.Http({ baseUrl })` from the OpenAPI `servers`
8
+ * entry; this protocol reads that URL and rewrites it for the configured
9
+ * region (`eu01` by default).
10
+ *
11
+ * Auth is `Authorization: Bearer <token>`. The API version is a PATH
12
+ * segment (`/v1`, `/v2`, …), so the generated operations carry versioned
13
+ * routes against a version-less host.
14
+ */
15
+ import * as Effect from "effect/Effect";
16
+ import * as Layer from "effect/Layer";
17
+ import * as Redacted from "effect/Redacted";
18
+ import type * as AST from "effect/SchemaAST";
19
+ import type * as HttpClient from "effect/http/HttpClient";
20
+ import type * as HttpClientError from "effect/http/HttpClientError";
21
+ import type * as HttpClientRequest from "effect/http/HttpClientRequest";
22
+ import type * as HttpClientResponse from "effect/http/HttpClientResponse";
23
+ import * as API from "@distilled.cloud/core/api";
24
+ import {
25
+ HTTP_STATUS_MAP,
26
+ InternalServerError,
27
+ type ConfigError,
28
+ } from "@distilled.cloud/core/errors";
29
+ import {
30
+ buildRequest,
31
+ getAnn,
32
+ mapKeys,
33
+ matchTypedError,
34
+ } from "@distilled.cloud/core/protocol-http";
35
+ import {
36
+ unwrapRedactedDeep,
37
+ wrapSensitive,
38
+ type RestErrorEnvelope,
39
+ } from "@distilled.cloud/core/protocol-rest";
40
+ import { validateResponse } from "@distilled.cloud/core/response-validation";
41
+ import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
42
+ import { httpSymbol } from "@distilled.cloud/core/trait";
43
+ import { Credentials, type Config } from "./credentials.ts";
44
+ import { StackitParseError, UnknownStackitError } from "./errors.ts";
45
+ import type { DefaultErrors } from "./errors.ts";
46
+ import type { StackitHttpTrait } from "./traits.ts";
47
+
48
+ /**
49
+ * Error channel shared by every generated STACKIT operation. Generated
50
+ * service files annotate operations with `API.OperationMethod<I, O,
51
+ * StackitOpError, StackitOpContext>` explicitly so the compiler never infers
52
+ * these back out of the schema generics.
53
+ */
54
+ export type StackitOpError =
55
+ | DefaultErrors
56
+ | UnknownStackitError
57
+ | StackitParseError
58
+ | ConfigError
59
+ | HttpClientError.HttpClientError;
60
+
61
+ /** Context (requirements) shared by every generated STACKIT operation. */
62
+ export type StackitOpContext = Credentials | HttpClient.HttpClient;
63
+
64
+ /**
65
+ * Rewrite a spec `servers` URL for the configured region.
66
+ *
67
+ * • `{region}` still in the template (IaaS v1:
68
+ * `https://iaas.api.{region}stackit.cloud`, default `eu01.`) is
69
+ * substituted. A glued `{region}stackit` means the spec's default
70
+ * includes the trailing dot.
71
+ * • `https://<svc>.api.stackit.cloud` becomes
72
+ * `https://<svc>.api.<region>.stackit.cloud`.
73
+ * • `region === "global"` leaves the URL alone (after dropping any
74
+ * leftover `{region}`).
75
+ */
76
+ export const applyRegion = (specUrl: string, region: string): string => {
77
+ if (specUrl.includes("{region}")) {
78
+ if (region === "global") return specUrl.replaceAll("{region}", "");
79
+ const glued = specUrl.includes("{region}stackit");
80
+ const value = glued && !region.endsWith(".") ? `${region}.` : region;
81
+ return specUrl.replaceAll("{region}", value);
82
+ }
83
+ if (region === "global") return specUrl;
84
+ return specUrl.replace(
85
+ /^(https:\/\/[a-z0-9-]+)\.api\.stackit\.cloud(?=[:/?]|$)/i,
86
+ `$1.api.${region}.stackit.cloud`,
87
+ );
88
+ };
89
+
90
+ const errorEnvelope = (body: unknown): RestErrorEnvelope | undefined => {
91
+ if (body === null || typeof body !== "object") return undefined;
92
+ const b = body as Record<string, unknown>;
93
+ const message = typeof b.message === "string" ? b.message : undefined;
94
+ const code =
95
+ typeof b.error === "string"
96
+ ? b.error
97
+ : typeof b.code === "string" || typeof b.code === "number"
98
+ ? b.code
99
+ : undefined;
100
+ if (message === undefined && code === undefined) return undefined;
101
+ return { code, message };
102
+ };
103
+
104
+ const fail = (e: unknown): Effect.Effect<never> =>
105
+ Effect.fail(e) as Effect.Effect<never>;
106
+
107
+ const baseUrlFor = (creds: Config, inputAst: AST.AST): string => {
108
+ if (creds.apiBaseUrl !== undefined && creds.apiBaseUrl !== "") {
109
+ return creds.apiBaseUrl;
110
+ }
111
+ const http = getAnn(inputAst, httpSymbol) as StackitHttpTrait | undefined;
112
+ const specUrl = http?.baseUrl;
113
+ if (typeof specUrl !== "string" || specUrl === "") {
114
+ throw new Error(
115
+ "STACKIT operation is missing T.Http({ baseUrl }) — regenerate from convert",
116
+ );
117
+ }
118
+ return applyRegion(specUrl, creds.region);
119
+ };
120
+
121
+ export const StackitProtocol: Layer.Layer<API.Protocol> = Layer.succeed(
122
+ API.Protocol,
123
+ API.Protocol.of({
124
+ encode: ({
125
+ input,
126
+ inputAst,
127
+ }: {
128
+ readonly input: unknown;
129
+ readonly inputAst: AST.AST;
130
+ }) =>
131
+ Effect.gen(function* () {
132
+ const resolve = yield* Credentials;
133
+ const creds = yield* resolve;
134
+ return buildRequest({
135
+ input: unwrapRedactedDeep(input),
136
+ inputAst,
137
+ baseUrl: baseUrlFor(creds, inputAst),
138
+ headers: {
139
+ Authorization: `Bearer ${Redacted.value(creds.token)}`,
140
+ },
141
+ });
142
+ }) as Effect.Effect<HttpClientRequest.HttpClientRequest>,
143
+ decode: ({
144
+ response,
145
+ outputAst,
146
+ errors: errorClasses,
147
+ }: {
148
+ readonly response: HttpClientResponse.HttpClientResponse;
149
+ readonly outputAst: AST.AST;
150
+ readonly errors: ReadonlyArray<unknown>;
151
+ }) =>
152
+ Effect.gen(function* () {
153
+ const text = (yield* response.text.pipe(Effect.orDie)) ?? "";
154
+ if (process.env.DISTILLED_DEBUG_HTTP) {
155
+ console.error(
156
+ `[distilled] <- ${response.status} ${text.slice(0, 400)}`,
157
+ );
158
+ }
159
+ let json: unknown;
160
+ let nonJson = false;
161
+ if (text.trim().length > 0) {
162
+ try {
163
+ json = JSON.parse(text);
164
+ } catch {
165
+ nonJson = true;
166
+ }
167
+ }
168
+ const status = response.status;
169
+ const headers = response.headers as Record<string, string | undefined>;
170
+
171
+ if (status >= 400) {
172
+ const env = (nonJson ? undefined : errorEnvelope(json)) ?? {};
173
+ const message =
174
+ env.message ??
175
+ (nonJson && text.trim() ? text.trim() : `HTTP ${status}`);
176
+
177
+ const typed = matchTypedError(
178
+ errorClasses,
179
+ status,
180
+ [
181
+ {
182
+ code: typeof env.code === "number" ? env.code : undefined,
183
+ message,
184
+ },
185
+ ],
186
+ { body: nonJson ? text : json, headers },
187
+ );
188
+ if (typed !== undefined) return yield* fail(typed);
189
+
190
+ const StatusErrorClass = (
191
+ HTTP_STATUS_MAP as Record<
192
+ number,
193
+ | (new (args: {
194
+ message: string;
195
+ retryAfter?: unknown;
196
+ }) => unknown)
197
+ | undefined
198
+ >
199
+ )[status];
200
+ if (StatusErrorClass) {
201
+ return yield* fail(
202
+ new StatusErrorClass({
203
+ message,
204
+ retryAfter: parseRetryAfterForStatus(status, headers),
205
+ }),
206
+ );
207
+ }
208
+
209
+ if (status >= 500) {
210
+ return yield* fail(
211
+ new InternalServerError({
212
+ message,
213
+ retryAfter: parseRetryAfterForStatus(status, headers),
214
+ }),
215
+ );
216
+ }
217
+
218
+ return yield* fail(
219
+ new UnknownStackitError({
220
+ code:
221
+ typeof env.code === "string"
222
+ ? env.code
223
+ : env.code !== undefined
224
+ ? String(env.code)
225
+ : undefined,
226
+ message,
227
+ body: nonJson ? text : json,
228
+ }),
229
+ );
230
+ }
231
+
232
+ // Strict mode checks the mapped body against the output schema.
233
+ const body: unknown = nonJson ? text : (json ?? {});
234
+ const mapped = yield* validateResponse(
235
+ outputAst,
236
+ mapKeys(outputAst, body, "decode"),
237
+ (cause) =>
238
+ new StackitParseError({ body: nonJson ? text : json, cause }),
239
+ ).pipe(Effect.catch(fail));
240
+ return wrapSensitive(outputAst, mapped);
241
+ }),
242
+ }),
243
+ );
@@ -0,0 +1,45 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { runValidationModes } from "@distilled.cloud/core/testing";
3
+ import * as Effect from "effect/Effect";
4
+ import { credentials } from "./credentials.ts";
5
+ import { StackitParseError } from "./errors.ts";
6
+ import * as Retry from "./retry.ts";
7
+ import { listKeyPairs } from "./services/iaas.ts";
8
+ import type { StackitOpError } from "./protocol.ts";
9
+
10
+ // listKeyPairs declares `{ items: Keypair[] }`.
11
+ const run = (body: string) =>
12
+ runValidationModes(
13
+ listKeyPairs({}).pipe(
14
+ Retry.none,
15
+ Effect.provide(credentials({ token: "test" })),
16
+ ),
17
+ { body },
18
+ );
19
+
20
+ describe("STACKIT response validation", () => {
21
+ test("a matching body succeeds unchanged in both modes", async () => {
22
+ const body = { items: [{ name: "key-1", publicKey: "ssh-ed25519 AAAA" }] };
23
+ const { lenient, strict } = await run(JSON.stringify(body));
24
+ expect(lenient).toMatchObject({ _tag: "Success", success: body });
25
+ expect(strict).toMatchObject({ _tag: "Success", success: body });
26
+ });
27
+
28
+ test("a body missing required members: lenient returns it, strict fails", async () => {
29
+ const { lenient, strict } = await run("{}");
30
+ expect(lenient).toMatchObject({ _tag: "Success", success: {} });
31
+ expect(strict._tag).toBe("Failure");
32
+ expect((strict as any).failure).toBeInstanceOf(StackitParseError);
33
+ });
34
+
35
+ test("a non-JSON body: lenient returns the text, strict fails", async () => {
36
+ const { lenient, strict } = await run("not json");
37
+ expect(lenient).toMatchObject({ _tag: "Success", success: "not json" });
38
+ expect((strict as any).failure).toBeInstanceOf(StackitParseError);
39
+ });
40
+ });
41
+
42
+ // StackitParseError is part of every operation's declared error type.
43
+ export const parseErrorIsDeclared: [StackitParseError] extends [StackitOpError]
44
+ ? true
45
+ : false = true;
package/src/retry.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * STACKIT retry configuration.
3
+ *
4
+ * Defines the per-SDK `Retry` Context.Service tag that generated operations
5
+ * wire into `API.make`. Callers can install a blanket retry policy at the
6
+ * layer level and have every STACKIT API call below it pick it up:
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import * as Stackit from "@distilled.cloud/stackit";
11
+ *
12
+ * myEffect.pipe(Stackit.Retry.transient);
13
+ * ```
14
+ */
15
+ import * as Context from "effect/Context";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Layer from "effect/Layer";
18
+ import {
19
+ type Policy,
20
+ throttlingFactory,
21
+ transientFactory,
22
+ } from "@distilled.cloud/core/retry";
23
+
24
+ export {
25
+ type Options,
26
+ type Factory,
27
+ type Policy,
28
+ makeDefault,
29
+ jittered,
30
+ capped,
31
+ throttlingOptions,
32
+ transientOptions,
33
+ throttlingFactory,
34
+ transientFactory,
35
+ } from "@distilled.cloud/core/retry";
36
+
37
+ /** Context tag for configuring retry behavior of STACKIT API calls. */
38
+ export class Retry extends Context.Service<Retry, Policy>()("StackitRetry") {}
39
+
40
+ /** Provides a custom retry policy to every STACKIT API call below it. */
41
+ export const policy = (optionsOrFactory: Policy) =>
42
+ Effect.provide(Layer.succeed(Retry, optionsOrFactory));
43
+
44
+ /** Disables all automatic retries. */
45
+ export const none = Effect.provide(
46
+ Layer.succeed(Retry, { while: () => false }),
47
+ );
48
+
49
+ /** Apply the throttling retry policy (retries throttling errors indefinitely). */
50
+ export const throttling = policy(throttlingFactory);
51
+
52
+ /** Apply the transient retry policy (retries all transient errors indefinitely). */
53
+ export const transient = policy(transientFactory);