@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 CHANGED
@@ -11,26 +11,51 @@ npm install @codecks/fetch
11
11
  ## Getting started
12
12
 
13
13
  ```ts
14
- import {buildFetchersWithSimpleLoader} from "@codecks/fetch";
15
-
16
- const {fetchFromRoot, fetchInstance, fetchInstances, fetchFromInstance} =
17
- buildFetchersWithSimpleLoader({
18
- baseUrl: "https://api.codecks.io/",
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 | Type | Description |
27
- | ------------- | ------------------------ | ------------------------------ |
28
- | `baseUrl` | `string` | API base URL |
29
- | `subdomain` | `string` | Sets the `X-Account` header |
30
- | `accessToken` | `string` | Sets the `X-Auth-Token` header |
31
- | `headers` | `Record<string, string>` | Additional request headers |
32
- | `timeout` | `number` | Request timeout in ms |
33
- | `fetch` | `typeof fetch` | Custom fetch implementation |
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 {buildFetchers} from "@codecks/fetch";
352
+ import {buildFetchersFromLoader} from "@codecks/fetch";
328
353
 
329
- const {fetchFromRoot} = buildFetchers({
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-BqZKAn_v.js";
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-CYJaA1a9.js";
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-BqZKAn_v.js";
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 buildFetchersWithSimpleLoader: (opts: SimpleLoaderOptions) => Fetchers;
18
- declare const buildFetchers: (loader: DataLoader) => Fetchers;
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, buildFetchersWithSimpleLoader };
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-CYJaA1a9.js";
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 buildFetchersWithSimpleLoader = (opts) => {
153
- const loader = createSimpleLoader(opts);
154
- return buildFetchers(loader);
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, buildFetchersWithSimpleLoader };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codecks/fetch",
3
- "version": "0.1.8",
3
+ "version": "1.0.0",
4
4
  "description": "Quickly create deeply nested queries for the Codecks API with advanced TypeScript support",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,8 +3,8 @@
3
3
  ## Basic Query
4
4
 
5
5
  ```ts
6
- const {fetchFromRoot, fetchInstance} = buildFetchersWithSimpleLoader({
7
- token: "your-api-token",
6
+ const {fetchFromRoot, fetchInstance} = buildFetchers({
7
+ token: "cdxat_…",
8
8
  });
9
9
 
10
10
  // Fetch from root entry points
@@ -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 };