@frogfish/k2db-api 1.0.10 → 1.0.11
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/dist/index.d.ts +32 -6
- package/dist/index.js +218 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,29 @@ interface K2ClientOptions {
|
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
fetch?: typeof fetch;
|
|
7
7
|
headers?: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Default TTL (ms) for cacheable, read-only requests.
|
|
10
|
+
* Set to 0 to disable client-side caching globally.
|
|
11
|
+
* @default 60000
|
|
12
|
+
*/
|
|
13
|
+
readCacheTtlMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of concurrent in-flight HTTP requests.
|
|
16
|
+
* 0 or undefined means unlimited.
|
|
17
|
+
*/
|
|
18
|
+
maxConcurrentRequests?: number;
|
|
8
19
|
}
|
|
9
20
|
interface RequestOptions {
|
|
10
21
|
apiKey?: string;
|
|
11
22
|
scope?: string;
|
|
12
23
|
headers?: Record<string, string>;
|
|
13
24
|
signal?: AbortSignal;
|
|
25
|
+
/**
|
|
26
|
+
* Per-request cache TTL override (ms). Only applies to cacheable read requests.
|
|
27
|
+
* - undefined: use client default
|
|
28
|
+
* - 0: disable caching for this request
|
|
29
|
+
*/
|
|
30
|
+
cacheTtlMs?: number;
|
|
14
31
|
}
|
|
15
32
|
interface CreateResult {
|
|
16
33
|
id: string;
|
|
@@ -74,11 +91,6 @@ interface ReadyNotOk {
|
|
|
74
91
|
interface HealthOk {
|
|
75
92
|
status: 'ok';
|
|
76
93
|
}
|
|
77
|
-
declare class K2ApiError extends Error {
|
|
78
|
-
status: number;
|
|
79
|
-
body: unknown;
|
|
80
|
-
constructor(message: string, status: number, body: unknown);
|
|
81
|
-
}
|
|
82
94
|
interface ProblemDetailsPayload {
|
|
83
95
|
type?: string;
|
|
84
96
|
title?: string;
|
|
@@ -98,6 +110,12 @@ declare class K2DbApiClient {
|
|
|
98
110
|
private apiKey;
|
|
99
111
|
private fetchImpl;
|
|
100
112
|
private defaultHeaders;
|
|
113
|
+
private readonly readCacheTtlMs;
|
|
114
|
+
private readonly inflight;
|
|
115
|
+
private readonly cache;
|
|
116
|
+
private readonly maxConcurrentRequests;
|
|
117
|
+
private activeRequests;
|
|
118
|
+
private readonly waiters;
|
|
101
119
|
constructor(options: K2ClientOptions);
|
|
102
120
|
health(options?: RequestOptions): Promise<HealthOk>;
|
|
103
121
|
ready(options?: RequestOptions): Promise<ReadyOk | ReadyNotOk>;
|
|
@@ -124,6 +142,14 @@ declare class K2DbApiClient {
|
|
|
124
142
|
adminCreateHistoryIndexes(collection: string, payload?: Record<string, unknown>, options?: RequestOptions): Promise<{
|
|
125
143
|
message: string;
|
|
126
144
|
}>;
|
|
145
|
+
private serviceErrorFromHttpStatus;
|
|
146
|
+
private invalidateCache;
|
|
147
|
+
private isCacheableRead;
|
|
148
|
+
private stableStringify;
|
|
149
|
+
private fnv1a64;
|
|
150
|
+
private requestSignature;
|
|
151
|
+
private acquireRequestSlot;
|
|
152
|
+
private releaseRequestSlot;
|
|
127
153
|
private request;
|
|
128
154
|
private tryRehydrateK2Error;
|
|
129
155
|
private parseServiceError;
|
|
@@ -131,4 +157,4 @@ declare class K2DbApiClient {
|
|
|
131
157
|
private buildHeaders;
|
|
132
158
|
}
|
|
133
159
|
|
|
134
|
-
export { type AggregateRequest, type BulkUpdateRequest, type CountRequest, type CountResult, type CreateResult, type ErrorChainItem, type HealthOk, type IndexRequest,
|
|
160
|
+
export { type AggregateRequest, type BulkUpdateRequest, type CountRequest, type CountResult, type CreateResult, type ErrorChainItem, type HealthOk, type IndexRequest, type K2ClientOptions, K2DbApiClient, type ProblemDetailsPayload, type ReadyNotOk, type ReadyOk, type RequestOptions, type RestoreRequest, type RestoreResult, type SearchRequest, type UpdateResult, type VersionInfo, type VersionedUpdateRequest, type VersionedUpdateResult };
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
// src/k2db-client.ts
|
|
2
2
|
import { K2Error, ServiceError } from "@frogfish/k2error";
|
|
3
|
-
var K2ApiError = class extends Error {
|
|
4
|
-
status;
|
|
5
|
-
body;
|
|
6
|
-
constructor(message, status, body) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "K2ApiError";
|
|
9
|
-
this.status = status;
|
|
10
|
-
this.body = body;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
3
|
var K2DbApiClient = class {
|
|
14
4
|
baseUrl;
|
|
15
5
|
apiKey;
|
|
16
6
|
fetchImpl;
|
|
17
7
|
defaultHeaders;
|
|
8
|
+
readCacheTtlMs;
|
|
9
|
+
// Read-only request memoization + in-flight collapse (Angular-proofing)
|
|
10
|
+
inflight = /* @__PURE__ */ new Map();
|
|
11
|
+
cache = /* @__PURE__ */ new Map();
|
|
12
|
+
// Optional global concurrency limiter
|
|
13
|
+
maxConcurrentRequests;
|
|
14
|
+
activeRequests = 0;
|
|
15
|
+
waiters = [];
|
|
18
16
|
constructor(options) {
|
|
19
17
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
20
18
|
this.apiKey = options.apiKey;
|
|
21
19
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
22
20
|
this.defaultHeaders = options.headers;
|
|
21
|
+
this.readCacheTtlMs = typeof options.readCacheTtlMs === "number" && Number.isFinite(options.readCacheTtlMs) ? Math.max(0, options.readCacheTtlMs) : 6e4;
|
|
22
|
+
this.maxConcurrentRequests = typeof options.maxConcurrentRequests === "number" && Number.isFinite(options.maxConcurrentRequests) ? Math.max(0, Math.floor(options.maxConcurrentRequests)) : 0;
|
|
23
23
|
}
|
|
24
24
|
async health(options = {}) {
|
|
25
25
|
return this.request("GET", "/health", { ...options, authOptional: true });
|
|
@@ -28,28 +28,35 @@ var K2DbApiClient = class {
|
|
|
28
28
|
return this.request("GET", "/ready", { ...options, authOptional: true });
|
|
29
29
|
}
|
|
30
30
|
async create(collection, document, options = {}) {
|
|
31
|
-
|
|
31
|
+
const res = await this.request("POST", `/v1/${encodeURIComponent(collection)}`, {
|
|
32
32
|
...options,
|
|
33
33
|
body: document
|
|
34
34
|
});
|
|
35
|
+
this.invalidateCache();
|
|
36
|
+
return res;
|
|
35
37
|
}
|
|
36
38
|
async getById(collection, id, options = {}) {
|
|
37
39
|
return this.request("GET", `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options);
|
|
38
40
|
}
|
|
39
41
|
async patchById(collection, id, updates, options = {}) {
|
|
40
|
-
|
|
42
|
+
const res = await this.request("PATCH", `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, {
|
|
41
43
|
...options,
|
|
42
44
|
body: updates
|
|
43
45
|
});
|
|
46
|
+
this.invalidateCache();
|
|
47
|
+
return res;
|
|
44
48
|
}
|
|
45
49
|
async deleteById(collection, id, options = {}) {
|
|
46
50
|
await this.request("DELETE", `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options);
|
|
51
|
+
this.invalidateCache();
|
|
47
52
|
}
|
|
48
53
|
async patchCollection(collection, payload, options = {}) {
|
|
49
|
-
|
|
54
|
+
const res = await this.request("PATCH", `/v1/${encodeURIComponent(collection)}`, {
|
|
50
55
|
...options,
|
|
51
56
|
body: payload
|
|
52
57
|
});
|
|
58
|
+
this.invalidateCache();
|
|
59
|
+
return res;
|
|
53
60
|
}
|
|
54
61
|
async search(collection, payload = {}, options = {}) {
|
|
55
62
|
return this.request("POST", `/v1/${encodeURIComponent(collection)}/search`, {
|
|
@@ -70,10 +77,12 @@ var K2DbApiClient = class {
|
|
|
70
77
|
});
|
|
71
78
|
}
|
|
72
79
|
async restore(collection, payload, options = {}) {
|
|
73
|
-
|
|
80
|
+
const res = await this.request("POST", `/v1/${encodeURIComponent(collection)}/restore`, {
|
|
74
81
|
...options,
|
|
75
82
|
body: payload
|
|
76
83
|
});
|
|
84
|
+
this.invalidateCache();
|
|
85
|
+
return res;
|
|
77
86
|
}
|
|
78
87
|
async getVersions(collection, id, query = {}, options = {}) {
|
|
79
88
|
return this.request("GET", `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {
|
|
@@ -82,39 +91,166 @@ var K2DbApiClient = class {
|
|
|
82
91
|
});
|
|
83
92
|
}
|
|
84
93
|
async patchVersions(collection, id, payload, options = {}) {
|
|
85
|
-
|
|
94
|
+
const res = await this.request("PATCH", `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {
|
|
86
95
|
...options,
|
|
87
96
|
body: payload
|
|
88
97
|
});
|
|
98
|
+
this.invalidateCache();
|
|
99
|
+
return res;
|
|
89
100
|
}
|
|
90
101
|
async revertVersion(collection, id, version, options = {}) {
|
|
91
|
-
|
|
102
|
+
const res = await this.request(
|
|
92
103
|
"POST",
|
|
93
104
|
`/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions/${encodeURIComponent(String(version))}/revert`,
|
|
94
105
|
options
|
|
95
106
|
);
|
|
107
|
+
this.invalidateCache();
|
|
108
|
+
return res;
|
|
96
109
|
}
|
|
97
110
|
async adminDeleteCollection(collection, options = {}) {
|
|
98
111
|
await this.request("DELETE", `/v1/admin/${encodeURIComponent(collection)}`, options);
|
|
112
|
+
this.invalidateCache();
|
|
99
113
|
}
|
|
100
114
|
async adminDeleteById(collection, id, options = {}) {
|
|
101
115
|
await this.request("DELETE", `/v1/admin/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options);
|
|
116
|
+
this.invalidateCache();
|
|
102
117
|
}
|
|
103
118
|
async adminCreateIndexes(collection, payload, options = {}) {
|
|
104
|
-
|
|
119
|
+
const res = await this.request("POST", `/v1/admin/${encodeURIComponent(collection)}/indexes`, {
|
|
105
120
|
...options,
|
|
106
121
|
body: payload
|
|
107
122
|
});
|
|
123
|
+
this.invalidateCache();
|
|
124
|
+
return res;
|
|
108
125
|
}
|
|
109
126
|
async adminCreateHistoryIndexes(collection, payload = {}, options = {}) {
|
|
110
|
-
|
|
127
|
+
const res = await this.request("POST", `/v1/admin/${encodeURIComponent(collection)}/history-indexes`, {
|
|
111
128
|
...options,
|
|
112
129
|
body: payload
|
|
113
130
|
});
|
|
131
|
+
this.invalidateCache();
|
|
132
|
+
return res;
|
|
133
|
+
}
|
|
134
|
+
serviceErrorFromHttpStatus(status) {
|
|
135
|
+
switch (status) {
|
|
136
|
+
case 400:
|
|
137
|
+
return ServiceError.BAD_REQUEST;
|
|
138
|
+
case 401:
|
|
139
|
+
return ServiceError.UNAUTHORIZED;
|
|
140
|
+
case 402:
|
|
141
|
+
return ServiceError.PAYMENT_REQUIRED;
|
|
142
|
+
case 403:
|
|
143
|
+
return ServiceError.FORBIDDEN;
|
|
144
|
+
case 404:
|
|
145
|
+
return ServiceError.NOT_FOUND;
|
|
146
|
+
case 405:
|
|
147
|
+
return ServiceError.UNSUPPORTED_METHOD;
|
|
148
|
+
case 409:
|
|
149
|
+
return ServiceError.CONFLICT;
|
|
150
|
+
case 429:
|
|
151
|
+
return ServiceError.TOO_MANY_REQUESTS;
|
|
152
|
+
case 501:
|
|
153
|
+
return ServiceError.NOT_IMPLEMENTED;
|
|
154
|
+
case 502:
|
|
155
|
+
return ServiceError.BAD_GATEWAY;
|
|
156
|
+
case 503:
|
|
157
|
+
return ServiceError.SERVICE_UNAVAILABLE;
|
|
158
|
+
case 504:
|
|
159
|
+
return ServiceError.GATEWAY_TIMEOUT;
|
|
160
|
+
default:
|
|
161
|
+
return status >= 500 ? ServiceError.SYSTEM_ERROR : ServiceError.SERVICE_ERROR;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
invalidateCache() {
|
|
165
|
+
this.cache.clear();
|
|
166
|
+
}
|
|
167
|
+
isCacheableRead(method, path) {
|
|
168
|
+
if (method === "GET") return true;
|
|
169
|
+
if (method !== "POST") return false;
|
|
170
|
+
return path.endsWith("/search") || path.endsWith("/aggregate") || path.endsWith("/count");
|
|
171
|
+
}
|
|
172
|
+
// Deterministic JSON stringifier (sorted keys) so request signatures are stable.
|
|
173
|
+
stableStringify(x) {
|
|
174
|
+
if (x === null || x === void 0) return "null";
|
|
175
|
+
const t = typeof x;
|
|
176
|
+
if (t === "string") return JSON.stringify(x);
|
|
177
|
+
if (t === "number" || t === "boolean") return JSON.stringify(x);
|
|
178
|
+
if (t !== "object") return JSON.stringify(String(x));
|
|
179
|
+
if (Array.isArray(x)) {
|
|
180
|
+
return `[${x.map((v) => this.stableStringify(v)).join(",")}]`;
|
|
181
|
+
}
|
|
182
|
+
const obj = x;
|
|
183
|
+
const keys = Object.keys(obj).sort();
|
|
184
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${this.stableStringify(obj[k])}`).join(",")}}`;
|
|
185
|
+
}
|
|
186
|
+
// Non-cryptographic 64-bit FNV-1a hash (hex). Fast and good enough for cache keys.
|
|
187
|
+
fnv1a64(s) {
|
|
188
|
+
let h = 0xcbf29ce484222325n;
|
|
189
|
+
const prime = 0x100000001b3n;
|
|
190
|
+
for (let i = 0; i < s.length; i++) {
|
|
191
|
+
h ^= BigInt(s.charCodeAt(i));
|
|
192
|
+
h = h * prime & 0xffffffffffffffffn;
|
|
193
|
+
}
|
|
194
|
+
return h.toString(16).padStart(16, "0");
|
|
195
|
+
}
|
|
196
|
+
requestSignature(method, url, headers, body) {
|
|
197
|
+
const scope = headers["x-scope"] ?? "";
|
|
198
|
+
const auth = headers.authorization ?? "";
|
|
199
|
+
const authKey = auth ? this.fnv1a64(auth) : "";
|
|
200
|
+
const bodyKey = body === void 0 ? "" : this.stableStringify(body);
|
|
201
|
+
const raw = `${method} ${url}
|
|
202
|
+
scope=${scope}
|
|
203
|
+
auth=${authKey}
|
|
204
|
+
body=${bodyKey}`;
|
|
205
|
+
return this.fnv1a64(raw);
|
|
206
|
+
}
|
|
207
|
+
async acquireRequestSlot(signal) {
|
|
208
|
+
if (!this.maxConcurrentRequests || this.maxConcurrentRequests <= 0) return;
|
|
209
|
+
if (this.activeRequests < this.maxConcurrentRequests) {
|
|
210
|
+
this.activeRequests++;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
await new Promise((resolve, reject) => {
|
|
214
|
+
const onAbort = () => {
|
|
215
|
+
const idx = this.waiters.indexOf(wakeup);
|
|
216
|
+
if (idx >= 0) this.waiters.splice(idx, 1);
|
|
217
|
+
reject(new K2Error(ServiceError.TOO_MANY_REQUESTS, "Request aborted while waiting for a slot", "k2db_client_semaphore_abort"));
|
|
218
|
+
};
|
|
219
|
+
const wakeup = () => {
|
|
220
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
221
|
+
this.activeRequests++;
|
|
222
|
+
resolve();
|
|
223
|
+
};
|
|
224
|
+
if (signal?.aborted) {
|
|
225
|
+
reject(new K2Error(ServiceError.TOO_MANY_REQUESTS, "Request aborted while waiting for a slot", "k2db_client_semaphore_abort"));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
229
|
+
this.waiters.push(wakeup);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
releaseRequestSlot() {
|
|
233
|
+
if (!this.maxConcurrentRequests || this.maxConcurrentRequests <= 0) return;
|
|
234
|
+
this.activeRequests = Math.max(0, this.activeRequests - 1);
|
|
235
|
+
const next = this.waiters.shift();
|
|
236
|
+
if (next) next();
|
|
114
237
|
}
|
|
115
238
|
async request(method, path, options) {
|
|
116
239
|
const url = this.buildUrl(path, options.query);
|
|
117
240
|
const headers = this.buildHeaders(options);
|
|
241
|
+
const cacheable = this.isCacheableRead(method, path);
|
|
242
|
+
const ttlMs = cacheable ? options.cacheTtlMs ?? this.readCacheTtlMs : 0;
|
|
243
|
+
const sig = ttlMs > 0 ? this.requestSignature(method, url, headers, options.body) : "";
|
|
244
|
+
if (ttlMs > 0) {
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
const hit = this.cache.get(sig);
|
|
247
|
+
if (hit) {
|
|
248
|
+
if (hit.expiresAt > now) return hit.value;
|
|
249
|
+
this.cache.delete(sig);
|
|
250
|
+
}
|
|
251
|
+
const inflight = this.inflight.get(sig);
|
|
252
|
+
if (inflight) return await inflight;
|
|
253
|
+
}
|
|
118
254
|
const init = {
|
|
119
255
|
method,
|
|
120
256
|
headers
|
|
@@ -125,32 +261,79 @@ var K2DbApiClient = class {
|
|
|
125
261
|
if (options.body !== void 0) {
|
|
126
262
|
init.body = JSON.stringify(options.body);
|
|
127
263
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
264
|
+
const run = (async () => {
|
|
265
|
+
let res;
|
|
266
|
+
await this.acquireRequestSlot(options.signal);
|
|
267
|
+
try {
|
|
268
|
+
res = await this.fetchImpl(url, init);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
throw new K2Error(
|
|
271
|
+
ServiceError.SERVICE_UNAVAILABLE,
|
|
272
|
+
"Network request failed",
|
|
273
|
+
"k2db_client_fetch",
|
|
274
|
+
err
|
|
275
|
+
);
|
|
276
|
+
} finally {
|
|
277
|
+
this.releaseRequestSlot();
|
|
278
|
+
}
|
|
279
|
+
if (res.status === 204) {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
283
|
+
const isJson = contentType.includes("application/json") || contentType.includes("application/problem+json");
|
|
284
|
+
let payload;
|
|
285
|
+
try {
|
|
286
|
+
payload = isJson ? await res.json() : await res.text();
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const k2 = new K2Error(
|
|
289
|
+
this.serviceErrorFromHttpStatus(res.status),
|
|
290
|
+
"Failed to parse server response",
|
|
291
|
+
"k2db_client_parse",
|
|
292
|
+
err
|
|
293
|
+
);
|
|
294
|
+
k2.code = res.status;
|
|
295
|
+
throw k2;
|
|
296
|
+
}
|
|
297
|
+
if (!res.ok) {
|
|
298
|
+
if (isJson) {
|
|
299
|
+
const k2err = this.tryRehydrateK2Error(payload, res.status);
|
|
300
|
+
if (k2err) {
|
|
301
|
+
throw k2err;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const detail = typeof payload === "string" && payload ? payload : typeof payload?.detail === "string" && payload.detail ? payload.detail : `Request failed: ${res.status}`;
|
|
305
|
+
const trace = typeof payload?.trace === "string" ? payload.trace : void 0;
|
|
306
|
+
const k2 = new K2Error(this.serviceErrorFromHttpStatus(res.status), detail, trace, payload);
|
|
307
|
+
k2.code = res.status;
|
|
308
|
+
if (payload && typeof payload === "object" && Array.isArray(payload.chain)) {
|
|
309
|
+
k2.chain = payload.chain;
|
|
140
310
|
}
|
|
311
|
+
throw k2;
|
|
312
|
+
}
|
|
313
|
+
return payload;
|
|
314
|
+
})();
|
|
315
|
+
if (ttlMs > 0) {
|
|
316
|
+
this.inflight.set(sig, run);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const value = await run;
|
|
320
|
+
if (ttlMs > 0) {
|
|
321
|
+
this.cache.set(sig, { expiresAt: Date.now() + ttlMs, value });
|
|
322
|
+
}
|
|
323
|
+
return value;
|
|
324
|
+
} finally {
|
|
325
|
+
if (ttlMs > 0) {
|
|
326
|
+
this.inflight.delete(sig);
|
|
141
327
|
}
|
|
142
|
-
const message = typeof payload === "string" && payload ? payload : `Request failed: ${res.status}`;
|
|
143
|
-
throw new K2ApiError(message, res.status, payload);
|
|
144
328
|
}
|
|
145
|
-
return payload;
|
|
146
329
|
}
|
|
147
330
|
tryRehydrateK2Error(payload, status) {
|
|
148
331
|
if (!payload || typeof payload !== "object") return null;
|
|
149
332
|
const problem = payload;
|
|
150
333
|
const errorId = this.parseServiceError(problem.type);
|
|
151
|
-
if (!errorId
|
|
334
|
+
if (!errorId) return null;
|
|
152
335
|
const err = new K2Error(
|
|
153
|
-
errorId
|
|
336
|
+
errorId,
|
|
154
337
|
problem.detail ?? `Request failed: ${status}`,
|
|
155
338
|
problem.trace
|
|
156
339
|
);
|
|
@@ -204,7 +387,6 @@ var K2DbApiClient = class {
|
|
|
204
387
|
}
|
|
205
388
|
};
|
|
206
389
|
export {
|
|
207
|
-
K2ApiError,
|
|
208
390
|
K2DbApiClient
|
|
209
391
|
};
|
|
210
392
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/k2db-client.ts"],"sourcesContent":["import { K2Error, ServiceError } from '@frogfish/k2error'\n\nexport interface K2ClientOptions {\n baseUrl: string\n apiKey?: string\n fetch?: typeof fetch\n headers?: Record<string, string>\n}\n\nexport interface RequestOptions {\n apiKey?: string\n scope?: string\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\nexport interface CreateResult {\n id: string\n created: boolean\n}\n\nexport interface UpdateResult {\n updated: number\n}\n\nexport interface RestoreResult {\n restored: number\n}\n\nexport interface CountResult {\n count: number\n}\n\nexport interface SearchRequest {\n filter?: Record<string, unknown>\n params?: Record<string, unknown>\n skip?: number\n limit?: number\n}\n\nexport interface AggregateRequest {\n criteria: Record<string, unknown>[]\n skip?: number\n limit?: number\n}\n\nexport interface CountRequest {\n criteria?: Record<string, unknown>\n}\n\nexport interface BulkUpdateRequest<TValues extends Record<string, unknown>> {\n criteria: Record<string, unknown>\n values: Partial<TValues>\n}\n\nexport interface RestoreRequest {\n criteria: Record<string, unknown>\n}\n\nexport interface VersionedUpdateRequest<TData extends Record<string, unknown>> {\n data: Partial<TData>\n replace?: boolean\n maxVersions?: number\n}\n\nexport interface VersionInfo<TData extends Record<string, unknown>> {\n version: number\n timestamp: string\n data: TData\n}\n\nexport interface VersionedUpdateResult {\n version: number\n updated: number\n}\n\nexport interface IndexRequest {\n indexSpec: Record<string, unknown>\n options?: Record<string, unknown>\n}\n\nexport interface ReadyOk {\n status: 'ready'\n}\n\nexport interface ReadyNotOk {\n status: 'not-ready'\n databases: string[]\n}\n\nexport interface HealthOk {\n status: 'ok'\n}\n\nexport class K2ApiError extends Error {\n status: number\n body: unknown\n\n constructor(message: string, status: number, body: unknown) {\n super(message)\n this.name = 'K2ApiError'\n this.status = status\n this.body = body\n }\n}\n\nexport interface ProblemDetailsPayload {\n type?: string\n title?: string\n status?: number\n detail?: string\n trace?: string\n chain?: ErrorChainItem[]\n}\n\nexport interface ErrorChainItem {\n error: ServiceError\n error_description: string\n stage?: string\n at: number\n}\n\nexport class K2DbApiClient {\n private baseUrl: string\n private apiKey: string | undefined\n private fetchImpl: typeof fetch\n private defaultHeaders: Record<string, string> | undefined\n\n constructor(options: K2ClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.apiKey = options.apiKey\n this.fetchImpl = options.fetch ?? globalThis.fetch\n this.defaultHeaders = options.headers\n }\n\n async health(options: RequestOptions = {}): Promise<HealthOk> {\n return this.request('GET', '/health', { ...options, authOptional: true })\n }\n\n async ready(options: RequestOptions = {}): Promise<ReadyOk | ReadyNotOk> {\n return this.request('GET', '/ready', { ...options, authOptional: true })\n }\n\n async create<TDoc extends Record<string, unknown>>(\n collection: string,\n document: Partial<TDoc>,\n options: RequestOptions = {}\n ): Promise<CreateResult> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}`, {\n ...options,\n body: document,\n })\n }\n\n async getById<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<TDoc> {\n return this.request('GET', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n }\n\n async patchById<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n updates: Partial<TDoc>,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n return this.request('PATCH', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, {\n ...options,\n body: updates,\n })\n }\n\n async deleteById(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n }\n\n async patchCollection<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: BulkUpdateRequest<TDoc>,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n return this.request('PATCH', `/v1/${encodeURIComponent(collection)}`, {\n ...options,\n body: payload,\n })\n }\n\n async search<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: SearchRequest = {},\n options: RequestOptions = {}\n ): Promise<TDoc[]> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/search`, {\n ...options,\n body: payload,\n })\n }\n\n async aggregate<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: AggregateRequest,\n options: RequestOptions = {}\n ): Promise<TDoc[]> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/aggregate`, {\n ...options,\n body: payload,\n })\n }\n\n async count(\n collection: string,\n payload: CountRequest = {},\n options: RequestOptions = {}\n ): Promise<CountResult> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/count`, {\n ...options,\n body: payload,\n })\n }\n\n async restore(\n collection: string,\n payload: RestoreRequest,\n options: RequestOptions = {}\n ): Promise<RestoreResult> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/restore`, {\n ...options,\n body: payload,\n })\n }\n\n async getVersions<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n query: { skip?: number; limit?: number } = {},\n options: RequestOptions = {}\n ): Promise<Array<VersionInfo<TDoc>>> {\n return this.request('GET', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {\n ...options,\n query,\n })\n }\n\n async patchVersions<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n payload: VersionedUpdateRequest<TDoc>,\n options: RequestOptions = {}\n ): Promise<VersionedUpdateResult[]> {\n return this.request('PATCH', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {\n ...options,\n body: payload,\n })\n }\n\n async revertVersion(\n collection: string,\n id: string,\n version: number,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n return this.request(\n 'POST',\n `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions/${encodeURIComponent(String(version))}/revert`,\n options\n )\n }\n\n async adminDeleteCollection(\n collection: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/admin/${encodeURIComponent(collection)}`, options)\n }\n\n async adminDeleteById(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/admin/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n }\n\n async adminCreateIndexes(\n collection: string,\n payload: IndexRequest,\n options: RequestOptions = {}\n ): Promise<{ message: string }> {\n return this.request('POST', `/v1/admin/${encodeURIComponent(collection)}/indexes`, {\n ...options,\n body: payload,\n })\n }\n\n async adminCreateHistoryIndexes(\n collection: string,\n payload: Record<string, unknown> = {},\n options: RequestOptions = {}\n ): Promise<{ message: string }> {\n return this.request('POST', `/v1/admin/${encodeURIComponent(collection)}/history-indexes`, {\n ...options,\n body: payload,\n })\n }\n\n private async request<T>(\n method: string,\n path: string,\n options: RequestOptions & {\n body?: unknown\n query?: Record<string, unknown>\n authOptional?: boolean\n }\n ): Promise<T> {\n const url = this.buildUrl(path, options.query)\n const headers = this.buildHeaders(options)\n const init: RequestInit = {\n method,\n headers,\n }\n if (options.signal) {\n init.signal = options.signal\n }\n if (options.body !== undefined) {\n init.body = JSON.stringify(options.body)\n }\n\n const res = await this.fetchImpl(url, init)\n\n if (res.status === 204) {\n return undefined as T\n }\n\n const contentType = res.headers.get('content-type') ?? ''\n const isJson = contentType.includes('application/json') || contentType.includes('application/problem+json')\n const payload = isJson ? await res.json() : await res.text()\n\n if (!res.ok) {\n if (isJson) {\n const k2err = this.tryRehydrateK2Error(payload, res.status)\n if (k2err) {\n throw k2err\n }\n }\n\n const message = typeof payload === 'string' && payload ? payload : `Request failed: ${res.status}`\n throw new K2ApiError(message, res.status, payload)\n }\n\n return payload as T\n }\n\n private tryRehydrateK2Error(payload: unknown, status: number): K2Error | null {\n if (!payload || typeof payload !== 'object') return null\n\n const problem = payload as ProblemDetailsPayload\n const errorId = this.parseServiceError(problem.type)\n if (!errorId && !problem.detail) return null\n\n const err = new K2Error(\n errorId ?? ServiceError.SERVICE_ERROR,\n problem.detail ?? `Request failed: ${status}`,\n problem.trace\n )\n\n if (typeof problem.status === 'number' && Number.isFinite(problem.status)) {\n err.code = problem.status\n } else {\n err.code = status\n }\n\n if (Array.isArray(problem.chain)) {\n err.chain = problem.chain\n }\n\n return err\n }\n\n private parseServiceError(typeValue: string | undefined): ServiceError | undefined {\n if (!typeValue || typeof typeValue !== 'string') return undefined\n const prefix = 'urn:service-error:'\n if (!typeValue.startsWith(prefix)) return undefined\n const id = typeValue.slice(prefix.length)\n if (!id) return undefined\n const values = Object.values(ServiceError) as string[]\n return values.includes(id) ? (id as ServiceError) : undefined\n }\n\n private buildUrl(path: string, query?: Record<string, unknown>): string {\n const base = `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`\n if (!query || Object.keys(query).length === 0) return base\n\n const params = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue\n params.set(key, String(value))\n }\n\n const qs = params.toString()\n return qs ? `${base}?${qs}` : base\n }\n\n private buildHeaders(options: RequestOptions & { body?: unknown; authOptional?: boolean }): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...this.defaultHeaders,\n ...options.headers,\n }\n\n if (options.body !== undefined) {\n headers['content-type'] = 'application/json'\n }\n\n if (options.scope) {\n headers['x-scope'] = options.scope\n }\n\n const apiKey = options.apiKey ?? this.apiKey\n if (apiKey && !options.authOptional) {\n headers.authorization = apiKey.startsWith('ApiKey ') ? apiKey : `ApiKey ${apiKey}`\n }\n\n return headers\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/k2db-client.ts"],"sourcesContent":["import { K2Error, ServiceError } from '@frogfish/k2error'\n\nexport interface K2ClientOptions {\n baseUrl: string\n apiKey?: string\n fetch?: typeof fetch\n headers?: Record<string, string>\n\n /**\n * Default TTL (ms) for cacheable, read-only requests.\n * Set to 0 to disable client-side caching globally.\n * @default 60000\n */\n readCacheTtlMs?: number\n\n /**\n * Maximum number of concurrent in-flight HTTP requests.\n * 0 or undefined means unlimited.\n */\n maxConcurrentRequests?: number\n}\n\nexport interface RequestOptions {\n apiKey?: string\n scope?: string\n headers?: Record<string, string>\n signal?: AbortSignal\n\n /**\n * Per-request cache TTL override (ms). Only applies to cacheable read requests.\n * - undefined: use client default\n * - 0: disable caching for this request\n */\n cacheTtlMs?: number\n}\n\nexport interface CreateResult {\n id: string\n created: boolean\n}\n\nexport interface UpdateResult {\n updated: number\n}\n\nexport interface RestoreResult {\n restored: number\n}\n\nexport interface CountResult {\n count: number\n}\n\nexport interface SearchRequest {\n filter?: Record<string, unknown>\n params?: Record<string, unknown>\n skip?: number\n limit?: number\n}\n\nexport interface AggregateRequest {\n criteria: Record<string, unknown>[]\n skip?: number\n limit?: number\n}\n\nexport interface CountRequest {\n criteria?: Record<string, unknown>\n}\n\nexport interface BulkUpdateRequest<TValues extends Record<string, unknown>> {\n criteria: Record<string, unknown>\n values: Partial<TValues>\n}\n\nexport interface RestoreRequest {\n criteria: Record<string, unknown>\n}\n\nexport interface VersionedUpdateRequest<TData extends Record<string, unknown>> {\n data: Partial<TData>\n replace?: boolean\n maxVersions?: number\n}\n\nexport interface VersionInfo<TData extends Record<string, unknown>> {\n version: number\n timestamp: string\n data: TData\n}\n\nexport interface VersionedUpdateResult {\n version: number\n updated: number\n}\n\nexport interface IndexRequest {\n indexSpec: Record<string, unknown>\n options?: Record<string, unknown>\n}\n\nexport interface ReadyOk {\n status: 'ready'\n}\n\nexport interface ReadyNotOk {\n status: 'not-ready'\n databases: string[]\n}\n\nexport interface HealthOk {\n status: 'ok'\n}\n\nexport interface ProblemDetailsPayload {\n type?: string\n title?: string\n status?: number\n detail?: string\n trace?: string\n chain?: ErrorChainItem[]\n}\n\nexport interface ErrorChainItem {\n error: ServiceError\n error_description: string\n stage?: string\n at: number\n}\n\nexport class K2DbApiClient {\n private baseUrl: string\n private apiKey: string | undefined\n private fetchImpl: typeof fetch\n private defaultHeaders: Record<string, string> | undefined\n\n private readonly readCacheTtlMs: number\n\n // Read-only request memoization + in-flight collapse (Angular-proofing)\n private readonly inflight = new Map<string, Promise<unknown>>()\n private readonly cache = new Map<string, { expiresAt: number; value: unknown }>()\n\n // Optional global concurrency limiter\n private readonly maxConcurrentRequests: number\n private activeRequests = 0\n private readonly waiters: Array<() => void> = []\n\n constructor(options: K2ClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.apiKey = options.apiKey\n this.fetchImpl = options.fetch ?? globalThis.fetch\n this.defaultHeaders = options.headers\n this.readCacheTtlMs = typeof options.readCacheTtlMs === 'number' && Number.isFinite(options.readCacheTtlMs)\n ? Math.max(0, options.readCacheTtlMs)\n : 60_000\n this.maxConcurrentRequests = typeof options.maxConcurrentRequests === 'number' && Number.isFinite(options.maxConcurrentRequests)\n ? Math.max(0, Math.floor(options.maxConcurrentRequests))\n : 0\n }\n\n async health(options: RequestOptions = {}): Promise<HealthOk> {\n return this.request('GET', '/health', { ...options, authOptional: true })\n }\n\n async ready(options: RequestOptions = {}): Promise<ReadyOk | ReadyNotOk> {\n return this.request('GET', '/ready', { ...options, authOptional: true })\n }\n\n async create<TDoc extends Record<string, unknown>>(\n collection: string,\n document: Partial<TDoc>,\n options: RequestOptions = {}\n ): Promise<CreateResult> {\n const res = await this.request<CreateResult>('POST', `/v1/${encodeURIComponent(collection)}`, {\n ...options,\n body: document,\n })\n this.invalidateCache()\n return res\n }\n\n async getById<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<TDoc> {\n return this.request('GET', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n }\n\n async patchById<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n updates: Partial<TDoc>,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n const res = await this.request<UpdateResult>('PATCH', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, {\n ...options,\n body: updates,\n })\n this.invalidateCache()\n return res\n }\n\n async deleteById(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n this.invalidateCache()\n }\n\n async patchCollection<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: BulkUpdateRequest<TDoc>,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n const res = await this.request<UpdateResult>('PATCH', `/v1/${encodeURIComponent(collection)}`, {\n ...options,\n body: payload,\n })\n this.invalidateCache()\n return res\n }\n\n async search<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: SearchRequest = {},\n options: RequestOptions = {}\n ): Promise<TDoc[]> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/search`, {\n ...options,\n body: payload,\n })\n }\n\n async aggregate<TDoc extends Record<string, unknown>>(\n collection: string,\n payload: AggregateRequest,\n options: RequestOptions = {}\n ): Promise<TDoc[]> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/aggregate`, {\n ...options,\n body: payload,\n })\n }\n\n async count(\n collection: string,\n payload: CountRequest = {},\n options: RequestOptions = {}\n ): Promise<CountResult> {\n return this.request('POST', `/v1/${encodeURIComponent(collection)}/count`, {\n ...options,\n body: payload,\n })\n }\n\n async restore(\n collection: string,\n payload: RestoreRequest,\n options: RequestOptions = {}\n ): Promise<RestoreResult> {\n const res = await this.request<RestoreResult>('POST', `/v1/${encodeURIComponent(collection)}/restore`, {\n ...options,\n body: payload,\n })\n this.invalidateCache()\n return res\n }\n\n async getVersions<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n query: { skip?: number; limit?: number } = {},\n options: RequestOptions = {}\n ): Promise<Array<VersionInfo<TDoc>>> {\n return this.request('GET', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {\n ...options,\n query,\n })\n }\n\n async patchVersions<TDoc extends Record<string, unknown>>(\n collection: string,\n id: string,\n payload: VersionedUpdateRequest<TDoc>,\n options: RequestOptions = {}\n ): Promise<VersionedUpdateResult[]> {\n const res = await this.request<VersionedUpdateResult[]>('PATCH', `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions`, {\n ...options,\n body: payload,\n })\n this.invalidateCache()\n return res\n }\n\n async revertVersion(\n collection: string,\n id: string,\n version: number,\n options: RequestOptions = {}\n ): Promise<UpdateResult> {\n const res = await this.request<UpdateResult>(\n 'POST',\n `/v1/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/versions/${encodeURIComponent(String(version))}/revert`,\n options\n )\n this.invalidateCache()\n return res\n }\n\n async adminDeleteCollection(\n collection: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/admin/${encodeURIComponent(collection)}`, options)\n this.invalidateCache()\n }\n\n async adminDeleteById(\n collection: string,\n id: string,\n options: RequestOptions = {}\n ): Promise<void> {\n await this.request('DELETE', `/v1/admin/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, options)\n this.invalidateCache()\n }\n\n async adminCreateIndexes(\n collection: string,\n payload: IndexRequest,\n options: RequestOptions = {}\n ): Promise<{ message: string }> {\n const res = await this.request<{ message: string }>('POST', `/v1/admin/${encodeURIComponent(collection)}/indexes`, {\n ...options,\n body: payload,\n })\n this.invalidateCache()\n return res\n }\n\n async adminCreateHistoryIndexes(\n collection: string,\n payload: Record<string, unknown> = {},\n options: RequestOptions = {}\n ): Promise<{ message: string }> {\n const res = await this.request<{ message: string }>('POST', `/v1/admin/${encodeURIComponent(collection)}/history-indexes`, {\n ...options,\n body: payload,\n })\n this.invalidateCache()\n return res\n }\n\n private serviceErrorFromHttpStatus(status: number): ServiceError {\n // Best-effort mapping when server didn't return a K2Error payload.\n switch (status) {\n case 400:\n return ServiceError.BAD_REQUEST\n case 401:\n return ServiceError.UNAUTHORIZED\n case 402:\n return ServiceError.PAYMENT_REQUIRED\n case 403:\n return ServiceError.FORBIDDEN\n case 404:\n return ServiceError.NOT_FOUND\n case 405:\n return ServiceError.UNSUPPORTED_METHOD\n case 409:\n return ServiceError.CONFLICT\n case 429:\n return ServiceError.TOO_MANY_REQUESTS\n case 501:\n return ServiceError.NOT_IMPLEMENTED\n case 502:\n return ServiceError.BAD_GATEWAY\n case 503:\n return ServiceError.SERVICE_UNAVAILABLE\n case 504:\n return ServiceError.GATEWAY_TIMEOUT\n default:\n // 5xx → system_error, otherwise service_error\n return status >= 500 ? ServiceError.SYSTEM_ERROR : ServiceError.SERVICE_ERROR\n }\n }\n\n private invalidateCache(): void {\n this.cache.clear()\n }\n\n private isCacheableRead(method: string, path: string): boolean {\n if (method === 'GET') return true\n if (method !== 'POST') return false\n // These POST endpoints are idempotent reads in k2db.\n return path.endsWith('/search') || path.endsWith('/aggregate') || path.endsWith('/count')\n }\n\n // Deterministic JSON stringifier (sorted keys) so request signatures are stable.\n private stableStringify(x: unknown): string {\n if (x === null || x === undefined) return 'null'\n const t = typeof x\n if (t === 'string') return JSON.stringify(x)\n if (t === 'number' || t === 'boolean') return JSON.stringify(x)\n if (t !== 'object') return JSON.stringify(String(x))\n\n if (Array.isArray(x)) {\n return `[${x.map((v) => this.stableStringify(v)).join(',')}]`\n }\n\n const obj = x as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n return `{${keys.map((k) => `${JSON.stringify(k)}:${this.stableStringify(obj[k])}`).join(',')}}`\n }\n\n // Non-cryptographic 64-bit FNV-1a hash (hex). Fast and good enough for cache keys.\n private fnv1a64(s: string): string {\n let h = 0xcbf29ce484222325n\n const prime = 0x100000001b3n\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i))\n h = (h * prime) & 0xffffffffffffffffn\n }\n return h.toString(16).padStart(16, '0')\n }\n\n private requestSignature(method: string, url: string, headers: Record<string, string>, body: unknown): string {\n const scope = headers['x-scope'] ?? ''\n const auth = headers.authorization ?? ''\n const authKey = auth ? this.fnv1a64(auth) : ''\n const bodyKey = body === undefined ? '' : this.stableStringify(body)\n const raw = `${method} ${url}\\nscope=${scope}\\nauth=${authKey}\\nbody=${bodyKey}`\n return this.fnv1a64(raw)\n }\n\n private async acquireRequestSlot(signal?: AbortSignal): Promise<void> {\n if (!this.maxConcurrentRequests || this.maxConcurrentRequests <= 0) return\n\n if (this.activeRequests < this.maxConcurrentRequests) {\n this.activeRequests++\n return\n }\n\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n // Remove this waiter if it's still queued\n const idx = this.waiters.indexOf(wakeup)\n if (idx >= 0) this.waiters.splice(idx, 1)\n reject(new K2Error(ServiceError.TOO_MANY_REQUESTS, 'Request aborted while waiting for a slot', 'k2db_client_semaphore_abort'))\n }\n\n const wakeup = () => {\n if (signal) signal.removeEventListener('abort', onAbort)\n this.activeRequests++\n resolve()\n }\n\n if (signal?.aborted) {\n reject(new K2Error(ServiceError.TOO_MANY_REQUESTS, 'Request aborted while waiting for a slot', 'k2db_client_semaphore_abort'))\n return\n }\n\n if (signal) signal.addEventListener('abort', onAbort, { once: true })\n this.waiters.push(wakeup)\n })\n }\n\n private releaseRequestSlot(): void {\n if (!this.maxConcurrentRequests || this.maxConcurrentRequests <= 0) return\n\n this.activeRequests = Math.max(0, this.activeRequests - 1)\n const next = this.waiters.shift()\n if (next) next()\n }\n\n private async request<T>(\n method: string,\n path: string,\n options: RequestOptions & {\n body?: unknown\n query?: Record<string, unknown>\n authOptional?: boolean\n }\n ): Promise<T> {\n const url = this.buildUrl(path, options.query)\n const headers = this.buildHeaders(options)\n\n const cacheable = this.isCacheableRead(method, path)\n const ttlMs = cacheable ? (options.cacheTtlMs ?? this.readCacheTtlMs) : 0\n const sig = ttlMs > 0 ? this.requestSignature(method, url, headers, options.body) : ''\n\n if (ttlMs > 0) {\n const now = Date.now()\n const hit = this.cache.get(sig)\n if (hit) {\n if (hit.expiresAt > now) return hit.value as T\n this.cache.delete(sig)\n }\n const inflight = this.inflight.get(sig)\n if (inflight) return (await inflight) as T\n }\n\n const init: RequestInit = {\n method,\n headers,\n }\n if (options.signal) {\n init.signal = options.signal\n }\n if (options.body !== undefined) {\n init.body = JSON.stringify(options.body)\n }\n\n const run = (async (): Promise<T> => {\n let res: Response\n await this.acquireRequestSlot(options.signal)\n try {\n res = await this.fetchImpl(url, init)\n } catch (err) {\n // Network / CORS / DNS / abort, etc.\n throw new K2Error(\n ServiceError.SERVICE_UNAVAILABLE,\n 'Network request failed',\n 'k2db_client_fetch',\n err\n )\n } finally {\n this.releaseRequestSlot()\n }\n\n if (res.status === 204) {\n return undefined as T\n }\n\n const contentType = res.headers.get('content-type') ?? ''\n const isJson = contentType.includes('application/json') || contentType.includes('application/problem+json')\n\n let payload: unknown\n try {\n payload = isJson ? await res.json() : await res.text()\n } catch (err) {\n // Response parse error. Keep as K2Error and include status.\n const k2 = new K2Error(\n this.serviceErrorFromHttpStatus(res.status),\n 'Failed to parse server response',\n 'k2db_client_parse',\n err\n )\n k2.code = res.status\n throw k2\n }\n\n if (!res.ok) {\n // Preferred path: server already sent RFC7807 K2Error payload.\n if (isJson) {\n const k2err = this.tryRehydrateK2Error(payload, res.status)\n if (k2err) {\n throw k2err\n }\n }\n\n // Fallback: synthesize a K2Error from HTTP + payload.\n const detail =\n typeof payload === 'string' && payload\n ? payload\n : typeof (payload as any)?.detail === 'string' && (payload as any).detail\n ? ((payload as any).detail as string)\n : `Request failed: ${res.status}`\n\n const trace = typeof (payload as any)?.trace === 'string' ? ((payload as any).trace as string) : undefined\n\n const k2 = new K2Error(this.serviceErrorFromHttpStatus(res.status), detail, trace, payload)\n k2.code = res.status\n\n // If payload looks like a Problem Details chain, keep it.\n if (payload && typeof payload === 'object' && Array.isArray((payload as any).chain)) {\n k2.chain = (payload as any).chain\n }\n\n throw k2\n }\n\n return payload as T\n })()\n\n if (ttlMs > 0) {\n this.inflight.set(sig, run as Promise<unknown>)\n }\n\n try {\n const value = await run\n if (ttlMs > 0) {\n this.cache.set(sig, { expiresAt: Date.now() + ttlMs, value })\n }\n return value\n } finally {\n if (ttlMs > 0) {\n this.inflight.delete(sig)\n }\n }\n }\n\n private tryRehydrateK2Error(payload: unknown, status: number): K2Error | null {\n if (!payload || typeof payload !== 'object') return null\n\n const problem = payload as ProblemDetailsPayload\n const errorId = this.parseServiceError(problem.type)\n if (!errorId) return null\n\n const err = new K2Error(\n errorId,\n problem.detail ?? `Request failed: ${status}`,\n problem.trace\n )\n\n if (typeof problem.status === 'number' && Number.isFinite(problem.status)) {\n err.code = problem.status\n } else {\n err.code = status\n }\n\n if (Array.isArray(problem.chain)) {\n err.chain = problem.chain\n }\n\n return err\n }\n\n private parseServiceError(typeValue: string | undefined): ServiceError | undefined {\n if (!typeValue || typeof typeValue !== 'string') return undefined\n const prefix = 'urn:service-error:'\n if (!typeValue.startsWith(prefix)) return undefined\n const id = typeValue.slice(prefix.length)\n if (!id) return undefined\n const values = Object.values(ServiceError) as string[]\n return values.includes(id) ? (id as ServiceError) : undefined\n }\n\n private buildUrl(path: string, query?: Record<string, unknown>): string {\n const base = `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`\n if (!query || Object.keys(query).length === 0) return base\n\n const params = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue\n params.set(key, String(value))\n }\n\n const qs = params.toString()\n return qs ? `${base}?${qs}` : base\n }\n\n private buildHeaders(options: RequestOptions & { body?: unknown; authOptional?: boolean }): Record<string, string> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...this.defaultHeaders,\n ...options.headers,\n }\n\n if (options.body !== undefined) {\n headers['content-type'] = 'application/json'\n }\n\n if (options.scope) {\n headers['x-scope'] = options.scope\n }\n\n const apiKey = options.apiKey ?? this.apiKey\n if (apiKey && !options.authOptional) {\n headers.authorization = apiKey.startsWith('ApiKey ') ? apiKey : `ApiKey ${apiKey}`\n }\n\n return headers\n }\n}\n"],"mappings":"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","names":[]}
|