@forklaunch/universal-sdk 0.2.8 → 0.3.2
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/lib/index.d.mts +35 -1
- package/lib/index.d.ts +35 -1
- package/lib/index.js +431 -21
- package/lib/index.mjs +421 -21
- package/package.json +11 -4
package/lib/index.d.mts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
+
|
|
3
|
+
type ResponseContentParserType = 'json' | 'file' | 'text' | 'stream';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} RequestType
|
|
7
|
+
* @property {Record<string, string | number | boolean>} [params] - URL parameters.
|
|
8
|
+
* @property {Record<string, unknown>} [body] - Request body.
|
|
9
|
+
* @property {Record<string, string | number | boolean>} [query] - Query parameters.
|
|
10
|
+
* @property {Record<string, string>} [headers] - Request headers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type RegistryOptions = {
|
|
14
|
+
path: string;
|
|
15
|
+
static?: boolean;
|
|
16
|
+
} | {
|
|
17
|
+
url: string;
|
|
18
|
+
static?: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
raw: OpenAPIObject;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initializes the Forklaunch SDK with HTTP methods proxied.
|
|
25
|
+
*
|
|
26
|
+
* @template TypedController
|
|
27
|
+
* @param {string} host - The host URL for the SDK.
|
|
28
|
+
* @returns {TypedController} - The SDK proxy with methods for HTTP requests.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
declare const universalSdk: <TypedController>(options: {
|
|
32
|
+
host: string;
|
|
33
|
+
registryOptions: RegistryOptions;
|
|
34
|
+
contentTypeParserMap?: Record<string, ResponseContentParserType>;
|
|
35
|
+
}) => Promise<TypedController>;
|
|
2
36
|
|
|
3
37
|
export { universalSdk };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
+
|
|
3
|
+
type ResponseContentParserType = 'json' | 'file' | 'text' | 'stream';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} RequestType
|
|
7
|
+
* @property {Record<string, string | number | boolean>} [params] - URL parameters.
|
|
8
|
+
* @property {Record<string, unknown>} [body] - Request body.
|
|
9
|
+
* @property {Record<string, string | number | boolean>} [query] - Query parameters.
|
|
10
|
+
* @property {Record<string, string>} [headers] - Request headers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type RegistryOptions = {
|
|
14
|
+
path: string;
|
|
15
|
+
static?: boolean;
|
|
16
|
+
} | {
|
|
17
|
+
url: string;
|
|
18
|
+
static?: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
raw: OpenAPIObject;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initializes the Forklaunch SDK with HTTP methods proxied.
|
|
25
|
+
*
|
|
26
|
+
* @template TypedController
|
|
27
|
+
* @param {string} host - The host URL for the SDK.
|
|
28
|
+
* @returns {TypedController} - The SDK proxy with methods for HTTP requests.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
declare const universalSdk: <TypedController>(options: {
|
|
32
|
+
host: string;
|
|
33
|
+
registryOptions: RegistryOptions;
|
|
34
|
+
contentTypeParserMap?: Record<string, ResponseContentParserType>;
|
|
35
|
+
}) => Promise<TypedController>;
|
|
2
36
|
|
|
3
37
|
export { universalSdk };
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// index.ts
|
|
@@ -24,7 +34,161 @@ __export(index_exports, {
|
|
|
24
34
|
});
|
|
25
35
|
module.exports = __toCommonJS(index_exports);
|
|
26
36
|
|
|
27
|
-
// src/
|
|
37
|
+
// src/universalSdk.ts
|
|
38
|
+
var import_common2 = require("@forklaunch/common");
|
|
39
|
+
var import_ajv = __toESM(require("ajv"));
|
|
40
|
+
var import_ajv_formats = __toESM(require("ajv-formats"));
|
|
41
|
+
|
|
42
|
+
// src/core/coerceSpecialTypes.ts
|
|
43
|
+
function handleSpecialString(v, format) {
|
|
44
|
+
if (typeof v !== "string") return v;
|
|
45
|
+
if (format === "date-time") {
|
|
46
|
+
const d = new Date(v);
|
|
47
|
+
return isNaN(d.getTime()) ? v : d;
|
|
48
|
+
}
|
|
49
|
+
if (format === "binary") {
|
|
50
|
+
try {
|
|
51
|
+
return Buffer.from(v, "base64");
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error("Invalid base64 string");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return v;
|
|
57
|
+
}
|
|
58
|
+
function getType(type) {
|
|
59
|
+
if (Array.isArray(type)) return type[0];
|
|
60
|
+
return type;
|
|
61
|
+
}
|
|
62
|
+
function getFormatsFromSchema(def) {
|
|
63
|
+
const formats = /* @__PURE__ */ new Set();
|
|
64
|
+
if (!def) return formats;
|
|
65
|
+
if (def.format) formats.add(def.format);
|
|
66
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
67
|
+
if (Array.isArray(def[keyword])) {
|
|
68
|
+
for (const sub of def[keyword]) {
|
|
69
|
+
if (sub && typeof sub === "object") {
|
|
70
|
+
if (sub.format) formats.add(sub.format);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return formats;
|
|
76
|
+
}
|
|
77
|
+
function getTypeFromSchema(def) {
|
|
78
|
+
if (!def) return void 0;
|
|
79
|
+
const t = getType(def.type);
|
|
80
|
+
if (!t) {
|
|
81
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
82
|
+
if (Array.isArray(def[keyword])) {
|
|
83
|
+
for (const sub of def[keyword]) {
|
|
84
|
+
const subType = getType(sub.type);
|
|
85
|
+
if (subType) return subType;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return t;
|
|
91
|
+
}
|
|
92
|
+
function getSpecialFormat(def) {
|
|
93
|
+
const formats = getFormatsFromSchema(def);
|
|
94
|
+
if (formats.has("date-time")) return "date-time";
|
|
95
|
+
if (formats.has("binary")) return "binary";
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
function coerceSpecialTypes(input, schema) {
|
|
99
|
+
const props = schema.properties || {};
|
|
100
|
+
for (const [key, def] of Object.entries(props)) {
|
|
101
|
+
if (!def) continue;
|
|
102
|
+
const value = input[key];
|
|
103
|
+
const type = getTypeFromSchema(def);
|
|
104
|
+
if (type === "object" && def.properties && typeof value === "object" && value !== null) {
|
|
105
|
+
input[key] = coerceSpecialTypes(
|
|
106
|
+
value,
|
|
107
|
+
def
|
|
108
|
+
);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (type === "array" && def.items && Array.isArray(value)) {
|
|
112
|
+
input[key] = value.map((item) => {
|
|
113
|
+
const itemDef = def.items;
|
|
114
|
+
const itemType = getTypeFromSchema(itemDef);
|
|
115
|
+
if (itemType === "object" && itemDef.properties && typeof item === "object" && item !== null) {
|
|
116
|
+
return coerceSpecialTypes(
|
|
117
|
+
item,
|
|
118
|
+
itemDef
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (itemType === "string") {
|
|
122
|
+
const format = getSpecialFormat(itemDef);
|
|
123
|
+
return handleSpecialString(item, format);
|
|
124
|
+
}
|
|
125
|
+
return item;
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (type === "string") {
|
|
130
|
+
const format = getSpecialFormat(def);
|
|
131
|
+
input[key] = handleSpecialString(value, format);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return input;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/mapContentType.ts
|
|
138
|
+
var import_common = require("@forklaunch/common");
|
|
139
|
+
function mapContentType(contentType) {
|
|
140
|
+
switch (contentType) {
|
|
141
|
+
case "json":
|
|
142
|
+
return "application/json";
|
|
143
|
+
case "file":
|
|
144
|
+
return "application/octet-stream";
|
|
145
|
+
case "text":
|
|
146
|
+
return "text/plain";
|
|
147
|
+
case "stream":
|
|
148
|
+
return "text/event-stream";
|
|
149
|
+
case void 0:
|
|
150
|
+
return "application/json";
|
|
151
|
+
default:
|
|
152
|
+
(0, import_common.isNever)(contentType);
|
|
153
|
+
return "application/json";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/core/refreshOpenApi.ts
|
|
158
|
+
async function refreshOpenApi(host, registryOptions, existingRegistryOpenApiHash) {
|
|
159
|
+
if (existingRegistryOpenApiHash === "static" || "static" in registryOptions && registryOptions.static) {
|
|
160
|
+
return {
|
|
161
|
+
updateRequired: false
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if ("raw" in registryOptions) {
|
|
165
|
+
return {
|
|
166
|
+
updateRequired: true,
|
|
167
|
+
registryOpenApiJson: registryOptions.raw,
|
|
168
|
+
registryOpenApiHash: "static"
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const registry = "path" in registryOptions ? `${host}/${registryOptions.path}` : "url" in registryOptions ? registryOptions.url : null;
|
|
172
|
+
if (registry == null) {
|
|
173
|
+
throw new Error("Raw OpenAPI JSON or registry information not provided");
|
|
174
|
+
}
|
|
175
|
+
const registryOpenApiHashFetch = await fetch(`${registry}-hash`);
|
|
176
|
+
const registryOpenApiHash = await registryOpenApiHashFetch.text();
|
|
177
|
+
if (existingRegistryOpenApiHash == null || existingRegistryOpenApiHash !== registryOpenApiHash) {
|
|
178
|
+
const registryOpenApiFetch = await fetch(registry);
|
|
179
|
+
const registryOpenApiJson = await registryOpenApiFetch.json();
|
|
180
|
+
return {
|
|
181
|
+
updateRequired: true,
|
|
182
|
+
registryOpenApiJson,
|
|
183
|
+
registryOpenApiHash
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
updateRequired: false
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/core/regex.ts
|
|
28
192
|
function generateStringFromRegex(regex) {
|
|
29
193
|
let regexStr = typeof regex === "object" ? regex.source : regex;
|
|
30
194
|
if (regexStr.startsWith("/")) regexStr = regexStr.slice(1);
|
|
@@ -115,7 +279,7 @@ function generateStringFromRegex(regex) {
|
|
|
115
279
|
return result;
|
|
116
280
|
}
|
|
117
281
|
|
|
118
|
-
// src/
|
|
282
|
+
// src/core/resolvePath.ts
|
|
119
283
|
function getSdkPath(path) {
|
|
120
284
|
let sdkPath = path;
|
|
121
285
|
if (Array.isArray(path)) {
|
|
@@ -131,14 +295,42 @@ function getSdkPath(path) {
|
|
|
131
295
|
}
|
|
132
296
|
|
|
133
297
|
// src/universalSdk.ts
|
|
134
|
-
var UniversalSdk = class {
|
|
298
|
+
var UniversalSdk = class _UniversalSdk {
|
|
299
|
+
constructor(host, ajv, registryOptions, contentTypeParserMap, registryOpenApiJson, registryOpenApiHash) {
|
|
300
|
+
this.host = host;
|
|
301
|
+
this.ajv = ajv;
|
|
302
|
+
this.registryOptions = registryOptions;
|
|
303
|
+
this.contentTypeParserMap = contentTypeParserMap;
|
|
304
|
+
this.registryOpenApiJson = registryOpenApiJson;
|
|
305
|
+
this.registryOpenApiHash = registryOpenApiHash;
|
|
306
|
+
}
|
|
135
307
|
/**
|
|
136
308
|
* Creates an instance of UniversalSdk.
|
|
137
309
|
*
|
|
138
310
|
* @param {string} host - The host URL for the SDK.
|
|
139
311
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
312
|
+
static async create(host, registryOptions, contentTypeParserMap) {
|
|
313
|
+
const refreshResult = await refreshOpenApi(host, registryOptions);
|
|
314
|
+
let registryOpenApiJson;
|
|
315
|
+
let registryOpenApiHash;
|
|
316
|
+
if (refreshResult.updateRequired) {
|
|
317
|
+
registryOpenApiJson = refreshResult.registryOpenApiJson;
|
|
318
|
+
registryOpenApiHash = refreshResult.registryOpenApiHash;
|
|
319
|
+
}
|
|
320
|
+
const ajv = new import_ajv.default({
|
|
321
|
+
coerceTypes: true,
|
|
322
|
+
allErrors: true,
|
|
323
|
+
strict: false
|
|
324
|
+
});
|
|
325
|
+
(0, import_ajv_formats.default)(ajv);
|
|
326
|
+
return new _UniversalSdk(
|
|
327
|
+
host,
|
|
328
|
+
ajv,
|
|
329
|
+
registryOptions,
|
|
330
|
+
contentTypeParserMap,
|
|
331
|
+
registryOpenApiJson,
|
|
332
|
+
registryOpenApiHash
|
|
333
|
+
);
|
|
142
334
|
}
|
|
143
335
|
/**
|
|
144
336
|
* Executes an HTTP request.
|
|
@@ -149,6 +341,18 @@ var UniversalSdk = class {
|
|
|
149
341
|
* @returns {Promise<ResponseType>} - The response object.
|
|
150
342
|
*/
|
|
151
343
|
async execute(route, method, request) {
|
|
344
|
+
if (!this.host) {
|
|
345
|
+
throw new Error("Host not initialized, please run .create(..) first");
|
|
346
|
+
}
|
|
347
|
+
const refreshResult = await refreshOpenApi(
|
|
348
|
+
this.host,
|
|
349
|
+
this.registryOptions,
|
|
350
|
+
this.registryOpenApiHash
|
|
351
|
+
);
|
|
352
|
+
if (refreshResult.updateRequired) {
|
|
353
|
+
this.registryOpenApiJson = refreshResult.registryOpenApiJson;
|
|
354
|
+
this.registryOpenApiHash = refreshResult.registryOpenApiHash;
|
|
355
|
+
}
|
|
152
356
|
const { params, body, query, headers } = request || {};
|
|
153
357
|
let url = getSdkPath(this.host + route);
|
|
154
358
|
if (params) {
|
|
@@ -156,22 +360,193 @@ var UniversalSdk = class {
|
|
|
156
360
|
url = url.replace(`:${key}`, encodeURIComponent(params[key]));
|
|
157
361
|
}
|
|
158
362
|
}
|
|
363
|
+
let defaultContentType = "application/json";
|
|
364
|
+
let parsedBody;
|
|
365
|
+
if (body != null) {
|
|
366
|
+
if ("schema" in body && body.schema != null) {
|
|
367
|
+
defaultContentType = "application/json";
|
|
368
|
+
parsedBody = (0, import_common2.safeStringify)(body.schema);
|
|
369
|
+
} else if ("json" in body && body.json != null) {
|
|
370
|
+
defaultContentType = "application/json";
|
|
371
|
+
parsedBody = (0, import_common2.safeStringify)(body.json);
|
|
372
|
+
} else if ("text" in body && body.text != null) {
|
|
373
|
+
defaultContentType = "text/plain";
|
|
374
|
+
parsedBody = body.text;
|
|
375
|
+
} else if ("file" in body && body.file != null) {
|
|
376
|
+
defaultContentType = "application/octet-stream";
|
|
377
|
+
parsedBody = await body.file.text();
|
|
378
|
+
} else if ("multipartForm" in body && body.multipartForm != null) {
|
|
379
|
+
defaultContentType = "multipart/form-data";
|
|
380
|
+
const formData = new FormData();
|
|
381
|
+
for (const key in body.multipartForm) {
|
|
382
|
+
if (Object.prototype.hasOwnProperty.call(body.multipartForm, key)) {
|
|
383
|
+
const value = body.multipartForm[key];
|
|
384
|
+
if (value instanceof Blob || value instanceof File) {
|
|
385
|
+
formData.append(key, value);
|
|
386
|
+
} else if (Array.isArray(value)) {
|
|
387
|
+
for (const item of value) {
|
|
388
|
+
formData.append(
|
|
389
|
+
key,
|
|
390
|
+
item instanceof Blob || item instanceof File ? item : (0, import_common2.safeStringify)(item)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
formData.append(key, (0, import_common2.safeStringify)(value));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
parsedBody = formData;
|
|
399
|
+
} else if ("urlEncodedForm" in body && body.urlEncodedForm != null) {
|
|
400
|
+
defaultContentType = "application/x-www-form-urlencoded";
|
|
401
|
+
parsedBody = new URLSearchParams(
|
|
402
|
+
Object.entries(body.urlEncodedForm).map(([key, value]) => [
|
|
403
|
+
key,
|
|
404
|
+
(0, import_common2.safeStringify)(value)
|
|
405
|
+
])
|
|
406
|
+
);
|
|
407
|
+
} else {
|
|
408
|
+
parsedBody = (0, import_common2.safeStringify)(body);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
159
411
|
if (query) {
|
|
160
412
|
const queryString = new URLSearchParams(
|
|
161
|
-
query
|
|
413
|
+
Object.entries(query).map(([key, value]) => [key, (0, import_common2.safeStringify)(value)])
|
|
162
414
|
).toString();
|
|
163
415
|
url += queryString ? `?${queryString}` : "";
|
|
164
416
|
}
|
|
165
417
|
const response = await fetch(encodeURI(url), {
|
|
166
|
-
method,
|
|
167
|
-
headers:
|
|
168
|
-
|
|
418
|
+
method: method.toUpperCase(),
|
|
419
|
+
headers: {
|
|
420
|
+
...headers,
|
|
421
|
+
...defaultContentType != "multipart/form-data" ? { "Content-Type": body?.contentType ?? defaultContentType } : {}
|
|
422
|
+
},
|
|
423
|
+
body: parsedBody
|
|
169
424
|
});
|
|
170
|
-
const
|
|
171
|
-
|
|
425
|
+
const responseOpenApi = this.registryOpenApiJson?.paths?.[route]?.[method.toLowerCase()]?.responses?.[response.status];
|
|
426
|
+
if (responseOpenApi == null) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Response ${response.status} not found in OpenAPI spec for route ${route}`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
const contentType = (response.headers.get("content-type") || response.headers.get("Content-Type"))?.split(";")[0];
|
|
432
|
+
const mappedContentType = (contentType != null ? this.contentTypeParserMap != null && contentType in this.contentTypeParserMap ? mapContentType(this.contentTypeParserMap[contentType]) : contentType : "application/json").split(";")[0];
|
|
433
|
+
let responseBody;
|
|
434
|
+
switch (mappedContentType) {
|
|
435
|
+
case "application/octet-stream": {
|
|
436
|
+
const contentDisposition = response.headers.get("content-disposition");
|
|
437
|
+
let fileName = null;
|
|
438
|
+
if (contentDisposition) {
|
|
439
|
+
const match = /filename\*?=(?:UTF-8''|")?([^;\r\n"]+)/i.exec(
|
|
440
|
+
contentDisposition
|
|
441
|
+
);
|
|
442
|
+
if (match) {
|
|
443
|
+
fileName = decodeURIComponent(match[1].replace(/['"]/g, ""));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const blob = await response.blob();
|
|
447
|
+
if (fileName == null) {
|
|
448
|
+
responseBody = blob;
|
|
449
|
+
} else {
|
|
450
|
+
responseBody = new File([blob], fileName);
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
case "text/event-stream": {
|
|
455
|
+
const ajv = this.ajv;
|
|
456
|
+
async function* streamEvents(reader) {
|
|
457
|
+
const decoder = new TextDecoder();
|
|
458
|
+
let buffer = "";
|
|
459
|
+
let lastEventId;
|
|
460
|
+
while (true) {
|
|
461
|
+
const { done, value } = await reader.read();
|
|
462
|
+
if (done) break;
|
|
463
|
+
buffer += decoder.decode(value, { stream: true });
|
|
464
|
+
let newlineIndex;
|
|
465
|
+
while ((newlineIndex = buffer.indexOf("\n")) >= 0) {
|
|
466
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
467
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
468
|
+
if (line.startsWith("id:")) {
|
|
469
|
+
lastEventId = line.slice(3).trim();
|
|
470
|
+
} else if (line.startsWith("data:")) {
|
|
471
|
+
const data = line.slice(5).trim();
|
|
472
|
+
const json = {
|
|
473
|
+
data: (0, import_common2.safeParse)(data),
|
|
474
|
+
id: lastEventId
|
|
475
|
+
};
|
|
476
|
+
const isValidJson = ajv.validate(
|
|
477
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
478
|
+
json
|
|
479
|
+
);
|
|
480
|
+
if (!isValidJson) {
|
|
481
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
482
|
+
}
|
|
483
|
+
yield coerceSpecialTypes(
|
|
484
|
+
json,
|
|
485
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (buffer.length > 0) {
|
|
491
|
+
let id;
|
|
492
|
+
let data;
|
|
493
|
+
const lines = buffer.trim().split("\n");
|
|
494
|
+
for (const l of lines) {
|
|
495
|
+
const line = l.trim();
|
|
496
|
+
if (line.startsWith("id:")) {
|
|
497
|
+
id = line.slice(3).trim();
|
|
498
|
+
} else if (line.startsWith("data:")) {
|
|
499
|
+
data = line.slice(5).trim();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (data !== void 0) {
|
|
503
|
+
const json = {
|
|
504
|
+
data: (0, import_common2.safeParse)(data),
|
|
505
|
+
id: id ?? lastEventId
|
|
506
|
+
};
|
|
507
|
+
const isValidJson = ajv.validate(
|
|
508
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
509
|
+
json
|
|
510
|
+
);
|
|
511
|
+
if (!isValidJson) {
|
|
512
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
513
|
+
}
|
|
514
|
+
yield coerceSpecialTypes(
|
|
515
|
+
json,
|
|
516
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (!response.body) {
|
|
522
|
+
throw new Error("No response body for event stream");
|
|
523
|
+
}
|
|
524
|
+
responseBody = streamEvents(response.body.getReader());
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case "text/plain":
|
|
528
|
+
responseBody = await response.text();
|
|
529
|
+
break;
|
|
530
|
+
case "application/json":
|
|
531
|
+
default: {
|
|
532
|
+
const json = await response.json();
|
|
533
|
+
const isValidJson = this.ajv.validate(
|
|
534
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
535
|
+
json
|
|
536
|
+
);
|
|
537
|
+
if (!isValidJson) {
|
|
538
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
539
|
+
}
|
|
540
|
+
responseBody = coerceSpecialTypes(
|
|
541
|
+
json,
|
|
542
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
543
|
+
);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
172
547
|
return {
|
|
173
548
|
code: response.status,
|
|
174
|
-
|
|
549
|
+
response: responseBody,
|
|
175
550
|
headers: response.headers
|
|
176
551
|
};
|
|
177
552
|
}
|
|
@@ -205,7 +580,7 @@ var UniversalSdk = class {
|
|
|
205
580
|
* @returns {Promise<ResponseType>} - The response object.
|
|
206
581
|
*/
|
|
207
582
|
async get(route, request) {
|
|
208
|
-
return this.pathParamRequest(route, "
|
|
583
|
+
return this.pathParamRequest(route, "get", request);
|
|
209
584
|
}
|
|
210
585
|
/**
|
|
211
586
|
* Executes a POST request.
|
|
@@ -215,7 +590,7 @@ var UniversalSdk = class {
|
|
|
215
590
|
* @returns {Promise<ResponseType>} - The response object.
|
|
216
591
|
*/
|
|
217
592
|
async post(route, request) {
|
|
218
|
-
return this.bodyRequest(route, "
|
|
593
|
+
return this.bodyRequest(route, "post", request);
|
|
219
594
|
}
|
|
220
595
|
/**
|
|
221
596
|
* Executes a PUT request.
|
|
@@ -225,7 +600,7 @@ var UniversalSdk = class {
|
|
|
225
600
|
* @returns {Promise<ResponseType>} - The response object.
|
|
226
601
|
*/
|
|
227
602
|
async put(route, request) {
|
|
228
|
-
return this.bodyRequest(route, "
|
|
603
|
+
return this.bodyRequest(route, "put", request);
|
|
229
604
|
}
|
|
230
605
|
/**
|
|
231
606
|
* Executes a PATCH request.
|
|
@@ -235,7 +610,7 @@ var UniversalSdk = class {
|
|
|
235
610
|
* @returns {Promise<ResponseType>} - The response object.
|
|
236
611
|
*/
|
|
237
612
|
async patch(route, request) {
|
|
238
|
-
return this.bodyRequest(route, "
|
|
613
|
+
return this.bodyRequest(route, "patch", request);
|
|
239
614
|
}
|
|
240
615
|
/**
|
|
241
616
|
* Executes a DELETE request.
|
|
@@ -245,19 +620,54 @@ var UniversalSdk = class {
|
|
|
245
620
|
* @returns {Promise<ResponseType>} - The response object.
|
|
246
621
|
*/
|
|
247
622
|
async delete(route, request) {
|
|
248
|
-
return this.pathParamRequest(route, "
|
|
623
|
+
return this.pathParamRequest(route, "delete", request);
|
|
249
624
|
}
|
|
250
625
|
};
|
|
251
626
|
|
|
252
627
|
// index.ts
|
|
253
|
-
var universalSdk = (
|
|
254
|
-
const sdkInternal =
|
|
628
|
+
var universalSdk = async (options) => {
|
|
629
|
+
const sdkInternal = await UniversalSdk.create(
|
|
630
|
+
options.host,
|
|
631
|
+
options.registryOptions,
|
|
632
|
+
"contentTypeParserMap" in options ? options.contentTypeParserMap : void 0
|
|
633
|
+
);
|
|
255
634
|
const proxyInternal = new Proxy(sdkInternal, {
|
|
256
635
|
get(target, prop) {
|
|
636
|
+
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
257
639
|
if (typeof prop === "string" && prop in target) {
|
|
258
|
-
|
|
640
|
+
const value = target[prop];
|
|
641
|
+
if (typeof value === "function") {
|
|
642
|
+
return value.bind(target);
|
|
643
|
+
}
|
|
644
|
+
return value;
|
|
259
645
|
}
|
|
260
|
-
|
|
646
|
+
return new Proxy(() => {
|
|
647
|
+
}, {
|
|
648
|
+
get(_innerTarget, innerProp) {
|
|
649
|
+
if (typeof innerProp === "string" && innerProp in target) {
|
|
650
|
+
const value = target[innerProp];
|
|
651
|
+
if (typeof value === "function") {
|
|
652
|
+
return value.bind(target);
|
|
653
|
+
}
|
|
654
|
+
return value;
|
|
655
|
+
}
|
|
656
|
+
return new Proxy(() => {
|
|
657
|
+
}, {
|
|
658
|
+
get(__innerTarget, deepProp) {
|
|
659
|
+
if (typeof deepProp === "string" && deepProp in target) {
|
|
660
|
+
const value = target[deepProp];
|
|
661
|
+
if (typeof value === "function") {
|
|
662
|
+
return value.bind(target);
|
|
663
|
+
}
|
|
664
|
+
return value;
|
|
665
|
+
}
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
});
|
|
261
671
|
}
|
|
262
672
|
});
|
|
263
673
|
return proxyInternal;
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,158 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/universalSdk.ts
|
|
2
|
+
import { safeParse, safeStringify } from "@forklaunch/common";
|
|
3
|
+
import Ajv from "ajv";
|
|
4
|
+
import addFormats from "ajv-formats";
|
|
5
|
+
|
|
6
|
+
// src/core/coerceSpecialTypes.ts
|
|
7
|
+
function handleSpecialString(v, format) {
|
|
8
|
+
if (typeof v !== "string") return v;
|
|
9
|
+
if (format === "date-time") {
|
|
10
|
+
const d = new Date(v);
|
|
11
|
+
return isNaN(d.getTime()) ? v : d;
|
|
12
|
+
}
|
|
13
|
+
if (format === "binary") {
|
|
14
|
+
try {
|
|
15
|
+
return Buffer.from(v, "base64");
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error("Invalid base64 string");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
function getType(type) {
|
|
23
|
+
if (Array.isArray(type)) return type[0];
|
|
24
|
+
return type;
|
|
25
|
+
}
|
|
26
|
+
function getFormatsFromSchema(def) {
|
|
27
|
+
const formats = /* @__PURE__ */ new Set();
|
|
28
|
+
if (!def) return formats;
|
|
29
|
+
if (def.format) formats.add(def.format);
|
|
30
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
31
|
+
if (Array.isArray(def[keyword])) {
|
|
32
|
+
for (const sub of def[keyword]) {
|
|
33
|
+
if (sub && typeof sub === "object") {
|
|
34
|
+
if (sub.format) formats.add(sub.format);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return formats;
|
|
40
|
+
}
|
|
41
|
+
function getTypeFromSchema(def) {
|
|
42
|
+
if (!def) return void 0;
|
|
43
|
+
const t = getType(def.type);
|
|
44
|
+
if (!t) {
|
|
45
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
46
|
+
if (Array.isArray(def[keyword])) {
|
|
47
|
+
for (const sub of def[keyword]) {
|
|
48
|
+
const subType = getType(sub.type);
|
|
49
|
+
if (subType) return subType;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return t;
|
|
55
|
+
}
|
|
56
|
+
function getSpecialFormat(def) {
|
|
57
|
+
const formats = getFormatsFromSchema(def);
|
|
58
|
+
if (formats.has("date-time")) return "date-time";
|
|
59
|
+
if (formats.has("binary")) return "binary";
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
function coerceSpecialTypes(input, schema) {
|
|
63
|
+
const props = schema.properties || {};
|
|
64
|
+
for (const [key, def] of Object.entries(props)) {
|
|
65
|
+
if (!def) continue;
|
|
66
|
+
const value = input[key];
|
|
67
|
+
const type = getTypeFromSchema(def);
|
|
68
|
+
if (type === "object" && def.properties && typeof value === "object" && value !== null) {
|
|
69
|
+
input[key] = coerceSpecialTypes(
|
|
70
|
+
value,
|
|
71
|
+
def
|
|
72
|
+
);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (type === "array" && def.items && Array.isArray(value)) {
|
|
76
|
+
input[key] = value.map((item) => {
|
|
77
|
+
const itemDef = def.items;
|
|
78
|
+
const itemType = getTypeFromSchema(itemDef);
|
|
79
|
+
if (itemType === "object" && itemDef.properties && typeof item === "object" && item !== null) {
|
|
80
|
+
return coerceSpecialTypes(
|
|
81
|
+
item,
|
|
82
|
+
itemDef
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (itemType === "string") {
|
|
86
|
+
const format = getSpecialFormat(itemDef);
|
|
87
|
+
return handleSpecialString(item, format);
|
|
88
|
+
}
|
|
89
|
+
return item;
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (type === "string") {
|
|
94
|
+
const format = getSpecialFormat(def);
|
|
95
|
+
input[key] = handleSpecialString(value, format);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return input;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/core/mapContentType.ts
|
|
102
|
+
import { isNever } from "@forklaunch/common";
|
|
103
|
+
function mapContentType(contentType) {
|
|
104
|
+
switch (contentType) {
|
|
105
|
+
case "json":
|
|
106
|
+
return "application/json";
|
|
107
|
+
case "file":
|
|
108
|
+
return "application/octet-stream";
|
|
109
|
+
case "text":
|
|
110
|
+
return "text/plain";
|
|
111
|
+
case "stream":
|
|
112
|
+
return "text/event-stream";
|
|
113
|
+
case void 0:
|
|
114
|
+
return "application/json";
|
|
115
|
+
default:
|
|
116
|
+
isNever(contentType);
|
|
117
|
+
return "application/json";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/core/refreshOpenApi.ts
|
|
122
|
+
async function refreshOpenApi(host, registryOptions, existingRegistryOpenApiHash) {
|
|
123
|
+
if (existingRegistryOpenApiHash === "static" || "static" in registryOptions && registryOptions.static) {
|
|
124
|
+
return {
|
|
125
|
+
updateRequired: false
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if ("raw" in registryOptions) {
|
|
129
|
+
return {
|
|
130
|
+
updateRequired: true,
|
|
131
|
+
registryOpenApiJson: registryOptions.raw,
|
|
132
|
+
registryOpenApiHash: "static"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const registry = "path" in registryOptions ? `${host}/${registryOptions.path}` : "url" in registryOptions ? registryOptions.url : null;
|
|
136
|
+
if (registry == null) {
|
|
137
|
+
throw new Error("Raw OpenAPI JSON or registry information not provided");
|
|
138
|
+
}
|
|
139
|
+
const registryOpenApiHashFetch = await fetch(`${registry}-hash`);
|
|
140
|
+
const registryOpenApiHash = await registryOpenApiHashFetch.text();
|
|
141
|
+
if (existingRegistryOpenApiHash == null || existingRegistryOpenApiHash !== registryOpenApiHash) {
|
|
142
|
+
const registryOpenApiFetch = await fetch(registry);
|
|
143
|
+
const registryOpenApiJson = await registryOpenApiFetch.json();
|
|
144
|
+
return {
|
|
145
|
+
updateRequired: true,
|
|
146
|
+
registryOpenApiJson,
|
|
147
|
+
registryOpenApiHash
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
updateRequired: false
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/core/regex.ts
|
|
2
156
|
function generateStringFromRegex(regex) {
|
|
3
157
|
let regexStr = typeof regex === "object" ? regex.source : regex;
|
|
4
158
|
if (regexStr.startsWith("/")) regexStr = regexStr.slice(1);
|
|
@@ -89,7 +243,7 @@ function generateStringFromRegex(regex) {
|
|
|
89
243
|
return result;
|
|
90
244
|
}
|
|
91
245
|
|
|
92
|
-
// src/
|
|
246
|
+
// src/core/resolvePath.ts
|
|
93
247
|
function getSdkPath(path) {
|
|
94
248
|
let sdkPath = path;
|
|
95
249
|
if (Array.isArray(path)) {
|
|
@@ -105,14 +259,42 @@ function getSdkPath(path) {
|
|
|
105
259
|
}
|
|
106
260
|
|
|
107
261
|
// src/universalSdk.ts
|
|
108
|
-
var UniversalSdk = class {
|
|
262
|
+
var UniversalSdk = class _UniversalSdk {
|
|
263
|
+
constructor(host, ajv, registryOptions, contentTypeParserMap, registryOpenApiJson, registryOpenApiHash) {
|
|
264
|
+
this.host = host;
|
|
265
|
+
this.ajv = ajv;
|
|
266
|
+
this.registryOptions = registryOptions;
|
|
267
|
+
this.contentTypeParserMap = contentTypeParserMap;
|
|
268
|
+
this.registryOpenApiJson = registryOpenApiJson;
|
|
269
|
+
this.registryOpenApiHash = registryOpenApiHash;
|
|
270
|
+
}
|
|
109
271
|
/**
|
|
110
272
|
* Creates an instance of UniversalSdk.
|
|
111
273
|
*
|
|
112
274
|
* @param {string} host - The host URL for the SDK.
|
|
113
275
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
276
|
+
static async create(host, registryOptions, contentTypeParserMap) {
|
|
277
|
+
const refreshResult = await refreshOpenApi(host, registryOptions);
|
|
278
|
+
let registryOpenApiJson;
|
|
279
|
+
let registryOpenApiHash;
|
|
280
|
+
if (refreshResult.updateRequired) {
|
|
281
|
+
registryOpenApiJson = refreshResult.registryOpenApiJson;
|
|
282
|
+
registryOpenApiHash = refreshResult.registryOpenApiHash;
|
|
283
|
+
}
|
|
284
|
+
const ajv = new Ajv({
|
|
285
|
+
coerceTypes: true,
|
|
286
|
+
allErrors: true,
|
|
287
|
+
strict: false
|
|
288
|
+
});
|
|
289
|
+
addFormats(ajv);
|
|
290
|
+
return new _UniversalSdk(
|
|
291
|
+
host,
|
|
292
|
+
ajv,
|
|
293
|
+
registryOptions,
|
|
294
|
+
contentTypeParserMap,
|
|
295
|
+
registryOpenApiJson,
|
|
296
|
+
registryOpenApiHash
|
|
297
|
+
);
|
|
116
298
|
}
|
|
117
299
|
/**
|
|
118
300
|
* Executes an HTTP request.
|
|
@@ -123,6 +305,18 @@ var UniversalSdk = class {
|
|
|
123
305
|
* @returns {Promise<ResponseType>} - The response object.
|
|
124
306
|
*/
|
|
125
307
|
async execute(route, method, request) {
|
|
308
|
+
if (!this.host) {
|
|
309
|
+
throw new Error("Host not initialized, please run .create(..) first");
|
|
310
|
+
}
|
|
311
|
+
const refreshResult = await refreshOpenApi(
|
|
312
|
+
this.host,
|
|
313
|
+
this.registryOptions,
|
|
314
|
+
this.registryOpenApiHash
|
|
315
|
+
);
|
|
316
|
+
if (refreshResult.updateRequired) {
|
|
317
|
+
this.registryOpenApiJson = refreshResult.registryOpenApiJson;
|
|
318
|
+
this.registryOpenApiHash = refreshResult.registryOpenApiHash;
|
|
319
|
+
}
|
|
126
320
|
const { params, body, query, headers } = request || {};
|
|
127
321
|
let url = getSdkPath(this.host + route);
|
|
128
322
|
if (params) {
|
|
@@ -130,22 +324,193 @@ var UniversalSdk = class {
|
|
|
130
324
|
url = url.replace(`:${key}`, encodeURIComponent(params[key]));
|
|
131
325
|
}
|
|
132
326
|
}
|
|
327
|
+
let defaultContentType = "application/json";
|
|
328
|
+
let parsedBody;
|
|
329
|
+
if (body != null) {
|
|
330
|
+
if ("schema" in body && body.schema != null) {
|
|
331
|
+
defaultContentType = "application/json";
|
|
332
|
+
parsedBody = safeStringify(body.schema);
|
|
333
|
+
} else if ("json" in body && body.json != null) {
|
|
334
|
+
defaultContentType = "application/json";
|
|
335
|
+
parsedBody = safeStringify(body.json);
|
|
336
|
+
} else if ("text" in body && body.text != null) {
|
|
337
|
+
defaultContentType = "text/plain";
|
|
338
|
+
parsedBody = body.text;
|
|
339
|
+
} else if ("file" in body && body.file != null) {
|
|
340
|
+
defaultContentType = "application/octet-stream";
|
|
341
|
+
parsedBody = await body.file.text();
|
|
342
|
+
} else if ("multipartForm" in body && body.multipartForm != null) {
|
|
343
|
+
defaultContentType = "multipart/form-data";
|
|
344
|
+
const formData = new FormData();
|
|
345
|
+
for (const key in body.multipartForm) {
|
|
346
|
+
if (Object.prototype.hasOwnProperty.call(body.multipartForm, key)) {
|
|
347
|
+
const value = body.multipartForm[key];
|
|
348
|
+
if (value instanceof Blob || value instanceof File) {
|
|
349
|
+
formData.append(key, value);
|
|
350
|
+
} else if (Array.isArray(value)) {
|
|
351
|
+
for (const item of value) {
|
|
352
|
+
formData.append(
|
|
353
|
+
key,
|
|
354
|
+
item instanceof Blob || item instanceof File ? item : safeStringify(item)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
formData.append(key, safeStringify(value));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
parsedBody = formData;
|
|
363
|
+
} else if ("urlEncodedForm" in body && body.urlEncodedForm != null) {
|
|
364
|
+
defaultContentType = "application/x-www-form-urlencoded";
|
|
365
|
+
parsedBody = new URLSearchParams(
|
|
366
|
+
Object.entries(body.urlEncodedForm).map(([key, value]) => [
|
|
367
|
+
key,
|
|
368
|
+
safeStringify(value)
|
|
369
|
+
])
|
|
370
|
+
);
|
|
371
|
+
} else {
|
|
372
|
+
parsedBody = safeStringify(body);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
133
375
|
if (query) {
|
|
134
376
|
const queryString = new URLSearchParams(
|
|
135
|
-
query
|
|
377
|
+
Object.entries(query).map(([key, value]) => [key, safeStringify(value)])
|
|
136
378
|
).toString();
|
|
137
379
|
url += queryString ? `?${queryString}` : "";
|
|
138
380
|
}
|
|
139
381
|
const response = await fetch(encodeURI(url), {
|
|
140
|
-
method,
|
|
141
|
-
headers:
|
|
142
|
-
|
|
382
|
+
method: method.toUpperCase(),
|
|
383
|
+
headers: {
|
|
384
|
+
...headers,
|
|
385
|
+
...defaultContentType != "multipart/form-data" ? { "Content-Type": body?.contentType ?? defaultContentType } : {}
|
|
386
|
+
},
|
|
387
|
+
body: parsedBody
|
|
143
388
|
});
|
|
144
|
-
const
|
|
145
|
-
|
|
389
|
+
const responseOpenApi = this.registryOpenApiJson?.paths?.[route]?.[method.toLowerCase()]?.responses?.[response.status];
|
|
390
|
+
if (responseOpenApi == null) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Response ${response.status} not found in OpenAPI spec for route ${route}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const contentType = (response.headers.get("content-type") || response.headers.get("Content-Type"))?.split(";")[0];
|
|
396
|
+
const mappedContentType = (contentType != null ? this.contentTypeParserMap != null && contentType in this.contentTypeParserMap ? mapContentType(this.contentTypeParserMap[contentType]) : contentType : "application/json").split(";")[0];
|
|
397
|
+
let responseBody;
|
|
398
|
+
switch (mappedContentType) {
|
|
399
|
+
case "application/octet-stream": {
|
|
400
|
+
const contentDisposition = response.headers.get("content-disposition");
|
|
401
|
+
let fileName = null;
|
|
402
|
+
if (contentDisposition) {
|
|
403
|
+
const match = /filename\*?=(?:UTF-8''|")?([^;\r\n"]+)/i.exec(
|
|
404
|
+
contentDisposition
|
|
405
|
+
);
|
|
406
|
+
if (match) {
|
|
407
|
+
fileName = decodeURIComponent(match[1].replace(/['"]/g, ""));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const blob = await response.blob();
|
|
411
|
+
if (fileName == null) {
|
|
412
|
+
responseBody = blob;
|
|
413
|
+
} else {
|
|
414
|
+
responseBody = new File([blob], fileName);
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case "text/event-stream": {
|
|
419
|
+
const ajv = this.ajv;
|
|
420
|
+
async function* streamEvents(reader) {
|
|
421
|
+
const decoder = new TextDecoder();
|
|
422
|
+
let buffer = "";
|
|
423
|
+
let lastEventId;
|
|
424
|
+
while (true) {
|
|
425
|
+
const { done, value } = await reader.read();
|
|
426
|
+
if (done) break;
|
|
427
|
+
buffer += decoder.decode(value, { stream: true });
|
|
428
|
+
let newlineIndex;
|
|
429
|
+
while ((newlineIndex = buffer.indexOf("\n")) >= 0) {
|
|
430
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
431
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
432
|
+
if (line.startsWith("id:")) {
|
|
433
|
+
lastEventId = line.slice(3).trim();
|
|
434
|
+
} else if (line.startsWith("data:")) {
|
|
435
|
+
const data = line.slice(5).trim();
|
|
436
|
+
const json = {
|
|
437
|
+
data: safeParse(data),
|
|
438
|
+
id: lastEventId
|
|
439
|
+
};
|
|
440
|
+
const isValidJson = ajv.validate(
|
|
441
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
442
|
+
json
|
|
443
|
+
);
|
|
444
|
+
if (!isValidJson) {
|
|
445
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
446
|
+
}
|
|
447
|
+
yield coerceSpecialTypes(
|
|
448
|
+
json,
|
|
449
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (buffer.length > 0) {
|
|
455
|
+
let id;
|
|
456
|
+
let data;
|
|
457
|
+
const lines = buffer.trim().split("\n");
|
|
458
|
+
for (const l of lines) {
|
|
459
|
+
const line = l.trim();
|
|
460
|
+
if (line.startsWith("id:")) {
|
|
461
|
+
id = line.slice(3).trim();
|
|
462
|
+
} else if (line.startsWith("data:")) {
|
|
463
|
+
data = line.slice(5).trim();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (data !== void 0) {
|
|
467
|
+
const json = {
|
|
468
|
+
data: safeParse(data),
|
|
469
|
+
id: id ?? lastEventId
|
|
470
|
+
};
|
|
471
|
+
const isValidJson = ajv.validate(
|
|
472
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
473
|
+
json
|
|
474
|
+
);
|
|
475
|
+
if (!isValidJson) {
|
|
476
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
477
|
+
}
|
|
478
|
+
yield coerceSpecialTypes(
|
|
479
|
+
json,
|
|
480
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!response.body) {
|
|
486
|
+
throw new Error("No response body for event stream");
|
|
487
|
+
}
|
|
488
|
+
responseBody = streamEvents(response.body.getReader());
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "text/plain":
|
|
492
|
+
responseBody = await response.text();
|
|
493
|
+
break;
|
|
494
|
+
case "application/json":
|
|
495
|
+
default: {
|
|
496
|
+
const json = await response.json();
|
|
497
|
+
const isValidJson = this.ajv.validate(
|
|
498
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema,
|
|
499
|
+
json
|
|
500
|
+
);
|
|
501
|
+
if (!isValidJson) {
|
|
502
|
+
throw new Error("Response does not match OpenAPI spec");
|
|
503
|
+
}
|
|
504
|
+
responseBody = coerceSpecialTypes(
|
|
505
|
+
json,
|
|
506
|
+
responseOpenApi.content?.[contentType || mappedContentType].schema
|
|
507
|
+
);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
146
511
|
return {
|
|
147
512
|
code: response.status,
|
|
148
|
-
|
|
513
|
+
response: responseBody,
|
|
149
514
|
headers: response.headers
|
|
150
515
|
};
|
|
151
516
|
}
|
|
@@ -179,7 +544,7 @@ var UniversalSdk = class {
|
|
|
179
544
|
* @returns {Promise<ResponseType>} - The response object.
|
|
180
545
|
*/
|
|
181
546
|
async get(route, request) {
|
|
182
|
-
return this.pathParamRequest(route, "
|
|
547
|
+
return this.pathParamRequest(route, "get", request);
|
|
183
548
|
}
|
|
184
549
|
/**
|
|
185
550
|
* Executes a POST request.
|
|
@@ -189,7 +554,7 @@ var UniversalSdk = class {
|
|
|
189
554
|
* @returns {Promise<ResponseType>} - The response object.
|
|
190
555
|
*/
|
|
191
556
|
async post(route, request) {
|
|
192
|
-
return this.bodyRequest(route, "
|
|
557
|
+
return this.bodyRequest(route, "post", request);
|
|
193
558
|
}
|
|
194
559
|
/**
|
|
195
560
|
* Executes a PUT request.
|
|
@@ -199,7 +564,7 @@ var UniversalSdk = class {
|
|
|
199
564
|
* @returns {Promise<ResponseType>} - The response object.
|
|
200
565
|
*/
|
|
201
566
|
async put(route, request) {
|
|
202
|
-
return this.bodyRequest(route, "
|
|
567
|
+
return this.bodyRequest(route, "put", request);
|
|
203
568
|
}
|
|
204
569
|
/**
|
|
205
570
|
* Executes a PATCH request.
|
|
@@ -209,7 +574,7 @@ var UniversalSdk = class {
|
|
|
209
574
|
* @returns {Promise<ResponseType>} - The response object.
|
|
210
575
|
*/
|
|
211
576
|
async patch(route, request) {
|
|
212
|
-
return this.bodyRequest(route, "
|
|
577
|
+
return this.bodyRequest(route, "patch", request);
|
|
213
578
|
}
|
|
214
579
|
/**
|
|
215
580
|
* Executes a DELETE request.
|
|
@@ -219,19 +584,54 @@ var UniversalSdk = class {
|
|
|
219
584
|
* @returns {Promise<ResponseType>} - The response object.
|
|
220
585
|
*/
|
|
221
586
|
async delete(route, request) {
|
|
222
|
-
return this.pathParamRequest(route, "
|
|
587
|
+
return this.pathParamRequest(route, "delete", request);
|
|
223
588
|
}
|
|
224
589
|
};
|
|
225
590
|
|
|
226
591
|
// index.ts
|
|
227
|
-
var universalSdk = (
|
|
228
|
-
const sdkInternal =
|
|
592
|
+
var universalSdk = async (options) => {
|
|
593
|
+
const sdkInternal = await UniversalSdk.create(
|
|
594
|
+
options.host,
|
|
595
|
+
options.registryOptions,
|
|
596
|
+
"contentTypeParserMap" in options ? options.contentTypeParserMap : void 0
|
|
597
|
+
);
|
|
229
598
|
const proxyInternal = new Proxy(sdkInternal, {
|
|
230
599
|
get(target, prop) {
|
|
600
|
+
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
231
603
|
if (typeof prop === "string" && prop in target) {
|
|
232
|
-
|
|
604
|
+
const value = target[prop];
|
|
605
|
+
if (typeof value === "function") {
|
|
606
|
+
return value.bind(target);
|
|
607
|
+
}
|
|
608
|
+
return value;
|
|
233
609
|
}
|
|
234
|
-
|
|
610
|
+
return new Proxy(() => {
|
|
611
|
+
}, {
|
|
612
|
+
get(_innerTarget, innerProp) {
|
|
613
|
+
if (typeof innerProp === "string" && innerProp in target) {
|
|
614
|
+
const value = target[innerProp];
|
|
615
|
+
if (typeof value === "function") {
|
|
616
|
+
return value.bind(target);
|
|
617
|
+
}
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
return new Proxy(() => {
|
|
621
|
+
}, {
|
|
622
|
+
get(__innerTarget, deepProp) {
|
|
623
|
+
if (typeof deepProp === "string" && deepProp in target) {
|
|
624
|
+
const value = target[deepProp];
|
|
625
|
+
if (typeof value === "function") {
|
|
626
|
+
return value.bind(target);
|
|
627
|
+
}
|
|
628
|
+
return value;
|
|
629
|
+
}
|
|
630
|
+
return void 0;
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
});
|
|
235
635
|
}
|
|
236
636
|
});
|
|
237
637
|
return proxyInternal;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forklaunch/universal-sdk",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Cross runtime fetch library for forklaunch router sdks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
|
@@ -30,12 +30,19 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"lib/**"
|
|
32
32
|
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"ajv": "^8.17.1",
|
|
35
|
+
"ajv-formats": "^3.0.1",
|
|
36
|
+
"@forklaunch/common": "0.3.2"
|
|
37
|
+
},
|
|
33
38
|
"devDependencies": {
|
|
34
39
|
"fetch-mock": "^12.5.2",
|
|
35
40
|
"jest": "^29.7.0",
|
|
36
|
-
"ts
|
|
37
|
-
"
|
|
38
|
-
"
|
|
41
|
+
"openapi3-ts": "^4.4.0",
|
|
42
|
+
"prettier": "^3.5.3",
|
|
43
|
+
"ts-jest": "^29.3.4",
|
|
44
|
+
"tsup": "^8.5.0",
|
|
45
|
+
"typedoc": "^0.28.5"
|
|
39
46
|
},
|
|
40
47
|
"scripts": {
|
|
41
48
|
"build": "tsc --noEmit && tsup index.ts --format cjs,esm --no-splitting --dts --tsconfig tsconfig.json --out-dir lib --clean",
|