highseat 0.7.0 → 0.8.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/api.d.ts +123 -0
- package/api.gen.d.ts +5155 -0
- package/api.js +84 -0
- package/api.js.map +1 -0
- package/package.json +5 -1
package/api.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//#region src/api.ts
|
|
2
|
+
const DEFAULT_BASE_URL = "https://api.highseat.com/v1";
|
|
3
|
+
/** A failure that carries no error body, such as an answer from a proxy in front of the API. */
|
|
4
|
+
const UNKNOWN_CODE = "INTERNAL_SERVER_ERROR";
|
|
5
|
+
/** A call the API refused. Branch on `code`, which is stable. */
|
|
6
|
+
var HighseatApiError = class extends Error {
|
|
7
|
+
/** The HTTP status. */
|
|
8
|
+
status;
|
|
9
|
+
/** Stable and machine-readable, such as `RATE_LIMITED`. */
|
|
10
|
+
code;
|
|
11
|
+
/** Names the request. Quote it to support. */
|
|
12
|
+
requestId;
|
|
13
|
+
/** On a conflict, the existing row's id. */
|
|
14
|
+
id;
|
|
15
|
+
/** One entry per invalid parameter. */
|
|
16
|
+
issues;
|
|
17
|
+
constructor(status, failure) {
|
|
18
|
+
super(failure.message ?? `The Highseat API answered ${status}.`);
|
|
19
|
+
this.name = "HighseatApiError";
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.code = failure.code ?? UNKNOWN_CODE;
|
|
22
|
+
this.requestId = failure.requestId;
|
|
23
|
+
this.id = failure.id;
|
|
24
|
+
this.issues = failure.issues;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function fillPath(path, values = {}) {
|
|
28
|
+
return Object.entries(values).reduce((filled, [name, value]) => filled.replace(`{${name}}`, encodeURIComponent(String(value))), path);
|
|
29
|
+
}
|
|
30
|
+
/** The query string of a call. A list repeats its parameter, and an absent value is left out. */
|
|
31
|
+
function formatQuery(query = {}) {
|
|
32
|
+
const params = new URLSearchParams();
|
|
33
|
+
for (const [name, value] of Object.entries(query)) for (const item of [value].flat()) if (item !== void 0 && item !== null) params.append(name, String(item));
|
|
34
|
+
const text = params.toString();
|
|
35
|
+
return text && `?${text}`;
|
|
36
|
+
}
|
|
37
|
+
async function parseFailure(response) {
|
|
38
|
+
try {
|
|
39
|
+
return (await response.json()).error ?? {};
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a client of the Highseat API. Paths, parameters and answers are typed from the API's
|
|
46
|
+
* OpenAPI document, and a refused call throws {@link HighseatApiError}.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { createApi } from "highseat/api"
|
|
51
|
+
*
|
|
52
|
+
* const api = createApi({ token: process.env.HIGHSEAT_API_KEY })
|
|
53
|
+
* const { data } = await api.get("/sites/{site}/realtime", { path: { site: "example.com" } })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function createApi({ token, baseUrl = DEFAULT_BASE_URL, fetch = globalThis.fetch }) {
|
|
57
|
+
async function send(method, path, options = {}) {
|
|
58
|
+
const headers = new Headers({ Authorization: `Bearer ${typeof token === "function" ? await token() : token}` });
|
|
59
|
+
if (options.body !== void 0) headers.set("Content-Type", "application/json");
|
|
60
|
+
if (options.idempotencyKey) headers.set("Idempotency-Key", options.idempotencyKey);
|
|
61
|
+
const response = await fetch(`${baseUrl}${fillPath(path, options.path)}${formatQuery(options.query)}`, {
|
|
62
|
+
method,
|
|
63
|
+
headers,
|
|
64
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
65
|
+
signal: options.signal
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) throw new HighseatApiError(response.status, await parseFailure(response));
|
|
68
|
+
if (response.status === 204) return;
|
|
69
|
+
return response.json();
|
|
70
|
+
}
|
|
71
|
+
function createCall(method) {
|
|
72
|
+
return (path, options) => send(method, path, options);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
get: createCall("GET"),
|
|
76
|
+
post: createCall("POST"),
|
|
77
|
+
patch: createCall("PATCH"),
|
|
78
|
+
delete: createCall("DELETE")
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { HighseatApiError, createApi };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=api.js.map
|
package/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["import type { paths } from \"./api.gen\"\n\ntype Method = \"get\" | \"post\" | \"patch\" | \"delete\"\n\n/** A path of the API that answers a method. */\ntype PathOf<Name extends Method> = {\n [Path in keyof paths]: paths[Path] extends Record<Name, object> ? Path : never\n}[keyof paths]\n\ntype Operation<Path extends keyof paths, Name extends Method> = paths[Path][Name &\n keyof paths[Path]]\n\ntype PathOptions<Route> = Route extends { parameters: { path: infer Values } }\n ? { path: Values }\n : { path?: undefined }\n\ntype QueryOptions<Route> = Route extends { parameters: { query: infer Values } }\n ? { query: Values }\n : Route extends { parameters: { query?: infer Values } }\n ? { query?: Values }\n : { query?: undefined }\n\ntype BodyOptions<Route> = Route extends {\n requestBody: { content: { \"application/json\": infer Body } }\n}\n ? { body: Body }\n : { body?: undefined }\n\n/** What every call may add. */\ninterface CallOptions {\n /** Makes a `POST` safe to retry: a repeat within 24 hours replays the first answer. */\n idempotencyKey?: string\n /** Cancels the call. */\n signal?: AbortSignal\n}\n\ntype Options<Route> = PathOptions<Route> & QueryOptions<Route> & BodyOptions<Route> & CallOptions\n\ntype Arguments<Route> =\n object extends Options<Route> ? [options?: Options<Route>] : [options: Options<Route>]\n\ntype JsonOf<Response> = Response extends { content: { \"application/json\": infer Body } }\n ? Body\n : undefined\n\ntype ResultOf<Route> = Route extends { responses: infer Responses }\n ? JsonOf<Responses[Extract<keyof Responses, 200 | 201 | 202 | 204>]>\n : never\n\ntype Call<Name extends Method> = <Path extends PathOf<Name>>(\n path: Path,\n ...options: Arguments<Operation<Path, Name>>\n) => Promise<ResultOf<Operation<Path, Name>>>\n\n/** What a call to a path answers, for naming it in your own code. */\nexport type ApiResult<Path extends keyof paths, Name extends Method = \"get\"> = ResultOf<\n Operation<Path, Name>\n>\n\n/** A client of the Highseat API, with one typed function per HTTP method. */\nexport interface HighseatApi {\n get: Call<\"get\">\n post: Call<\"post\">\n patch: Call<\"patch\">\n delete: Call<\"delete\">\n}\n\n/** How to reach the API. */\nexport interface ApiOptions {\n /**\n * An API key or an OAuth access token. Pass a function when the token changes, as an OAuth\n * token does when it is refreshed: it runs before every call.\n */\n token: string | (() => string | Promise<string>)\n /** The API's address, with its version. Defaults to `https://api.highseat.com/v1`. */\n baseUrl?: string\n /** Replaces the global `fetch`. */\n fetch?: typeof globalThis.fetch\n}\n\n/** One invalid parameter of a refused call. */\nexport interface ApiIssue {\n /** The parameter at fault, such as `[\"filter\", 1]` for the second filter. */\n path: (string | number)[]\n message: string\n}\n\ninterface ApiFailure {\n code?: string\n message?: string\n requestId?: string\n id?: string\n issues?: ApiIssue[]\n}\n\n/** The options a call carries, whichever route it went to. */\ninterface RawOptions extends CallOptions {\n path?: object\n query?: object\n body?: unknown\n}\n\nconst DEFAULT_BASE_URL = \"https://api.highseat.com/v1\"\n\n/** A failure that carries no error body, such as an answer from a proxy in front of the API. */\nconst UNKNOWN_CODE = \"INTERNAL_SERVER_ERROR\"\n\n/** A call the API refused. Branch on `code`, which is stable. */\nexport class HighseatApiError extends Error {\n /** The HTTP status. */\n readonly status: number\n /** Stable and machine-readable, such as `RATE_LIMITED`. */\n readonly code: string\n /** Names the request. Quote it to support. */\n readonly requestId?: string\n /** On a conflict, the existing row's id. */\n readonly id?: string\n /** One entry per invalid parameter. */\n readonly issues?: ApiIssue[]\n\n constructor(status: number, failure: ApiFailure) {\n super(failure.message ?? `The Highseat API answered ${status}.`)\n this.name = \"HighseatApiError\"\n this.status = status\n this.code = failure.code ?? UNKNOWN_CODE\n this.requestId = failure.requestId\n this.id = failure.id\n this.issues = failure.issues\n }\n}\n\nfunction fillPath(path: string, values: object = {}) {\n return Object.entries(values).reduce(\n (filled, [name, value]) => filled.replace(`{${name}}`, encodeURIComponent(String(value))),\n path,\n )\n}\n\n/** The query string of a call. A list repeats its parameter, and an absent value is left out. */\nfunction formatQuery(query: object = {}) {\n const params = new URLSearchParams()\n\n for (const [name, value] of Object.entries(query)) {\n for (const item of [value].flat()) {\n if (item !== undefined && item !== null) params.append(name, String(item))\n }\n }\n\n const text = params.toString()\n return text && `?${text}`\n}\n\nasync function parseFailure(response: Response): Promise<ApiFailure> {\n try {\n const body = (await response.json()) as { error?: ApiFailure }\n return body.error ?? {}\n } catch {\n return {}\n }\n}\n\n/**\n * Creates a client of the Highseat API. Paths, parameters and answers are typed from the API's\n * OpenAPI document, and a refused call throws {@link HighseatApiError}.\n *\n * @example\n * ```ts\n * import { createApi } from \"highseat/api\"\n *\n * const api = createApi({ token: process.env.HIGHSEAT_API_KEY })\n * const { data } = await api.get(\"/sites/{site}/realtime\", { path: { site: \"example.com\" } })\n * ```\n */\nexport function createApi({\n token,\n baseUrl = DEFAULT_BASE_URL,\n fetch = globalThis.fetch,\n}: ApiOptions): HighseatApi {\n async function send(method: string, path: string, options: RawOptions = {}) {\n const headers = new Headers({\n Authorization: `Bearer ${typeof token === \"function\" ? await token() : token}`,\n })\n\n if (options.body !== undefined) headers.set(\"Content-Type\", \"application/json\")\n if (options.idempotencyKey) headers.set(\"Idempotency-Key\", options.idempotencyKey)\n\n const url = `${baseUrl}${fillPath(path, options.path)}${formatQuery(options.query)}`\n const response = await fetch(url, {\n method,\n headers,\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n signal: options.signal,\n })\n\n if (!response.ok) throw new HighseatApiError(response.status, await parseFailure(response))\n if (response.status === 204) return\n\n return response.json()\n }\n\n function createCall(method: string) {\n return (path: string, options?: RawOptions) => send(method, path, options)\n }\n\n return {\n get: createCall(\"GET\"),\n post: createCall(\"POST\"),\n patch: createCall(\"PATCH\"),\n delete: createCall(\"DELETE\"),\n } as HighseatApi\n}\n"],"mappings":";AAsGA,MAAM,mBAAmB;;AAGzB,MAAM,eAAe;;AAGrB,IAAa,mBAAb,cAAsC,MAAM;;CAE1C;;CAEA;;CAEA;;CAEA;;CAEA;CAEA,YAAY,QAAgB,SAAqB;EAC/C,MAAM,QAAQ,WAAW,6BAA6B,OAAO,EAAE;EAC/D,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO,QAAQ,QAAQ;EAC5B,KAAK,YAAY,QAAQ;EACzB,KAAK,KAAK,QAAQ;EAClB,KAAK,SAAS,QAAQ;CACxB;AACF;AAEA,SAAS,SAAS,MAAc,SAAiB,CAAC,GAAG;CACnD,OAAO,OAAO,QAAQ,MAAM,CAAC,CAAC,QAC3B,QAAQ,CAAC,MAAM,WAAW,OAAO,QAAQ,IAAI,KAAK,IAAI,mBAAmB,OAAO,KAAK,CAAC,CAAC,GACxF,IACF;AACF;;AAGA,SAAS,YAAY,QAAgB,CAAC,GAAG;CACvC,MAAM,SAAS,IAAI,gBAAgB;CAEnC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,KAAK,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,GAC9B,IAAI,SAAS,KAAA,KAAa,SAAS,MAAM,OAAO,OAAO,MAAM,OAAO,IAAI,CAAC;CAI7E,MAAM,OAAO,OAAO,SAAS;CAC7B,OAAO,QAAQ,IAAI;AACrB;AAEA,eAAe,aAAa,UAAyC;CACnE,IAAI;EAEF,QAAO,MADa,SAAS,KAAK,EAAA,CACtB,SAAS,CAAC;CACxB,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;;;;;;;;;;;;AAcA,SAAgB,UAAU,EACxB,OACA,UAAU,kBACV,QAAQ,WAAW,SACO;CAC1B,eAAe,KAAK,QAAgB,MAAc,UAAsB,CAAC,GAAG;EAC1E,MAAM,UAAU,IAAI,QAAQ,EAC1B,eAAe,UAAU,OAAO,UAAU,aAAa,MAAM,MAAM,IAAI,QACzE,CAAC;EAED,IAAI,QAAQ,SAAS,KAAA,GAAW,QAAQ,IAAI,gBAAgB,kBAAkB;EAC9E,IAAI,QAAQ,gBAAgB,QAAQ,IAAI,mBAAmB,QAAQ,cAAc;EAGjF,MAAM,WAAW,MAAM,MAAM,GADd,UAAU,SAAS,MAAM,QAAQ,IAAI,IAAI,YAAY,QAAQ,KAAK,KAC/C;GAChC;GACA;GACA,MAAM,QAAQ,SAAS,KAAA,IAAY,KAAA,IAAY,KAAK,UAAU,QAAQ,IAAI;GAC1E,QAAQ,QAAQ;EAClB,CAAC;EAED,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,iBAAiB,SAAS,QAAQ,MAAM,aAAa,QAAQ,CAAC;EAC1F,IAAI,SAAS,WAAW,KAAK;EAE7B,OAAO,SAAS,KAAK;CACvB;CAEA,SAAS,WAAW,QAAgB;EAClC,QAAQ,MAAc,YAAyB,KAAK,QAAQ,MAAM,OAAO;CAC3E;CAEA,OAAO;EACL,KAAK,WAAW,KAAK;EACrB,MAAM,WAAW,MAAM;EACvB,OAAO,WAAW,OAAO;EACzB,QAAQ,WAAW,QAAQ;CAC7B;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "highseat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Highseat analytics for JavaScript: typed tracking in the browser and on the server, a first-party proxy and framework components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"analytics",
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"./mcp": {
|
|
47
47
|
"types": "./mcp.d.ts",
|
|
48
48
|
"default": "./mcp.js"
|
|
49
|
+
},
|
|
50
|
+
"./api": {
|
|
51
|
+
"types": "./api.d.ts",
|
|
52
|
+
"default": "./api.js"
|
|
49
53
|
}
|
|
50
54
|
},
|
|
51
55
|
"publishConfig": {
|