@metrichor/epi2me-client-node
Advanced tools
Comparing version 0.2.10211410 to 0.2.10360724
@@ -1,5 +0,6 @@ | ||
import { ApolloClient, ApolloLink } from '@apollo/client/core'; | ||
import { ApolloLink } from '@apollo/client/core'; | ||
import { ErrorHandler } from '@apollo/client/link/error'; | ||
import type { ClientOptions } from './ClientOptions.type'; | ||
export declare function create_client(client: ClientOptions['client'], http_link: ApolloLink, on_error?: ErrorHandler): ApolloClient<unknown>; | ||
import type { GraphQLClient } from './GraphQLClient.type'; | ||
export declare function create_client(client: ClientOptions['client'], http_link: ApolloLink, on_error?: ErrorHandler): GraphQLClient; | ||
//# sourceMappingURL=create_client.d.ts.map |
export type { ErrorHandler } from '@apollo/client/link/error'; | ||
export type { ClientOptions } from './ClientOptions.type'; | ||
export type { MockResponse } from '../common/mock_client'; | ||
export type { ReportComponent } from './chain.type'; | ||
export type { Watchable } from './Watchable.type'; | ||
export type { TelemetryClientOptions, TelemetryClient } from './telemetry_client.type'; | ||
export type { DocumentNode } from 'graphql'; | ||
@@ -10,4 +13,7 @@ export type { TypedDocumentNode } from '@graphql-typed-document-node/core'; | ||
export { create_mock_client } from '../common/mock_client'; | ||
export { GraphQLClient } from './GraphQLClient.type'; | ||
export { GraphQLError } from 'graphql'; | ||
export { ApolloError, isApolloError, gql } from '@apollo/client/core'; | ||
export { get_report_components } from './chain'; | ||
export { create_telemetry_client, watch_report_available, watch_report_content, has_telemetry, get_telemetry_content } from './telemetry_client'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,4 +0,4 @@ | ||
import { ApolloClient, ApolloError, ApolloLink, Operation } from '@apollo/client/core'; | ||
import { ApolloError, ApolloLink, Operation } from '@apollo/client/core'; | ||
import type { GraphQLError } from 'graphql'; | ||
import type { ClientOptions } from './ClientOptions.type'; | ||
import type { GraphQLClient } from './GraphQLClient.type'; | ||
export interface MockResponse { | ||
@@ -12,4 +12,5 @@ data?: { | ||
} | ||
export declare function create_mock_client(client: ClientOptions['client'], on_request: (op: Operation) => MockResponse): ApolloClient<unknown>; | ||
export declare function offline_requester(): MockResponse; | ||
export declare function create_mock_client(on_request?: (op: Operation) => MockResponse): GraphQLClient; | ||
export declare function create_mock_http_link(on_request: (op: Operation) => MockResponse): ApolloLink; | ||
//# sourceMappingURL=mock_client.d.ts.map |
@@ -7,3 +7,3 @@ import type { Dictionary } from 'ts-runtime-typecheck'; | ||
} | ||
export type RestrictedResponse = Pick<Response, 'json' | 'ok' | 'statusText' | 'headers'>; | ||
export type RestrictedResponse = Pick<Response, 'json' | 'ok' | 'statusText' | 'status' | 'headers'>; | ||
export type RestrictedHeaderInit = [string, string][] | Record<string, string> | { | ||
@@ -10,0 +10,0 @@ [Symbol.iterator](): Iterator<[string, string]>; |
220
index.js
@@ -8,2 +8,3 @@ 'use strict'; | ||
var graphql = require('graphql'); | ||
var tsRuntimeTypecheck = require('ts-runtime-typecheck'); | ||
@@ -175,5 +176,10 @@ function sha1_digest(message, key) { | ||
function create_mock_client(client, on_request) { | ||
function offline_requester() { | ||
return { | ||
error: new core.ApolloError({ networkError: new Error('Client is offline') }), | ||
}; | ||
} | ||
function create_mock_client(on_request = offline_requester) { | ||
const mock_link = create_mock_http_link(on_request); | ||
return create_client(client, mock_link); | ||
return create_client({ name: 'mock client', version: '1.0' }, mock_link); | ||
} | ||
@@ -195,2 +201,206 @@ function create_mock_http_link(on_request) { | ||
const TELEMETRY_SOURCE_QUERY_DOCUMENT = core.gql `query getTelemetrySource ($id_instance: ID!, $report: String!) { | ||
source: workflowInstanceTelemetry (idWorkflowInstance: $id_instance, report: $report) { | ||
getUrl | ||
headUrl | ||
expiresIn | ||
} | ||
}`; | ||
const INSTANCE_CHAIN_QUERY_DOCUMENT = core.gql `query getInstanceChain ($id_instance: ID!) { | ||
instance: workflowInstance (idWorkflowInstance: $id_instance) { | ||
idWorkflowInstance | ||
chain | ||
telemetryNames | ||
} | ||
}`; | ||
const is_chain = tsRuntimeTypecheck.isStruct({ | ||
components: tsRuntimeTypecheck.isDictionaryOf(tsRuntimeTypecheck.isStruct({ | ||
wid: tsRuntimeTypecheck.isOptNumber, | ||
next: tsRuntimeTypecheck.isOptDictionaryOf(tsRuntimeTypecheck.isString), | ||
})), | ||
targetComponentId: tsRuntimeTypecheck.isString, | ||
}); | ||
const CHAIN_END = '0'; | ||
async function get_workflow_chain(graphql_client, id_instance) { | ||
var _a; | ||
const response = await graphql_client.query({ | ||
query: INSTANCE_CHAIN_QUERY_DOCUMENT, | ||
variables: { id_instance } | ||
}); | ||
const { chain, telemetryNames } = response.data.instance; | ||
tsRuntimeTypecheck.invariant(is_chain(chain), 'Invalid instance chain'); | ||
const components = {}; | ||
for (const [chain_id, component] of Object.entries(chain.components)) { | ||
if (!component.wid) { | ||
components[chain_id] = component; | ||
continue; | ||
} | ||
const component_name = Object.keys((_a = telemetryNames[component.wid]) !== null && _a !== void 0 ? _a : {})[0]; | ||
if (!component_name) { | ||
components[chain_id] = component; | ||
continue; | ||
} | ||
components[chain_id] = { | ||
...component, | ||
component_name, | ||
}; | ||
} | ||
const extended_chain = { | ||
components, | ||
targetComponentId: chain.targetComponentId, | ||
}; | ||
return extended_chain; | ||
} | ||
async function get_report_components(graphql_client, id_instance) { | ||
var _a, _b; | ||
const chain = await get_workflow_chain(graphql_client, id_instance); | ||
const results = []; | ||
const seen_components = new Set([CHAIN_END]); | ||
const component_lifo = [chain.targetComponentId]; | ||
while (component_lifo.length > 0) { | ||
const current_workflow_key = component_lifo.shift(); | ||
if (!current_workflow_key || seen_components.has(current_workflow_key)) { | ||
continue; | ||
} | ||
seen_components.add(current_workflow_key); | ||
const component = chain.components[current_workflow_key]; | ||
tsRuntimeTypecheck.invariant(tsRuntimeTypecheck.isDefined(component), `Component "${current_workflow_key}" is missing from the workflow chain.`); | ||
tsRuntimeTypecheck.invariant(tsRuntimeTypecheck.isDefined(component.wid), `Component "${current_workflow_key}" does not have a workflow ID.`); | ||
if (component.next) { | ||
component_lifo.push(...Object.values(component.next)); | ||
} | ||
results.push({ | ||
component_id: component.wid, | ||
chain_id: current_workflow_key, | ||
report_name: (_a = component.component_name) !== null && _a !== void 0 ? _a : '', | ||
output: (_b = component.next) !== null && _b !== void 0 ? _b : {}, | ||
}); | ||
} | ||
return results; | ||
} | ||
const EXPIRATION_OFFSET = -5; | ||
function interval(duration, action) { | ||
let timer; | ||
let stopped = false; | ||
const interval = async () => { | ||
const start_time = Date.now(); | ||
try { | ||
await action(); | ||
} | ||
catch (_a) { | ||
} | ||
if (stopped) { | ||
return; | ||
} | ||
const delta = Date.now() - start_time; | ||
timer = setTimeout(interval, Math.max(duration - delta, 0)); | ||
}; | ||
void interval(); | ||
return () => { | ||
stopped = true; | ||
timer && clearTimeout(timer); | ||
}; | ||
} | ||
function create_telemetry_client(graphql_client, fetcher = fetch) { | ||
return { | ||
graphql_client, | ||
source_cache: new Map(), | ||
existence_cache: new Set(), | ||
fetch: fetcher, | ||
}; | ||
} | ||
function watch_report_available(tele_client, period, report_name, id_instance) { | ||
const watch = (listener) => { | ||
const stop = interval(period, async () => { | ||
const exists = await has_telemetry(tele_client, report_name, id_instance); | ||
if (exists) { | ||
stop(); | ||
} | ||
listener(exists); | ||
}); | ||
return stop; | ||
}; | ||
return { watch }; | ||
} | ||
function watch_report_content(tele_client, period, report_name, id_instance) { | ||
const watch = (listener) => { | ||
let version = ''; | ||
const stop = interval(period, async () => { | ||
const latest_version = await get_telemetry_version(tele_client, report_name, id_instance); | ||
if (latest_version === version) { | ||
return; | ||
} | ||
version = latest_version; | ||
const content = await get_telemetry_content(tele_client, report_name, id_instance); | ||
listener(content); | ||
}); | ||
return stop; | ||
}; | ||
return { watch }; | ||
} | ||
async function has_telemetry(telemetry_context, report_name, id_instance) { | ||
const cache_key = `${id_instance}:${report_name}`; | ||
if (telemetry_context.existence_cache.has(cache_key)) { | ||
return true; | ||
} | ||
const version = await get_telemetry_version(telemetry_context, report_name, id_instance); | ||
if (version === '') { | ||
return false; | ||
} | ||
telemetry_context.existence_cache.add(cache_key); | ||
return true; | ||
} | ||
async function get_telemetry_version(telemetry_context, report_name, id_instance) { | ||
const { fetch } = telemetry_context; | ||
const { head_url } = await get_telemetry_source(telemetry_context, report_name, id_instance); | ||
const response = await fetch(head_url, { method: 'head' }); | ||
if (response.status === 404) { | ||
return ''; | ||
} | ||
if (!response.ok) { | ||
throw new Error('Unable to retrieve report version'); | ||
} | ||
const etag = response.headers.get('etag'); | ||
if (!etag) { | ||
throw new Error('Server responded without etag on telemetry HEAD request'); | ||
} | ||
return etag; | ||
} | ||
async function get_telemetry_content(telemetry_context, report_name, id_instance) { | ||
const { fetch } = telemetry_context; | ||
const { get_url } = await get_telemetry_source(telemetry_context, report_name, id_instance); | ||
const response = await fetch(get_url, {}); | ||
if (response.status === 404) { | ||
return null; | ||
} | ||
if (!response.ok) { | ||
throw new Error('Unable to retrieve report content'); | ||
} | ||
return response.json(); | ||
} | ||
async function get_telemetry_source({ source_cache, graphql_client }, report_name, id_instance) { | ||
const cache_key = `${id_instance}:${report_name}`; | ||
const start_time = Date.now(); | ||
const cached_value = source_cache.get(cache_key); | ||
if (cached_value && cached_value.expires > start_time) { | ||
return cached_value; | ||
} | ||
const response = await graphql_client.query({ | ||
query: TELEMETRY_SOURCE_QUERY_DOCUMENT, | ||
variables: { id_instance, report: report_name }, | ||
fetchPolicy: 'no-cache' | ||
}); | ||
const { expiresIn: expires_in, getUrl: get_url, headUrl: head_url } = response.data.source; | ||
const expires = start_time + (expires_in + EXPIRATION_OFFSET) * 1000; | ||
const source = { | ||
expires, | ||
get_url, | ||
head_url, | ||
}; | ||
source_cache.set(cache_key, source); | ||
return source; | ||
} | ||
async function register_profile_common(code, description, endpoint, fetcher) { | ||
@@ -285,4 +495,10 @@ const url = new URL('apiaccess', resolve_endpoint(endpoint)); | ||
exports.create_mock_client = create_mock_client; | ||
exports.create_telemetry_client = create_telemetry_client; | ||
exports.get_report_components = get_report_components; | ||
exports.get_telemetry_content = get_telemetry_content; | ||
exports.has_telemetry = has_telemetry; | ||
exports.register_profile = register_profile; | ||
exports.resolve_endpoint = resolve_endpoint; | ||
exports.session_jwt = session_jwt; | ||
exports.watch_report_available = watch_report_available; | ||
exports.watch_report_content = watch_report_content; |
@@ -1,7 +0,7 @@ | ||
import { ApolloClient } from '@apollo/client/core'; | ||
import type { ErrorHandler } from '@apollo/client/link/error'; | ||
import type { Agent } from 'node:https'; | ||
import type { NodeClientJWTOptions, NodeClientOptions } from './ClientOptions.type'; | ||
export declare function create_epi2me_client_jwt(options: NodeClientJWTOptions, on_error?: ErrorHandler): ApolloClient<unknown>; | ||
export declare function create_epi2me_client(options: NodeClientOptions, on_error?: ErrorHandler): ApolloClient<unknown>; | ||
import type { GraphQLClient } from '../common/GraphQLClient.type'; | ||
export declare function create_epi2me_client_jwt(options: NodeClientJWTOptions, on_error?: ErrorHandler): GraphQLClient; | ||
export declare function create_epi2me_client(options: NodeClientOptions, on_error?: ErrorHandler): GraphQLClient; | ||
export declare const register_profile: (code: string, description: string, endpoint?: string, agent?: Agent) => Promise<import("../common/register_profile.type").CredentialsResponse>; | ||
@@ -8,0 +8,0 @@ export declare function authenticated_fetch(info: RequestInfo, init: RequestInit, options: NodeClientOptions): Promise<Response>; |
{ | ||
"name": "@metrichor/epi2me-client-node", | ||
"version": "0.2.10211410", | ||
"version": "0.2.10360724", | ||
"license": "MPL-2.0", | ||
@@ -12,4 +12,5 @@ "author": "Metrichor <support@nanoporetech.com>", | ||
"graphql": "^16.6.0", | ||
"node-fetch": "^3.3.0" | ||
"node-fetch": "^3.3.0", | ||
"ts-runtime-typecheck": "^2.6.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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
62446
54
1155
4
6
+ Addedts-runtime-typecheck@^2.6.0
+ Addedts-runtime-typecheck@2.6.0(transitive)