@codecks/fetch 0.1.8 → 1.0.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/README.md +43 -18
- package/dist/_exploration/api-requester.d.ts +1 -1
- package/dist/_exploration/api-requester.js +2 -2
- package/dist/collection-utils-T3bx0BS1.js +78 -0
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -8
- package/dist/loader-utils-hvdC-xsQ.d.ts +42 -0
- package/package.json +1 -1
- package/schema/query-syntax.md +2 -2
- package/dist/collection-utils-CYJaA1a9.js +0 -32
- package/dist/loader-utils-BqZKAn_v.d.ts +0 -19
package/README.md
CHANGED
|
@@ -11,26 +11,51 @@ npm install @codecks/fetch
|
|
|
11
11
|
## Getting started
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
const {fetchFromRoot, fetchInstance, fetchInstances, fetchFromInstance} =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
subdomain: "my-org",
|
|
20
|
-
accessToken: "your-token",
|
|
21
|
-
});
|
|
14
|
+
import {buildFetchers} from "@codecks/fetch";
|
|
15
|
+
|
|
16
|
+
const {fetchFromRoot, fetchInstance, fetchInstances, fetchFromInstance} = buildFetchers({
|
|
17
|
+
token: "cdxat_…",
|
|
18
|
+
});
|
|
22
19
|
```
|
|
23
20
|
|
|
21
|
+
`token` is an organization token (`cdxat_…`) or a personal token (`cdxut_…`). Create one under
|
|
22
|
+
**Organization Settings → Integrations → API Tokens** or **Your Profile → API Tokens**. It is sent
|
|
23
|
+
as `Authorization: Bearer <token>` and already names its organization, so no subdomain is needed.
|
|
24
|
+
|
|
24
25
|
### Configuration options
|
|
25
26
|
|
|
26
|
-
| Option
|
|
27
|
-
|
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
|
|
27
|
+
| Option | Type | Description |
|
|
28
|
+
| --------- | ------------------------ | ---------------------------------------------------- |
|
|
29
|
+
| `token` | `string` | API token, `cdxat_…` or `cdxut_…` (required) |
|
|
30
|
+
| `baseUrl` | `string` | API base URL, defaults to `https://api.codecks.io/` |
|
|
31
|
+
| `headers` | `Record<string, string>` | Additional request headers |
|
|
32
|
+
| `timeout` | `number` | Request timeout in ms; the request aborts after that |
|
|
33
|
+
| `fetch` | `typeof fetch` | Custom fetch implementation |
|
|
34
|
+
|
|
35
|
+
### Errors
|
|
36
|
+
|
|
37
|
+
A non-2xx answer throws a `CodecksApiError`:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import {CodecksApiError} from "@codecks/fetch";
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await fetchFromRoot({account: {fields: ["name"]}});
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e instanceof CodecksApiError) {
|
|
46
|
+
e.status; // 400, 401, 403, 429, …
|
|
47
|
+
e.code; // "invalid_token", "token_expired", "missing_scope", "unknown_field", …
|
|
48
|
+
e.path; // "_root.account.cards.titel" for a query error
|
|
49
|
+
e.body; // the full response body, e.g. `hint` or `requiredScope`
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Legacy tokens
|
|
55
|
+
|
|
56
|
+
`buildLegacyFetchers({accessToken, subdomain, baseUrl, …})` sends the old `X-Auth-Token` and
|
|
57
|
+
`X-Account` headers. The API stops accepting `X-Auth-Token` on **2026-12-31**; move to an API token
|
|
58
|
+
before then.
|
|
34
59
|
|
|
35
60
|
## Fetching data
|
|
36
61
|
|
|
@@ -324,9 +349,9 @@ Point your LLM's project instructions (e.g. `CLAUDE.md`) at `schema/overview.md`
|
|
|
324
349
|
For advanced use cases (batching, caching, custom transports), you can provide your own `DataLoader`:
|
|
325
350
|
|
|
326
351
|
```ts
|
|
327
|
-
import {
|
|
352
|
+
import {buildFetchersFromLoader} from "@codecks/fetch";
|
|
328
353
|
|
|
329
|
-
const {fetchFromRoot} =
|
|
354
|
+
const {fetchFromRoot} = buildFetchersFromLoader({
|
|
330
355
|
fetchModel: async (model, ids, query) => {
|
|
331
356
|
// your custom loading logic
|
|
332
357
|
return recordOfResults;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../query-type-BqQ9oX_U.js";
|
|
2
2
|
import "../index-k-KPWV9R.js";
|
|
3
|
-
import { FetchOptions } from "../loader-utils-
|
|
3
|
+
import { FetchOptions } from "../loader-utils-hvdC-xsQ.js";
|
|
4
4
|
import { BaseRequester, MissingDataRequest } from "../loader-types-fd5tV0FD.js";
|
|
5
5
|
|
|
6
6
|
//#region src/_exploration/api-requester.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { configuredFetch, ensureMapValue } from "../collection-utils-
|
|
1
|
+
import { bearerTransport, configuredFetch, ensureMapValue } from "../collection-utils-T3bx0BS1.js";
|
|
2
2
|
import { serializeModel } from "../query-helpers-iX1fiqLv.js";
|
|
3
3
|
|
|
4
4
|
//#region src/_exploration/utils/concurrency-limiter.ts
|
|
@@ -66,7 +66,7 @@ var ApiRequester = class {
|
|
|
66
66
|
fetchOptions;
|
|
67
67
|
limiter;
|
|
68
68
|
constructor(fetchOptions, maxConcurrent = 3) {
|
|
69
|
-
this.fetchOptions = fetchOptions;
|
|
69
|
+
this.fetchOptions = bearerTransport(fetchOptions);
|
|
70
70
|
this.limiter = new ConcurrencyLimiter(maxConcurrent);
|
|
71
71
|
}
|
|
72
72
|
async request(requests) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/loaders/loader-utils.ts
|
|
2
|
+
const DEFAULT_BASE_URL = "https://api.codecks.io/";
|
|
3
|
+
const bearerTransport = (opts) => {
|
|
4
|
+
const { token,...rest } = opts;
|
|
5
|
+
if (!/^cdx[au]t_/.test(token)) throw new Error("Expected an API token starting with `cdxat_` or `cdxut_`. Legacy tokens need `buildLegacyFetchers`.");
|
|
6
|
+
return {
|
|
7
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
8
|
+
...rest,
|
|
9
|
+
authHeaders: { Authorization: `Bearer ${token}` }
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const legacyTransport = (opts) => {
|
|
13
|
+
const { accessToken, subdomain,...rest } = opts;
|
|
14
|
+
const authHeaders = {};
|
|
15
|
+
if (accessToken) authHeaders["X-Auth-Token"] = accessToken;
|
|
16
|
+
if (subdomain) authHeaders["X-Account"] = subdomain;
|
|
17
|
+
return {
|
|
18
|
+
...rest,
|
|
19
|
+
authHeaders
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/** A non-2xx answer. `code`, `path` and the rest of the body follow the API's error format. */
|
|
23
|
+
var CodecksApiError = class extends Error {
|
|
24
|
+
status;
|
|
25
|
+
/** e.g. `invalid_token`, `token_expired`, `missing_scope`, `unknown_field`, `rate_limit` */
|
|
26
|
+
code;
|
|
27
|
+
/** where in the query the error sits, e.g. `_root.account.cards.titel` */
|
|
28
|
+
path;
|
|
29
|
+
/** the parsed JSON body, or the raw text if it wasn't JSON — holds `hint`, `requiredScope`, … */
|
|
30
|
+
body;
|
|
31
|
+
constructor(status, body) {
|
|
32
|
+
const obj = typeof body === "object" && body !== null ? body : {};
|
|
33
|
+
const str = (v) => typeof v === "string" ? v : null;
|
|
34
|
+
const code = str(obj.error);
|
|
35
|
+
super(`[${status}] ${str(obj.message) ?? code ?? (str(body) || "request failed")}`);
|
|
36
|
+
this.name = "CodecksApiError";
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.path = str(obj.path);
|
|
40
|
+
this.body = body;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const readErrorBody = async (r) => {
|
|
44
|
+
const text = await r.text();
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(text);
|
|
47
|
+
} catch {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const configuredFetch = async (opts, url, init = {}) => {
|
|
52
|
+
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
53
|
+
const headers = new Headers(init.headers);
|
|
54
|
+
for (const [key, value] of Object.entries(opts.authHeaders)) headers.set(key, value);
|
|
55
|
+
if (opts.headers) for (const [key, value] of Object.entries(opts.headers)) headers.set(key, value);
|
|
56
|
+
const fullUrl = opts.baseUrl ? `${opts.baseUrl}${url}` : url;
|
|
57
|
+
const signal = opts.timeout ? AbortSignal.timeout(opts.timeout) : init.signal;
|
|
58
|
+
const r = await fetchImpl(fullUrl, {
|
|
59
|
+
...init,
|
|
60
|
+
headers,
|
|
61
|
+
signal
|
|
62
|
+
});
|
|
63
|
+
if (r.status < 200 || r.status >= 300) throw new CodecksApiError(r.status, await readErrorBody(r));
|
|
64
|
+
return await r.json();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/collection-utils.ts
|
|
69
|
+
const ensureMapValue = (map, key, fallback) => {
|
|
70
|
+
const exist = map.get(key);
|
|
71
|
+
if (exist) return exist;
|
|
72
|
+
const newValue = fallback();
|
|
73
|
+
map.set(key, newValue);
|
|
74
|
+
return newValue;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { CodecksApiError, bearerTransport, configuredFetch, ensureMapValue, legacyTransport };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { InferModelQuery, InferRelQuery, Instance, ModelQuery, RelQuery } from "./query-type-BqQ9oX_U.js";
|
|
2
2
|
import { modelMap$1 as modelMap } from "./index-k-KPWV9R.js";
|
|
3
|
-
import { DataLoader, FetchOptions } from "./loader-utils-
|
|
3
|
+
import { CodecksApiError$1 as CodecksApiError, DataLoader, FetchOptions, LegacyFetchOptions } from "./loader-utils-hvdC-xsQ.js";
|
|
4
4
|
import { _rootDesc$1 as _rootDesc } from "./_root-BW9uzB79.js";
|
|
5
5
|
|
|
6
|
-
//#region src/loaders/simple-loader.d.ts
|
|
7
|
-
type SimpleLoaderOptions = FetchOptions;
|
|
8
|
-
//#endregion
|
|
9
6
|
//#region src/index.d.ts
|
|
10
7
|
type ModelMap = typeof modelMap;
|
|
11
8
|
type Fetchers = {
|
|
@@ -14,7 +11,9 @@ type Fetchers = {
|
|
|
14
11
|
fetchInstance: <K extends keyof ModelMap, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: string, q: Q) => Promise<InferModelQuery<ModelMap[K], Q, ModelMap>>;
|
|
15
12
|
fetchInstances: <K extends keyof ModelMap, Id extends string, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap[K], Q, ModelMap>>>;
|
|
16
13
|
};
|
|
17
|
-
declare const
|
|
18
|
-
|
|
14
|
+
declare const buildFetchers: (opts: FetchOptions) => Fetchers;
|
|
15
|
+
/** @deprecated `X-Auth-Token` stops working on 2026-12-31. Use `buildFetchers` with an API token. */
|
|
16
|
+
declare const buildLegacyFetchers: (opts: LegacyFetchOptions) => Fetchers;
|
|
17
|
+
declare const buildFetchersFromLoader: (loader: DataLoader) => Fetchers;
|
|
19
18
|
//#endregion
|
|
20
|
-
export { buildFetchers,
|
|
19
|
+
export { CodecksApiError, type DataLoader, type FetchOptions, type LegacyFetchOptions, buildFetchers, buildFetchersFromLoader, buildLegacyFetchers };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { configuredFetch, ensureMapValue } from "./collection-utils-
|
|
1
|
+
import { CodecksApiError, bearerTransport, configuredFetch, ensureMapValue, legacyTransport } from "./collection-utils-T3bx0BS1.js";
|
|
2
2
|
import { _rootDesc, getRelKey, makeModelQuerySerializable, modelMap, serializeInstanceQuery } from "./query-helpers-iX1fiqLv.js";
|
|
3
3
|
|
|
4
4
|
//#region src/model-pool.ts
|
|
@@ -126,7 +126,7 @@ const reconcileInstanceQuery = (query, response, instanceModel, key, store) => {
|
|
|
126
126
|
|
|
127
127
|
//#endregion
|
|
128
128
|
//#region src/loaders/simple-loader.ts
|
|
129
|
-
const createSimpleLoader = (opts
|
|
129
|
+
const createSimpleLoader = (opts) => {
|
|
130
130
|
const fetchWithQuery = async (query) => {
|
|
131
131
|
return configuredFetch(opts, "", {
|
|
132
132
|
method: "POST",
|
|
@@ -149,11 +149,10 @@ const createSimpleLoader = (opts = {}) => {
|
|
|
149
149
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/index.ts
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const buildFetchers = (loader) => {
|
|
152
|
+
const buildFetchers = (opts) => buildFetchersFromLoader(createSimpleLoader(bearerTransport(opts)));
|
|
153
|
+
/** @deprecated `X-Auth-Token` stops working on 2026-12-31. Use `buildFetchers` with an API token. */
|
|
154
|
+
const buildLegacyFetchers = (opts) => buildFetchersFromLoader(createSimpleLoader(legacyTransport(opts)));
|
|
155
|
+
const buildFetchersFromLoader = (loader) => {
|
|
157
156
|
return {
|
|
158
157
|
fetchFromRoot: async (q) => {
|
|
159
158
|
const res = await loader.fetchModel("_root", [""], { relations: q });
|
|
@@ -173,4 +172,4 @@ const buildFetchers = (loader) => {
|
|
|
173
172
|
};
|
|
174
173
|
|
|
175
174
|
//#endregion
|
|
176
|
-
export { buildFetchers,
|
|
175
|
+
export { CodecksApiError, buildFetchers, buildFetchersFromLoader, buildLegacyFetchers };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { InferModelQuery, ModelQuery } from "./query-type-BqQ9oX_U.js";
|
|
2
|
+
import { modelMap$1 as modelMap } from "./index-k-KPWV9R.js";
|
|
3
|
+
|
|
4
|
+
//#region src/loaders/loader-utils.d.ts
|
|
5
|
+
type ModelMap = typeof modelMap;
|
|
6
|
+
type DataLoader = {
|
|
7
|
+
fetchModel: <K extends keyof ModelMap, Id extends string, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap[K], Q, ModelMap>>>;
|
|
8
|
+
};
|
|
9
|
+
type FetchFunction = (url: string, init?: RequestInit) => Promise<Response>;
|
|
10
|
+
type BaseFetchOptions = {
|
|
11
|
+
fetch?: FetchFunction;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
/** in milliseconds */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
};
|
|
17
|
+
type FetchOptions = BaseFetchOptions & {
|
|
18
|
+
/** an organization (`cdxat_…`) or personal (`cdxut_…`) API token */
|
|
19
|
+
token: string;
|
|
20
|
+
};
|
|
21
|
+
/** @deprecated `X-Auth-Token` stops working on 2026-12-31. Use `FetchOptions` with an API token. */
|
|
22
|
+
type LegacyFetchOptions = BaseFetchOptions & {
|
|
23
|
+
/** sent as `X-Auth-Token` */
|
|
24
|
+
accessToken?: string;
|
|
25
|
+
/** sent as `X-Account` */
|
|
26
|
+
subdomain?: string;
|
|
27
|
+
};
|
|
28
|
+
/** Fetch options with the auth headers already folded into `headers`. */
|
|
29
|
+
|
|
30
|
+
/** A non-2xx answer. `code`, `path` and the rest of the body follow the API's error format. */
|
|
31
|
+
declare class CodecksApiError extends Error {
|
|
32
|
+
readonly status: number;
|
|
33
|
+
/** e.g. `invalid_token`, `token_expired`, `missing_scope`, `unknown_field`, `rate_limit` */
|
|
34
|
+
readonly code: string | null;
|
|
35
|
+
/** where in the query the error sits, e.g. `_root.account.cards.titel` */
|
|
36
|
+
readonly path: string | null;
|
|
37
|
+
/** the parsed JSON body, or the raw text if it wasn't JSON — holds `hint`, `requiredScope`, … */
|
|
38
|
+
readonly body: unknown;
|
|
39
|
+
constructor(status: number, body: unknown);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { CodecksApiError as CodecksApiError$1, DataLoader, FetchOptions, LegacyFetchOptions };
|
package/package.json
CHANGED
package/schema/query-syntax.md
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
//#region src/loaders/loader-utils.ts
|
|
2
|
-
const configuredFetch = async (opts, url, init = {}) => {
|
|
3
|
-
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
4
|
-
const headers = new Headers(init.headers);
|
|
5
|
-
if (opts.accessToken) headers.set("X-Auth-Token", opts.accessToken);
|
|
6
|
-
if (opts.subdomain) headers.set("X-Account", opts.subdomain);
|
|
7
|
-
if (opts.headers) Object.entries(opts.headers).forEach(([key, value]) => {
|
|
8
|
-
headers.set(key, value);
|
|
9
|
-
});
|
|
10
|
-
const fullUrl = opts.baseUrl ? `${opts.baseUrl}${url}` : url;
|
|
11
|
-
return fetchImpl(fullUrl, {
|
|
12
|
-
...init,
|
|
13
|
-
headers
|
|
14
|
-
}).then(async (r) => {
|
|
15
|
-
const content = await r.json();
|
|
16
|
-
if (r.status !== 200) throw new Error(`[${r.status}] ${JSON.stringify(content)}`);
|
|
17
|
-
return content;
|
|
18
|
-
});
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/collection-utils.ts
|
|
23
|
-
const ensureMapValue = (map, key, fallback) => {
|
|
24
|
-
const exist = map.get(key);
|
|
25
|
-
if (exist) return exist;
|
|
26
|
-
const newValue = fallback();
|
|
27
|
-
map.set(key, newValue);
|
|
28
|
-
return newValue;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
//#endregion
|
|
32
|
-
export { configuredFetch, ensureMapValue };
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { InferModelQuery, ModelQuery } from "./query-type-BqQ9oX_U.js";
|
|
2
|
-
import { modelMap$1 as modelMap } from "./index-k-KPWV9R.js";
|
|
3
|
-
|
|
4
|
-
//#region src/loaders/loader-utils.d.ts
|
|
5
|
-
type ModelMap = typeof modelMap;
|
|
6
|
-
type DataLoader = {
|
|
7
|
-
fetchModel: <K extends keyof ModelMap, Id extends string, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap[K], Q, ModelMap>>>;
|
|
8
|
-
};
|
|
9
|
-
type FetchFunction = (url: string, init?: RequestInit) => Promise<Response>;
|
|
10
|
-
type FetchOptions = {
|
|
11
|
-
fetch?: FetchFunction;
|
|
12
|
-
accessToken?: string;
|
|
13
|
-
subdomain?: string;
|
|
14
|
-
baseUrl?: string;
|
|
15
|
-
headers?: Record<string, string>;
|
|
16
|
-
timeout?: number;
|
|
17
|
-
};
|
|
18
|
-
//#endregion
|
|
19
|
-
export { DataLoader, FetchOptions };
|