@oada/client
Advanced tools
Comparing version 3.1.4 to 3.1.5
@@ -32,2 +32,2 @@ /** | ||
concurrency?: number; | ||
}): Promise<HttpClient | WebSocketClient>; | ||
}): Promise<WebSocketClient | HttpClient>; |
@@ -97,4 +97,4 @@ /** | ||
lastCheck: number | undefined; | ||
items: Record<number, boolean | number>; | ||
recorded: Record<number, boolean | number>; | ||
items: Map<number, boolean | number>; | ||
recorded: Map<number, boolean | number>; | ||
} | ||
@@ -149,3 +149,4 @@ export interface WatchRequestBase { | ||
contentType?: string; | ||
revIfMatch?: number; | ||
/** If-Match */ | ||
etagIfMatch?: string | readonly string[]; | ||
tree?: Record<string, unknown>; | ||
@@ -152,0 +153,0 @@ timeout?: number; |
@@ -18,7 +18,7 @@ "use strict"; | ||
*/ | ||
var _OADAClient_instances, _OADAClient_token, _OADAClient_domain, _OADAClient_concurrency, _OADAClient_connection, _OADAClient_persistList, _OADAClient_recursiveGet, _OADAClient_persistWatch, _OADAClient_recordLapsedRevs, _OADAClient_createResource, _OADAClient_resourceExists; | ||
var _OADAClient_instances, _OADAClient_token, _OADAClient_domain, _OADAClient_concurrency, _OADAClient_connection, _OADAClient_persistList, _OADAClient_recursiveGet, _OADAClient_ensureTree, _OADAClient_guessContentType, _OADAClient_retryEnsureTree, _OADAClient_persistWatch, _OADAClient_recordLapsedRevs, _OADAClient_createResource, _OADAClient_resourceExists; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.OADAClient = void 0; | ||
const tslib_1 = require("tslib"); | ||
const node_abort_controller_1 = require("node-abort-controller"); | ||
const abort_controller_1 = require("abort-controller"); | ||
const buffer_1 = require("buffer"); | ||
@@ -33,2 +33,3 @@ const isomorphic_timers_promises_1 = require("isomorphic-timers-promises"); | ||
const websocket_1 = require("./websocket"); | ||
const node_path_1 = require("node:path"); | ||
const trace = (0, debug_1.default)('@oada/client:client:trace'); | ||
@@ -150,5 +151,5 @@ const info = (0, debug_1.default)('@oada/client:client:info'); | ||
async watch(request) { | ||
const restart = new node_abort_controller_1.AbortController(); | ||
const restart = new abort_controller_1.AbortController(); | ||
const headers = {}; | ||
// TODO: Decide whether this should go after persist to allow it to override the persist rev | ||
// ???: Decide whether this should go after persist to allow it to override the persist rev | ||
if ('rev' in request) { | ||
@@ -177,6 +178,6 @@ headers['x-oada-rev'] = `${request.rev}`; | ||
headers['x-oada-rev'] = lastRev.toString(); | ||
trace(`Watch persist found _meta entry for [${name}]. Setting x-oada-rev header to ${lastRev}`); | ||
trace('Watch persist found _meta entry for [%s]. Setting x-oada-rev header to %d', name, lastRev); | ||
} | ||
if (!lastRev) { | ||
trace(`Watch persist found _meta entry for [${name}], but 'rev' is undefined. Writing 'rev' as ${rev}`); | ||
trace("Watch persist found _meta entry for [%s], but 'rev' is undefined. Writing 'rev' as %d", name, lastRev); | ||
await this.put({ | ||
@@ -220,3 +221,3 @@ path: `${persistPath}/rev`, | ||
// @ts-expect-error stupid error handling | ||
if (cError.status === 404) { | ||
if (cError?.code === '404') { | ||
recorded = {}; | ||
@@ -232,4 +233,4 @@ } | ||
lastRev, | ||
items: {}, | ||
recorded, | ||
items: new Map(), | ||
recorded: new Map(Object.entries(recorded).map(([k, v]) => [Number(k), v])), | ||
}); | ||
@@ -275,4 +276,5 @@ } | ||
if (persistPath && tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").has(persistPath)) { | ||
tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").get(persistPath).items[Number(parentRev)] = | ||
Date.now(); | ||
tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f") | ||
.get(persistPath) | ||
.items.set(Number(parentRev), Date.now()); | ||
} | ||
@@ -353,88 +355,6 @@ } | ||
if (request.tree) { | ||
// Retry counter | ||
let retryCount = 0; | ||
// Link object (eventually substituted by an actual link object) | ||
// eslint-disable-next-line unicorn/no-null | ||
let linkObject = null; | ||
let newResourcePathArray = []; | ||
for (let index = pathArray.length - 1; index >= 0; index--) { | ||
// Get current path | ||
const partialPathArray = pathArray.slice(0, index + 1); | ||
// Get corresponding data definition from the provided tree | ||
const treeObject = (0, utils_1.getObjectAtPath)(request.tree, partialPathArray); | ||
if ('_type' in treeObject) { | ||
// It's a resource | ||
const contentType = treeObject._type; | ||
const partialPath = (0, utils_1.toStringPath)(partialPathArray); | ||
// Check if resource already exists on the remote server | ||
// eslint-disable-next-line no-await-in-loop | ||
const resourceCheckResult = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_resourceExists).call(this, partialPath); | ||
if (resourceCheckResult.exist) { | ||
// CASE 1: resource exists on server. | ||
// simply create a link using PUT request | ||
if (linkObject && newResourcePathArray.length > 0) { | ||
// eslint-disable-next-line no-await-in-loop | ||
const linkPutResponse = await this.put({ | ||
path: (0, utils_1.toStringPath)(newResourcePathArray), | ||
contentType, | ||
data: linkObject, | ||
// Ensure the resource has not been modified (opportunistic lock) | ||
revIfMatch: resourceCheckResult.rev, | ||
}).catch((cError) => { | ||
// @ts-expect-error stupid error checking | ||
if (cError.status === 412) { | ||
return cError; | ||
} | ||
// @ts-expect-error stupid error checking | ||
throw new Error(cError.statusText); | ||
}); | ||
// Handle return code 412 (If-Match failed) | ||
if (linkPutResponse.status === 412) { | ||
// Retry with exponential backoff | ||
if (retryCount++ < 5) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await (0, isomorphic_timers_promises_1.setTimeout)(100 * (retryCount * retryCount + Math.random())); | ||
// Reset loop counter and do tree construction again. | ||
index = pathArray.length; | ||
continue; | ||
} | ||
else { | ||
throw new Error('If-match failed.'); | ||
} | ||
} | ||
} | ||
// We hit a resource that already exists. | ||
// No need to further traverse the tree. | ||
break; | ||
} | ||
else { | ||
// CASE 2: resource does NOT exist on server. | ||
// create a new nested object containing a link | ||
const relativePathArray = newResourcePathArray.slice(index + 1); | ||
const newResource = linkObject | ||
? (0, utils_1.createNestedObject)(linkObject, relativePathArray) | ||
: {}; | ||
// Create a new resource | ||
// eslint-disable-next-line no-await-in-loop | ||
const resourceId = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_createResource).call(this, contentType, newResource); | ||
// Save a link | ||
linkObject = | ||
'_rev' in treeObject | ||
? { _id: resourceId, _rev: 0 } // Versioned link | ||
: { _id: resourceId }; // Non-versioned link | ||
newResourcePathArray = partialPathArray.slice(); // Clone | ||
} | ||
} | ||
} | ||
await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_retryEnsureTree).call(this, request.tree, pathArray); | ||
} | ||
// Get content-type | ||
const contentType = request.contentType || // 1) get content-type from the argument | ||
(buffer_1.Buffer.isBuffer(request.data) && | ||
// eslint-disable-next-line unicorn/no-await-expression-member | ||
(await (0, core_1.fromBuffer)(request.data))?.mime) || | ||
request.data?._type || // 2) get content-type from the resource body | ||
(request.tree | ||
? (0, utils_1.getObjectAtPath)(request.tree, pathArray)._type // 3) get content-type from the tree | ||
: 'application/json'); // 4) Assume application/json | ||
// return PUT response | ||
const contentType = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_guessContentType).call(this, request, pathArray); | ||
const etag = request.etagIfMatch && (0, utils_1.toArray)(request.etagIfMatch); | ||
const [response] = await tslib_1.__classPrivateFieldGet(this, _OADAClient_connection, "f").request({ | ||
@@ -445,4 +365,4 @@ method: 'put', | ||
'content-type': contentType, | ||
...(request.revIfMatch && { | ||
'if-match': request.revIfMatch.toString(), | ||
...(etag && { | ||
'if-match': etag.join(', '), | ||
}), // Add if-match header if revIfMatch is provided | ||
@@ -468,14 +388,5 @@ }, | ||
const { string: newkey } = await ksuid_1.default.random(); | ||
request.path += `/${newkey}`; | ||
return this.put(request); | ||
return this.put({ ...request, path: (0, node_path_1.join)(path, newkey) }); | ||
} | ||
// Get content-type | ||
const contentType = request.contentType ?? // 1) get content-type from the argument | ||
// eslint-disable-next-line unicorn/no-await-expression-member | ||
(buffer_1.Buffer.isBuffer(data) && (await (0, core_1.fromBuffer)(data))?.mime) ?? | ||
data?._type ?? // 2) get content-type from the resource body | ||
(tree | ||
? (0, utils_1.getObjectAtPath)(tree, pathArray)._type // 3) get content-type from the tree | ||
: 'application/json'); // 4) Assume application/json | ||
// return PUT response | ||
const contentType = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_guessContentType).call(this, request, pathArray); | ||
const [response] = await tslib_1.__classPrivateFieldGet(this, _OADAClient_connection, "f").request({ | ||
@@ -540,4 +451,4 @@ method: 'post', | ||
// @ts-expect-error stupid errors | ||
if (cError.status !== 404) { | ||
throw cError; | ||
if (cError?.code !== '404') { | ||
throw await (0, utils_1.fixError)(cError); | ||
} | ||
@@ -564,6 +475,5 @@ trace('Path to ensure did not exist. Creating'); | ||
// TODO: should this error? | ||
if (buffer_1.Buffer.isBuffer(body)) { | ||
if (buffer_1.Buffer.isBuffer(body) || !body) { | ||
return body; | ||
} | ||
const data = body; | ||
// Select children to traverse | ||
@@ -574,3 +484,3 @@ const children = []; | ||
// get all children from the server | ||
for (const [key, value] of Object.entries(data)) { | ||
for (const [key, value] of Object.entries(body)) { | ||
if (typeof value === 'object') { | ||
@@ -584,3 +494,3 @@ children.push({ treeKey: '*', dataKey: key }); | ||
for (const key of Object.keys(subTree ?? {})) { | ||
if (typeof data[key] === 'object') { | ||
if (typeof body[key] === 'object') { | ||
children.push({ treeKey: key, dataKey: key }); | ||
@@ -590,20 +500,118 @@ } | ||
} | ||
// Initiate recursive calls | ||
const promises = children.map(async (item) => { | ||
// Await recursive calls | ||
await Promise.all(children.map(async (item) => { | ||
const childPath = `${path}/${item.dataKey}`; | ||
if (!data) { | ||
return; | ||
} | ||
const response = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_recursiveGet).call(this, childPath, subTree[item.treeKey], data[item.dataKey]); | ||
const response = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_recursiveGet).call(this, childPath, subTree[item.treeKey], body[item.dataKey]); | ||
if (buffer_1.Buffer.isBuffer(response)) { | ||
throw new TypeError('Non JSON is not supported.'); | ||
} | ||
data[item.dataKey] = response; | ||
}); | ||
await Promise.all(promises); | ||
return data; // Return object at "path" | ||
body[item.dataKey] = response; | ||
})); | ||
return body; // Return object at "path" | ||
}, _OADAClient_ensureTree = async function _OADAClient_ensureTree(tree, pathArray) { | ||
// Link object (eventually substituted by an actual link object) | ||
let linkObject = null; | ||
let newResourcePathArray = []; | ||
for await (const index of Array.from(pathArray.keys()).reverse()) { | ||
// Get current path | ||
const partialPathArray = pathArray.slice(0, index + 1); | ||
// Get corresponding data definition from the provided tree | ||
const treeObject = (0, utils_1.getObjectAtPath)(tree, partialPathArray); | ||
if (!treeObject._type) { | ||
// No resource break here | ||
continue; | ||
} | ||
// It's a resource | ||
const contentType = treeObject._type; | ||
const partialPath = (0, utils_1.toStringPath)(partialPathArray); | ||
// Check if resource already exists on the remote server | ||
const resourceCheckResult = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_resourceExists).call(this, partialPath); | ||
// CASE 1: resource exists on server. | ||
if (resourceCheckResult.exist) { | ||
// Simply create a link using PUT request | ||
if (linkObject && newResourcePathArray.length > 0) { | ||
await this.put({ | ||
path: (0, utils_1.toStringPath)(newResourcePathArray), | ||
contentType, | ||
data: linkObject, | ||
// Ensure the resource has not been modified (opportunistic lock) | ||
etagIfMatch: resourceCheckResult.etag, | ||
}); | ||
} | ||
// Resource already exists, no need to further traverse the tree. | ||
return; | ||
} | ||
// CASE 2: resource does NOT exist on server. | ||
// create a new nested object containing a link | ||
const relativePathArray = newResourcePathArray.slice(index + 1); | ||
const newResource = linkObject | ||
? (0, utils_1.createNestedObject)(linkObject, relativePathArray) | ||
: {}; | ||
// Create a new resource | ||
const resourceId = await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_createResource).call(this, contentType, newResource); | ||
// Save a link | ||
linkObject = | ||
'_rev' in treeObject | ||
? { _id: resourceId, _rev: 0 } // Versioned link | ||
: { _id: resourceId }; // Non-versioned link | ||
newResourcePathArray = partialPathArray.slice(); // Clone | ||
} | ||
}, _OADAClient_guessContentType = async function _OADAClient_guessContentType({ contentType, data, tree }, pathArray) { | ||
// 1) get content-type from the argument | ||
if (contentType) { | ||
return contentType; | ||
} | ||
// 2) get content-type from the resource body | ||
if (buffer_1.Buffer.isBuffer(data)) { | ||
const type = await (0, core_1.fromBuffer)(data); | ||
if (type?.mime) { | ||
return type.mime; | ||
} | ||
} | ||
else { | ||
const type = data?._type; | ||
if (type) { | ||
return type; | ||
} | ||
} | ||
// 3) get content-type from the tree | ||
if (tree) { | ||
const { _type } = (0, utils_1.getObjectAtPath)(tree, pathArray); | ||
if (_type) { | ||
return _type; | ||
} | ||
} | ||
// Assume it is JSON? | ||
return 'application/json'; | ||
}, _OADAClient_retryEnsureTree = async function _OADAClient_retryEnsureTree(tree, pathArray) { | ||
// Retry on certain errors | ||
const CODES = new Set(['412', '422']); | ||
const MAX_RETRIES = 5; | ||
for await (const retryCount of Array.from({ | ||
length: MAX_RETRIES - 1, | ||
}).keys()) { | ||
try { | ||
await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_ensureTree).call(this, tree, pathArray); | ||
return; | ||
} | ||
catch (cError) { | ||
// Handle 412 (If-Match failed) | ||
// @ts-expect-error stupid errors | ||
if (CODES.has(cError?.code)) { | ||
await (0, isomorphic_timers_promises_1.setTimeout)( | ||
// Retry with exponential backoff | ||
100 * ((retryCount + 1) ** 2 + Math.random())); | ||
} | ||
else { | ||
throw await (0, utils_1.fixError)(cError); | ||
} | ||
} | ||
} | ||
await tslib_1.__classPrivateFieldGet(this, _OADAClient_instances, "m", _OADAClient_ensureTree).call(this, tree, pathArray); | ||
}, _OADAClient_persistWatch = | ||
/** Attempt to save the latest rev processed, accommodating concurrency */ | ||
/** | ||
* Attempt to save the latest rev processed, accommodating concurrency | ||
*/ | ||
async function _OADAClient_persistWatch(persistPath, rev) { | ||
trace(`Persisting watch for path ${persistPath} to rev ${rev}`); | ||
trace('Persisting watch for path %s to rev %d', persistPath, rev); | ||
if (tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").has(persistPath)) { | ||
@@ -613,4 +621,4 @@ let { lastRev, recorded, items, recordLapsedTimeout, lastCheck } = tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").get(persistPath); | ||
// Handle finished revs that were previously recorded | ||
if (rev in recorded) { | ||
info(`Lapsed rev [${rev}] on path ${persistPath} is now resolved. Removing from 'items' list.`); | ||
if (recorded.has(rev)) { | ||
info("Lapsed rev [%d] on path %s is now resolved. Removing from 'items' list.", rev, persistPath); | ||
await this.delete({ | ||
@@ -627,8 +635,8 @@ path: `${persistPath}/items/${rev}`, | ||
} | ||
items[Number(rev)] = true; | ||
while (items[lastRev + 1] === true) { | ||
items.set(Number(rev), true); | ||
while (items.get(lastRev + 1) === true) { | ||
// Truthy won't work with items as timestamps | ||
lastRev++; | ||
tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").get(persistPath).lastRev = lastRev; | ||
delete items[Number(lastRev)]; | ||
items.delete(Number(lastRev)); | ||
} | ||
@@ -646,6 +654,6 @@ await this.put({ | ||
async function _OADAClient_recordLapsedRevs(persistPath, now) { | ||
trace(`Checking for lapsed revs for path [${persistPath}] time: [${now}]`); | ||
trace('Checking for lapsed revs for path [%s] time: [%s]', persistPath, now); | ||
const { items, recorded, recordLapsedTimeout } = tslib_1.__classPrivateFieldGet(this, _OADAClient_persistList, "f").get(persistPath); | ||
// Iterate over items; | ||
for (const [key, item] of Object.entries(items)) { | ||
for (const [key, item] of items) { | ||
if (recordLapsedTimeout !== undefined && | ||
@@ -663,4 +671,4 @@ typeof item === 'number' && | ||
// Mark them as resolved | ||
items[Number(key)] = true; | ||
recorded[Number(key)] = true; | ||
items.set(Number(key), true); | ||
recorded.set(Number(key), true); | ||
} | ||
@@ -672,4 +680,4 @@ } | ||
// Create unique resource ID | ||
// eslint-disable-next-line unicorn/no-await-expression-member | ||
const resourceId = `resources/${(await ksuid_1.default.random()).string}`; | ||
const { string: id } = await ksuid_1.default.random(); | ||
const resourceId = `resources/${id}`; | ||
// Append resource ID and content type to object | ||
@@ -703,3 +711,6 @@ // const fullData = { _id: resourceId, _type: contentType, ...data }; | ||
if (headResponse.status === 200) { | ||
return { exist: true, rev: Number(headResponse.headers['x-oada-rev']) }; | ||
return { | ||
exist: true, | ||
etag: headResponse.headers.etag, | ||
}; | ||
} | ||
@@ -712,13 +723,11 @@ if (headResponse.status === 404) { | ||
// @ts-expect-error stupid stupid error handling | ||
if (cError.status === 404) { | ||
if (cError?.code === '404') { | ||
return { exist: false }; | ||
} | ||
// @ts-expect-error stupid stupid error handling | ||
if (cError.status === 403 && path.startsWith('/resources')) { | ||
if (cError?.code === '403' && path.startsWith('/resources')) { | ||
// 403 is what you get on resources that don't exist (i.e. Forbidden) | ||
return { exist: false }; | ||
} | ||
throw new Error( | ||
// @ts-expect-error stupid stupid error handling | ||
`Error: head for resource returned ${cError.statusText ?? cError}`); | ||
throw await (0, utils_1.fixError)(cError); | ||
} | ||
@@ -725,0 +734,0 @@ throw new Error('Status code is neither 200 nor 404.'); |
@@ -30,2 +30,3 @@ "use strict"; | ||
const debug_1 = tslib_1.__importDefault(require("debug")); | ||
const utils_1 = require("./utils"); | ||
const warn = (0, debug_1.default)('@oada/client:errors:warn'); | ||
@@ -84,7 +85,7 @@ const trace = (0, debug_1.default)('@oada/client:errors:trace'); | ||
// @ts-expect-error stupid error handling | ||
switch (error.status ?? cError?.code) { | ||
case 429: | ||
switch (`${error.status ?? cError?.code}`) { | ||
case '429': | ||
return await handleRatelimit(error, request, ...rest); | ||
// Some servers use 503 for rate limit... | ||
case 503: { | ||
case '503': { | ||
const headers = new fetch_1.Headers(error.headers); | ||
@@ -103,3 +104,3 @@ if (headers.has('Retry-After')) { | ||
// Pass error up | ||
throw cError; | ||
throw await (0, utils_1.fixError)(cError); | ||
} | ||
@@ -106,0 +107,0 @@ } |
@@ -22,4 +22,4 @@ "use strict"; | ||
const tslib_1 = require("tslib"); | ||
const node_abort_controller_1 = require("node-abort-controller"); | ||
const buffer_1 = require("buffer"); | ||
const fetch_h2_1 = require("fetch-h2"); | ||
const eventemitter3_1 = tslib_1.__importDefault(require("eventemitter3")); | ||
@@ -31,2 +31,3 @@ const p_queue_1 = tslib_1.__importDefault(require("p-queue")); | ||
const request_1 = require("@oada/types/oada/websockets/request"); | ||
const utils_1 = require("./utils"); | ||
const fetch_1 = tslib_1.__importStar(require("./fetch")); | ||
@@ -40,2 +41,7 @@ const websocket_1 = require("./websocket"); | ||
} | ||
async function getBody(result) { | ||
return type_is_1.default.is(result.headers.get('content-type'), ['json', '+json']) | ||
? (await result.json()) | ||
: buffer_1.Buffer.from(await result.arrayBuffer()); | ||
} | ||
class HttpClient extends eventemitter3_1.default { | ||
@@ -143,10 +149,12 @@ /** | ||
trace('Req looks like socket request, awaiting race of timeout and fetch to %s%s', tslib_1.__classPrivateFieldGet(this, _HttpClient_domain, "f"), request.path); | ||
let done = false; | ||
let timedout = false; | ||
let signal; | ||
let controller; | ||
if (timeout) { | ||
const controller = new node_abort_controller_1.AbortController(); | ||
({ signal } = controller); | ||
controller = new fetch_h2_1.AbortController(); | ||
setTimeout(() => { | ||
controller.abort(); | ||
timedout = true; | ||
if (!done) { | ||
timedout = true; | ||
controller.abort(); | ||
} | ||
}, timeout); | ||
@@ -161,4 +169,3 @@ } | ||
method: request.method.toUpperCase(), | ||
// @ts-expect-error fetch has a crazy type for this | ||
signal, | ||
signal: controller?.signal, | ||
timeout, | ||
@@ -170,5 +177,3 @@ body, | ||
}); | ||
if (timedout) { | ||
throw new Error('Request timeout'); | ||
} | ||
done = true; | ||
trace('Fetch did not throw, checking status of %s', result.status); | ||
@@ -178,4 +183,3 @@ // This is the same test as in ./websocket.ts | ||
trace('result.status %s is not 2xx, throwing', result.status); | ||
// eslint-disable-next-line @typescript-eslint/no-throw-literal | ||
throw result; | ||
throw await (0, utils_1.fixError)(result); | ||
} | ||
@@ -185,9 +189,5 @@ trace('result.status ok, pulling headers'); | ||
const headers = Object.fromEntries(result.headers.entries()); | ||
// Const length = +(result.headers.get("content-length") || 0); | ||
let data; | ||
if (request.method.toUpperCase() !== 'HEAD') { | ||
data = type_is_1.default.is(result.headers.get('content-type'), ['json', '+json']) | ||
? (await result.json()) | ||
: buffer_1.Buffer.from(await result.arrayBuffer()); | ||
} | ||
const data = request.method.toUpperCase() === 'HEAD' | ||
? undefined | ||
: await getBody(result); | ||
// Trace("length = %d, result.headers = %O", length, headers); | ||
@@ -205,2 +205,5 @@ return [ | ||
catch (cError) { | ||
if (timedout) { | ||
throw new utils_1.TimeoutError(request); | ||
} | ||
// @ts-expect-error stupid error handling | ||
@@ -207,0 +210,0 @@ // eslint-disable-next-line sonarjs/no-small-switch |
@@ -21,2 +21,3 @@ /** | ||
export declare function createInstance(config: Config): OADAClient; | ||
export declare function normalizeDomain(domain: string): string; | ||
/** Create a new instance and wrap it with Promise */ | ||
@@ -23,0 +24,0 @@ export declare function connect({ connection: proto, ...config }: Config & { |
@@ -30,5 +30,2 @@ "use strict"; | ||
exports.createInstance = createInstance; | ||
/** | ||
* @internal | ||
*/ | ||
function normalizeDomain(domain) { | ||
@@ -35,0 +32,0 @@ const { hostname, protocol, port } = (0, auto_1.parseDomain)(domain); |
@@ -22,7 +22,24 @@ /** | ||
import type { OADATree } from './client'; | ||
export declare function toStringPath(path: string[]): string; | ||
export declare function toArray<E extends unknown[] | readonly unknown[]>(itemOrArray: E | E[0]): E; | ||
export declare function toStringPath(path: readonly string[]): string; | ||
export declare function toArrayPath(path: string): string[]; | ||
export declare function getObjectAtPath(tree: OADATree, path: string[]): OADATree; | ||
export declare function getObjectAtPath(tree: OADATree, path: readonly string[]): OADATree; | ||
export declare function toTreePath(tree: OADATree, path: string[]): string[]; | ||
export declare function isResource(tree: OADATree, path: string[]): boolean; | ||
export declare function createNestedObject(object: Record<string, unknown>, nestPath: string[]): Record<string, unknown>; | ||
/** | ||
* Use an Error class for timed out requests | ||
*/ | ||
export declare class TimeoutError extends Error { | ||
get code(): string; | ||
get name(): string; | ||
constructor(request: unknown); | ||
} | ||
/** | ||
* Ensure we throw real `Error`s | ||
*/ | ||
export declare function fixError<E extends { | ||
message?: string; | ||
status?: string | number; | ||
statusText?: string; | ||
}>(error: E): Promise<E & Error>; |
@@ -19,3 +19,11 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createNestedObject = exports.isResource = exports.toTreePath = exports.getObjectAtPath = exports.toArrayPath = exports.toStringPath = void 0; | ||
exports.fixError = exports.TimeoutError = exports.createNestedObject = exports.isResource = exports.toTreePath = exports.getObjectAtPath = exports.toArrayPath = exports.toStringPath = exports.toArray = void 0; | ||
// Typescript sucks at figuring out Array.isArray on its own | ||
function isArray(value) { | ||
return Array.isArray(value); | ||
} | ||
function toArray(itemOrArray) { | ||
return isArray(itemOrArray) ? itemOrArray : [itemOrArray]; | ||
} | ||
exports.toArray = toArray; | ||
function toStringPath(path) { | ||
@@ -73,6 +81,3 @@ return `/${path.join('/')}`; | ||
const object = getObjectAtPath(tree, path); | ||
if ('_id' in object) { | ||
return true; | ||
} | ||
return false; | ||
return '_id' in object; | ||
} | ||
@@ -89,2 +94,42 @@ exports.isResource = isResource; | ||
exports.createNestedObject = createNestedObject; | ||
/** | ||
* Use an Error class for timed out requests | ||
*/ | ||
class TimeoutError extends Error { | ||
get code() { | ||
return 'REQUEST_TIMEDOUT'; | ||
} | ||
get name() { | ||
return 'TimeoutError'; | ||
} | ||
constructor(request) { | ||
super('Request timed out'); | ||
Object.assign(this, request); | ||
} | ||
} | ||
exports.TimeoutError = TimeoutError; | ||
/** | ||
* Ensure we throw real `Error`s | ||
*/ | ||
async function fixError(error) { | ||
if (error instanceof Error) { | ||
return error; | ||
} | ||
const code = `${error.status}`; | ||
// TODO: Clean up this mess | ||
let body = {}; | ||
try { | ||
// @ts-expect-error try to get error body? | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call | ||
body = (await error.json?.()) ?? error.data; | ||
} | ||
catch { } | ||
const message = error.message ?? | ||
body?.message ?? | ||
(error.statusText | ||
? `${error.status} ${error.statusText}` | ||
: `${error.status}`); | ||
return Object.assign(new Error(message), { code, ...error }); | ||
} | ||
exports.fixError = fixError; | ||
//# sourceMappingURL=utils.js.map |
@@ -34,2 +34,3 @@ "use strict"; | ||
const event_iterator_1 = require("./event-iterator"); | ||
const utils_1 = require("./utils"); | ||
const errors_1 = require("./errors"); | ||
@@ -145,3 +146,3 @@ const trace = (0, debug_1.default)('@oada/client:ws:trace'); | ||
(0, isomorphic_timers_promises_1.setTimeout)(timeout).then(() => { | ||
throw new Error('Request timeout'); | ||
throw new utils_1.TimeoutError(request); | ||
})); | ||
@@ -157,4 +158,3 @@ } | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-throw-literal | ||
throw response.status ? response : new Error('Request failed'); | ||
throw await (0, utils_1.fixError)(response); | ||
}, _WebSocketClient_receive = function _WebSocketClient_receive(m) { | ||
@@ -161,0 +161,0 @@ try { |
{ | ||
"name": "@oada/client", | ||
"version": "3.1.4", | ||
"version": "3.1.5", | ||
"description": "A lightweight client tool to interact with an OADA-compliant server", | ||
@@ -29,6 +29,8 @@ "repository": "https://github.com/OADA/client", | ||
"files": [ | ||
"lib/**/*", | ||
"dist/**/*" | ||
], | ||
"scripts": { | ||
"test": "yarn build test && ava", | ||
"test": "yarn run build test && c8 ava", | ||
"test:debug": "ava -T 60m -svc 1 --no-worker-threads", | ||
"build": "tsc -b", | ||
@@ -41,2 +43,7 @@ "dev": "tsc -w", | ||
"ava": { | ||
"concurrency": 2, | ||
"failFast": false, | ||
"files": [ | ||
"**/*.test.ts" | ||
], | ||
"typescript": { | ||
@@ -47,3 +54,3 @@ "extensions": [ | ||
"rewritePaths": { | ||
"src/": "dist/", | ||
"lib/": "dist/", | ||
"test/": ".test/" | ||
@@ -54,10 +61,23 @@ }, | ||
}, | ||
"author": "", | ||
"c8": { | ||
"reporter": [ | ||
"text", | ||
"lcov" | ||
], | ||
"all": true, | ||
"src": "lib", | ||
"exclude": [ | ||
"*.d.ts", | ||
".pnp.*", | ||
".test" | ||
] | ||
}, | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"@oada/types": "^1.8.1", | ||
"abort-controller": "^3.0.0", | ||
"buffer": "^6.0.3", | ||
"bufferutil": "^4.0.6", | ||
"cross-fetch": "^3.1.5", | ||
"debug": "^4.3.3", | ||
"debug": "^4.3.4", | ||
"deep-clone": "^3.0.3", | ||
@@ -72,3 +92,2 @@ "encoding": "^0.1.13", | ||
"ksuid": "^3.0.0", | ||
"node-abort-controller": "^3.0.1", | ||
"p-queue": "^6.6.2", | ||
@@ -80,3 +99,3 @@ "reconnecting-websocket": "^4.4.0", | ||
"type-is": "^1.6.18", | ||
"utf-8-validate": "^5.0.8", | ||
"utf-8-validate": "^5.0.9", | ||
"ws": "^8.5.0" | ||
@@ -92,10 +111,11 @@ }, | ||
"@types/uuid": "^8.3.4", | ||
"@types/ws": "^8.5.2", | ||
"@typescript-eslint/eslint-plugin": "^5.14.0", | ||
"@typescript-eslint/parser": "^5.14.0", | ||
"@types/ws": "^8.5.3", | ||
"@typescript-eslint/eslint-plugin": "^5.16.0", | ||
"@typescript-eslint/parser": "^5.16.0", | ||
"@yarnpkg/sdks": "2.6.0", | ||
"ava": "^4.1.0", | ||
"axios": "^0.26.0", | ||
"dotenv": "^10.0.0", | ||
"eslint": "^8.10.0", | ||
"ava": "4.0.0-rc.1", | ||
"axios": "^0.26.1", | ||
"c8": "^7.11.0", | ||
"dotenv": "^16.0.0", | ||
"eslint": "^8.11.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
@@ -107,5 +127,6 @@ "eslint-config-xo": "^0.40.0", | ||
"eslint-plugin-array-func": "^3.1.7", | ||
"eslint-plugin-ava": "^13.2.0", | ||
"eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-filenames": "^1.3.2", | ||
"eslint-plugin-github": "^4.3.5", | ||
"eslint-plugin-github": "^4.3.6", | ||
"eslint-plugin-i18n-text": "^1.0.1", | ||
@@ -121,7 +142,7 @@ "eslint-plugin-import": "^2.25.4", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-regexp": "^1.5.1", | ||
"eslint-plugin-regexp": "^1.6.0", | ||
"eslint-plugin-security": "^1.4.0", | ||
"eslint-plugin-sonarjs": "^0.12.0", | ||
"eslint-plugin-unicorn": "^41.0.0", | ||
"prettier": "^2.5.1", | ||
"eslint-plugin-unicorn": "^41.0.1", | ||
"prettier": "^2.6.0", | ||
"typescript": "4.6.2" | ||
@@ -128,0 +149,0 @@ }, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
209222
44
4141
42
+ Addedabort-controller@^3.0.0
+ Addedabort-controller@3.0.0(transitive)
+ Addedevent-target-shim@5.0.1(transitive)
- Removednode-abort-controller@^3.0.1
- Removednode-abort-controller@3.1.1(transitive)
Updateddebug@^4.3.4
Updatedutf-8-validate@^5.0.9