-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathfeatures.ts
More file actions
124 lines (99 loc) · 3.44 KB
/
features.ts
File metadata and controls
124 lines (99 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Auto-generated by https://github.com/vladkens/apigen-ts
// Source: file://examples/features.yaml
type Headers = Record<string, string>
export type ApigenHeaders = Headers | ((method: string, path: string) => Headers | Promise<Headers>)
export interface ApigenConfig {
baseUrl: string
headers: ApigenHeaders
}
export interface ApigenRequest extends Omit<RequestInit, "body"> {
search?: Record<string, unknown>
body?: unknown
}
export class ApiClient {
ISO_FORMAT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?(?:[-+]\d{2}:?\d{2}|Z)?$/
Config: ApigenConfig
constructor(config?: Partial<ApigenConfig>) {
this.Config = { baseUrl: "/", headers: {}, ...config }
}
PopulateDates<T>(d: T): T {
if (d === null || d === undefined || typeof d !== "object") return d
const t = d as unknown as Record<string, unknown>
for (const [k, v] of Object.entries(t)) {
if (typeof v === "string" && this.ISO_FORMAT.test(v)) t[k] = new Date(v)
else if (typeof v === "object") this.PopulateDates(v)
}
return d
}
async ParseError(rep: Response) {
try {
return await rep.json()
} catch (e) {
throw rep
}
}
PrepareFetchUrl(path: string): URL {
let base = this.Config.baseUrl
if ("location" in globalThis && (base === "" || base.startsWith("/"))) {
const { location } = globalThis as unknown as { location: { origin: string } }
base = `${location.origin}${base.endsWith("/") ? base : `/${base}`}`
}
return new URL(path, base)
}
async Fetch<T>(method: string, path: string, opts: ApigenRequest = {}): Promise<T> {
const url = this.PrepareFetchUrl(path)
for (const [k, v] of Object.entries(opts?.search ?? {})) {
url.searchParams.append(k, Array.isArray(v) ? v.join(",") : (v as string))
}
const configHeaders =
typeof this.Config.headers === "function"
? await this.Config.headers(method, path)
: this.Config.headers
const headers = new Headers({ ...configHeaders, ...opts.headers })
const ct = headers.get("content-type") ?? "application/json"
let body: FormData | URLSearchParams | string | undefined = undefined
if (ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded") {
headers.delete("content-type")
body = ct === "multipart/form-data" ? new FormData() : new URLSearchParams()
for (const [k, v] of Object.entries(opts.body as Record<string, string>)) {
body.append(k, v)
}
}
if (ct === "application/json" && typeof opts.body !== "string") {
headers.set("content-type", "application/json")
body = JSON.stringify(opts.body)
}
const credentials = opts.credentials ?? "include"
const rep = await fetch(url.toString(), { method, ...opts, headers, body, credentials })
if (!rep.ok) throw await this.ParseError(rep)
const rs = await rep.text()
try {
return this.PopulateDates(JSON.parse(rs) as T)
} catch (e) {
return rs as unknown as T
}
}
general = {
getStatus: () => {
return this.Fetch<{
status?: "active"
}>("get", "/status", {})
},
getVersion: () => {
return this.Fetch<{
version?: "v1"
}>("get", "/version", {})
},
getEvents: () => {
return this.Fetch<UserCreated | UserDeleted>("get", "/events", {})
},
}
}
export type UserCreated = {
type: "user_created"
userId: string
}
export type UserDeleted = {
type: "user_deleted"
userId: string
}