@anchor-secrets/sdk 0.0.0-stage → 0.5.0

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.
@@ -0,0 +1,130 @@
1
+ /** Public types of the SDK. They never come from `@anchor/shared`, which is bundled. */
2
+ import type { AnchorError } from "./errors.cjs";
3
+ /** The value type a secret declares. */
4
+ export type AnchorValueType = "string" | "json" | "pem" | "number" | "boolean" | "list";
5
+ /** A decoded secret value, as `loadSecrets` returns it. */
6
+ export type AnchorValue = string | number | boolean | unknown[] | Record<string, unknown>;
7
+ /** One secret, with its value as the server returned it (a string). */
8
+ export type AnchorSecret = {
9
+ key: string;
10
+ path: string;
11
+ type: AnchorValueType;
12
+ sensitive: boolean;
13
+ version: number;
14
+ value: string;
15
+ /** Set when the secret comes from a folder this folder imports. */
16
+ importedFrom?: {
17
+ environment: string;
18
+ path: string;
19
+ };
20
+ };
21
+ export type ClientOptions = {
22
+ /** Base URL of the Anchor server. Defaults to `ANCHOR_API_URL`. */
23
+ apiUrl?: string;
24
+ /** A service token or another bearer Anchor accepts. Defaults to `ANCHOR_TOKEN`. */
25
+ token?: string;
26
+ /** Replaces the global `fetch`, for proxies and tests. */
27
+ fetch?: typeof fetch;
28
+ /** Per attempt; default 10 000 ms. */
29
+ timeoutMs?: number;
30
+ /** Retries of network errors, timeouts and HTTP 429/502/503/504; `false` turns them off. */
31
+ retry?: RetryOptions | false;
32
+ /** Caching of fetched folders; `false` turns it off. */
33
+ cache?: CacheOptions | false;
34
+ /** Receives retries and failed refreshes. Exceptions it throws are ignored. */
35
+ onEvent?: (event: AnchorEvent) => void;
36
+ };
37
+ export type RetryOptions = {
38
+ /** Attempts in total, including the first; default 3. */
39
+ attempts?: number;
40
+ /** Upper bound of the first wait; it doubles per attempt. Default 250 ms. */
41
+ baseDelayMs?: number;
42
+ /** Upper bound of any wait, including `Retry-After`; default 5 000 ms. */
43
+ maxDelayMs?: number;
44
+ };
45
+ /** What the client reports through `onEvent`. Never carries a token or a value. */
46
+ export type AnchorEvent = {
47
+ type: "retry";
48
+ attempt: number;
49
+ delayMs: number;
50
+ error: AnchorError;
51
+ } | {
52
+ type: "refresh_failed";
53
+ error: AnchorError;
54
+ staleForMs: number;
55
+ }
56
+ /** A custom `CacheStore` threw; the read went to Anchor, or the fetched values were not kept. */
57
+ | {
58
+ type: "store_failed";
59
+ operation: "get" | "set";
60
+ error: unknown;
61
+ };
62
+ export type ListQuery = {
63
+ /** Project slug. */
64
+ project: string;
65
+ /** Environment slug. */
66
+ environment: string;
67
+ /** Folder path; default `/`. */
68
+ path?: string;
69
+ /** Expand `${...}` references; default `true`, like `anchor run`. */
70
+ expand?: boolean;
71
+ /** Add the secrets of the folder's imports; default `true`, like `anchor run`. */
72
+ includeImports?: boolean;
73
+ };
74
+ export type RequestOptions = {
75
+ /** Cancels the call; it rejects with code `aborted`. */
76
+ signal?: AbortSignal;
77
+ /** Ask the server even when the cached folder is fresh; still falls back to it on failure. */
78
+ fresh?: boolean;
79
+ };
80
+ export interface AnchorClient {
81
+ /** The secrets of one folder, with values. */
82
+ listSecrets(query: ListQuery, options?: RequestOptions): Promise<AnchorSecret[]>;
83
+ /** The same secrets as `{ KEY: value }`, with typed values decoded. */
84
+ loadSecrets(query: ListQuery, options?: RequestOptions): Promise<Record<string, AnchorValue>>;
85
+ /** Loads the folder once, then serves it synchronously and refreshes it in the background. */
86
+ folder(query: ListQuery, options?: {
87
+ signal?: AbortSignal;
88
+ }): Promise<AnchorFolder>;
89
+ }
90
+ /**
91
+ * One folder's decoded secrets. Reads are synchronous and never wait for the network: a read of
92
+ * values older than the cache ttl starts one background refresh and returns the current values.
93
+ * Once a refresh has failed past `ttlMs + maxStaleMs`, reads throw `AnchorError` code `stale`
94
+ * until Anchor answers again.
95
+ */
96
+ export interface AnchorFolder {
97
+ /** The value of `key`, or `undefined` when the folder has no such secret. */
98
+ get<T = AnchorValue>(key: string): T | undefined;
99
+ /** The value of `key`; throws `Anchor secret <KEY> is not set` when it is missing. */
100
+ getOrThrow<T = AnchorValue>(key: string): T;
101
+ has(key: string): boolean;
102
+ keys(): string[];
103
+ /** Every value, frozen; the same object until new values arrive. */
104
+ toObject(): Readonly<Record<string, AnchorValue>>;
105
+ /** Fetches now; rejects when that fails, keeping the current values. */
106
+ refresh(): Promise<void>;
107
+ }
108
+ /** What the cache keeps per folder. `secrets` hold plaintext values. */
109
+ export type CacheEntry = {
110
+ secrets: AnchorSecret[];
111
+ fetchedAt: number;
112
+ /** The server's tag for these secrets, sent back as `If-None-Match`; absent before 0.5.0. */
113
+ etag?: string;
114
+ };
115
+ /**
116
+ * Where cached folders live; the default keeps them in memory. A custom store (a file, Redis)
117
+ * receives plaintext values and must protect them, for example by encrypting them.
118
+ */
119
+ export interface CacheStore {
120
+ get(key: string): CacheEntry | undefined | Promise<CacheEntry | undefined>;
121
+ set(key: string, entry: CacheEntry): void | Promise<void>;
122
+ }
123
+ export type CacheOptions = {
124
+ /** How long a fetched folder is served without asking the server; default 60 000 ms. */
125
+ ttlMs?: number;
126
+ /** How long past `ttlMs` a folder may still be served when the server fails; default no limit. */
127
+ maxStaleMs?: number;
128
+ /** Default: an in-memory store per client. */
129
+ store?: CacheStore;
130
+ };
@@ -0,0 +1,130 @@
1
+ /** Public types of the SDK. They never come from `@anchor/shared`, which is bundled. */
2
+ import type { AnchorError } from "./errors.js";
3
+ /** The value type a secret declares. */
4
+ export type AnchorValueType = "string" | "json" | "pem" | "number" | "boolean" | "list";
5
+ /** A decoded secret value, as `loadSecrets` returns it. */
6
+ export type AnchorValue = string | number | boolean | unknown[] | Record<string, unknown>;
7
+ /** One secret, with its value as the server returned it (a string). */
8
+ export type AnchorSecret = {
9
+ key: string;
10
+ path: string;
11
+ type: AnchorValueType;
12
+ sensitive: boolean;
13
+ version: number;
14
+ value: string;
15
+ /** Set when the secret comes from a folder this folder imports. */
16
+ importedFrom?: {
17
+ environment: string;
18
+ path: string;
19
+ };
20
+ };
21
+ export type ClientOptions = {
22
+ /** Base URL of the Anchor server. Defaults to `ANCHOR_API_URL`. */
23
+ apiUrl?: string;
24
+ /** A service token or another bearer Anchor accepts. Defaults to `ANCHOR_TOKEN`. */
25
+ token?: string;
26
+ /** Replaces the global `fetch`, for proxies and tests. */
27
+ fetch?: typeof fetch;
28
+ /** Per attempt; default 10 000 ms. */
29
+ timeoutMs?: number;
30
+ /** Retries of network errors, timeouts and HTTP 429/502/503/504; `false` turns them off. */
31
+ retry?: RetryOptions | false;
32
+ /** Caching of fetched folders; `false` turns it off. */
33
+ cache?: CacheOptions | false;
34
+ /** Receives retries and failed refreshes. Exceptions it throws are ignored. */
35
+ onEvent?: (event: AnchorEvent) => void;
36
+ };
37
+ export type RetryOptions = {
38
+ /** Attempts in total, including the first; default 3. */
39
+ attempts?: number;
40
+ /** Upper bound of the first wait; it doubles per attempt. Default 250 ms. */
41
+ baseDelayMs?: number;
42
+ /** Upper bound of any wait, including `Retry-After`; default 5 000 ms. */
43
+ maxDelayMs?: number;
44
+ };
45
+ /** What the client reports through `onEvent`. Never carries a token or a value. */
46
+ export type AnchorEvent = {
47
+ type: "retry";
48
+ attempt: number;
49
+ delayMs: number;
50
+ error: AnchorError;
51
+ } | {
52
+ type: "refresh_failed";
53
+ error: AnchorError;
54
+ staleForMs: number;
55
+ }
56
+ /** A custom `CacheStore` threw; the read went to Anchor, or the fetched values were not kept. */
57
+ | {
58
+ type: "store_failed";
59
+ operation: "get" | "set";
60
+ error: unknown;
61
+ };
62
+ export type ListQuery = {
63
+ /** Project slug. */
64
+ project: string;
65
+ /** Environment slug. */
66
+ environment: string;
67
+ /** Folder path; default `/`. */
68
+ path?: string;
69
+ /** Expand `${...}` references; default `true`, like `anchor run`. */
70
+ expand?: boolean;
71
+ /** Add the secrets of the folder's imports; default `true`, like `anchor run`. */
72
+ includeImports?: boolean;
73
+ };
74
+ export type RequestOptions = {
75
+ /** Cancels the call; it rejects with code `aborted`. */
76
+ signal?: AbortSignal;
77
+ /** Ask the server even when the cached folder is fresh; still falls back to it on failure. */
78
+ fresh?: boolean;
79
+ };
80
+ export interface AnchorClient {
81
+ /** The secrets of one folder, with values. */
82
+ listSecrets(query: ListQuery, options?: RequestOptions): Promise<AnchorSecret[]>;
83
+ /** The same secrets as `{ KEY: value }`, with typed values decoded. */
84
+ loadSecrets(query: ListQuery, options?: RequestOptions): Promise<Record<string, AnchorValue>>;
85
+ /** Loads the folder once, then serves it synchronously and refreshes it in the background. */
86
+ folder(query: ListQuery, options?: {
87
+ signal?: AbortSignal;
88
+ }): Promise<AnchorFolder>;
89
+ }
90
+ /**
91
+ * One folder's decoded secrets. Reads are synchronous and never wait for the network: a read of
92
+ * values older than the cache ttl starts one background refresh and returns the current values.
93
+ * Once a refresh has failed past `ttlMs + maxStaleMs`, reads throw `AnchorError` code `stale`
94
+ * until Anchor answers again.
95
+ */
96
+ export interface AnchorFolder {
97
+ /** The value of `key`, or `undefined` when the folder has no such secret. */
98
+ get<T = AnchorValue>(key: string): T | undefined;
99
+ /** The value of `key`; throws `Anchor secret <KEY> is not set` when it is missing. */
100
+ getOrThrow<T = AnchorValue>(key: string): T;
101
+ has(key: string): boolean;
102
+ keys(): string[];
103
+ /** Every value, frozen; the same object until new values arrive. */
104
+ toObject(): Readonly<Record<string, AnchorValue>>;
105
+ /** Fetches now; rejects when that fails, keeping the current values. */
106
+ refresh(): Promise<void>;
107
+ }
108
+ /** What the cache keeps per folder. `secrets` hold plaintext values. */
109
+ export type CacheEntry = {
110
+ secrets: AnchorSecret[];
111
+ fetchedAt: number;
112
+ /** The server's tag for these secrets, sent back as `If-None-Match`; absent before 0.5.0. */
113
+ etag?: string;
114
+ };
115
+ /**
116
+ * Where cached folders live; the default keeps them in memory. A custom store (a file, Redis)
117
+ * receives plaintext values and must protect them, for example by encrypting them.
118
+ */
119
+ export interface CacheStore {
120
+ get(key: string): CacheEntry | undefined | Promise<CacheEntry | undefined>;
121
+ set(key: string, entry: CacheEntry): void | Promise<void>;
122
+ }
123
+ export type CacheOptions = {
124
+ /** How long a fetched folder is served without asking the server; default 60 000 ms. */
125
+ ttlMs?: number;
126
+ /** How long past `ttlMs` a folder may still be served when the server fails; default no limit. */
127
+ maxStaleMs?: number;
128
+ /** Default: an in-memory store per client. */
129
+ store?: CacheStore;
130
+ };
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@anchor-secrets/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.5.0",
4
+ "description": "Node client for Anchor, a self-hosted secrets manager on Cloudflare Workers",
5
+ "keywords": [
6
+ "secrets",
7
+ "config",
8
+ "env",
9
+ "cloudflare",
10
+ "anchor"
11
+ ],
12
+ "license": "Apache-2.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/sotarak/anchor.git",
16
+ "directory": "packages/sdk"
17
+ },
18
+ "homepage": "https://sotarak.github.io/anchor/guides/sdk/",
19
+ "bugs": {
20
+ "url": "https://github.com/sotarak/anchor/issues"
21
+ },
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.cts",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.cts",
34
+ "default": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "sideEffects": false,
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "devDependencies": {
50
+ "@anchor/shared": "0.5.0",
51
+ "@arethetypeswrong/cli": "^0.18.5",
52
+ "@types/node": "^22.20.5",
53
+ "esbuild": "^0.28.2",
54
+ "publint": "^0.3.25",
55
+ "typescript": "^5.9.3",
56
+ "vitest": "^5.1.0"
57
+ },
58
+ "scripts": {
59
+ "build": "node build.mjs",
60
+ "test": "vitest run",
61
+ "typecheck": "tsc --noEmit",
62
+ "check:package": "publint && attw --pack ."
63
+ }
6
64
  }