@avenlabs/halal-trace-sdk 0.1.1

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/src/client.ts ADDED
@@ -0,0 +1,491 @@
1
+ import type {
2
+ Anchor,
3
+ ApiResponse,
4
+ AuditLog,
5
+ AuditPack,
6
+ AuditPackManifest,
7
+ AuthResult,
8
+ Batch,
9
+ CertificationRecord,
10
+ Device,
11
+ DocumentAnchor,
12
+ EventDepartment,
13
+ HealthResponse,
14
+ Hologram,
15
+ HologramVerification,
16
+ Invite,
17
+ Member,
18
+ Org,
19
+ OrgInviteResult,
20
+ OrgUserLookup,
21
+ Permission,
22
+ PublicAttribute,
23
+ RelayerJobSummary,
24
+ SignatureRecord,
25
+ TraceEvent,
26
+ } from "./types.js";
27
+ import { ApiError, request, requestRaw, type AuthOptions, type ClientHooks, type RequestOptions, type RetryOptions } from "./http.js";
28
+
29
+ type BaseClientOptions = {
30
+ baseUrl: string;
31
+ timeoutMs?: number;
32
+ headers?: Record<string, string>;
33
+ auth?: AuthOptions;
34
+ retry?: RetryOptions;
35
+ allowInsecure?: boolean;
36
+ onAuthError?: (error: ApiError) => void | Promise<void>;
37
+ hooks?: ClientHooks;
38
+ userAgent?: string;
39
+ clientVersion?: string;
40
+ signingHook?: RequestOptions["signingHook"];
41
+ };
42
+
43
+ type RequestConfig = Omit<RequestOptions, "sdkHeaders" | "hooks" | "signingHook"> & {
44
+ auth?: AuthOptions;
45
+ timeoutMs?: number;
46
+ retry?: RetryOptions;
47
+ allowInsecure?: boolean;
48
+ onAuthError?: (error: ApiError) => void | Promise<void>;
49
+ };
50
+
51
+ const sdkVersion = "0.1.0";
52
+
53
+ const withDefaults = (client: ApiClient, options: RequestConfig = {}): RequestOptions => {
54
+ return {
55
+ ...options,
56
+ timeoutMs: options.timeoutMs ?? client.timeoutMs,
57
+ allowInsecure: options.allowInsecure ?? client.allowInsecure,
58
+ retry: options.retry ?? client.retry,
59
+ auth: options.auth ?? client.authOptions,
60
+ onAuthError: options.onAuthError ?? client.onAuthError,
61
+ sdkHeaders: {
62
+ "x-sdk-name": "@halal-trace/sdk",
63
+ "x-sdk-version": sdkVersion,
64
+ ...(client.clientVersion ? { "x-client-version": client.clientVersion } : {}),
65
+ ...(client.userAgent ? { "User-Agent": client.userAgent } : {}),
66
+ },
67
+ hooks: client.hooks,
68
+ signingHook: client.signingHook,
69
+ };
70
+ };
71
+
72
+ const unwrap = <T>(response: ApiResponse<T>): T => response.data ?? (undefined as T);
73
+
74
+ type PaginatedQuery = Record<string, string | number | boolean | undefined> & { limit?: number };
75
+
76
+ async function* listPaginated<T>(client: ApiClient, path: string, query?: PaginatedQuery) {
77
+ const limit = query?.limit ?? 50;
78
+ let offset = 0;
79
+ while (true) {
80
+ const response = await client.request<T[]>("GET", path, { query: { ...query, limit, offset }, canRetry: true });
81
+ const items = response.body.data ?? [];
82
+ for (const item of items) {
83
+ yield item;
84
+ }
85
+ if (items.length < limit) {
86
+ break;
87
+ }
88
+ offset += limit;
89
+ }
90
+ }
91
+
92
+ async function* listSingle<T>(client: ApiClient, path: string, query?: Record<string, string | number | boolean | undefined>) {
93
+ const response = await client.request<T[]>("GET", path, { query, canRetry: true });
94
+ const items = response.body.data ?? [];
95
+ for (const item of items) {
96
+ yield item;
97
+ }
98
+ }
99
+
100
+ async function* listRelayerJobsAll(
101
+ client: ApiClient,
102
+ orgId: string,
103
+ query?: { status?: string; jobType?: string; batchId?: string; requestId?: string; limit?: number },
104
+ ) {
105
+ const limit = query?.limit ?? 100;
106
+ let offset = 0;
107
+ while (true) {
108
+ const response = await client.orgs.listRelayerJobs(orgId, { ...query, limit, offset });
109
+ const items = response.data ?? [];
110
+ for (const item of items) {
111
+ yield item;
112
+ }
113
+ if (items.length < limit) {
114
+ break;
115
+ }
116
+ offset += limit;
117
+ }
118
+ }
119
+
120
+ export class ApiClient {
121
+ baseUrl: string;
122
+ timeoutMs?: number;
123
+ headers?: Record<string, string>;
124
+ authOptions?: AuthOptions;
125
+ retry?: RetryOptions;
126
+ allowInsecure?: boolean;
127
+ onAuthError?: (error: ApiError) => void | Promise<void>;
128
+ hooks?: ClientHooks;
129
+ userAgent?: string;
130
+ clientVersion?: string;
131
+ signingHook?: RequestOptions["signingHook"];
132
+
133
+ constructor(options: BaseClientOptions) {
134
+ if (!options.baseUrl) {
135
+ throw new Error("baseUrl is required");
136
+ }
137
+ this.baseUrl = options.baseUrl;
138
+ this.timeoutMs = options.timeoutMs;
139
+ this.headers = options.headers;
140
+ this.authOptions = options.auth;
141
+ this.retry = options.retry;
142
+ this.allowInsecure = options.allowInsecure;
143
+ this.onAuthError = options.onAuthError;
144
+ this.hooks = options.hooks;
145
+ this.userAgent = options.userAgent;
146
+ this.clientVersion = options.clientVersion;
147
+ this.signingHook = options.signingHook;
148
+ }
149
+
150
+ async request<T>(method: string, path: string, options: RequestConfig = {}) {
151
+ const response = await request<ApiResponse<T>>(this.baseUrl, path, method, {
152
+ ...withDefaults(this, options),
153
+ headers: {
154
+ ...(this.headers ?? {}),
155
+ ...(options.headers ?? {}),
156
+ },
157
+ });
158
+ return {
159
+ body: {
160
+ ...response.body,
161
+ requestId: response.requestId,
162
+ },
163
+ requestId: response.requestId,
164
+ };
165
+ }
166
+
167
+ health = {
168
+ get: async () => unwrap((await this.request<HealthResponse>("GET", "/health", { canRetry: true })).body),
169
+ live: async () => unwrap((await this.request<HealthResponse>("GET", "/live", { canRetry: true })).body),
170
+ ready: async () => unwrap((await this.request<HealthResponse>("GET", "/ready", { canRetry: true })).body),
171
+ };
172
+
173
+ system = {
174
+ docs: async () => {
175
+ const response = await requestRaw(this.baseUrl, "/docs", "GET", { ...withDefaults(this, {}), canRetry: true });
176
+ return response.body as string;
177
+ },
178
+ openApi: async () => {
179
+ const response = await requestRaw(this.baseUrl, "/docs/openapi.json", "GET", { ...withDefaults(this, {}), canRetry: true });
180
+ return response.body as unknown;
181
+ },
182
+ metrics: async () => {
183
+ const response = await requestRaw(this.baseUrl, "/metrics", "GET", { ...withDefaults(this, {}), canRetry: true });
184
+ return response.body as string;
185
+ },
186
+ inviteLanding: async (orgId: string, token: string) => {
187
+ const response = await requestRaw(this.baseUrl, "/invites/accept", "GET", {
188
+ ...withDefaults(this, {}),
189
+ query: { orgId, token },
190
+ canRetry: true,
191
+ });
192
+ return response.body as string;
193
+ },
194
+ };
195
+
196
+ auth = {
197
+ signUpEmail: async (payload: { name: string; email: string; password: string; callbackURL?: string }, options?: RequestConfig): Promise<AuthResult> => {
198
+ const response = await requestRaw(this.baseUrl, "/api/auth/sign-up/email", "POST", {
199
+ ...withDefaults(this, options ?? {}),
200
+ body: payload,
201
+ canRetry: false,
202
+ });
203
+ return { data: response.body, token: response.headers.get("set-auth-token"), requestId: response.requestId };
204
+ },
205
+ signInEmail: async (payload: { email: string; password: string }, options?: RequestConfig): Promise<AuthResult> => {
206
+ const response = await requestRaw(this.baseUrl, "/api/auth/sign-in/email", "POST", {
207
+ ...withDefaults(this, options ?? {}),
208
+ body: payload,
209
+ canRetry: false,
210
+ });
211
+ return { data: response.body, token: response.headers.get("set-auth-token"), requestId: response.requestId };
212
+ },
213
+ verifyEmail: async (token: string, callbackURL?: string): Promise<AuthResult> => {
214
+ const response = await requestRaw(this.baseUrl, "/api/auth/verify-email", "GET", {
215
+ ...withDefaults(this, {}),
216
+ query: { token, callbackURL },
217
+ canRetry: true,
218
+ });
219
+ return { data: response.body, token: response.headers.get("set-auth-token"), requestId: response.requestId };
220
+ },
221
+ requestPasswordReset: async (payload: { email: string }): Promise<AuthResult> => {
222
+ const response = await requestRaw(this.baseUrl, "/api/auth/request-password-reset", "POST", {
223
+ ...withDefaults(this, {}),
224
+ body: payload,
225
+ canRetry: false,
226
+ });
227
+ return { data: response.body, token: response.headers.get("set-auth-token"), requestId: response.requestId };
228
+ },
229
+ resetPassword: async (payload: { token: string; newPassword: string }): Promise<AuthResult> => {
230
+ const response = await requestRaw(this.baseUrl, "/api/auth/reset-password", "POST", {
231
+ ...withDefaults(this, {}),
232
+ body: payload,
233
+ canRetry: false,
234
+ });
235
+ return { data: response.body, token: response.headers.get("set-auth-token"), requestId: response.requestId };
236
+ },
237
+ };
238
+
239
+ orgs = {
240
+ create: async (payload: { orgId: string; name?: string }, options?: RequestConfig) =>
241
+ await this.request<Org>("POST", "/orgs", {
242
+ ...options,
243
+ body: payload,
244
+ canRetry: true,
245
+ idempotencyKey: options?.idempotencyKey,
246
+ }).then((res) => res.body),
247
+ addMember: async (orgId: string, payload: { userId: string; role: string; department?: string }, options?: RequestConfig) =>
248
+ await this.request<Member>("POST", `/orgs/${orgId}/members`, {
249
+ ...options,
250
+ body: payload,
251
+ canRetry: true,
252
+ idempotencyKey: options?.idempotencyKey,
253
+ }).then((res) => res.body),
254
+ updateMember: async (orgId: string, userId: string, payload: { role?: string; department?: string }) =>
255
+ await this.request<Member>("PATCH", `/orgs/${orgId}/members/${userId}`, { body: payload, canRetry: false }).then((res) => res.body),
256
+ removeMember: async (orgId: string, userId: string) =>
257
+ await this.request<Member>("DELETE", `/orgs/${orgId}/members/${userId}`, { canRetry: false }).then((res) => res.body),
258
+ listMembers: async (orgId: string) =>
259
+ await this.request<Member[]>("GET", `/orgs/${orgId}/members`, { canRetry: true }).then((res) => res.body),
260
+ listMembersAll: (orgId: string) => listSingle<Member>(this, `/orgs/${orgId}/members`),
261
+ listAuditLogs: async (orgId: string, query?: { limit?: number; offset?: number }) =>
262
+ await this.request<AuditLog[]>("GET", `/orgs/${orgId}/audit-logs`, { query, canRetry: true }).then((res) => res.body),
263
+ listAuditLogsAll: (orgId: string, query?: { limit?: number }) => listPaginated<AuditLog>(this, `/orgs/${orgId}/audit-logs`, query),
264
+ listRelayerJobs: async (
265
+ orgId: string,
266
+ query?: { status?: string; jobType?: string; batchId?: string; requestId?: string; limit?: number; offset?: number },
267
+ ) =>
268
+ await this.request<RelayerJobSummary[]>("GET", `/orgs/${orgId}/relayer-jobs`, { query, canRetry: true }).then((res) => res.body),
269
+ getRelayerJob: async (orgId: string, jobId: number) =>
270
+ await this.request<RelayerJobSummary>("GET", `/orgs/${orgId}/relayer-jobs/${jobId}`, { canRetry: true }).then((res) => res.body),
271
+ listDeadRelayerJobs: async (orgId: string) =>
272
+ await this.request<RelayerJobSummary[]>("GET", `/orgs/${orgId}/relayer-jobs/dead`, { canRetry: true }).then((res) => res.body),
273
+ retryRelayerJob: async (orgId: string, jobId: number) =>
274
+ await this.request<RelayerJobSummary>("POST", `/orgs/${orgId}/relayer-jobs/${jobId}/retry`, { canRetry: false }).then((res) => res.body),
275
+ backfillOrg: async (orgId: string) =>
276
+ await this.request<RelayerJobSummary>("POST", `/orgs/${orgId}/relayer-jobs/backfill`, { canRetry: false }).then((res) => res.body),
277
+ listInvites: async (orgId: string, query?: { status?: string }) =>
278
+ await this.request<Invite[]>("GET", `/orgs/${orgId}/invites`, { query, canRetry: true }).then((res) => res.body),
279
+ listInvitesAll: (orgId: string, query?: { status?: string }) => listSingle<Invite>(this, `/orgs/${orgId}/invites`, query),
280
+ inviteMember: async (orgId: string, payload: { email: string; role: string; department?: string; inviteUrl?: string }, options?: RequestConfig) =>
281
+ await this.request<OrgInviteResult>("POST", `/orgs/${orgId}/invites`, {
282
+ ...options,
283
+ body: payload,
284
+ canRetry: true,
285
+ idempotencyKey: options?.idempotencyKey,
286
+ }).then((res) => res.body),
287
+ acceptInvite: async (orgId: string, token: string) =>
288
+ await this.request<{ invite: Invite; member: Member }>("POST", `/orgs/${orgId}/invites/${token}/accept`, { canRetry: false }).then((res) => res.body),
289
+ lookupUserByEmail: async (orgId: string, email: string) =>
290
+ await this.request<OrgUserLookup>("GET", `/orgs/${orgId}/users`, { query: { email }, canRetry: true }).then((res) => res.body),
291
+ listPermissions: async (orgId: string) =>
292
+ await this.request<Permission[]>("GET", `/orgs/${orgId}/permissions`, { canRetry: true }).then((res) => res.body),
293
+ addPermission: async (orgId: string, payload: { action: string; role: string }) =>
294
+ await this.request<Permission>("POST", `/orgs/${orgId}/permissions`, { body: payload, canRetry: false }).then((res) => res.body),
295
+ removePermission: async (orgId: string, payload: { action: string; role: string }) =>
296
+ await this.request<{ orgId: string; action: string; role: string }>("DELETE", `/orgs/${orgId}/permissions`, {
297
+ body: payload,
298
+ canRetry: false,
299
+ }).then((res) => res.body),
300
+ setEventDepartment: async (orgId: string, payload: { eventType: string; department: string }) =>
301
+ await this.request<EventDepartment>("POST", `/orgs/${orgId}/event-departments`, { body: payload, canRetry: false }).then((res) => res.body),
302
+ };
303
+
304
+ relayerJobs = {
305
+ listAll: (orgId: string, query?: { status?: string; jobType?: string; batchId?: string; limit?: number }) =>
306
+ listRelayerJobsAll(this, orgId, query),
307
+ get: async (orgId: string, jobId: number) => this.orgs.getRelayerJob(orgId, jobId).then((res) => res.data),
308
+ waitFor: async (orgId: string, jobId: number, options?: { timeoutMs?: number; intervalMs?: number }) => {
309
+ const timeoutMs = options?.timeoutMs ?? 60000;
310
+ const intervalMs = options?.intervalMs ?? 2000;
311
+ const start = Date.now();
312
+ while (Date.now() - start < timeoutMs) {
313
+ const job = await this.relayerJobs.get(orgId, jobId);
314
+ if (job && (job.status === "completed" || job.status === "dead" || job.status === "failed")) {
315
+ return job;
316
+ }
317
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
318
+ }
319
+ throw new ApiError("Timeout waiting for relayer job");
320
+ },
321
+ waitForRequest: async (orgId: string, requestId: string, options?: { timeoutMs?: number; intervalMs?: number }) => {
322
+ const timeoutMs = options?.timeoutMs ?? 60000;
323
+ const intervalMs = options?.intervalMs ?? 2000;
324
+ const start = Date.now();
325
+ while (Date.now() - start < timeoutMs) {
326
+ const jobs = await this.orgs.listRelayerJobs(orgId, { requestId, limit: 10, offset: 0 });
327
+ const job = jobs.data?.[0];
328
+ if (job) {
329
+ return job;
330
+ }
331
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
332
+ }
333
+ throw new ApiError("Timeout waiting for request relayer job");
334
+ },
335
+ };
336
+
337
+ batches = {
338
+ create: async (payload: { batchId: string; orgId: string; productId: string; facilityId?: string }, options?: RequestConfig) =>
339
+ await this.request<Batch>("POST", "/batches", {
340
+ ...options,
341
+ body: payload,
342
+ canRetry: true,
343
+ idempotencyKey: options?.idempotencyKey,
344
+ }).then((res) => res.body),
345
+ addPublicAttribute: async (batchId: string, payload: { key: string; value: string }, options?: RequestConfig) =>
346
+ await this.request<PublicAttribute>("POST", `/batches/${batchId}/public-attributes`, {
347
+ ...options,
348
+ body: payload,
349
+ canRetry: true,
350
+ idempotencyKey: options?.idempotencyKey,
351
+ }).then((res) => res.body),
352
+ addEvent: async (
353
+ batchId: string,
354
+ payload: { eventType: string; metadataHash: string; actorRole: string; deviceId?: string },
355
+ options?: RequestConfig,
356
+ ) =>
357
+ await this.request<TraceEvent>("POST", `/batches/${batchId}/events`, {
358
+ ...options,
359
+ body: payload,
360
+ canRetry: Boolean(options?.idempotencyKey),
361
+ idempotencyKey: options?.idempotencyKey,
362
+ }).then((res) => res.body),
363
+ addDocument: async (batchId: string, payload: { docHash: string; docType: string; uri: string }, options?: RequestConfig) =>
364
+ await this.request<DocumentAnchor>("POST", `/batches/${batchId}/documents`, {
365
+ ...options,
366
+ body: payload,
367
+ canRetry: true,
368
+ idempotencyKey: options?.idempotencyKey,
369
+ }).then((res) => res.body),
370
+ addSignature: async (batchId: string, payload: { signatureHash: string; signerRole: string; signerIdHash: string }, options?: RequestConfig) =>
371
+ await this.request<SignatureRecord>("POST", `/batches/${batchId}/signatures`, {
372
+ ...options,
373
+ body: payload,
374
+ canRetry: Boolean(options?.idempotencyKey),
375
+ idempotencyKey: options?.idempotencyKey,
376
+ }).then((res) => res.body),
377
+ addCertification: async (
378
+ batchId: string,
379
+ payload: { status: "pending" | "approved" | "rejected"; decisionHash: string; reviewerRole: string; reviewerIdHash: string },
380
+ options?: RequestConfig,
381
+ ) =>
382
+ await this.request<CertificationRecord>("POST", `/batches/${batchId}/certifications`, {
383
+ ...options,
384
+ body: payload,
385
+ canRetry: Boolean(options?.idempotencyKey),
386
+ idempotencyKey: options?.idempotencyKey,
387
+ }).then((res) => res.body),
388
+ getAuditPackManifest: async (batchId: string) =>
389
+ await this.request<AuditPackManifest>("GET", `/batches/${batchId}/audit-pack/manifest`, { canRetry: true }).then((res) => res.body),
390
+ listRelayerJobs: async (batchId: string, query?: { status?: string; jobType?: string; limit?: number; offset?: number }) =>
391
+ await this.request<RelayerJobSummary[]>("GET", `/batches/${batchId}/relayer-jobs`, { query, canRetry: true }).then((res) => res.body),
392
+ listRelayerJobsAll: (batchId: string, query?: { status?: string; jobType?: string; limit?: number }) =>
393
+ listPaginated<RelayerJobSummary>(this, `/batches/${batchId}/relayer-jobs`, query),
394
+ backfillBatch: async (batchId: string) =>
395
+ await this.request<RelayerJobSummary>("POST", `/batches/${batchId}/relayer-jobs/backfill`, { canRetry: false }).then((res) => res.body),
396
+ };
397
+
398
+ devices = {
399
+ register: async (payload: { deviceId: string; orgId: string; deviceType: string; label: string }, options?: RequestConfig) =>
400
+ await this.request<Device>("POST", "/devices", {
401
+ ...options,
402
+ body: payload,
403
+ canRetry: true,
404
+ idempotencyKey: options?.idempotencyKey,
405
+ }).then((res) => res.body),
406
+ update: async (deviceId: string, payload: { deviceType: string; label: string }, options?: RequestConfig) =>
407
+ await this.request<Device>("PATCH", `/devices/${deviceId}`, {
408
+ ...options,
409
+ body: payload,
410
+ canRetry: Boolean(options?.idempotencyKey),
411
+ idempotencyKey: options?.idempotencyKey,
412
+ }).then((res) => res.body),
413
+ setStatus: async (deviceId: string, payload: { active: boolean }, options?: RequestConfig) =>
414
+ await this.request<Device>("PATCH", `/devices/${deviceId}/status`, {
415
+ ...options,
416
+ body: payload,
417
+ canRetry: Boolean(options?.idempotencyKey),
418
+ idempotencyKey: options?.idempotencyKey,
419
+ }).then((res) => res.body),
420
+ };
421
+
422
+ anchors = {
423
+ create: async (
424
+ payload: { orgId: string; merkleRoot: string; metadataHash: string; periodStart: number; periodEnd: number; anchorChainId: number; anchorTxHash: string },
425
+ options?: RequestConfig,
426
+ ) =>
427
+ await this.request<Anchor>("POST", "/anchors", {
428
+ ...options,
429
+ body: payload,
430
+ canRetry: true,
431
+ idempotencyKey: options?.idempotencyKey,
432
+ }).then((res) => res.body),
433
+ };
434
+
435
+ auditPacks = {
436
+ create: async (
437
+ payload: { batchId: string; pdfHash: string; jsonHash?: string; packVersion: string; pdfUri: string; jsonUri?: string },
438
+ options?: RequestConfig,
439
+ ) =>
440
+ await this.request<AuditPack>("POST", "/audit-packs", {
441
+ ...options,
442
+ body: payload,
443
+ canRetry: true,
444
+ idempotencyKey: options?.idempotencyKey,
445
+ }).then((res) => res.body),
446
+ downloadPdf: async (pdfUri: string) => {
447
+ const response = await fetch(pdfUri);
448
+ if (!response.ok) {
449
+ throw new ApiError(`Failed to download PDF: ${response.status}`, response.status);
450
+ }
451
+ return response.arrayBuffer();
452
+ },
453
+ downloadJson: async (jsonUri: string) => {
454
+ const response = await fetch(jsonUri);
455
+ if (!response.ok) {
456
+ throw new ApiError(`Failed to download JSON: ${response.status}`, response.status);
457
+ }
458
+ return response.text();
459
+ },
460
+ };
461
+
462
+ holograms = {
463
+ issue: async (
464
+ payload: { batchId: string; hologramId: string; metadataHash: string; uri: string; publicCode?: string },
465
+ options?: RequestConfig,
466
+ ) =>
467
+ await this.request<Hologram>("POST", "/holograms", {
468
+ ...options,
469
+ body: payload,
470
+ canRetry: true,
471
+ idempotencyKey: options?.idempotencyKey,
472
+ }).then((res) => res.body),
473
+ revoke: async (payload: { hologramId: string; reasonHash: string }, options?: RequestConfig) =>
474
+ await this.request<{ hologramId: string; active: boolean }>("POST", "/holograms/revoke", {
475
+ ...options,
476
+ body: payload,
477
+ canRetry: true,
478
+ idempotencyKey: options?.idempotencyKey,
479
+ }).then((res) => res.body),
480
+ verify: async (publicCode: string) => {
481
+ const response = await this.request<HologramVerification>("GET", `/verify/holograms/${publicCode}`, { canRetry: true });
482
+ if (response.body.status !== "ok") {
483
+ return null;
484
+ }
485
+ return response.body.data ?? null;
486
+ },
487
+ };
488
+
489
+ }
490
+
491
+ export type { BaseClientOptions, RequestConfig };
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { request } from "./http.js";
3
+
4
+ describe("request", () => {
5
+ it("rejects insecure baseUrl without allowInsecure", async () => {
6
+ await expect(
7
+ request("http://localhost:4000", "/health", "GET", { canRetry: true }),
8
+ ).rejects.toThrow("Insecure baseUrl blocked");
9
+ });
10
+ });