@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.
- package/LICENSE +201 -0
- package/lib/credentials.d.ts +32 -0
- package/lib/credentials.d.ts.map +1 -0
- package/lib/credentials.js +38 -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 +25 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +29 -0
- package/lib/index.js.map +1 -0
- package/lib/pagination.d.ts +8 -0
- package/lib/pagination.d.ts.map +1 -0
- package/lib/pagination.js +8 -0
- package/lib/pagination.js.map +1 -0
- package/lib/protocol.d.ts +18 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +68 -0
- package/lib/protocol.js.map +1 -0
- package/lib/retry.d.ts +32 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/retry.js +32 -0
- package/lib/retry.js.map +1 -0
- package/lib/services/doppler.d.ts +3424 -0
- package/lib/services/doppler.d.ts.map +1 -0
- package/lib/services/doppler.js +4330 -0
- package/lib/services/doppler.js.map +1 -0
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +3 -0
- package/lib/services/index.js.map +1 -0
- package/lib/traits.d.ts +11 -0
- package/lib/traits.d.ts.map +1 -0
- package/lib/traits.js +11 -0
- package/lib/traits.js.map +1 -0
- package/package.json +77 -6
- package/src/credentials.ts +61 -0
- package/src/errors.ts +50 -0
- package/src/index.ts +32 -0
- package/src/pagination.ts +7 -0
- package/src/protocol.ts +91 -0
- package/src/retry.ts +54 -0
- package/src/services/doppler.ts +10151 -0
- package/src/services/index.ts +2 -0
- 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";
|
package/src/protocol.ts
ADDED
|
@@ -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);
|