@distilled.cloud/doppler 0.0.0 → 1.0.0-rc.9

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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/lib/credentials.d.ts +32 -0
  3. package/lib/credentials.d.ts.map +1 -0
  4. package/lib/credentials.js +38 -0
  5. package/lib/credentials.js.map +1 -0
  6. package/lib/errors.d.ts +36 -0
  7. package/lib/errors.d.ts.map +1 -0
  8. package/lib/errors.js +26 -0
  9. package/lib/errors.js.map +1 -0
  10. package/lib/index.d.ts +25 -0
  11. package/lib/index.d.ts.map +1 -0
  12. package/lib/index.js +29 -0
  13. package/lib/index.js.map +1 -0
  14. package/lib/pagination.d.ts +8 -0
  15. package/lib/pagination.d.ts.map +1 -0
  16. package/lib/pagination.js +8 -0
  17. package/lib/pagination.js.map +1 -0
  18. package/lib/protocol.d.ts +18 -0
  19. package/lib/protocol.d.ts.map +1 -0
  20. package/lib/protocol.js +68 -0
  21. package/lib/protocol.js.map +1 -0
  22. package/lib/retry.d.ts +32 -0
  23. package/lib/retry.d.ts.map +1 -0
  24. package/lib/retry.js +32 -0
  25. package/lib/retry.js.map +1 -0
  26. package/lib/services/doppler.d.ts +3424 -0
  27. package/lib/services/doppler.d.ts.map +1 -0
  28. package/lib/services/doppler.js +4330 -0
  29. package/lib/services/doppler.js.map +1 -0
  30. package/lib/services/index.d.ts +2 -0
  31. package/lib/services/index.d.ts.map +1 -0
  32. package/lib/services/index.js +3 -0
  33. package/lib/services/index.js.map +1 -0
  34. package/lib/traits.d.ts +11 -0
  35. package/lib/traits.d.ts.map +1 -0
  36. package/lib/traits.js +11 -0
  37. package/lib/traits.js.map +1 -0
  38. package/package.json +77 -6
  39. package/src/credentials.ts +61 -0
  40. package/src/errors.ts +50 -0
  41. package/src/index.ts +32 -0
  42. package/src/pagination.ts +7 -0
  43. package/src/protocol.ts +91 -0
  44. package/src/retry.ts +54 -0
  45. package/src/services/doppler.ts +10151 -0
  46. package/src/services/index.ts +2 -0
  47. package/src/traits.ts +44 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Doppler credentials — hand-written.
3
+ *
4
+ * The `Credentials` service holds an *effect* that resolves the current
5
+ * credentials on every request (the protocol layer resolves it per request
6
+ * on the calling fiber). Auth is a Doppler API token sent as
7
+ * `Authorization: Bearer <token>`.
8
+ */
9
+ import * as Context from "effect/Context";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Redacted from "effect/Redacted";
13
+ import { ConfigError } from "@distilled.cloud/core/errors";
14
+
15
+ export const DEFAULT_API_BASE_URL = "https://api.doppler.com";
16
+
17
+ export interface Config {
18
+ readonly apiKey: Redacted.Redacted<string>;
19
+ readonly apiBaseUrl: string;
20
+ }
21
+
22
+ export class Credentials extends Context.Service<
23
+ Credentials,
24
+ Effect.Effect<Config>
25
+ >()("DopplerCredentials") {}
26
+
27
+ /** Layer from a plain API token + optional base URL. */
28
+ export const fromApiKey = (config: {
29
+ readonly apiKey: string;
30
+ readonly apiBaseUrl?: string;
31
+ }): Layer.Layer<Credentials> =>
32
+ Layer.succeed(
33
+ Credentials,
34
+ Effect.succeed({
35
+ apiKey: Redacted.make(config.apiKey),
36
+ apiBaseUrl: config.apiBaseUrl ?? DEFAULT_API_BASE_URL,
37
+ }),
38
+ );
39
+
40
+ /**
41
+ * Reads DOPPLER_TOKEN or DOPPLER_API_KEY (required) and
42
+ * DOPPLER_API_BASE_URL (optional).
43
+ */
44
+ export const CredentialsFromEnv: Layer.Layer<Credentials> = Layer.succeed(
45
+ Credentials,
46
+ Effect.gen(function* () {
47
+ const apiKey = process.env.DOPPLER_TOKEN ?? process.env.DOPPLER_API_KEY;
48
+
49
+ if (!apiKey) {
50
+ return yield* new ConfigError({
51
+ message:
52
+ "DOPPLER_TOKEN (or DOPPLER_API_KEY) environment variable is required",
53
+ });
54
+ }
55
+
56
+ return {
57
+ apiKey: Redacted.make(apiKey),
58
+ apiBaseUrl: process.env.DOPPLER_API_BASE_URL ?? DEFAULT_API_BASE_URL,
59
+ };
60
+ }).pipe(Effect.orDie),
61
+ );
package/src/errors.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Doppler-specific error types.
3
+ *
4
+ * Re-exports the common HTTP errors from core and adds the Doppler fallback
5
+ * errors. Note the generated service module additionally defines its own
6
+ * per-status matcher classes (BadRequest/NotFound/…) for the statuses each
7
+ * operation declares — those share `_tag`s with the core classes here, so
8
+ * `catchTag` works against either.
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 Doppler error — returned when nothing else matches the failure. */
34
+ export class UnknownDopplerError extends Schema.TaggedError<UnknownDopplerError>()(
35
+ "UnknownDopplerError",
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 DopplerParseError extends Schema.TaggedError<DopplerParseError>()(
45
+ "DopplerParseError",
46
+ {
47
+ body: Schema.Unknown,
48
+ cause: Schema.Unknown,
49
+ },
50
+ ).pipe(Category.withParseError) {}
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @distilled.cloud/doppler — Doppler SDK for Effect.
3
+ *
4
+ * `./services` is generated by `scripts/generate.ts` from the Smithy model
5
+ * in `.generated-specs` (written by `scripts/convert.ts` from the OpenAPI
6
+ * spec assembled from https://docs.doppler.com/llms.txt). Everything else
7
+ * in this folder is hand-written.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import * as Doppler from "@distilled.cloud/doppler";
12
+ *
13
+ * const projects = yield* Doppler.Services.doppler.projectsList({});
14
+ * ```
15
+ */
16
+ export * from "./credentials.ts";
17
+ export * from "./errors.ts";
18
+ export * as T from "./traits.ts";
19
+ export {
20
+ DopplerProtocol,
21
+ type DopplerOpError,
22
+ type DopplerOpContext,
23
+ } from "./protocol.ts";
24
+ export { paginateCursor } from "./pagination.ts";
25
+ export * as Retry from "./retry.ts";
26
+ export * as Services from "./services/index.ts";
27
+ // Operations (and their request/response types) are importable straight off
28
+ // the package root. The service-local typed error classes shadow the
29
+ // same-named shared ones from ./errors.ts — the ops actually raise the
30
+ // service-local classes.
31
+ export * from "./services/doppler.ts";
32
+ export { BadRequest, Conflict, NotFound } from "./services/doppler.ts";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Doppler pagination — hand-written.
3
+ *
4
+ * Most Doppler lists are page/per_page. Generated operations that the
5
+ * converter stamps as cursor-mode pass core's {@link paginateCursor}.
6
+ */
7
+ export { paginateCursor } from "@distilled.cloud/core/pagination";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * DopplerProtocol — hand-written.
3
+ *
4
+ * Doppler speaks plain bearer-authenticated JSON REST (no response envelope),
5
+ * so the whole protocol is one `makeRestProtocol` call from
6
+ * `core/protocol-rest`:
7
+ *
8
+ * request: credentials → `Authorization: Bearer <token>` + base URL
9
+ * (default https://api.doppler.com), resolved from the
10
+ * calling fiber on every request
11
+ *
12
+ * response: 2xx JSON is the payload (sensitive members delivered as
13
+ * `Redacted`); non-2xx `{ messages?: string[], message? }`
14
+ * bodies map to the operation's typed error classes by status,
15
+ * then the shared HTTP-status classes, then
16
+ * {@link UnknownDopplerError}.
17
+ */
18
+ import * as Effect from "effect/Effect";
19
+ import type * as Layer from "effect/Layer";
20
+ import * as Redacted from "effect/Redacted";
21
+ import type * as HttpClient from "effect/unstable/http/HttpClient";
22
+ import type * as HttpClientError from "effect/unstable/http/HttpClientError";
23
+ import type * as API from "@distilled.cloud/core/api";
24
+ import { makeRestProtocol } from "@distilled.cloud/core/protocol-rest";
25
+ import type { API_ERRORS, ConfigError } from "@distilled.cloud/core/errors";
26
+ import { Credentials, type Config } from "./credentials.ts";
27
+ import { UnknownDopplerError } from "./errors.ts";
28
+
29
+ /**
30
+ * Error channel shared by every generated Doppler operation. Generated
31
+ * service files annotate operations with `API.OperationMethod<I, O,
32
+ * DopplerOpError, DopplerOpContext>` explicitly so the compiler never
33
+ * infers these back out of the schema generics.
34
+ */
35
+ export type DopplerOpError =
36
+ | InstanceType<(typeof API_ERRORS)[number]>
37
+ | UnknownDopplerError
38
+ | ConfigError
39
+ | HttpClientError.HttpClientError;
40
+
41
+ /** Context (requirements) shared by every generated Doppler operation. */
42
+ export type DopplerOpContext = Credentials | HttpClient.HttpClient;
43
+
44
+ export const DopplerProtocol: Layer.Layer<API.Protocol> =
45
+ makeRestProtocol<Config>({
46
+ // The Credentials service holds an effect — resolving it here (per
47
+ // request, on the calling fiber) picks up context-provided credentials.
48
+ credentials: Effect.gen(function* () {
49
+ const resolve = yield* Credentials;
50
+ return yield* resolve;
51
+ }),
52
+ baseUrl: (creds) => creds.apiBaseUrl,
53
+ headers: (creds) => ({
54
+ Authorization: `Bearer ${Redacted.value(creds.apiKey)}`,
55
+ }),
56
+ // Doppler's error body is `{ messages?: string[], message?: string,
57
+ // success?: boolean }`. The factory's default envelope covers `message`;
58
+ // `messages[0]` is the form most endpoints actually send.
59
+ errorEnvelope: (body) => {
60
+ if (body === null || typeof body !== "object") return undefined;
61
+ const rec = body as Record<string, unknown>;
62
+ const code =
63
+ typeof rec.code === "string"
64
+ ? rec.code
65
+ : typeof rec.error === "string"
66
+ ? rec.error
67
+ : undefined;
68
+ const fromMessages = Array.isArray(rec.messages)
69
+ ? rec.messages.find((m) => typeof m === "string")
70
+ : undefined;
71
+ const message =
72
+ typeof rec.message === "string"
73
+ ? rec.message
74
+ : typeof fromMessages === "string"
75
+ ? fromMessages
76
+ : undefined;
77
+ if (code === undefined && message === undefined) return undefined;
78
+ return { code, message };
79
+ },
80
+ unknownError: ({ code, message, body }) =>
81
+ new UnknownDopplerError({
82
+ code:
83
+ typeof code === "string"
84
+ ? code
85
+ : code !== undefined
86
+ ? String(code)
87
+ : undefined,
88
+ message,
89
+ body,
90
+ }),
91
+ });
package/src/retry.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Doppler 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 Doppler API call below it pick it up:
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import * as Doppler from "@distilled.cloud/doppler";
11
+ *
12
+ * myEffect.pipe(Doppler.Retry.transient);
13
+ * Effect.provide(myEffect, Layer.succeed(Doppler.Retry.Retry, customPolicy));
14
+ * ```
15
+ */
16
+ import * as Context from "effect/Context";
17
+ import * as Effect from "effect/Effect";
18
+ import * as Layer from "effect/Layer";
19
+ import {
20
+ type Policy,
21
+ throttlingFactory,
22
+ transientFactory,
23
+ } from "@distilled.cloud/core/retry";
24
+
25
+ export {
26
+ type Options,
27
+ type Factory,
28
+ type Policy,
29
+ makeDefault,
30
+ jittered,
31
+ capped,
32
+ throttlingOptions,
33
+ transientOptions,
34
+ throttlingFactory,
35
+ transientFactory,
36
+ } from "@distilled.cloud/core/retry";
37
+
38
+ /** Context tag for configuring retry behavior of Doppler API calls. */
39
+ export class Retry extends Context.Service<Retry, Policy>()("DopplerRetry") {}
40
+
41
+ /** Provides a custom retry policy to every Doppler API call below it. */
42
+ export const policy = (optionsOrFactory: Policy) =>
43
+ Effect.provide(Layer.succeed(Retry, optionsOrFactory));
44
+
45
+ /** Disables all automatic retries. */
46
+ export const none = Effect.provide(
47
+ Layer.succeed(Retry, { while: () => false }),
48
+ );
49
+
50
+ /** Apply the throttling retry policy (retries throttling errors indefinitely). */
51
+ export const throttling = policy(throttlingFactory);
52
+
53
+ /** Apply the transient retry policy (retries all transient errors indefinitely). */
54
+ export const transient = policy(transientFactory);