@noidmejs/atomkit-http 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 noidmejs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @noidmejs/atomkit-http
2
+
3
+ **Declarative HTTP / backend connector.** Call any API by config — every verb,
4
+ headers, query, body, auth, retries, cache, response transforms — and keep secrets
5
+ **server-only** behind a proxy with an **SSRF host allow-list**.
6
+
7
+ ```bash
8
+ npm i @noidmejs/atomkit-http
9
+ ```
10
+
11
+ ## Define a source
12
+
13
+ ```ts
14
+ const listUsers = {
15
+ id: 'listUsers',
16
+ method: 'GET',
17
+ url: 'https://api.example.com/users',
18
+ query: { q: '{{params.q}}' },
19
+ auth: { type: 'bearer', token: '{{secret.API_TOKEN}}' },
20
+ retry: { attempts: 3, backoffMs: 300 },
21
+ cache: { ttlMs: 30000 },
22
+ transform: 'data.items',
23
+ };
24
+ ```
25
+
26
+ ## Server: the proxy (secrets never reach the client)
27
+
28
+ ```ts
29
+ import { createProxy } from '@noidmejs/atomkit-http';
30
+
31
+ const proxy = createProxy({
32
+ sources: { listUsers },
33
+ secrets: { API_TOKEN: process.env.API_TOKEN! },
34
+ allowHosts: ['api.example.com'], // SSRF guard
35
+ });
36
+
37
+ // Next route handler / Cloudflare Worker:
38
+ export const POST = (req: Request) => proxy.handleRequest(req);
39
+ ```
40
+
41
+ The client POSTs `{ sourceId, params }`; the proxy injects secrets, enforces the
42
+ host allow-list, calls the API, and returns the (transformed) JSON. Tokens stay
43
+ on the server.
44
+
45
+ ## Client: run a public source directly
46
+
47
+ ```ts
48
+ import { runSource } from '@noidmejs/atomkit-http';
49
+ const res = await runSource(publicSource, { params: { q: 'hi' } });
50
+ // res: { ok, status, data, error }
51
+ ```
52
+
53
+ ## Config
54
+
55
+ `DataSource`: `method`, `url`, `headers`, `query`, `body`, `auth`
56
+ (`bearer` / `basic` / `apiKey`), `timeoutMs`, `retry`, `cache`, `transform`
57
+ (JSON path). `{{params.x}}` interpolates caller params; `{{secret.X}}` resolves
58
+ **server-side only**, in the proxy.
59
+
60
+ ## Security status (v0.1 — read this)
61
+
62
+ Hostname allow-list SSRF tier + server-only secrets + prototype-pollution-guarded
63
+ templating. It is **not yet pen-tested**, and it does **not** pin DNS/IPs (a
64
+ resolved hostname could still point at a private IP). Do not expose it to
65
+ untrusted multi-tenant input without adding IP-pinning and a security review.
66
+
67
+ MIT © noidmejs
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export { interpolate, interpolateDeep, getPath } from './template.js';
3
+ export { runSource } from './run.js';
4
+ export { createProxy, type ProxyConfig } from './proxy.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @noidmejs/atomkit-http — declarative HTTP/backend connector.
2
+ // Call any API by config; keep secrets server-only behind an SSRF-guarded proxy.
3
+ export * from './types.js';
4
+ export { interpolate, interpolateDeep, getPath } from './template.js';
5
+ export { runSource } from './run.js';
6
+ export { createProxy } from './proxy.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iFAAiF;AACjF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { DataSource, RunResult } from './types.js';
2
+ export interface ProxyConfig {
3
+ sources: Record<string, DataSource>;
4
+ /** Server-only secret values referenced as {{secret.NAME}}. Never leaves the server. */
5
+ secrets?: Record<string, string>;
6
+ /** Allow-listed request hosts — exact ("api.example.com") or suffix (".example.com"). */
7
+ allowHosts: string[];
8
+ fetchImpl?: typeof fetch;
9
+ }
10
+ /**
11
+ * A server-side proxy: resolves a named source with secrets injected, enforcing a
12
+ * host allow-list (SSRF guard). The client calls `resolve(sourceId, params)` and
13
+ * never sees secrets.
14
+ *
15
+ * SECURITY (v0.1): this is the hostname allow-list tier — it blocks off-list and
16
+ * param-injected hosts, but does NOT yet pin DNS/IPs (a resolved host could still
17
+ * point at a private IP). It is NOT pen-tested. Do not expose to untrusted
18
+ * multi-tenant input without adding IP-pinning + a security review.
19
+ */
20
+ export declare function createProxy(config: ProxyConfig): {
21
+ resolve: (sourceId: string, params?: Record<string, unknown>) => Promise<RunResult>;
22
+ handleRequest: (req: Request) => Promise<Response>;
23
+ };
24
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIxD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yFAAyF;IACzF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW;wBACZ,MAAM,WAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAQ,OAAO,CAAC,SAAS,CAAC;yBAahE,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAqB9D"}
package/dist/proxy.js ADDED
@@ -0,0 +1,66 @@
1
+ import { interpolate } from './template.js';
2
+ import { runSource } from './run.js';
3
+ function hostAllowed(rawUrl, allow) {
4
+ let host;
5
+ try {
6
+ const u = new URL(rawUrl);
7
+ if (u.protocol !== 'https:' && u.protocol !== 'http:')
8
+ return false;
9
+ host = u.hostname.toLowerCase();
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ return allow.some((a) => {
15
+ const s = a.toLowerCase();
16
+ return s.startsWith('.') ? host === s.slice(1) || host.endsWith(s) : host === s;
17
+ });
18
+ }
19
+ /**
20
+ * A server-side proxy: resolves a named source with secrets injected, enforcing a
21
+ * host allow-list (SSRF guard). The client calls `resolve(sourceId, params)` and
22
+ * never sees secrets.
23
+ *
24
+ * SECURITY (v0.1): this is the hostname allow-list tier — it blocks off-list and
25
+ * param-injected hosts, but does NOT yet pin DNS/IPs (a resolved host could still
26
+ * point at a private IP). It is NOT pen-tested. Do not expose to untrusted
27
+ * multi-tenant input without adding IP-pinning + a security review.
28
+ */
29
+ export function createProxy(config) {
30
+ async function resolve(sourceId, params = {}) {
31
+ const source = config.sources[sourceId];
32
+ if (!source)
33
+ return { ok: false, status: 404, error: `unknown source "${sourceId}"` };
34
+ // Compute the effective host from params (secrets stripped) and allow-list it,
35
+ // so neither a static URL nor a param-injected one can reach an off-list host.
36
+ const withoutSecrets = source.url.replace(/\{\{\s*secret\.[\w.$-]+\s*\}\}/g, '');
37
+ const checkUrl = interpolate(withoutSecrets, { params, secret: {} });
38
+ if (!hostAllowed(checkUrl, config.allowHosts))
39
+ return { ok: false, status: 403, error: 'host not allowed' };
40
+ return runSource(source, { params, secrets: config.secrets, fetchImpl: config.fetchImpl });
41
+ }
42
+ /** Web-standard handler: POST { sourceId, params } → JSON RunResult. Mount in a
43
+ * Next route handler, a Cloudflare Worker, an Express adapter, etc. */
44
+ async function handleRequest(req) {
45
+ let payload = {};
46
+ try {
47
+ payload = (await req.json());
48
+ }
49
+ catch {
50
+ /* invalid body → handled below */
51
+ }
52
+ if (!payload.sourceId) {
53
+ return new Response(JSON.stringify({ ok: false, status: 400, error: 'missing sourceId' }), {
54
+ status: 400,
55
+ headers: { 'content-type': 'application/json' },
56
+ });
57
+ }
58
+ const result = await resolve(payload.sourceId, payload.params ?? {});
59
+ return new Response(JSON.stringify(result), {
60
+ status: result.ok ? 200 : result.status || 500,
61
+ headers: { 'content-type': 'application/json', 'cache-control': 'no-store' },
62
+ });
63
+ }
64
+ return { resolve, handleRequest };
65
+ }
66
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { DataSource, RunContext, RunResult } from './types.js';
2
+ /**
3
+ * Resolve a data source: build the request from config, fetch (with retry/backoff/
4
+ * timeout), extract via `transform`, and cache successful GETs for `cache.ttlMs`.
5
+ * Works client- or server-side; pass `secrets` only on the server.
6
+ */
7
+ export declare function runSource<T = unknown>(source: DataSource, ctx?: RunContext): Promise<RunResult<T>>;
8
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA8BpE;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CA2C5G"}
package/dist/run.js ADDED
@@ -0,0 +1,84 @@
1
+ import { interpolate, interpolateDeep, getPath } from './template.js';
2
+ const memo = new Map();
3
+ function buildUrl(source, scope) {
4
+ let url = interpolate(source.url, scope);
5
+ const entries = [];
6
+ for (const [k, v] of Object.entries(source.query ?? {}))
7
+ entries.push([k, interpolate(v, scope)]);
8
+ if (source.auth?.type === 'apiKey' && source.auth.queryParam) {
9
+ entries.push([source.auth.queryParam, interpolate(source.auth.key ?? '', scope)]);
10
+ }
11
+ if (entries.length) {
12
+ const u = new URL(url);
13
+ for (const [k, v] of entries)
14
+ u.searchParams.set(k, v);
15
+ url = u.toString();
16
+ }
17
+ return url;
18
+ }
19
+ function buildHeaders(source, scope) {
20
+ const h = {};
21
+ for (const [k, v] of Object.entries(source.headers ?? {}))
22
+ h[k.toLowerCase()] = interpolate(v, scope);
23
+ const a = source.auth;
24
+ if (a?.type === 'bearer' && a.token)
25
+ h['authorization'] = `Bearer ${interpolate(a.token, scope)}`;
26
+ else if (a?.type === 'basic')
27
+ h['authorization'] = `Basic ${btoa(`${interpolate(a.username ?? '', scope)}:${interpolate(a.password ?? '', scope)}`)}`;
28
+ else if (a?.type === 'apiKey' && a.header)
29
+ h[a.header.toLowerCase()] = interpolate(a.key ?? '', scope);
30
+ return h;
31
+ }
32
+ /**
33
+ * Resolve a data source: build the request from config, fetch (with retry/backoff/
34
+ * timeout), extract via `transform`, and cache successful GETs for `cache.ttlMs`.
35
+ * Works client- or server-side; pass `secrets` only on the server.
36
+ */
37
+ export async function runSource(source, ctx = {}) {
38
+ const scope = { params: ctx.params ?? {}, secret: ctx.secrets ?? {} };
39
+ const method = source.method ?? 'GET';
40
+ const cacheKey = source.cache?.ttlMs && method === 'GET' ? `${source.id}:${JSON.stringify(ctx.params ?? {})}` : '';
41
+ if (cacheKey) {
42
+ const hit = memo.get(cacheKey);
43
+ if (hit && Date.now() - hit.at < source.cache.ttlMs)
44
+ return { ok: true, status: 200, data: hit.data };
45
+ }
46
+ const url = buildUrl(source, scope);
47
+ const headers = buildHeaders(source, scope);
48
+ let body;
49
+ if (source.body !== undefined && method !== 'GET' && method !== 'HEAD') {
50
+ const b = interpolateDeep(source.body, scope);
51
+ body = typeof b === 'string' ? b : JSON.stringify(b);
52
+ if (typeof b !== 'string' && !headers['content-type'])
53
+ headers['content-type'] = 'application/json';
54
+ }
55
+ const f = ctx.fetchImpl ?? fetch;
56
+ const attempts = Math.max(1, source.retry?.attempts ?? 1);
57
+ const backoff = source.retry?.backoffMs ?? 300;
58
+ let last = { ok: false, status: 0, error: 'not run' };
59
+ for (let i = 0; i < attempts; i++) {
60
+ try {
61
+ const ctrl = source.timeoutMs ? new AbortController() : null;
62
+ const timer = ctrl ? setTimeout(() => ctrl.abort(), source.timeoutMs) : null;
63
+ const res = await f(url, { method, headers, body, signal: ctrl?.signal ?? null });
64
+ if (timer)
65
+ clearTimeout(timer);
66
+ const ct = res.headers.get('content-type') ?? '';
67
+ const raw = ct.includes('json') ? await res.json() : await res.text();
68
+ const data = (source.transform ? getPath(raw, source.transform) : raw);
69
+ last = { ok: res.ok, status: res.status, data, error: res.ok ? undefined : `HTTP ${res.status}` };
70
+ if (res.ok) {
71
+ if (cacheKey)
72
+ memo.set(cacheKey, { at: Date.now(), data });
73
+ return last;
74
+ }
75
+ }
76
+ catch (e) {
77
+ last = { ok: false, status: 0, error: e instanceof Error ? e.message : 'fetch failed' };
78
+ }
79
+ if (i < attempts - 1)
80
+ await new Promise((r) => setTimeout(r, backoff * (i + 1)));
81
+ }
82
+ return last;
83
+ }
84
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function getPath(obj: unknown, path: string): unknown;
2
+ export declare function interpolate(input: string, ctx: Record<string, unknown>): string;
3
+ export declare function interpolateDeep(value: unknown, ctx: Record<string, unknown>): unknown;
4
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ3D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAK/E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CASrF"}
@@ -0,0 +1,33 @@
1
+ // Safe {{path}} interpolation over a context (NO eval). Paths dot-walk the
2
+ // context (e.g. params.id, secret.KEY) with a prototype-pollution guard.
3
+ export function getPath(obj, path) {
4
+ let cur = obj;
5
+ for (const seg of path.split(/[.[\]]+/).filter(Boolean)) {
6
+ if (cur == null)
7
+ return undefined;
8
+ if (seg === '__proto__' || seg === 'constructor' || seg === 'prototype')
9
+ return undefined;
10
+ cur = cur[seg];
11
+ }
12
+ return cur;
13
+ }
14
+ export function interpolate(input, ctx) {
15
+ return input.replace(/\{\{\s*([\w.$-]+)\s*\}\}/g, (_m, path) => {
16
+ const v = getPath(ctx, path);
17
+ return v == null ? '' : String(v);
18
+ });
19
+ }
20
+ export function interpolateDeep(value, ctx) {
21
+ if (typeof value === 'string')
22
+ return interpolate(value, ctx);
23
+ if (Array.isArray(value))
24
+ return value.map((v) => interpolateDeep(v, ctx));
25
+ if (value && typeof value === 'object') {
26
+ const out = {};
27
+ for (const [k, v] of Object.entries(value))
28
+ out[k] = interpolateDeep(v, ctx);
29
+ return out;
30
+ }
31
+ return value;
32
+ }
33
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,UAAU,OAAO,CAAC,GAAY,EAAE,IAAY;IAChD,IAAI,GAAG,GAAY,GAAG,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,SAAS,CAAC;QAClC,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,WAAW;YAAE,OAAO,SAAS,CAAC;QAC1F,GAAG,GAAI,GAA+B,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,GAA4B;IACrE,OAAO,KAAK,CAAC,OAAO,CAAC,2BAA2B,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE;QACrE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,GAA4B;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3E,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC7E,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,50 @@
1
+ export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
2
+ export interface AuthConfig {
3
+ type: 'bearer' | 'basic' | 'apiKey';
4
+ /** bearer token (may be a {{secret.X}} reference — resolved server-side only). */
5
+ token?: string;
6
+ /** basic auth. */
7
+ username?: string;
8
+ password?: string;
9
+ /** apiKey: the value + where it goes (a header name OR a query param name). */
10
+ key?: string;
11
+ header?: string;
12
+ queryParam?: string;
13
+ }
14
+ export interface RetryConfig {
15
+ attempts?: number;
16
+ backoffMs?: number;
17
+ }
18
+ export interface CacheConfig {
19
+ ttlMs?: number;
20
+ }
21
+ /** A declarative data source. url/headers/query/body may contain {{params.x}}
22
+ * and (server-side only) {{secret.X}} template references. */
23
+ export interface DataSource {
24
+ id: string;
25
+ method?: HttpVerb;
26
+ url: string;
27
+ headers?: Record<string, string>;
28
+ query?: Record<string, string>;
29
+ body?: unknown;
30
+ auth?: AuthConfig;
31
+ timeoutMs?: number;
32
+ retry?: RetryConfig;
33
+ cache?: CacheConfig;
34
+ /** Dot/bracket path into the JSON response to extract, e.g. "data.items". */
35
+ transform?: string;
36
+ }
37
+ export interface RunContext {
38
+ params?: Record<string, unknown>;
39
+ /** Server-only secret values referenced as {{secret.NAME}}. Never pass on the client. */
40
+ secrets?: Record<string, string>;
41
+ /** Override fetch (for tests / custom runtimes). */
42
+ fetchImpl?: typeof fetch;
43
+ }
44
+ export interface RunResult<T = unknown> {
45
+ ok: boolean;
46
+ status: number;
47
+ data?: T;
48
+ error?: string;
49
+ }
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;+DAC+D;AAC/D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@noidmejs/atomkit-http",
3
+ "version": "0.1.0",
4
+ "description": "Declarative HTTP/backend connector: call any API by config (all verbs, headers, query, body, auth, retries, cache, transforms). Secrets stay server-only via a proxy with an SSRF host allow-list.",
5
+ "keywords": ["atomkit", "http", "connector", "declarative", "fetch", "api", "proxy", "ssrf", "no-code"],
6
+ "license": "MIT",
7
+ "author": "noidmejs",
8
+ "type": "module",
9
+ "sideEffects": false,
10
+ "files": ["dist"],
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "test": "node test/http.test.mjs",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "devDependencies": { "typescript": "^5.6.0", "@types/node": "^20.0.0" },
20
+ "publishConfig": { "access": "public" },
21
+ "repository": { "type": "git", "url": "git+https://github.com/noidme-git/atomkit-http.git" },
22
+ "engines": { "node": ">=18" }
23
+ }