@prover-coder-ai/openapi-effect 1.0.19 → 1.0.21

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.
@@ -0,0 +1,82 @@
1
+ import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "./openapi-compat-serializers.js"
2
+ import { isPrimitive, isRecord } from "./openapi-compat-value-guards.js"
3
+
4
+ const PATH_PARAM_RE = /\{[^{}]+\}/g
5
+
6
+ type PathStyle = "simple" | "label" | "matrix"
7
+
8
+ type PathTokenMeta = {
9
+ name: string
10
+ explode: boolean
11
+ style: PathStyle
12
+ }
13
+
14
+ const toPathTokenMeta = (rawName: string): PathTokenMeta => {
15
+ let name = rawName
16
+ let explode = false
17
+ let style: PathStyle = "simple"
18
+
19
+ if (name.endsWith("*")) {
20
+ explode = true
21
+ name = name.slice(0, Math.max(0, name.length - 1))
22
+ }
23
+
24
+ if (name.startsWith(".")) {
25
+ style = "label"
26
+ name = name.slice(1)
27
+ } else if (name.startsWith(";")) {
28
+ style = "matrix"
29
+ name = name.slice(1)
30
+ }
31
+
32
+ return { name, explode, style }
33
+ }
34
+
35
+ const serializePathValue = (
36
+ name: string,
37
+ value: unknown,
38
+ meta: PathTokenMeta
39
+ ): string | undefined => {
40
+ if (Array.isArray(value)) {
41
+ return serializeArrayParam(name, value, { style: meta.style, explode: meta.explode })
42
+ }
43
+
44
+ if (isRecord(value)) {
45
+ return serializeObjectParam(name, value, { style: meta.style, explode: meta.explode })
46
+ }
47
+
48
+ if (!isPrimitive(value)) {
49
+ return
50
+ }
51
+
52
+ if (meta.style === "matrix") {
53
+ return `;${serializePrimitiveParam(name, value)}`
54
+ }
55
+
56
+ const encoded = encodeURIComponent(String(value))
57
+ return meta.style === "label" ? `.${encoded}` : encoded
58
+ }
59
+
60
+ export const defaultPathSerializer = (
61
+ pathname: string,
62
+ pathParams: Record<string, unknown>
63
+ ): string => {
64
+ let nextURL = pathname
65
+
66
+ for (const match of pathname.match(PATH_PARAM_RE) ?? []) {
67
+ const rawName = match.slice(1, -1)
68
+ const meta = toPathTokenMeta(rawName)
69
+ const value = pathParams[meta.name]
70
+
71
+ if (value === undefined || value === null) {
72
+ continue
73
+ }
74
+
75
+ const serializedValue = serializePathValue(meta.name, value, meta)
76
+ if (serializedValue !== undefined) {
77
+ nextURL = nextURL.replace(match, serializedValue)
78
+ }
79
+ }
80
+
81
+ return nextURL
82
+ }
@@ -0,0 +1,153 @@
1
+ import type { HeadersOptions, PathSerializer, QuerySerializer } from "./create-client-types.js"
2
+
3
+ const isRecord = (value: unknown): value is Record<string, unknown> => (
4
+ value !== null && typeof value === "object" && !Array.isArray(value)
5
+ )
6
+
7
+ const toFormRecord = (value: unknown): Record<string, string> => {
8
+ if (!isRecord(value)) {
9
+ return {}
10
+ }
11
+
12
+ const formRecord: Record<string, string> = {}
13
+ for (const [key, item] of Object.entries(value)) {
14
+ formRecord[key] = String(item)
15
+ }
16
+
17
+ return formRecord
18
+ }
19
+
20
+ type HeaderRecord = Record<
21
+ string,
22
+ string | number | boolean | Array<string | number | boolean> | null | undefined
23
+ >
24
+
25
+ const isHeaderRecord = (headers: HeadersOptions): headers is HeaderRecord => (
26
+ !(headers instanceof Headers) && !Array.isArray(headers)
27
+ )
28
+
29
+ const getHeaderValue = (headers: Headers | HeadersOptions | undefined, key: string): string | undefined => {
30
+ if (headers === undefined) {
31
+ return
32
+ }
33
+
34
+ if (headers instanceof Headers) {
35
+ return headers.get(key) ?? undefined
36
+ }
37
+
38
+ if (!isHeaderRecord(headers)) {
39
+ return
40
+ }
41
+
42
+ const value = headers[key]
43
+ if (value === undefined || value === null || Array.isArray(value)) {
44
+ return
45
+ }
46
+
47
+ return String(value)
48
+ }
49
+
50
+ const stringifyBody = (body: unknown): string => {
51
+ return JSON.stringify(body)
52
+ }
53
+
54
+ export const defaultBodySerializer = (
55
+ body: unknown,
56
+ headers?: Headers | HeadersOptions
57
+ ): string => {
58
+ if (body === undefined) {
59
+ return ""
60
+ }
61
+
62
+ const contentType = getHeaderValue(headers, "Content-Type") ?? getHeaderValue(headers, "content-type")
63
+ if (contentType === "application/x-www-form-urlencoded") {
64
+ return new URLSearchParams(toFormRecord(body)).toString()
65
+ }
66
+
67
+ return stringifyBody(body)
68
+ }
69
+
70
+ export const createFinalURL = (
71
+ pathname: string,
72
+ options: {
73
+ baseUrl: string
74
+ params: {
75
+ query?: Record<string, unknown>
76
+ path?: Record<string, unknown>
77
+ }
78
+ querySerializer: QuerySerializer<object>
79
+ pathSerializer: PathSerializer
80
+ }
81
+ ): string => {
82
+ let finalURL = `${options.baseUrl}${pathname}`
83
+
84
+ if (options.params.path) {
85
+ finalURL = options.pathSerializer(finalURL, options.params.path)
86
+ }
87
+
88
+ let queryString = options.querySerializer(options.params.query ?? {})
89
+ if (queryString.startsWith("?")) {
90
+ queryString = queryString.slice(1)
91
+ }
92
+
93
+ if (queryString.length > 0) {
94
+ finalURL = `${finalURL}?${queryString}`
95
+ }
96
+
97
+ return finalURL
98
+ }
99
+
100
+ const applyHeaderValue = (target: Headers, key: string, value: HeaderRecord[string]): void => {
101
+ if (value === null) {
102
+ target.delete(key)
103
+ return
104
+ }
105
+
106
+ if (Array.isArray(value)) {
107
+ for (const item of value) {
108
+ target.append(key, String(item))
109
+ }
110
+ return
111
+ }
112
+
113
+ if (value !== undefined) {
114
+ target.set(key, String(value))
115
+ }
116
+ }
117
+
118
+ const mergeHeaderSource = (target: Headers, source: HeadersOptions): void => {
119
+ if (source instanceof Headers) {
120
+ for (const [key, value] of source.entries()) {
121
+ target.set(key, value)
122
+ }
123
+ return
124
+ }
125
+
126
+ if (!isHeaderRecord(source)) {
127
+ return
128
+ }
129
+
130
+ for (const [key, value] of Object.entries(source)) {
131
+ applyHeaderValue(target, key, value)
132
+ }
133
+ }
134
+
135
+ export const mergeHeaders = (
136
+ ...allHeaders: Array<HeadersOptions | undefined>
137
+ ): Headers => {
138
+ const finalHeaders = new Headers()
139
+
140
+ for (const source of allHeaders) {
141
+ if (source === undefined || typeof source !== "object") {
142
+ continue
143
+ }
144
+
145
+ mergeHeaderSource(finalHeaders, source)
146
+ }
147
+
148
+ return finalHeaders
149
+ }
150
+
151
+ export const removeTrailingSlash = (url: string): string => (
152
+ url.endsWith("/") ? url.slice(0, Math.max(0, url.length - 1)) : url
153
+ )
@@ -0,0 +1,277 @@
1
+ import type { QuerySerializer, QuerySerializerOptions } from "./create-client-types.js"
2
+ import { isPrimitive, isRecord, type Primitive } from "./openapi-compat-value-guards.js"
3
+
4
+ type PathStyle = "simple" | "label" | "matrix"
5
+ type ObjectParamStyle = PathStyle | "form" | "deepObject"
6
+ type ArrayParamStyle = PathStyle | "form" | "spaceDelimited" | "pipeDelimited"
7
+
8
+ const OBJECT_JOINER_BY_STYLE: Readonly<Record<ObjectParamStyle, string>> = {
9
+ simple: ",",
10
+ label: ".",
11
+ matrix: ";",
12
+ form: "&",
13
+ deepObject: "&"
14
+ }
15
+
16
+ const ARRAY_JOINER_BY_STYLE: Readonly<
17
+ Record<ArrayParamStyle, { explodeFalse: string; explodeTrue: string }>
18
+ > = {
19
+ simple: { explodeFalse: ",", explodeTrue: "," },
20
+ label: { explodeFalse: ",", explodeTrue: "." },
21
+ matrix: { explodeFalse: ",", explodeTrue: ";" },
22
+ form: { explodeFalse: ",", explodeTrue: "&" },
23
+ spaceDelimited: { explodeFalse: "%20", explodeTrue: "&" },
24
+ pipeDelimited: { explodeFalse: "|", explodeTrue: "&" }
25
+ }
26
+
27
+ const encodeValue = (value: Primitive, allowReserved: boolean): string => (
28
+ allowReserved ? String(value) : encodeURIComponent(String(value))
29
+ )
30
+
31
+ const formatExplodeFalse = (
32
+ name: string,
33
+ style: ObjectParamStyle | ArrayParamStyle,
34
+ value: string
35
+ ): string => {
36
+ if (style === "simple") {
37
+ return value
38
+ }
39
+ if (style === "label") {
40
+ return `.${value}`
41
+ }
42
+ if (style === "matrix") {
43
+ return `;${name}=${value}`
44
+ }
45
+ return `${name}=${value}`
46
+ }
47
+
48
+ const formatExplodeTrue = (
49
+ style: ObjectParamStyle | ArrayParamStyle,
50
+ joiner: string,
51
+ value: string
52
+ ): string => (
53
+ style === "label" || style === "matrix" ? `${joiner}${value}` : value
54
+ )
55
+
56
+ const toPrimitiveList = (value: Array<unknown>): Array<Primitive> => {
57
+ const items: Array<Primitive> = []
58
+ for (const item of value) {
59
+ if (isPrimitive(item)) {
60
+ items.push(item)
61
+ }
62
+ }
63
+ return items
64
+ }
65
+
66
+ const getQueryEntries = (queryParams: unknown): Array<[string, unknown]> => (
67
+ isRecord(queryParams) ? Object.entries(queryParams) : []
68
+ )
69
+
70
+ const toObjectPairs = (
71
+ name: string,
72
+ value: Record<string, unknown>,
73
+ allowReserved: boolean,
74
+ explode: boolean,
75
+ style: ObjectParamStyle
76
+ ): Array<string> => {
77
+ const entries: Array<string> = []
78
+
79
+ for (const [key, rawValue] of Object.entries(value)) {
80
+ if (!isPrimitive(rawValue)) {
81
+ continue
82
+ }
83
+
84
+ if (!explode) {
85
+ entries.push(key, encodeValue(rawValue, allowReserved))
86
+ continue
87
+ }
88
+
89
+ const nextName = style === "deepObject" ? `${name}[${key}]` : key
90
+ entries.push(
91
+ serializePrimitiveParam(nextName, rawValue, {
92
+ allowReserved
93
+ })
94
+ )
95
+ }
96
+
97
+ return entries
98
+ }
99
+
100
+ const toArrayValues = (
101
+ name: string,
102
+ value: Array<unknown>,
103
+ style: ArrayParamStyle,
104
+ allowReserved: boolean,
105
+ explode: boolean
106
+ ): Array<string> => {
107
+ const entries: Array<string> = []
108
+
109
+ for (const item of toPrimitiveList(value)) {
110
+ if (explode && style !== "simple" && style !== "label") {
111
+ entries.push(
112
+ serializePrimitiveParam(name, item, {
113
+ allowReserved
114
+ })
115
+ )
116
+ continue
117
+ }
118
+
119
+ entries.push(encodeValue(item, allowReserved))
120
+ }
121
+
122
+ return entries
123
+ }
124
+
125
+ const finalizeSerializedParam = (options: {
126
+ name: string
127
+ style: ObjectParamStyle | ArrayParamStyle
128
+ explode: boolean
129
+ values: Array<string>
130
+ joinerWhenExplodeFalse: string
131
+ joinerWhenExplodeTrue: string
132
+ }): string => {
133
+ const joiner = options.explode ? options.joinerWhenExplodeTrue : options.joinerWhenExplodeFalse
134
+ const serializedValue = options.values.join(joiner)
135
+
136
+ return options.explode
137
+ ? formatExplodeTrue(options.style, options.joinerWhenExplodeTrue, serializedValue)
138
+ : formatExplodeFalse(options.name, options.style, serializedValue)
139
+ }
140
+
141
+ export const serializePrimitiveParam = (
142
+ name: string,
143
+ value: Primitive,
144
+ options?: { allowReserved?: boolean }
145
+ ): string => (
146
+ `${name}=${encodeValue(value, options?.allowReserved === true)}`
147
+ )
148
+
149
+ export const serializeObjectParam = (
150
+ name: string,
151
+ value: unknown,
152
+ options: {
153
+ style: ObjectParamStyle
154
+ explode: boolean
155
+ allowReserved?: boolean
156
+ }
157
+ ): string => {
158
+ if (!isRecord(value)) {
159
+ return ""
160
+ }
161
+
162
+ const pairs = toObjectPairs(
163
+ name,
164
+ value,
165
+ options.allowReserved === true,
166
+ options.explode,
167
+ options.style
168
+ )
169
+
170
+ return finalizeSerializedParam({
171
+ name,
172
+ style: options.style,
173
+ explode: options.explode,
174
+ values: pairs,
175
+ joinerWhenExplodeFalse: ",",
176
+ joinerWhenExplodeTrue: OBJECT_JOINER_BY_STYLE[options.style]
177
+ })
178
+ }
179
+
180
+ export const serializeArrayParam = (
181
+ name: string,
182
+ value: Array<unknown>,
183
+ options: {
184
+ style: ArrayParamStyle
185
+ explode: boolean
186
+ allowReserved?: boolean
187
+ }
188
+ ): string => {
189
+ if (!Array.isArray(value)) {
190
+ return ""
191
+ }
192
+
193
+ const values = toArrayValues(
194
+ name,
195
+ value,
196
+ options.style,
197
+ options.allowReserved === true,
198
+ options.explode
199
+ )
200
+
201
+ return finalizeSerializedParam({
202
+ name,
203
+ style: options.style,
204
+ explode: options.explode,
205
+ values,
206
+ joinerWhenExplodeFalse: ARRAY_JOINER_BY_STYLE[options.style].explodeFalse,
207
+ joinerWhenExplodeTrue: ARRAY_JOINER_BY_STYLE[options.style].explodeTrue
208
+ })
209
+ }
210
+
211
+ const serializeQueryEntry = (
212
+ name: string,
213
+ value: unknown,
214
+ options?: QuerySerializerOptions
215
+ ): string | undefined => {
216
+ if (value === undefined || value === null) {
217
+ return
218
+ }
219
+
220
+ return Array.isArray(value)
221
+ ? serializeArrayQueryEntry(name, value, options)
222
+ : serializeNonArrayQueryEntry(name, value, options)
223
+ }
224
+
225
+ const serializeArrayQueryEntry = (
226
+ name: string,
227
+ value: Array<unknown>,
228
+ options?: QuerySerializerOptions
229
+ ): string | undefined => {
230
+ if (value.length === 0) {
231
+ return
232
+ }
233
+
234
+ return serializeArrayParam(name, value, {
235
+ style: "form",
236
+ explode: true,
237
+ ...options?.array,
238
+ allowReserved: options?.allowReserved === true
239
+ })
240
+ }
241
+
242
+ const serializeNonArrayQueryEntry = (
243
+ name: string,
244
+ value: unknown,
245
+ options?: QuerySerializerOptions
246
+ ): string | undefined => {
247
+ if (isRecord(value)) {
248
+ return serializeObjectParam(name, value, {
249
+ style: "deepObject",
250
+ explode: true,
251
+ ...options?.object,
252
+ allowReserved: options?.allowReserved === true
253
+ })
254
+ }
255
+
256
+ if (isPrimitive(value)) {
257
+ return serializePrimitiveParam(name, value, options)
258
+ }
259
+
260
+ return undefined
261
+ }
262
+
263
+ export const createQuerySerializer = <T = unknown>(
264
+ options?: QuerySerializerOptions
265
+ ): QuerySerializer<T> =>
266
+ (queryParams) => {
267
+ const serialized: Array<string> = []
268
+
269
+ for (const [name, value] of getQueryEntries(queryParams)) {
270
+ const entry = serializeQueryEntry(name, value, options)
271
+ if (entry !== undefined) {
272
+ serialized.push(entry)
273
+ }
274
+ }
275
+
276
+ return serialized.join("&")
277
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ createQuerySerializer,
3
+ serializeArrayParam,
4
+ serializeObjectParam,
5
+ serializePrimitiveParam
6
+ } from "./openapi-compat-serializers.js"
7
+
8
+ export { defaultPathSerializer } from "./openapi-compat-path.js"
9
+
10
+ export { createFinalURL, defaultBodySerializer, mergeHeaders, removeTrailingSlash } from "./openapi-compat-request.js"
@@ -0,0 +1,9 @@
1
+ export type Primitive = string | number | boolean
2
+
3
+ export const isPrimitive = (value: unknown): value is Primitive => (
4
+ typeof value === "string" || typeof value === "number" || typeof value === "boolean"
5
+ )
6
+
7
+ export const isRecord = (value: unknown): value is Record<string, unknown> => (
8
+ value !== null && typeof value === "object" && !Array.isArray(value)
9
+ )