@elding/sdk 0.6.2 → 0.6.5
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/README.md +66 -38
- package/dist/api.js +5 -5
- package/dist/apiUrl.js +4 -4
- package/dist/client.js +3 -3
- package/dist/configure.d.ts +2 -1
- package/dist/configure.js +4 -3
- package/dist/index.d.ts +7 -10
- package/dist/index.js +11 -16
- package/dist/proxy.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,67 +1,95 @@
|
|
|
1
1
|
# @elding/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Your API keys are never in your code or in a `.env` file. Elding keeps them, your code calls the API normally, and the real key is injected at the last moment.
|
|
4
|
+
|
|
5
|
+
**The same code works in dev and in prod.** You change nothing.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @elding/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
> ⚠️ **Elding warning:** always use the **scoped** name `@elding/sdk`. `npm install elding` (without the `@elding/` scope) installs an unrelated third-party package, not Elding.
|
|
12
|
+
|
|
13
|
+
## Quickstart (2 min)
|
|
14
|
+
|
|
15
|
+
### 1. Sign in and choose your set
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx elding login # opens the browser, signs you in
|
|
19
|
+
npx elding init # creates .elding.json (links this project to a set)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Write your code
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
Resend, etc.). Retourne de quoi faire un `fetch` normal, sans jamais exposer la clé.
|
|
24
|
+
`configure()` replaces your real key. 1st argument = the secret name in Elding, 2nd = the API domain.
|
|
15
25
|
|
|
16
26
|
```ts
|
|
27
|
+
import OpenAI from "openai";
|
|
17
28
|
import { configure } from "@elding/sdk";
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
const openai = new OpenAI(
|
|
31
|
+
await configure("OPENAI_API_KEY", "https://api.openai.com")
|
|
32
|
+
);
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
method: "POST",
|
|
25
|
-
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
26
|
-
body: JSON.stringify({ model: "mistral-small-latest", messages: [...] }),
|
|
27
|
-
});
|
|
34
|
+
// use openai normally, the real key is never in your code
|
|
28
35
|
```
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
- **Utilise toujours `baseURL`** dans ton `fetch`, jamais l'URL de l'API en dur.
|
|
32
|
-
En dev, `baseURL` pointe vers le proxy local qui injecte la vraie clé.
|
|
33
|
-
- Mets `apiKey` dans un **header** (`Authorization`, `x-api-key`…), jamais dans l'URL.
|
|
34
|
-
- `configure()` est réservé aux **API HTTP**. Pour autre chose, voir `secret()`.
|
|
37
|
+
### 3. Run
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
```bash
|
|
40
|
+
npx elding proxy -- npm run dev
|
|
41
|
+
```
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
Retourne la valeur brute. Jamais dans `process.env`, effacée de la mémoire après
|
|
40
|
-
5 min (réglable via `ELDING_CACHE_TTL_MS`).
|
|
43
|
+
That's it. The key **never** enters your application.
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
import { secret } from "@elding/sdk";
|
|
45
|
+
## In production (Vercel, server, CI)
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
No proxy in prod. Elding fetches the key at runtime. **You don't change your code**, you just add **2 environment variables**:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ELDING_REFRESH_TOKEN=eld_rt_... # generate it in the dashboard → API keys
|
|
51
|
+
ELDING_SET_ID=... # your set's id (set page)
|
|
46
52
|
```
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
And... that's all. The same `configure("OPENAI_API_KEY", "https://api.openai.com")` works.
|
|
55
|
+
|
|
56
|
+
> The SDK reads these 2 variables automatically. Keep these exact names (`ELDING_REFRESH_TOKEN`, `ELDING_SET_ID`) and you have **no option** to pass.
|
|
57
|
+
|
|
58
|
+
### How to get the 2 keys
|
|
59
|
+
|
|
60
|
+
| Variable | Where to find it |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `ELDING_REFRESH_TOKEN` | Dashboard → **API keys** → New key (shown only once) |
|
|
63
|
+
| `ELDING_SET_ID` | Open your set in the dashboard, the id is in the URL |
|
|
64
|
+
|
|
65
|
+
## The 2 functions
|
|
66
|
+
|
|
67
|
+
**`configure(name, domain)`** — for any **HTTP API** key (OpenAI, Mistral, Stripe, Resend…).
|
|
68
|
+
You pass it directly to the provider's SDK:
|
|
49
69
|
|
|
50
70
|
```ts
|
|
51
|
-
|
|
71
|
+
const openai = new OpenAI(await configure("OPENAI_API_KEY", "https://api.openai.com"));
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**`secret(name)`** — for everything **else** (`DATABASE_URL`, `JWT_SECRET`, `REDIS_URL`…).
|
|
75
|
+
Returns the raw value, wiped from memory after 5 min.
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
```ts
|
|
78
|
+
import { secret } from "@elding/sdk";
|
|
79
|
+
const dbUrl = await secret("DATABASE_URL");
|
|
55
80
|
```
|
|
56
81
|
|
|
57
|
-
##
|
|
82
|
+
## Dev vs prod, at a glance
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
| | Dev | Prod |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Command | `elding proxy -- npm run dev` | `npm run build && npm start` |
|
|
87
|
+
| Mechanism | local proxy | runtime fetch |
|
|
88
|
+
| To configure | `elding login` + `elding init` | `ELDING_REFRESH_TOKEN` + `ELDING_SET_ID` |
|
|
89
|
+
| Your code | identical | **identical** |
|
|
62
90
|
|
|
63
|
-
##
|
|
91
|
+
## Rules
|
|
64
92
|
|
|
65
|
-
- **
|
|
66
|
-
-
|
|
67
|
-
|
|
93
|
+
- **Never** hardcode a key, never in `process.env`, never in the URL.
|
|
94
|
+
- A key that goes into an HTTP request → `configure(name, "https://…")`.
|
|
95
|
+
- Any other secret → `secret(name)`.
|
package/dist/api.js
CHANGED
|
@@ -8,14 +8,14 @@ const REQUEST_TIMEOUT_MS = 15_000;
|
|
|
8
8
|
const REFRESH_TOKEN = /^eld_rt_[a-f0-9]{64}$/i;
|
|
9
9
|
async function exchangeToken(refreshToken) {
|
|
10
10
|
if (!REFRESH_TOKEN.test(refreshToken))
|
|
11
|
-
throw new Error("[elding]
|
|
11
|
+
throw new Error("[elding] Invalid local token.");
|
|
12
12
|
const body = await requestJson("/api/cli/auth/token", {
|
|
13
13
|
method: "POST",
|
|
14
14
|
headers: { "Content-Type": "application/json" },
|
|
15
15
|
body: JSON.stringify({ refreshToken }),
|
|
16
16
|
});
|
|
17
17
|
if (!body.success || !body.accessToken)
|
|
18
|
-
throw new Error(safeError(body.error) || "
|
|
18
|
+
throw new Error(safeError(body.error) || "Elding authentication failed");
|
|
19
19
|
return body.accessToken;
|
|
20
20
|
}
|
|
21
21
|
async function fetchSecrets(accessToken, setId) {
|
|
@@ -23,7 +23,7 @@ async function fetchSecrets(accessToken, setId) {
|
|
|
23
23
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
24
24
|
});
|
|
25
25
|
if (!body.success || !body.secrets || typeof body.secrets !== "object")
|
|
26
|
-
throw new Error(safeError(body.error) || "
|
|
26
|
+
throw new Error(safeError(body.error) || "Invalid response");
|
|
27
27
|
return sanitizeSecrets(body.secrets);
|
|
28
28
|
}
|
|
29
29
|
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -48,9 +48,9 @@ async function requestJson(path, init) {
|
|
|
48
48
|
});
|
|
49
49
|
const body = (await res.json().catch(() => null));
|
|
50
50
|
if (!res.ok)
|
|
51
|
-
throw new Error(safeError(body?.error) || `
|
|
51
|
+
throw new Error(safeError(body?.error) || `Error ${res.status}`);
|
|
52
52
|
if (!body || typeof body !== "object")
|
|
53
|
-
throw new Error("[elding]
|
|
53
|
+
throw new Error("[elding] Invalid response.");
|
|
54
54
|
return body;
|
|
55
55
|
}
|
|
56
56
|
function safeError(value) {
|
package/dist/apiUrl.js
CHANGED
|
@@ -28,17 +28,17 @@ function resolveBaseUrl(raw = process.env.ELDING_API_URL) {
|
|
|
28
28
|
url = new URL(input);
|
|
29
29
|
}
|
|
30
30
|
catch {
|
|
31
|
-
throw new Error("[elding] ELDING_API_URL
|
|
31
|
+
throw new Error("[elding] Invalid ELDING_API_URL.");
|
|
32
32
|
}
|
|
33
33
|
if (url.username || url.password) {
|
|
34
|
-
throw new Error("[elding] ELDING_API_URL
|
|
34
|
+
throw new Error("[elding] ELDING_API_URL must not contain credentials.");
|
|
35
35
|
}
|
|
36
36
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
37
|
-
throw new Error("[elding] ELDING_API_URL
|
|
37
|
+
throw new Error("[elding] ELDING_API_URL must be an origin only.");
|
|
38
38
|
}
|
|
39
39
|
if (url.protocol === "https:")
|
|
40
40
|
return url.origin;
|
|
41
41
|
if (url.protocol === "http:" && isLoopbackHost(url.hostname))
|
|
42
42
|
return url.origin;
|
|
43
|
-
throw new Error("[elding] ELDING_API_URL
|
|
43
|
+
throw new Error("[elding] ELDING_API_URL must use HTTPS, except for localhost in development.");
|
|
44
44
|
}
|
package/dist/client.js
CHANGED
|
@@ -14,11 +14,11 @@ class EldingClient {
|
|
|
14
14
|
// 1. Resolve setId : option explicite → .elding.json (dev) → ELDING_SET_ID (serverless/CI)
|
|
15
15
|
const setId = options.setId ?? (0, config_js_1.readProjectConfig)()?.setId ?? process.env.ELDING_SET_ID?.trim();
|
|
16
16
|
if (!setId)
|
|
17
|
-
throw new Error("[elding] setId
|
|
17
|
+
throw new Error("[elding] setId not found. Run `elding init`, pass setId as an option, or set ELDING_SET_ID.");
|
|
18
18
|
// 2. Resolve refresh token
|
|
19
19
|
const refreshToken = options.refreshToken ?? (0, config_js_1.readGlobalConfig)()?.refreshToken;
|
|
20
20
|
if (!refreshToken)
|
|
21
|
-
throw new Error("[elding] Token
|
|
21
|
+
throw new Error("[elding] Token not found. Run `elding login` or pass refreshToken as an option.");
|
|
22
22
|
// 3. Fetch secrets
|
|
23
23
|
const accessToken = await (0, api_js_1.exchangeToken)(refreshToken);
|
|
24
24
|
const secrets = await (0, api_js_1.fetchSecrets)(accessToken, setId);
|
|
@@ -33,7 +33,7 @@ class EldingClient {
|
|
|
33
33
|
if (envValue !== undefined)
|
|
34
34
|
return envValue;
|
|
35
35
|
}
|
|
36
|
-
throw new Error(`[elding] Secret "${name}"
|
|
36
|
+
throw new Error(`[elding] Secret "${name}" not found in the set.`);
|
|
37
37
|
}
|
|
38
38
|
// Returns undefined instead of throwing
|
|
39
39
|
secretOrUndefined(name) {
|
package/dist/configure.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type ClientOptions } from "./client.js";
|
|
1
|
+
import { EldingClient, type ClientOptions } from "./client.js";
|
|
2
2
|
export type ProviderConfig = {
|
|
3
3
|
apiKey: string;
|
|
4
4
|
baseURL?: string;
|
|
5
5
|
defaultHeaders?: Record<string, string>;
|
|
6
6
|
};
|
|
7
|
+
export declare function getClient(options: ClientOptions): Promise<EldingClient>;
|
|
7
8
|
/** Efface immédiatement les secrets gardés en mémoire par le SDK. */
|
|
8
9
|
export declare function clearSecretCache(): void;
|
|
9
10
|
export declare function configure(secretName: string, target: string, options?: ClientOptions): Promise<ProviderConfig>;
|
package/dist/configure.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getClient = getClient;
|
|
3
4
|
exports.clearSecretCache = clearSecretCache;
|
|
4
5
|
exports.configure = configure;
|
|
5
6
|
exports.secret = secret;
|
|
@@ -60,12 +61,12 @@ function isHttpTarget(target) {
|
|
|
60
61
|
}
|
|
61
62
|
async function configure(secretName, target, options = {}) {
|
|
62
63
|
if (!SECRET_NAME.test(secretName))
|
|
63
|
-
throw new Error("[elding]
|
|
64
|
+
throw new Error("[elding] Invalid secret name (A-Z, 0-9, _).");
|
|
64
65
|
// configure() protège uniquement les API HTTP via le proxy. Pour une valeur
|
|
65
66
|
// non-HTTP (DATABASE_URL, config...), le proxy ne peut rien intercepter :
|
|
66
67
|
// on guide vers secret() plutot que de renvoyer une valeur faussement "protégée".
|
|
67
68
|
if (!isHttpTarget(target))
|
|
68
|
-
throw new Error(`[elding] configure()
|
|
69
|
+
throw new Error(`[elding] configure() expects an HTTP API. For "${secretName}" (non-HTTP, e.g. DATABASE_URL), use secret("${secretName}") — raw value, never protected by the proxy.`);
|
|
69
70
|
// Mode proxy (dev) : placeholder, la vraie clé n'entre jamais dans le process.
|
|
70
71
|
if ((0, proxy_js_1.isProxyActive)()) {
|
|
71
72
|
const { baseURL, headers } = (0, proxy_js_1.proxyConfig)(target);
|
|
@@ -86,7 +87,7 @@ async function configure(secretName, target, options = {}) {
|
|
|
86
87
|
*/
|
|
87
88
|
async function secret(name, options = {}) {
|
|
88
89
|
if (!SECRET_NAME.test(name))
|
|
89
|
-
throw new Error("[elding]
|
|
90
|
+
throw new Error("[elding] Invalid secret name (A-Z, 0-9, _).");
|
|
90
91
|
const elding = await getClient(options);
|
|
91
92
|
return elding.secret(name);
|
|
92
93
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { EldingClient, type ClientOptions } from "./client.js";
|
|
2
|
+
import { configure, secret } from "./configure.js";
|
|
2
3
|
export { configure, secret, clearSecretCache, type ProviderConfig } from "./configure.js";
|
|
4
|
+
declare const elding: {
|
|
5
|
+
configure: typeof configure;
|
|
6
|
+
secret: typeof secret;
|
|
7
|
+
};
|
|
8
|
+
export default elding;
|
|
3
9
|
export { EldingClient };
|
|
4
10
|
export type { ClientOptions };
|
|
5
11
|
export { isProxyActive } from "./proxy.js";
|
|
6
|
-
/**
|
|
7
|
-
* Crée un client Elding et charge tous les secrets du set configuré.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* const elding = await client();
|
|
11
|
-
* const key = elding.secret("OPENAI_API_KEY");
|
|
12
|
-
*
|
|
13
|
-
* @example avec options
|
|
14
|
-
* const elding = await client({ setId: "xxx", refreshToken: "eld_rt_..." });
|
|
15
|
-
*/
|
|
12
|
+
/** Crée un client Elding et charge tous les secrets du set configuré. */
|
|
16
13
|
export declare function client(options?: ClientOptions): Promise<EldingClient>;
|
package/dist/index.js
CHANGED
|
@@ -4,25 +4,20 @@ exports.isProxyActive = exports.EldingClient = exports.clearSecretCache = export
|
|
|
4
4
|
exports.client = client;
|
|
5
5
|
const client_js_1 = require("./client.js");
|
|
6
6
|
Object.defineProperty(exports, "EldingClient", { enumerable: true, get: function () { return client_js_1.EldingClient; } });
|
|
7
|
+
const configure_js_1 = require("./configure.js");
|
|
7
8
|
// ── API principale ──
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "
|
|
12
|
-
|
|
9
|
+
// configure(name, "https://…") pour une clé d'API HTTP (passée au SDK provider
|
|
10
|
+
// ou à fetch), secret(name) pour les secrets non-HTTP (DATABASE_URL, JWT_SECRET…).
|
|
11
|
+
var configure_js_2 = require("./configure.js");
|
|
12
|
+
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return configure_js_2.configure; } });
|
|
13
|
+
Object.defineProperty(exports, "secret", { enumerable: true, get: function () { return configure_js_2.secret; } });
|
|
14
|
+
Object.defineProperty(exports, "clearSecretCache", { enumerable: true, get: function () { return configure_js_2.clearSecretCache; } });
|
|
15
|
+
// Objet namespace : `import elding from "@elding/sdk"; elding.configure(...)`.
|
|
16
|
+
const elding = { configure: configure_js_1.configure, secret: configure_js_1.secret };
|
|
17
|
+
exports.default = elding;
|
|
13
18
|
var proxy_js_1 = require("./proxy.js");
|
|
14
19
|
Object.defineProperty(exports, "isProxyActive", { enumerable: true, get: function () { return proxy_js_1.isProxyActive; } });
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Crée un client Elding et charge tous les secrets du set configuré.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* const elding = await client();
|
|
21
|
-
* const key = elding.secret("OPENAI_API_KEY");
|
|
22
|
-
*
|
|
23
|
-
* @example avec options
|
|
24
|
-
* const elding = await client({ setId: "xxx", refreshToken: "eld_rt_..." });
|
|
25
|
-
*/
|
|
20
|
+
/** Crée un client Elding et charge tous les secrets du set configuré. */
|
|
26
21
|
async function client(options) {
|
|
27
22
|
return client_js_1.EldingClient.create(options);
|
|
28
23
|
}
|
package/dist/proxy.js
CHANGED
|
@@ -24,7 +24,7 @@ function proxyConfig(target) {
|
|
|
24
24
|
const url = validateProxyUrl(process.env.ELDING_PROXY_URL);
|
|
25
25
|
const token = process.env.ELDING_PROXY_TOKEN;
|
|
26
26
|
if (!url || !token || !PROXY_TOKEN.test(token))
|
|
27
|
-
throw new Error("[elding] Proxy
|
|
27
|
+
throw new Error("[elding] Proxy not active. Run via `elding proxy -- <cmd>`.");
|
|
28
28
|
const safeTarget = validateTarget(target);
|
|
29
29
|
return {
|
|
30
30
|
baseURL: url,
|
|
@@ -53,14 +53,14 @@ function validateProxyUrl(value) {
|
|
|
53
53
|
url.pathname !== "/" ||
|
|
54
54
|
url.search ||
|
|
55
55
|
url.hash) {
|
|
56
|
-
throw new Error("[elding] ELDING_PROXY_URL
|
|
56
|
+
throw new Error("[elding] Invalid ELDING_PROXY_URL.");
|
|
57
57
|
}
|
|
58
58
|
return url.origin;
|
|
59
59
|
}
|
|
60
60
|
function validateTarget(value) {
|
|
61
61
|
const url = new URL(value);
|
|
62
62
|
if (url.protocol !== "https:" || url.username || url.password) {
|
|
63
|
-
throw new Error("[elding]
|
|
63
|
+
throw new Error("[elding] The proxy target must be an HTTPS URL without credentials.");
|
|
64
64
|
}
|
|
65
65
|
url.hash = "";
|
|
66
66
|
return url.origin;
|