@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.
- package/LICENSE +201 -0
- package/README.md +34 -0
- package/lib/credentials.d.ts +29 -0
- package/lib/credentials.d.ts.map +1 -0
- package/lib/credentials.js +49 -0
- package/lib/credentials.js.map +1 -0
- package/lib/errors.d.ts +36 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +26 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +30 -0
- package/lib/index.js.map +1 -0
- package/lib/pagination.d.ts +9 -0
- package/lib/pagination.d.ts.map +1 -0
- package/lib/pagination.js +48 -0
- package/lib/pagination.js.map +1 -0
- package/lib/protocol.d.ts +32 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +148 -0
- package/lib/protocol.js.map +1 -0
- package/lib/response-validation.test.d.ts +4 -0
- package/lib/response-validation.test.d.ts.map +1 -0
- package/lib/response-validation.test.js +31 -0
- package/lib/response-validation.test.js.map +1 -0
- package/lib/retry.d.ts +31 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/retry.js +31 -0
- package/lib/retry.js.map +1 -0
- package/lib/services/alb.d.ts +603 -0
- package/lib/services/alb.d.ts.map +1 -0
- package/lib/services/alb.js +562 -0
- package/lib/services/alb.js.map +1 -0
- package/lib/services/alb_waf.d.ts +526 -0
- package/lib/services/alb_waf.d.ts.map +1 -0
- package/lib/services/alb_waf.js +545 -0
- package/lib/services/alb_waf.js.map +1 -0
- package/lib/services/authorization.d.ts +251 -0
- package/lib/services/authorization.d.ts.map +1 -0
- package/lib/services/authorization.js +353 -0
- package/lib/services/authorization.js.map +1 -0
- package/lib/services/automation.d.ts +358 -0
- package/lib/services/automation.d.ts.map +1 -0
- package/lib/services/automation.js +426 -0
- package/lib/services/automation.js.map +1 -0
- package/lib/services/cdn.d.ts +1239 -0
- package/lib/services/cdn.d.ts.map +1 -0
- package/lib/services/cdn.js +992 -0
- package/lib/services/cdn.js.map +1 -0
- package/lib/services/certificates.d.ts +201 -0
- package/lib/services/certificates.d.ts.map +1 -0
- package/lib/services/certificates.js +197 -0
- package/lib/services/certificates.js.map +1 -0
- package/lib/services/cost.d.ts +222 -0
- package/lib/services/cost.d.ts.map +1 -0
- package/lib/services/cost.js +188 -0
- package/lib/services/cost.js.map +1 -0
- package/lib/services/dns.d.ts +875 -0
- package/lib/services/dns.d.ts.map +1 -0
- package/lib/services/dns.js +854 -0
- package/lib/services/dns.js.map +1 -0
- package/lib/services/dremio.d.ts +358 -0
- package/lib/services/dremio.d.ts.map +1 -0
- package/lib/services/dremio.js +339 -0
- package/lib/services/dremio.js.map +1 -0
- package/lib/services/edge.d.ts +312 -0
- package/lib/services/edge.d.ts.map +1 -0
- package/lib/services/edge.js +375 -0
- package/lib/services/edge.js.map +1 -0
- package/lib/services/functions.d.ts +409 -0
- package/lib/services/functions.d.ts.map +1 -0
- package/lib/services/functions.js +423 -0
- package/lib/services/functions.js.map +1 -0
- package/lib/services/git.d.ts +497 -0
- package/lib/services/git.d.ts.map +1 -0
- package/lib/services/git.js +552 -0
- package/lib/services/git.js.map +1 -0
- package/lib/services/iaas.d.ts +2965 -0
- package/lib/services/iaas.d.ts.map +1 -0
- package/lib/services/iaas.js +3768 -0
- package/lib/services/iaas.js.map +1 -0
- package/lib/services/index.d.ts +51 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +52 -0
- package/lib/services/index.js.map +1 -0
- package/lib/services/intake.d.ts +555 -0
- package/lib/services/intake.d.ts.map +1 -0
- package/lib/services/intake.js +513 -0
- package/lib/services/intake.js.map +1 -0
- package/lib/services/kms.d.ts +654 -0
- package/lib/services/kms.d.ts.map +1 -0
- package/lib/services/kms.js +703 -0
- package/lib/services/kms.js.map +1 -0
- package/lib/services/lb_ip_lists.d.ts +162 -0
- package/lib/services/lb_ip_lists.d.ts.map +1 -0
- package/lib/services/lb_ip_lists.js +173 -0
- package/lib/services/lb_ip_lists.js.map +1 -0
- package/lib/services/load_balancer.d.ts +563 -0
- package/lib/services/load_balancer.d.ts.map +1 -0
- package/lib/services/load_balancer.js +542 -0
- package/lib/services/load_balancer.js.map +1 -0
- package/lib/services/logme.d.ts +516 -0
- package/lib/services/logme.d.ts.map +1 -0
- package/lib/services/logme.js +636 -0
- package/lib/services/logme.js.map +1 -0
- package/lib/services/logs.d.ts +436 -0
- package/lib/services/logs.d.ts.map +1 -0
- package/lib/services/logs.js +498 -0
- package/lib/services/logs.js.map +1 -0
- package/lib/services/mariadb.d.ts +479 -0
- package/lib/services/mariadb.d.ts.map +1 -0
- package/lib/services/mariadb.js +611 -0
- package/lib/services/mariadb.js.map +1 -0
- package/lib/services/model_experiments.d.ts +306 -0
- package/lib/services/model_experiments.d.ts.map +1 -0
- package/lib/services/model_experiments.js +356 -0
- package/lib/services/model_experiments.js.map +1 -0
- package/lib/services/model_serving.d.ts +289 -0
- package/lib/services/model_serving.d.ts.map +1 -0
- package/lib/services/model_serving.js +319 -0
- package/lib/services/model_serving.js.map +1 -0
- package/lib/services/mongodb_flex.d.ts +826 -0
- package/lib/services/mongodb_flex.d.ts.map +1 -0
- package/lib/services/mongodb_flex.js +889 -0
- package/lib/services/mongodb_flex.js.map +1 -0
- package/lib/services/object_storage.d.ts +446 -0
- package/lib/services/object_storage.d.ts.map +1 -0
- package/lib/services/object_storage.js +577 -0
- package/lib/services/object_storage.js.map +1 -0
- package/lib/services/observability.d.ts +2750 -0
- package/lib/services/observability.d.ts.map +1 -0
- package/lib/services/observability.js +3407 -0
- package/lib/services/observability.js.map +1 -0
- package/lib/services/opensearch.d.ts +505 -0
- package/lib/services/opensearch.d.ts.map +1 -0
- package/lib/services/opensearch.js +623 -0
- package/lib/services/opensearch.js.map +1 -0
- package/lib/services/pim.d.ts +997 -0
- package/lib/services/pim.d.ts.map +1 -0
- package/lib/services/pim.js +789 -0
- package/lib/services/pim.js.map +1 -0
- package/lib/services/postgres_flex.d.ts +854 -0
- package/lib/services/postgres_flex.d.ts.map +1 -0
- package/lib/services/postgres_flex.js +1018 -0
- package/lib/services/postgres_flex.js.map +1 -0
- package/lib/services/pubsub.d.ts +489 -0
- package/lib/services/pubsub.d.ts.map +1 -0
- package/lib/services/pubsub.js +614 -0
- package/lib/services/pubsub.js.map +1 -0
- package/lib/services/rabbitmq.d.ts +523 -0
- package/lib/services/rabbitmq.d.ts.map +1 -0
- package/lib/services/rabbitmq.js +636 -0
- package/lib/services/rabbitmq.js.map +1 -0
- package/lib/services/registry.d.ts +212 -0
- package/lib/services/registry.d.ts.map +1 -0
- package/lib/services/registry.js +221 -0
- package/lib/services/registry.js.map +1 -0
- package/lib/services/resource_manager.d.ts +458 -0
- package/lib/services/resource_manager.d.ts.map +1 -0
- package/lib/services/resource_manager.js +503 -0
- package/lib/services/resource_manager.js.map +1 -0
- package/lib/services/run_command.d.ts +175 -0
- package/lib/services/run_command.d.ts.map +1 -0
- package/lib/services/run_command.js +203 -0
- package/lib/services/run_command.js.map +1 -0
- package/lib/services/sca.d.ts +541 -0
- package/lib/services/sca.d.ts.map +1 -0
- package/lib/services/sca.js +478 -0
- package/lib/services/sca.js.map +1 -0
- package/lib/services/scf.d.ts +498 -0
- package/lib/services/scf.d.ts.map +1 -0
- package/lib/services/scf.js +718 -0
- package/lib/services/scf.js.map +1 -0
- package/lib/services/secrets_manager.d.ts +505 -0
- package/lib/services/secrets_manager.d.ts.map +1 -0
- package/lib/services/secrets_manager.js +659 -0
- package/lib/services/secrets_manager.js.map +1 -0
- package/lib/services/server_backup.d.ts +380 -0
- package/lib/services/server_backup.d.ts.map +1 -0
- package/lib/services/server_backup.js +474 -0
- package/lib/services/server_backup.js.map +1 -0
- package/lib/services/server_update.d.ts +292 -0
- package/lib/services/server_update.d.ts.map +1 -0
- package/lib/services/server_update.js +364 -0
- package/lib/services/server_update.js.map +1 -0
- package/lib/services/service_account.d.ts +465 -0
- package/lib/services/service_account.d.ts.map +1 -0
- package/lib/services/service_account.js +523 -0
- package/lib/services/service_account.js.map +1 -0
- package/lib/services/service_enablement.d.ts +149 -0
- package/lib/services/service_enablement.d.ts.map +1 -0
- package/lib/services/service_enablement.js +159 -0
- package/lib/services/service_enablement.js.map +1 -0
- package/lib/services/sfs.d.ts +721 -0
- package/lib/services/sfs.d.ts.map +1 -0
- package/lib/services/sfs.js +845 -0
- package/lib/services/sfs.js.map +1 -0
- package/lib/services/ske.d.ts +536 -0
- package/lib/services/ske.d.ts.map +1 -0
- package/lib/services/ske.js +598 -0
- package/lib/services/ske.js.map +1 -0
- package/lib/services/sqlserverflex.d.ts +956 -0
- package/lib/services/sqlserverflex.d.ts.map +1 -0
- package/lib/services/sqlserverflex.js +1087 -0
- package/lib/services/sqlserverflex.js.map +1 -0
- package/lib/services/stackit_marketplace.d.ts +288 -0
- package/lib/services/stackit_marketplace.d.ts.map +1 -0
- package/lib/services/stackit_marketplace.js +393 -0
- package/lib/services/stackit_marketplace.js.map +1 -0
- package/lib/services/telemetry_link.d.ts +443 -0
- package/lib/services/telemetry_link.d.ts.map +1 -0
- package/lib/services/telemetry_link.js +550 -0
- package/lib/services/telemetry_link.js.map +1 -0
- package/lib/services/telemetry_router.d.ts +493 -0
- package/lib/services/telemetry_router.d.ts.map +1 -0
- package/lib/services/telemetry_router.js +523 -0
- package/lib/services/telemetry_router.js.map +1 -0
- package/lib/services/ufw.d.ts +295 -0
- package/lib/services/ufw.d.ts.map +1 -0
- package/lib/services/ufw.js +301 -0
- package/lib/services/ufw.js.map +1 -0
- package/lib/services/valkey.d.ts +505 -0
- package/lib/services/valkey.d.ts.map +1 -0
- package/lib/services/valkey.js +627 -0
- package/lib/services/valkey.js.map +1 -0
- package/lib/services/vpn.d.ts +1024 -0
- package/lib/services/vpn.d.ts.map +1 -0
- package/lib/services/vpn.js +1013 -0
- package/lib/services/vpn.js.map +1 -0
- package/lib/services/workflows.d.ts +600 -0
- package/lib/services/workflows.d.ts.map +1 -0
- package/lib/services/workflows.js +556 -0
- package/lib/services/workflows.js.map +1 -0
- package/lib/traits.d.ts +24 -0
- package/lib/traits.d.ts.map +1 -0
- package/lib/traits.js +15 -0
- package/lib/traits.js.map +1 -0
- package/package.json +78 -7
- package/src/credentials.ts +90 -0
- package/src/errors.ts +50 -0
- package/src/index.ts +34 -0
- package/src/pagination.ts +73 -0
- package/src/protocol.ts +243 -0
- package/src/response-validation.test.ts +45 -0
- package/src/retry.ts +53 -0
- package/src/services/alb.ts +1409 -0
- package/src/services/alb_waf.ts +1347 -0
- package/src/services/authorization.ts +740 -0
- package/src/services/automation.ts +966 -0
- package/src/services/cdn.ts +2736 -0
- package/src/services/certificates.ts +450 -0
- package/src/services/cost.ts +484 -0
- package/src/services/dns.ts +2271 -0
- package/src/services/dremio.ts +803 -0
- package/src/services/edge.ts +839 -0
- package/src/services/functions.ts +1010 -0
- package/src/services/git.ts +1275 -0
- package/src/services/iaas.ts +8670 -0
- package/src/services/index.ts +51 -0
- package/src/services/intake.ts +1233 -0
- package/src/services/kms.ts +1638 -0
- package/src/services/lb_ip_lists.ts +377 -0
- package/src/services/load_balancer.ts +1342 -0
- package/src/services/logme.ts +1379 -0
- package/src/services/logs.ts +1150 -0
- package/src/services/mariadb.ts +1293 -0
- package/src/services/model_experiments.ts +797 -0
- package/src/services/model_serving.ts +697 -0
- package/src/services/mongodb_flex.ts +2078 -0
- package/src/services/object_storage.ts +1248 -0
- package/src/services/observability.ts +7856 -0
- package/src/services/opensearch.ts +1354 -0
- package/src/services/pim.ts +2090 -0
- package/src/services/postgres_flex.ts +2326 -0
- package/src/services/pubsub.ts +1336 -0
- package/src/services/rabbitmq.ts +1402 -0
- package/src/services/registry.ts +494 -0
- package/src/services/resource_manager.ts +1155 -0
- package/src/services/run_command.ts +441 -0
- package/src/services/sca.ts +1222 -0
- package/src/services/scf.ts +1537 -0
- package/src/services/secrets_manager.ts +1399 -0
- package/src/services/server_backup.ts +1032 -0
- package/src/services/server_update.ts +767 -0
- package/src/services/service_account.ts +1208 -0
- package/src/services/service_enablement.ts +359 -0
- package/src/services/sfs.ts +1913 -0
- package/src/services/ske.ts +1408 -0
- package/src/services/sqlserverflex.ts +2543 -0
- package/src/services/stackit_marketplace.ts +853 -0
- package/src/services/telemetry_link.ts +1245 -0
- package/src/services/telemetry_router.ts +1205 -0
- package/src/services/ufw.ts +704 -0
- package/src/services/valkey.ts +1357 -0
- package/src/services/vpn.ts +2554 -0
- package/src/services/workflows.ts +1361 -0
- package/src/traits.ts +65 -0
package/package.json
CHANGED
|
@@ -1,15 +1,86 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@distilled.cloud/stackit",
|
|
3
|
-
"version": "
|
|
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
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
};
|
package/src/protocol.ts
ADDED
|
@@ -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);
|