@openctx/client
Advanced tools
Comparing version 0.0.8 to 0.0.9
@@ -1,6 +0,6 @@ | ||
import type { AnnotationsParams, ItemsParams, ProviderSettings } from '@openctx/protocol'; | ||
import type { Annotation as AnnotationWithPlainRange, Item, Range } from '@openctx/schema'; | ||
import type { AnnotationsParams, CapabilitiesParams, CapabilitiesResult, ItemsParams, ItemsResult, MentionsParams, MentionsResult, ProviderSettings } from '@openctx/protocol'; | ||
import type { Annotation as AnnotationWithPlainRange, Range } from '@openctx/schema'; | ||
import { type Observable, type ObservableInput } from 'rxjs'; | ||
import type { ClientEnv } from './client/client'; | ||
import type { ProviderClient } from './providerClient/createProviderClient'; | ||
import type { ClientEnv } from './client/client.js'; | ||
import type { ProviderClient } from './providerClient/createProviderClient.js'; | ||
/** | ||
@@ -20,2 +20,3 @@ * An OpenCtx annotation. | ||
export interface ProviderClientWithSettings { | ||
uri: string; | ||
providerClient: ObservableProviderClient | ProviderClient; | ||
@@ -33,9 +34,23 @@ settings: ProviderSettings; | ||
/** | ||
* This type is used internally by the OpenCtx client to assign a provider URI to each item. | ||
*/ | ||
export type EachWithProviderUri<T extends unknown[]> = ((T extends readonly (infer ElementType)[] ? ElementType : never) & { | ||
providerUri: string; | ||
})[]; | ||
/** | ||
* Observes OpenCtx items kinds from the configured providers. | ||
*/ | ||
export declare function observeCapabilities(providerClients: Observable<ProviderClientWithSettings[]>, params: CapabilitiesParams, { logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions): Observable<EachWithProviderUri<CapabilitiesResult[]>>; | ||
/** | ||
* Observes OpenCtx candidate items from the configured providers. | ||
*/ | ||
export declare function observeMentions(providerClients: Observable<ProviderClientWithSettings[]>, params: MentionsParams, { logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions): Observable<EachWithProviderUri<MentionsResult>>; | ||
/** | ||
* Observes OpenCtx items from the configured providers. | ||
*/ | ||
export declare function observeItems(providerClients: Observable<ProviderClientWithSettings[]>, params: ItemsParams, { logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions): Observable<Item[]>; | ||
export declare function observeItems(providerClients: Observable<ProviderClientWithSettings[]>, params: ItemsParams, { logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions): Observable<EachWithProviderUri<ItemsResult>>; | ||
/** | ||
* Observes OpenCtx annotations from the configured providers. | ||
*/ | ||
export declare function observeAnnotations<R extends Range>(providerClients: Observable<ProviderClientWithSettings[]>, params: AnnotationsParams, { logger, makeRange, emitPartial }: Pick<ClientEnv<R>, 'logger' | 'makeRange'> & ObserveOptions): Observable<Annotation<R>[]>; | ||
export declare function observeAnnotations<R extends Range>(providerClients: Observable<ProviderClientWithSettings[]>, params: AnnotationsParams, { logger, makeRange, emitPartial }: Pick<ClientEnv<R>, 'logger' | 'makeRange'> & ObserveOptions): Observable<EachWithProviderUri<Annotation<R>[]>>; | ||
//# sourceMappingURL=api.d.ts.map |
import { catchError, combineLatest, defer, from, map, mergeMap, of, startWith, tap, } from 'rxjs'; | ||
function observeProviderCall(providerClients, fn, { emitPartial, logger }) { | ||
return providerClients.pipe(mergeMap(providerClients => providerClients && providerClients.length > 0 | ||
? combineLatest(providerClients.map(({ providerClient, settings }) => defer(() => fn({ providerClient, settings })).pipe(emitPartial ? startWith(null) : tap(), catchError(error => { | ||
? combineLatest(providerClients.map(({ uri, providerClient, settings }) => defer(() => fn({ uri, providerClient, settings })) | ||
.pipe(emitPartial ? startWith(null) : tap(), catchError(error => { | ||
logger?.(`failed to call provider: ${error}`); | ||
console.error(error); | ||
return of(null); | ||
})))) | ||
})) | ||
.pipe(map(items => (items || []).map(item => ({ ...item, providerUri: uri })))))) | ||
: of([])), map(result => result.filter((v) => v !== null).flat()), tap(items => { | ||
@@ -16,2 +18,14 @@ if (LOG_VERBOSE) { | ||
/** | ||
* Observes OpenCtx items kinds from the configured providers. | ||
*/ | ||
export function observeCapabilities(providerClients, params, { logger, emitPartial }) { | ||
return observeProviderCall(providerClients, ({ providerClient, settings }) => from(providerClient.capabilities(params, settings)).pipe(map(result => [result])), { logger, emitPartial }); | ||
} | ||
/** | ||
* Observes OpenCtx candidate items from the configured providers. | ||
*/ | ||
export function observeMentions(providerClients, params, { logger, emitPartial }) { | ||
return observeProviderCall(providerClients, ({ providerClient, settings }) => from(providerClient.mentions(params, settings)), { logger, emitPartial }); | ||
} | ||
/** | ||
* Observes OpenCtx items from the configured providers. | ||
@@ -18,0 +32,0 @@ */ |
@@ -1,8 +0,8 @@ | ||
import type { AnnotationsParams, ItemsParams } from '@openctx/protocol'; | ||
import type { AnnotationsParams, CapabilitiesParams, CapabilitiesResult, ItemsParams, ItemsResult, MentionsParams, MentionsResult } from '@openctx/protocol'; | ||
import type { Provider } from '@openctx/provider'; | ||
import type { Item, Range } from '@openctx/schema'; | ||
import type { Range } from '@openctx/schema'; | ||
import { type Observable, type ObservableInput } from 'rxjs'; | ||
import { type Annotation, type ObservableProviderClient } from '../api'; | ||
import { type ConfigurationUserInput } from '../configuration'; | ||
import type { Logger } from '../logger'; | ||
import { type Annotation, type EachWithProviderUri, type ObservableProviderClient } from '../api.js'; | ||
import { type ConfigurationUserInput } from '../configuration.js'; | ||
import type { Logger } from '../logger.js'; | ||
/** | ||
@@ -52,16 +52,6 @@ * Hooks for the OpenCtx {@link Client} to access information about the environment, such as | ||
*/ | ||
dynamicImportFromUri?: (uri: string) => Promise<{ | ||
importProvider?: (uri: string) => Promise<{ | ||
default: Provider; | ||
}>; | ||
/** | ||
* Called (if set) to dynamically import an OpenCtx provider from its ES module source | ||
* code. This can be used by runtimes that only support `require()` and CommonJS (such as VS | ||
* Code). | ||
*/ | ||
dynamicImportFromSource?: (uri: string, esmSource: string) => Promise<{ | ||
exports: { | ||
default: Provider; | ||
}; | ||
}>; | ||
/** | ||
* @internal | ||
@@ -90,2 +80,34 @@ */ | ||
/** | ||
* Get the capabilities returned by the configured providers. | ||
* | ||
* It does not continue to listen for changes, as {@link Client.capabilitiesChanges} does. Using | ||
* {@link Client.capabilities} is simpler and does not require use of observables (with a library like | ||
* RxJS), but it means that the client needs to manually poll for updated item kinds if freshness is | ||
* important. | ||
*/ | ||
capabilities(params: CapabilitiesParams, providerUri?: string): Promise<EachWithProviderUri<CapabilitiesResult[]>>; | ||
/** | ||
* Observe OpenCtx capabilities of the configured providers. | ||
* | ||
* The returned observable streams capabilities as they are received from the providers and continues | ||
* passing along any updates until unsubscribed. | ||
*/ | ||
capabilitiesChanges(params: CapabilitiesParams, providerUri?: string): Observable<EachWithProviderUri<CapabilitiesResult[]>>; | ||
/** | ||
* Get the candidate items returned by the configured providers. | ||
* | ||
* It does not continue to listen for changes, as {@link Client.mentionsChanges} does. Using | ||
* {@link Client.Mentions} is simpler and does not require use of observables (with a library like | ||
* RxJS), but it means that the client needs to manually poll for updated items if freshness is | ||
* important. | ||
*/ | ||
mentions(params: MentionsParams, providerUri?: string): Promise<EachWithProviderUri<MentionsResult>>; | ||
/** | ||
* Observe OpenCtx candidate items from the configured providers. | ||
* | ||
* The returned observable streams candidate items as they are received from the providers and continues | ||
* passing along any updates until unsubscribed. | ||
*/ | ||
mentionsChanges(params: MentionsParams, providerUri?: string): Observable<EachWithProviderUri<MentionsResult>>; | ||
/** | ||
* Get the items returned by the configured providers. | ||
@@ -98,3 +120,3 @@ * | ||
*/ | ||
items(params: ItemsParams): Promise<Item[]>; | ||
items(params: ItemsParams, providerUri?: string): Promise<EachWithProviderUri<ItemsResult>>; | ||
/** | ||
@@ -106,3 +128,3 @@ * Observe OpenCtx items from the configured providers. | ||
*/ | ||
itemsChanges(params: ItemsParams): Observable<Item[]>; | ||
itemsChanges(params: ItemsParams, providerUri?: string): Observable<EachWithProviderUri<ItemsResult>>; | ||
/** | ||
@@ -116,3 +138,3 @@ * Get the annotations returned by the configured providers for the given resource. | ||
*/ | ||
annotations(params: AnnotationsParams): Promise<Annotation<R>[]>; | ||
annotations(params: AnnotationsParams, providerUri?: string): Promise<EachWithProviderUri<Annotation<R>[]>>; | ||
/** | ||
@@ -124,3 +146,3 @@ * Observe OpenCtx annotations from the configured providers for the given resource. | ||
*/ | ||
annotationsChanges(params: AnnotationsParams): Observable<Annotation<R>[]>; | ||
annotationsChanges(params: AnnotationsParams, providerUri?: string): Observable<EachWithProviderUri<Annotation<R>[]>>; | ||
/** | ||
@@ -127,0 +149,0 @@ * Dispose of the client and release all resources. |
import { LRUCache } from 'lru-cache'; | ||
import { catchError, combineLatest, distinctUntilChanged, firstValueFrom, from, map, mergeMap, of, shareReplay, } from 'rxjs'; | ||
import { observeAnnotations, observeItems, } from '../api'; | ||
import { configurationFromUserInput } from '../configuration'; | ||
import { createProviderClient } from '../providerClient/createProviderClient'; | ||
import { observeAnnotations, observeCapabilities, observeItems, observeMentions, } from '../api.js'; | ||
import { configurationFromUserInput } from '../configuration.js'; | ||
import { createProviderClient } from '../providerClient/createProviderClient.js'; | ||
/** | ||
@@ -36,2 +36,3 @@ * Create a new OpenCtx client. | ||
? combineLatest(configuration.providers.map(({ providerUri, settings }) => (env.authInfo ? from(env.authInfo(providerUri)) : of(null)).pipe(map(authInfo => ({ | ||
uri: providerUri, | ||
providerClient: env.__mock__?.getProviderClient | ||
@@ -42,4 +43,3 @@ ? env.__mock__.getProviderClient() | ||
logger, | ||
dynamicImportFromUri: env.dynamicImportFromUri, | ||
dynamicImportFromSource: env.dynamicImportFromSource, | ||
importProvider: env.importProvider, | ||
}), | ||
@@ -55,4 +55,10 @@ settings, | ||
} | ||
const itemsChanges = (params, { emitPartial }) => { | ||
return observeItems(providerClientsWithSettings(undefined), params, { | ||
const filterProviders = (providersObservable, uri) => { | ||
if (!uri) { | ||
return providersObservable; | ||
} | ||
return providersObservable.pipe(map(providers => providers.filter(provider => provider.uri === uri))); | ||
}; | ||
const capabilitiesChanges = (params, { emitPartial }, providerUri) => { | ||
return observeCapabilities(filterProviders(providerClientsWithSettings(undefined), providerUri), params, { | ||
logger: env.logger, | ||
@@ -62,5 +68,17 @@ emitPartial, | ||
}; | ||
const annotationsChanges = (params, { emitPartial }) => { | ||
return observeAnnotations(providerClientsWithSettings(params.uri), params, { | ||
const mentionsChanges = (params, { emitPartial }, providerUri) => { | ||
return observeMentions(filterProviders(providerClientsWithSettings(undefined), providerUri), params, { | ||
logger: env.logger, | ||
emitPartial, | ||
}); | ||
}; | ||
const itemsChanges = (params, { emitPartial }, providerUri) => { | ||
return observeItems(filterProviders(providerClientsWithSettings(undefined), providerUri), params, { | ||
logger: env.logger, | ||
emitPartial, | ||
}); | ||
}; | ||
const annotationsChanges = (params, { emitPartial }, providerUri) => { | ||
return observeAnnotations(filterProviders(providerClientsWithSettings(params.uri), providerUri), params, { | ||
logger: env.logger, | ||
makeRange: env.makeRange, | ||
@@ -71,6 +89,16 @@ emitPartial, | ||
return { | ||
items: params => firstValueFrom(itemsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
itemsChanges: params => itemsChanges(params, { emitPartial: true }), | ||
annotations: params => firstValueFrom(annotationsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
annotationsChanges: params => annotationsChanges(params, { emitPartial: true }), | ||
capabilities: (params, providerUri) => firstValueFrom(capabilitiesChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
capabilitiesChanges: (params, providerUri) => capabilitiesChanges(params, { emitPartial: true }, providerUri), | ||
mentions: params => firstValueFrom(mentionsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
mentionsChanges: (params, providerUri) => mentionsChanges(params, { emitPartial: true }, providerUri), | ||
items: (params, providerUri) => firstValueFrom(itemsChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
itemsChanges: (params, providerUri) => itemsChanges(params, { emitPartial: true }, providerUri), | ||
annotations: (params, providerUri) => firstValueFrom(annotationsChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
annotationsChanges: (params, providerUri) => annotationsChanges(params, { emitPartial: true }, providerUri), | ||
dispose() { | ||
@@ -102,4 +130,3 @@ for (const sub of subscriptions) { | ||
logger: env.logger, | ||
dynamicImportFromUri: env.dynamicImportFromUri, | ||
dynamicImportFromSource: env.dynamicImportFromSource, | ||
importProvider: env.importProvider, | ||
}); | ||
@@ -106,0 +133,0 @@ cache.set(cacheKey(key), c); |
export type * from '@openctx/protocol'; | ||
export type { Provider } from '@openctx/provider'; | ||
export type * from '@openctx/schema'; | ||
export { observeItems, type Annotation } from './api'; | ||
export { createClient, type AuthInfo, type Client } from './client/client'; | ||
export { type ConfigurationUserInput as ClientConfiguration } from './configuration'; | ||
export type { Logger } from './logger'; | ||
export { observeItems, type Annotation, type EachWithProviderUri } from './api.js'; | ||
export { createClient, type AuthInfo, type Client, type ClientEnv } from './client/client.js'; | ||
export { type ConfigurationUserInput as ClientConfiguration } from './configuration.js'; | ||
export type { Logger } from './logger.js'; | ||
export { fetchProviderSource } from './providerClient/transport/module.js'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,3 +0,4 @@ | ||
export { observeItems } from './api'; | ||
export { createClient } from './client/client'; | ||
export { observeItems } from './api.js'; | ||
export { createClient } from './client/client.js'; | ||
export { fetchProviderSource } from './providerClient/transport/module.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -1,3 +0,3 @@ | ||
import type { AnnotationsParams, AnnotationsResult, ItemsParams, ItemsResult, ProviderSettings } from '@openctx/protocol'; | ||
import { type ProviderTransportOptions } from './transport/createTransport'; | ||
import type { AnnotationsParams, AnnotationsResult, CapabilitiesResult, ItemsParams, ItemsResult, MentionsParams, MentionsResult, ProviderSettings } from '@openctx/protocol'; | ||
import { type ProviderTransportOptions } from './transport/createTransport.js'; | ||
/** | ||
@@ -8,2 +8,6 @@ * A {@link ProviderClient} communicates with a single OpenCtx provider. It is stateless and | ||
export interface ProviderClient { | ||
/** Get capabilities from the provider. */ | ||
capabilities(params: MentionsParams, settings: ProviderSettings): Promise<CapabilitiesResult>; | ||
/** Get candidate items from the provider. */ | ||
mentions(params: MentionsParams, settings: ProviderSettings): Promise<MentionsResult | null>; | ||
/** Get items from the provider. */ | ||
@@ -18,3 +22,3 @@ items(params: ItemsParams, settings: ProviderSettings): Promise<ItemsResult | null>; | ||
} | ||
export interface ProviderClientOptions extends Pick<ProviderTransportOptions, 'providerBaseUri' | 'authInfo' | 'logger' | 'dynamicImportFromUri' | 'dynamicImportFromSource'> { | ||
export interface ProviderClientOptions extends Pick<ProviderTransportOptions, 'providerBaseUri' | 'authInfo' | 'logger' | 'importProvider'> { | ||
} | ||
@@ -21,0 +25,0 @@ /** |
@@ -1,4 +0,4 @@ | ||
import { scopedLogger } from '../logger'; | ||
import { matchSelectors } from './selector'; | ||
import { createTransport } from './transport/createTransport'; | ||
import { scopedLogger } from '../logger.js'; | ||
import { matchSelectors } from './selector.js'; | ||
import { createTransport } from './transport/createTransport.js'; | ||
/** | ||
@@ -11,2 +11,20 @@ * Create a new {@link ProviderClient}. | ||
return { | ||
async capabilities(params, settings) { | ||
try { | ||
return await transport.capabilities(params, settings); | ||
} | ||
catch (error) { | ||
logger?.(`failed to get capabilities: ${error}`); | ||
return Promise.reject(error); | ||
} | ||
}, | ||
async mentions(params, settings) { | ||
try { | ||
return await transport.mentions(params, settings); | ||
} | ||
catch (error) { | ||
logger?.(`failed to get mentions: ${error}`); | ||
return Promise.reject(error); | ||
} | ||
}, | ||
async items(params, settings) { | ||
@@ -13,0 +31,0 @@ try { |
// | ||
// Import from a subpackage because the main module calls `os.platform()`, which doesn't work on | ||
// non-Node engines. | ||
import match from 'picomatch/lib/picomatch'; | ||
import match from 'picomatch/lib/picomatch.js'; | ||
/** | ||
@@ -6,0 +6,0 @@ * Creates a function that matches the request parameters against the selector. See {@link Selector} |
@@ -1,3 +0,3 @@ | ||
import type { ProviderTransport } from './createTransport'; | ||
import type { ProviderTransport } from './createTransport.js'; | ||
export declare function cachedTransport(provider: ProviderTransport): ProviderTransport; | ||
//# sourceMappingURL=cache.d.ts.map |
@@ -8,3 +8,3 @@ import { LRUCache } from 'lru-cache'; | ||
function cachedMethodCall(method, args, fn) { | ||
const fullKey = `${method}:${JSON.stringify(args)}`; | ||
const fullKey = `${String(method)}:${JSON.stringify(args)}`; | ||
const entry = cache.get(fullKey); | ||
@@ -21,2 +21,3 @@ if (entry) { | ||
capabilities: (...args) => cachedMethodCall('capabilities', args, (params, settings) => provider.capabilities(params, settings)), | ||
mentions: (...args) => cachedMethodCall('mentions', args, (params, settings) => provider.mentions(params, settings)), | ||
items: (...args) => cachedMethodCall('items', args, (params, settings) => provider.items(params, settings)), | ||
@@ -23,0 +24,0 @@ annotations: (...args) => cachedMethodCall('annotations', args, (params, settings) => provider.annotations(params, settings)), |
@@ -1,4 +0,4 @@ | ||
import type { Provider } from '@openctx/provider/src/provider'; | ||
import type { AuthInfo, ClientEnv } from '../../client/client'; | ||
import type { Logger } from '../../logger'; | ||
import type { Provider } from '@openctx/provider'; | ||
import type { AuthInfo, ClientEnv } from '../../client/client.js'; | ||
import type { Logger } from '../../logger.js'; | ||
/** | ||
@@ -15,3 +15,3 @@ * A provider transport is a low-level TypeScript wrapper around the provider protocol. It is a | ||
}; | ||
export interface ProviderTransportOptions extends Pick<ClientEnv<any>, 'providerBaseUri' | 'dynamicImportFromUri' | 'dynamicImportFromSource'> { | ||
export interface ProviderTransportOptions extends Pick<ClientEnv<any>, 'providerBaseUri' | 'importProvider'> { | ||
authInfo?: AuthInfo; | ||
@@ -22,3 +22,3 @@ cache?: boolean; | ||
/** | ||
* Create a transport that communicates with a provider URI using the provider API. | ||
* Create a transport that communicates with a provider using the provider API. | ||
* | ||
@@ -25,0 +25,0 @@ * @internal |
@@ -1,6 +0,6 @@ | ||
import { cachedTransport } from './cache'; | ||
import { createHttpTransport } from './http'; | ||
import { createLocalModuleFileTransport, createRemoteModuleFileTransport } from './module'; | ||
import { cachedTransport } from './cache.js'; | ||
import { createHttpTransport } from './http.js'; | ||
import { createModuleTransport } from './module.js'; | ||
/** | ||
* Create a transport that communicates with a provider URI using the provider API. | ||
* Create a transport that communicates with a provider using the provider API. | ||
* | ||
@@ -12,19 +12,11 @@ * @internal | ||
let url = new URL(providerUri, options.providerBaseUri); | ||
if (url.protocol === 'file:' || | ||
(runtimeSupportsImportFromUrl() && isRemoteJavaScriptFile(url))) { | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url); | ||
} | ||
return createLocalModuleFileTransport(url.toString(), options); | ||
if (isHttpOrHttps(url) && !isRemoteJavaScriptFile(url)) { | ||
// Provider is an HTTP endpoint. | ||
return createHttpTransport(providerUri, options); | ||
} | ||
if (isRemoteJavaScriptFile(url)) { | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url); | ||
} | ||
return createRemoteModuleFileTransport(url.toString(), options); | ||
// Provider is a JavaScript module. | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url); | ||
} | ||
if (isHttpOrHttps(url)) { | ||
return createHttpTransport(providerUri, options); | ||
} | ||
throw new Error(`Unsupported OpenCtx provider URI: ${providerUri}`); | ||
return createModuleTransport(url.toString(), options); | ||
} | ||
@@ -60,11 +52,2 @@ let provider = doResolveProvider(providerUri); | ||
} | ||
function runtimeSupportsImportFromUrl() { | ||
// `import('https://...')` is not supported natively in Node.js; see | ||
// https://nodejs.org/api/esm.html#urls. | ||
// | ||
// TODO(sqs): this is hacky and not correct in general | ||
// | ||
// @ts-ignore | ||
return typeof window !== 'undefined'; | ||
} | ||
//# sourceMappingURL=createTransport.js.map |
@@ -1,3 +0,3 @@ | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport'; | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport.js'; | ||
export declare function createHttpTransport(providerUri: string, { authInfo, logger }: Pick<ProviderTransportOptions, 'authInfo' | 'logger'>): ProviderTransport; | ||
//# sourceMappingURL=http.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { scopedLogger } from '../../logger'; | ||
import { scopedLogger } from '../../logger.js'; | ||
export function createHttpTransport(providerUri, { authInfo, logger }) { | ||
@@ -64,2 +64,3 @@ logger = scopedLogger(logger, 'http'); | ||
capabilities: async (params) => send({ method: 'capabilities', params }), | ||
mentions: async (params) => send({ method: 'mentions', params }), | ||
items: async (params) => send({ method: 'items', params }), | ||
@@ -66,0 +67,0 @@ annotations: async (params) => send({ method: 'annotations', params }), |
@@ -1,4 +0,4 @@ | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport'; | ||
export declare function createRemoteModuleFileTransport(providerUri: string, { dynamicImportFromUri, dynamicImportFromSource, }: Pick<ProviderTransportOptions, 'dynamicImportFromUri' | 'dynamicImportFromSource'>): ProviderTransport; | ||
export declare function createLocalModuleFileTransport(moduleUrl: string, { dynamicImportFromUri }: Pick<ProviderTransportOptions, 'dynamicImportFromUri'>): ProviderTransport; | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport.js'; | ||
export declare function createModuleTransport(providerUri: string, { importProvider }: Pick<ProviderTransportOptions, 'importProvider'>): ProviderTransport; | ||
export declare function fetchProviderSource(providerUri: string): Promise<string>; | ||
//# sourceMappingURL=module.d.ts.map |
@@ -1,48 +0,19 @@ | ||
export function createRemoteModuleFileTransport(providerUri, { dynamicImportFromUri, dynamicImportFromSource, }) { | ||
return lazyProvider(dynamicImportFromUri | ||
? dynamicImportFromUri(providerUri).then(mod => providerFromModule(mod)) | ||
: fetch(providerUri).then(async (resp) => { | ||
if (!resp.ok) { | ||
throw new Error(`OpenCtx remote provider module URL ${providerUri} responded with HTTP error ${resp.status} ${resp.statusText}`); | ||
} | ||
const contentType = resp.headers.get('Content-Type')?.trim()?.replace(/;.*$/, ''); | ||
if (!contentType || | ||
(contentType !== 'text/javascript' && | ||
contentType !== 'application/javascript' && | ||
contentType !== 'text/plain')) { | ||
throw new Error(`OpenCtx remote provider module URL ${providerUri} reported invalid Content-Type ${JSON.stringify(contentType)} (expected "text/javascript" or "text/plain")`); | ||
} | ||
const moduleSource = await resp.text(); | ||
try { | ||
const mod = await importModuleFromString(providerUri, moduleSource, dynamicImportFromSource); | ||
return providerFromModule(mod); | ||
} | ||
catch (error) { | ||
console.log(error); | ||
throw error; | ||
} | ||
})); | ||
export function createModuleTransport(providerUri, { importProvider }) { | ||
return lazyProvider((importProvider ? importProvider(providerUri) : import(/* @vite-ignore */ providerUri)).then(mod => providerFromModule(mod))); | ||
} | ||
export function createLocalModuleFileTransport(moduleUrl, { dynamicImportFromUri }) { | ||
return lazyProvider((dynamicImportFromUri | ||
? dynamicImportFromUri(moduleUrl) | ||
: import(/* @vite-ignore */ moduleUrl)).then(providerFromModule)); | ||
} | ||
async function importModuleFromString(uri, source, dynamicImportFromSource) { | ||
if (dynamicImportFromSource) { | ||
return (await dynamicImportFromSource(uri, source)).exports; | ||
export async function fetchProviderSource(providerUri) { | ||
const resp = await fetch(providerUri); | ||
if (!resp.ok) { | ||
throw new Error(`OpenCtx remote provider module URL ${providerUri} responded with HTTP error ${resp.status} ${resp.statusText}`); | ||
} | ||
// Note: Used by VS Code Web. | ||
const url = `data:text/javascript;charset=utf-8;base64,${base64Encode(source)}`; | ||
return import(/* @vite-ignore */ url); | ||
const contentType = resp.headers.get('Content-Type')?.trim()?.replace(/;.*$/, ''); | ||
if (!contentType || | ||
(contentType !== 'text/javascript' && | ||
contentType !== 'application/javascript' && | ||
contentType !== 'text/plain')) { | ||
throw new Error(`OpenCtx remote provider module URL ${providerUri} reported invalid Content-Type ${JSON.stringify(contentType)} (expected "text/javascript" or "text/plain")`); | ||
} | ||
const moduleSource = await resp.text(); | ||
return moduleSource; | ||
} | ||
/** | ||
* See https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem for why we need | ||
* something other than just `btoa` for base64 encoding. | ||
*/ | ||
function base64Encode(text) { | ||
const bytes = new TextEncoder().encode(text); | ||
const binString = String.fromCodePoint(...bytes); | ||
return btoa(binString); | ||
} | ||
function providerFromModule(providerModule) { | ||
@@ -58,2 +29,3 @@ let impl = providerModule.default; | ||
capabilities: async (params, settings) => (await provider).capabilities(params, settings), | ||
mentions: async (params, settings) => (await provider).mentions?.(params, settings) ?? [], | ||
items: async (params, settings) => (await provider).items?.(params, settings) ?? [], | ||
@@ -60,0 +32,0 @@ annotations: async (params, settings) => (await provider).annotations?.(params, settings) ?? [], |
{ | ||
"name": "@openctx/client", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "OpenCtx client library", | ||
@@ -25,5 +25,5 @@ "license": "Apache-2.0", | ||
"rxjs": "^7.8.1", | ||
"@openctx/protocol": "0.0.7", | ||
"@openctx/provider": "0.0.7", | ||
"@openctx/schema": "0.0.6" | ||
"@openctx/protocol": "0.0.8", | ||
"@openctx/provider": "0.0.8", | ||
"@openctx/schema": "0.0.7" | ||
}, | ||
@@ -30,0 +30,0 @@ "devDependencies": { |
@@ -1,3 +0,12 @@ | ||
import type { AnnotationsParams, ItemsParams, ProviderSettings } from '@openctx/protocol' | ||
import type { Annotation as AnnotationWithPlainRange, Item, Range } from '@openctx/schema' | ||
import type { | ||
AnnotationsParams, | ||
CapabilitiesParams, | ||
CapabilitiesResult, | ||
ItemsParams, | ||
ItemsResult, | ||
MentionsParams, | ||
MentionsResult, | ||
ProviderSettings, | ||
} from '@openctx/protocol' | ||
import type { Annotation as AnnotationWithPlainRange, Range } from '@openctx/schema' | ||
import { | ||
@@ -16,4 +25,4 @@ type Observable, | ||
} from 'rxjs' | ||
import type { ClientEnv } from './client/client' | ||
import type { ProviderClient } from './providerClient/createProviderClient' | ||
import type { ClientEnv } from './client/client.js' | ||
import type { ProviderClient } from './providerClient/createProviderClient.js' | ||
@@ -38,2 +47,3 @@ /** | ||
export interface ProviderClientWithSettings { | ||
uri: string | ||
providerClient: ObservableProviderClient | ProviderClient | ||
@@ -52,2 +62,11 @@ settings: ProviderSettings | ||
/** | ||
* This type is used internally by the OpenCtx client to assign a provider URI to each item. | ||
*/ | ||
export type EachWithProviderUri<T extends unknown[]> = ((T extends readonly (infer ElementType)[] | ||
? ElementType | ||
: never) & { | ||
providerUri: string | ||
})[] | ||
function observeProviderCall<R>( | ||
@@ -57,3 +76,3 @@ providerClients: Observable<ProviderClientWithSettings[]>, | ||
{ emitPartial, logger }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions | ||
): Observable<R[]> { | ||
): Observable<EachWithProviderUri<R[]>> { | ||
return providerClients.pipe( | ||
@@ -63,11 +82,17 @@ mergeMap(providerClients => | ||
? combineLatest( | ||
providerClients.map(({ providerClient, settings }) => | ||
defer(() => fn({ providerClient, settings })).pipe( | ||
emitPartial ? startWith(null) : tap(), | ||
catchError(error => { | ||
logger?.(`failed to call provider: ${error}`) | ||
console.error(error) | ||
return of(null) | ||
}) | ||
) | ||
providerClients.map(({ uri, providerClient, settings }) => | ||
defer(() => fn({ uri, providerClient, settings })) | ||
.pipe( | ||
emitPartial ? startWith(null) : tap(), | ||
catchError(error => { | ||
logger?.(`failed to call provider: ${error}`) | ||
console.error(error) | ||
return of(null) | ||
}) | ||
) | ||
.pipe( | ||
map(items => | ||
(items || []).map(item => ({ ...item, providerUri: uri })) | ||
) | ||
) | ||
) | ||
@@ -77,3 +102,3 @@ ) | ||
), | ||
map(result => result.filter((v): v is R[] => v !== null).flat()), | ||
map(result => result.filter((v): v is EachWithProviderUri<R[]> => v !== null).flat()), | ||
tap(items => { | ||
@@ -88,2 +113,33 @@ if (LOG_VERBOSE) { | ||
/** | ||
* Observes OpenCtx items kinds from the configured providers. | ||
*/ | ||
export function observeCapabilities( | ||
providerClients: Observable<ProviderClientWithSettings[]>, | ||
params: CapabilitiesParams, | ||
{ logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions | ||
): Observable<EachWithProviderUri<CapabilitiesResult[]>> { | ||
return observeProviderCall<CapabilitiesResult>( | ||
providerClients, | ||
({ providerClient, settings }) => | ||
from(providerClient.capabilities(params, settings)).pipe(map(result => [result])), | ||
{ logger, emitPartial } | ||
) | ||
} | ||
/** | ||
* Observes OpenCtx candidate items from the configured providers. | ||
*/ | ||
export function observeMentions( | ||
providerClients: Observable<ProviderClientWithSettings[]>, | ||
params: MentionsParams, | ||
{ logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions | ||
): Observable<EachWithProviderUri<MentionsResult>> { | ||
return observeProviderCall( | ||
providerClients, | ||
({ providerClient, settings }) => from(providerClient.mentions(params, settings)), | ||
{ logger, emitPartial } | ||
) | ||
} | ||
/** | ||
* Observes OpenCtx items from the configured providers. | ||
@@ -95,3 +151,3 @@ */ | ||
{ logger, emitPartial }: Pick<ClientEnv<never>, 'logger'> & ObserveOptions | ||
): Observable<Item[]> { | ||
): Observable<EachWithProviderUri<ItemsResult>> { | ||
return observeProviderCall( | ||
@@ -111,3 +167,3 @@ providerClients, | ||
{ logger, makeRange, emitPartial }: Pick<ClientEnv<R>, 'logger' | 'makeRange'> & ObserveOptions | ||
): Observable<Annotation<R>[]> { | ||
): Observable<EachWithProviderUri<Annotation<R>[]>> { | ||
return observeProviderCall( | ||
@@ -114,0 +170,0 @@ providerClients, |
@@ -1,4 +0,12 @@ | ||
import type { AnnotationsParams, ItemsParams } from '@openctx/protocol' | ||
import type { | ||
AnnotationsParams, | ||
CapabilitiesParams, | ||
CapabilitiesResult, | ||
ItemsParams, | ||
ItemsResult, | ||
MentionsParams, | ||
MentionsResult, | ||
} from '@openctx/protocol' | ||
import type { Provider } from '@openctx/provider' | ||
import type { Item, Range } from '@openctx/schema' | ||
import type { Range } from '@openctx/schema' | ||
import { LRUCache } from 'lru-cache' | ||
@@ -21,2 +29,3 @@ import { | ||
type Annotation, | ||
type EachWithProviderUri, | ||
type ObservableProviderClient, | ||
@@ -26,7 +35,9 @@ type ObserveOptions, | ||
observeAnnotations, | ||
observeCapabilities, | ||
observeItems, | ||
} from '../api' | ||
import { type ConfigurationUserInput, configurationFromUserInput } from '../configuration' | ||
import type { Logger } from '../logger' | ||
import { type ProviderClient, createProviderClient } from '../providerClient/createProviderClient' | ||
observeMentions, | ||
} from '../api.js' | ||
import { type ConfigurationUserInput, configurationFromUserInput } from '../configuration.js' | ||
import type { Logger } from '../logger.js' | ||
import { type ProviderClient, createProviderClient } from '../providerClient/createProviderClient.js' | ||
@@ -82,15 +93,5 @@ /** | ||
*/ | ||
dynamicImportFromUri?: (uri: string) => Promise<{ default: Provider }> | ||
importProvider?: (uri: string) => Promise<{ default: Provider }> | ||
/** | ||
* Called (if set) to dynamically import an OpenCtx provider from its ES module source | ||
* code. This can be used by runtimes that only support `require()` and CommonJS (such as VS | ||
* Code). | ||
*/ | ||
dynamicImportFromSource?: ( | ||
uri: string, | ||
esmSource: string | ||
) => Promise<{ exports: { default: Provider } }> | ||
/** | ||
* @internal | ||
@@ -119,2 +120,47 @@ */ | ||
/** | ||
* Get the capabilities returned by the configured providers. | ||
* | ||
* It does not continue to listen for changes, as {@link Client.capabilitiesChanges} does. Using | ||
* {@link Client.capabilities} is simpler and does not require use of observables (with a library like | ||
* RxJS), but it means that the client needs to manually poll for updated item kinds if freshness is | ||
* important. | ||
*/ | ||
capabilities( | ||
params: CapabilitiesParams, | ||
providerUri?: string | ||
): Promise<EachWithProviderUri<CapabilitiesResult[]>> | ||
/** | ||
* Observe OpenCtx capabilities of the configured providers. | ||
* | ||
* The returned observable streams capabilities as they are received from the providers and continues | ||
* passing along any updates until unsubscribed. | ||
*/ | ||
capabilitiesChanges( | ||
params: CapabilitiesParams, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<CapabilitiesResult[]>> | ||
/** | ||
* Get the candidate items returned by the configured providers. | ||
* | ||
* It does not continue to listen for changes, as {@link Client.mentionsChanges} does. Using | ||
* {@link Client.Mentions} is simpler and does not require use of observables (with a library like | ||
* RxJS), but it means that the client needs to manually poll for updated items if freshness is | ||
* important. | ||
*/ | ||
mentions(params: MentionsParams, providerUri?: string): Promise<EachWithProviderUri<MentionsResult>> | ||
/** | ||
* Observe OpenCtx candidate items from the configured providers. | ||
* | ||
* The returned observable streams candidate items as they are received from the providers and continues | ||
* passing along any updates until unsubscribed. | ||
*/ | ||
mentionsChanges( | ||
params: MentionsParams, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<MentionsResult>> | ||
/** | ||
* Get the items returned by the configured providers. | ||
@@ -127,3 +173,3 @@ * | ||
*/ | ||
items(params: ItemsParams): Promise<Item[]> | ||
items(params: ItemsParams, providerUri?: string): Promise<EachWithProviderUri<ItemsResult>> | ||
@@ -136,3 +182,3 @@ /** | ||
*/ | ||
itemsChanges(params: ItemsParams): Observable<Item[]> | ||
itemsChanges(params: ItemsParams, providerUri?: string): Observable<EachWithProviderUri<ItemsResult>> | ||
@@ -147,3 +193,6 @@ /** | ||
*/ | ||
annotations(params: AnnotationsParams): Promise<Annotation<R>[]> | ||
annotations( | ||
params: AnnotationsParams, | ||
providerUri?: string | ||
): Promise<EachWithProviderUri<Annotation<R>[]>> | ||
@@ -156,3 +205,6 @@ /** | ||
*/ | ||
annotationsChanges(params: AnnotationsParams): Observable<Annotation<R>[]> | ||
annotationsChanges( | ||
params: AnnotationsParams, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<Annotation<R>[]>> | ||
@@ -211,2 +263,3 @@ /** | ||
map(authInfo => ({ | ||
uri: providerUri, | ||
providerClient: env.__mock__?.getProviderClient | ||
@@ -219,5 +272,3 @@ ? env.__mock__.getProviderClient() | ||
logger, | ||
dynamicImportFromUri: env.dynamicImportFromUri, | ||
dynamicImportFromSource: | ||
env.dynamicImportFromSource, | ||
importProvider: env.importProvider, | ||
} | ||
@@ -249,27 +300,98 @@ ), | ||
const itemsChanges = (params: ItemsParams, { emitPartial }: ObserveOptions): Observable<Item[]> => { | ||
return observeItems(providerClientsWithSettings(undefined), params, { | ||
logger: env.logger, | ||
emitPartial, | ||
}) | ||
const filterProviders = ( | ||
providersObservable: Observable<ProviderClientWithSettings[]>, | ||
uri?: string | ||
): Observable<ProviderClientWithSettings[]> => { | ||
if (!uri) { | ||
return providersObservable | ||
} | ||
return providersObservable.pipe( | ||
map(providers => providers.filter(provider => provider.uri === uri)) | ||
) | ||
} | ||
const capabilitiesChanges = ( | ||
params: CapabilitiesParams, | ||
{ emitPartial }: ObserveOptions, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<CapabilitiesResult[]>> => { | ||
return observeCapabilities( | ||
filterProviders(providerClientsWithSettings(undefined), providerUri), | ||
params, | ||
{ | ||
logger: env.logger, | ||
emitPartial, | ||
} | ||
) | ||
} | ||
const mentionsChanges = ( | ||
params: MentionsParams, | ||
{ emitPartial }: ObserveOptions, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<MentionsResult>> => { | ||
return observeMentions( | ||
filterProviders(providerClientsWithSettings(undefined), providerUri), | ||
params, | ||
{ | ||
logger: env.logger, | ||
emitPartial, | ||
} | ||
) | ||
} | ||
const itemsChanges = ( | ||
params: ItemsParams, | ||
{ emitPartial }: ObserveOptions, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<ItemsResult>> => { | ||
return observeItems( | ||
filterProviders(providerClientsWithSettings(undefined), providerUri), | ||
params, | ||
{ | ||
logger: env.logger, | ||
emitPartial, | ||
} | ||
) | ||
} | ||
const annotationsChanges = ( | ||
params: AnnotationsParams, | ||
{ emitPartial }: ObserveOptions | ||
): Observable<Annotation<R>[]> => { | ||
return observeAnnotations(providerClientsWithSettings(params.uri), params, { | ||
logger: env.logger, | ||
makeRange: env.makeRange, | ||
emitPartial, | ||
}) | ||
{ emitPartial }: ObserveOptions, | ||
providerUri?: string | ||
): Observable<EachWithProviderUri<Annotation<R>[]>> => { | ||
return observeAnnotations( | ||
filterProviders(providerClientsWithSettings(params.uri), providerUri), | ||
params, | ||
{ | ||
logger: env.logger, | ||
makeRange: env.makeRange, | ||
emitPartial, | ||
} | ||
) | ||
} | ||
return { | ||
items: params => | ||
firstValueFrom(itemsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
itemsChanges: params => itemsChanges(params, { emitPartial: true }), | ||
annotations: params => | ||
firstValueFrom(annotationsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
annotationsChanges: params => annotationsChanges(params, { emitPartial: true }), | ||
capabilities: (params, providerUri) => | ||
firstValueFrom(capabilitiesChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
capabilitiesChanges: (params, providerUri) => | ||
capabilitiesChanges(params, { emitPartial: true }, providerUri), | ||
mentions: params => | ||
firstValueFrom(mentionsChanges(params, { emitPartial: false }), { defaultValue: [] }), | ||
mentionsChanges: (params, providerUri) => | ||
mentionsChanges(params, { emitPartial: true }, providerUri), | ||
items: (params, providerUri) => | ||
firstValueFrom(itemsChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
itemsChanges: (params, providerUri) => itemsChanges(params, { emitPartial: true }, providerUri), | ||
annotations: (params, providerUri) => | ||
firstValueFrom(annotationsChanges(params, { emitPartial: false }, providerUri), { | ||
defaultValue: [], | ||
}), | ||
annotationsChanges: (params, providerUri) => | ||
annotationsChanges(params, { emitPartial: true }, providerUri), | ||
dispose() { | ||
@@ -291,6 +413,3 @@ for (const sub of subscriptions) { | ||
key: ProviderCacheKey, | ||
env: Pick< | ||
ClientEnv<any>, | ||
'providerBaseUri' | 'logger' | 'dynamicImportFromUri' | 'dynamicImportFromSource' | ||
> | ||
env: Pick<ClientEnv<any>, 'providerBaseUri' | 'logger' | 'importProvider'> | ||
) => ProviderClient | ||
@@ -319,4 +438,3 @@ } { | ||
logger: env.logger, | ||
dynamicImportFromUri: env.dynamicImportFromUri, | ||
dynamicImportFromSource: env.dynamicImportFromSource, | ||
importProvider: env.importProvider, | ||
}) | ||
@@ -323,0 +441,0 @@ cache.set(cacheKey(key), c) |
export type * from '@openctx/protocol' | ||
export type { Provider } from '@openctx/provider' | ||
export type * from '@openctx/schema' | ||
export { observeItems, type Annotation } from './api' | ||
export { createClient, type AuthInfo, type Client } from './client/client' | ||
export { type ConfigurationUserInput as ClientConfiguration } from './configuration' | ||
export type { Logger } from './logger' | ||
export { observeItems, type Annotation, type EachWithProviderUri } from './api.js' | ||
export { createClient, type AuthInfo, type Client, type ClientEnv } from './client/client.js' | ||
export { type ConfigurationUserInput as ClientConfiguration } from './configuration.js' | ||
export type { Logger } from './logger.js' | ||
export { fetchProviderSource } from './providerClient/transport/module.js' |
import type { | ||
AnnotationsParams, | ||
AnnotationsResult, | ||
CapabilitiesParams, | ||
CapabilitiesResult, | ||
ItemsParams, | ||
ItemsResult, | ||
MentionsParams, | ||
MentionsResult, | ||
ProviderSettings, | ||
} from '@openctx/protocol' | ||
import { scopedLogger } from '../logger' | ||
import { matchSelectors } from './selector' | ||
import { type ProviderTransportOptions, createTransport } from './transport/createTransport' | ||
import { scopedLogger } from '../logger.js' | ||
import { matchSelectors } from './selector.js' | ||
import { type ProviderTransportOptions, createTransport } from './transport/createTransport.js' | ||
@@ -17,2 +21,8 @@ /** | ||
export interface ProviderClient { | ||
/** Get capabilities from the provider. */ | ||
capabilities(params: MentionsParams, settings: ProviderSettings): Promise<CapabilitiesResult> | ||
/** Get candidate items from the provider. */ | ||
mentions(params: MentionsParams, settings: ProviderSettings): Promise<MentionsResult | null> | ||
/** Get items from the provider. */ | ||
@@ -32,3 +42,3 @@ items(params: ItemsParams, settings: ProviderSettings): Promise<ItemsResult | null> | ||
ProviderTransportOptions, | ||
'providerBaseUri' | 'authInfo' | 'logger' | 'dynamicImportFromUri' | 'dynamicImportFromSource' | ||
'providerBaseUri' | 'authInfo' | 'logger' | 'importProvider' | ||
> {} | ||
@@ -48,2 +58,24 @@ | ||
return { | ||
async capabilities( | ||
params: CapabilitiesParams, | ||
settings: ProviderSettings | ||
): Promise<CapabilitiesResult> { | ||
try { | ||
return await transport.capabilities(params, settings) | ||
} catch (error) { | ||
logger?.(`failed to get capabilities: ${error}`) | ||
return Promise.reject(error) | ||
} | ||
}, | ||
async mentions( | ||
params: MentionsParams, | ||
settings: ProviderSettings | ||
): Promise<MentionsResult | null> { | ||
try { | ||
return await transport.mentions(params, settings) | ||
} catch (error) { | ||
logger?.(`failed to get mentions: ${error}`) | ||
return Promise.reject(error) | ||
} | ||
}, | ||
async items(params: ItemsParams, settings: ProviderSettings): Promise<ItemsResult | null> { | ||
@@ -50,0 +82,0 @@ try { |
@@ -5,3 +5,3 @@ import type { AnnotationsParams, Selector } from '@openctx/protocol' | ||
// non-Node engines. | ||
import match from 'picomatch/lib/picomatch' | ||
import match from 'picomatch/lib/picomatch.js' | ||
@@ -8,0 +8,0 @@ /** |
import type { AnnotationsResult, CapabilitiesResult, ItemsResult } from '@openctx/protocol' | ||
import { LRUCache } from 'lru-cache' | ||
import type { ProviderTransport } from './createTransport' | ||
import type { ProviderTransport } from './createTransport.js' | ||
@@ -17,3 +17,3 @@ export function cachedTransport(provider: ProviderTransport): ProviderTransport { | ||
): Promise<Awaited<ReturnType<ProviderTransport[M]>>> { | ||
const fullKey = `${method}:${JSON.stringify(args)}` | ||
const fullKey = `${String(method)}:${JSON.stringify(args)}` | ||
const entry = cache.get(fullKey) as Awaited<ReturnType<ProviderTransport[M]>> | undefined | ||
@@ -34,2 +34,6 @@ if (entry) { | ||
), | ||
mentions: (...args) => | ||
cachedMethodCall('mentions', args, (params, settings) => | ||
provider.mentions(params, settings) | ||
), | ||
items: (...args) => | ||
@@ -36,0 +40,0 @@ cachedMethodCall('items', args, (params, settings) => provider.items(params, settings)), |
@@ -1,7 +0,7 @@ | ||
import type { Provider } from '@openctx/provider/src/provider' | ||
import type { AuthInfo, ClientEnv } from '../../client/client' | ||
import type { Logger } from '../../logger' | ||
import { cachedTransport } from './cache' | ||
import { createHttpTransport } from './http' | ||
import { createLocalModuleFileTransport, createRemoteModuleFileTransport } from './module' | ||
import type { Provider } from '@openctx/provider' | ||
import type { AuthInfo, ClientEnv } from '../../client/client.js' | ||
import type { Logger } from '../../logger.js' | ||
import { cachedTransport } from './cache.js' | ||
import { createHttpTransport } from './http.js' | ||
import { createModuleTransport } from './module.js' | ||
@@ -23,6 +23,3 @@ /** | ||
export interface ProviderTransportOptions | ||
extends Pick< | ||
ClientEnv<any>, | ||
'providerBaseUri' | 'dynamicImportFromUri' | 'dynamicImportFromSource' | ||
> { | ||
extends Pick<ClientEnv<any>, 'providerBaseUri' | 'importProvider'> { | ||
authInfo?: AuthInfo | ||
@@ -34,3 +31,3 @@ cache?: boolean | ||
/** | ||
* Create a transport that communicates with a provider URI using the provider API. | ||
* Create a transport that communicates with a provider using the provider API. | ||
* | ||
@@ -45,21 +42,13 @@ * @internal | ||
let url = new URL(providerUri, options.providerBaseUri) | ||
if ( | ||
url.protocol === 'file:' || | ||
(runtimeSupportsImportFromUrl() && isRemoteJavaScriptFile(url)) | ||
) { | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url) | ||
} | ||
return createLocalModuleFileTransport(url.toString(), options) | ||
if (isHttpOrHttps(url) && !isRemoteJavaScriptFile(url)) { | ||
// Provider is an HTTP endpoint. | ||
return createHttpTransport(providerUri, options) | ||
} | ||
if (isRemoteJavaScriptFile(url)) { | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url) | ||
} | ||
return createRemoteModuleFileTransport(url.toString(), options) | ||
// Provider is a JavaScript module. | ||
if (isHttpsPlusJs(url)) { | ||
url = removePlusJs(url) | ||
} | ||
if (isHttpOrHttps(url)) { | ||
return createHttpTransport(providerUri, options) | ||
} | ||
throw new Error(`Unsupported OpenCtx provider URI: ${providerUri}`) | ||
return createModuleTransport(url.toString(), options) | ||
} | ||
@@ -104,11 +93,1 @@ | ||
} | ||
function runtimeSupportsImportFromUrl(): boolean { | ||
// `import('https://...')` is not supported natively in Node.js; see | ||
// https://nodejs.org/api/esm.html#urls. | ||
// | ||
// TODO(sqs): this is hacky and not correct in general | ||
// | ||
// @ts-ignore | ||
return typeof window !== 'undefined' | ||
} |
@@ -5,7 +5,8 @@ import type { | ||
ItemsResult, | ||
MentionsResult, | ||
RequestMessage, | ||
ResponseMessage, | ||
} from '@openctx/protocol' | ||
import { scopedLogger } from '../../logger' | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport' | ||
import { scopedLogger } from '../../logger.js' | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport.js' | ||
@@ -89,2 +90,3 @@ export function createHttpTransport( | ||
capabilities: async params => send<CapabilitiesResult>({ method: 'capabilities', params }), | ||
mentions: async params => send<MentionsResult>({ method: 'mentions', params }), | ||
items: async params => send<ItemsResult>({ method: 'items', params }), | ||
@@ -91,0 +93,0 @@ annotations: async params => send<AnnotationsResult>({ method: 'annotations', params }), |
import type { Provider } from '@openctx/provider' | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport' | ||
import type { ProviderTransport, ProviderTransportOptions } from './createTransport.js' | ||
export function createRemoteModuleFileTransport( | ||
export function createModuleTransport( | ||
providerUri: string, | ||
{ | ||
dynamicImportFromUri, | ||
dynamicImportFromSource, | ||
}: Pick<ProviderTransportOptions, 'dynamicImportFromUri' | 'dynamicImportFromSource'> | ||
{ importProvider }: Pick<ProviderTransportOptions, 'importProvider'> | ||
): ProviderTransport { | ||
return lazyProvider( | ||
dynamicImportFromUri | ||
? dynamicImportFromUri(providerUri).then(mod => providerFromModule(mod)) | ||
: fetch(providerUri).then(async resp => { | ||
if (!resp.ok) { | ||
throw new Error( | ||
`OpenCtx remote provider module URL ${providerUri} responded with HTTP error ${resp.status} ${resp.statusText}` | ||
) | ||
} | ||
const contentType = resp.headers.get('Content-Type')?.trim()?.replace(/;.*$/, '') | ||
if ( | ||
!contentType || | ||
(contentType !== 'text/javascript' && | ||
contentType !== 'application/javascript' && | ||
contentType !== 'text/plain') | ||
) { | ||
throw new Error( | ||
`OpenCtx remote provider module URL ${providerUri} reported invalid Content-Type ${JSON.stringify( | ||
contentType | ||
)} (expected "text/javascript" or "text/plain")` | ||
) | ||
} | ||
const moduleSource = await resp.text() | ||
try { | ||
const mod = await importModuleFromString( | ||
providerUri, | ||
moduleSource, | ||
dynamicImportFromSource | ||
) | ||
return providerFromModule(mod) | ||
} catch (error) { | ||
console.log(error) | ||
throw error | ||
} | ||
}) | ||
(importProvider ? importProvider(providerUri) : import(/* @vite-ignore */ providerUri)).then( | ||
mod => providerFromModule(mod) | ||
) | ||
) | ||
} | ||
export function createLocalModuleFileTransport( | ||
moduleUrl: string, | ||
{ dynamicImportFromUri }: Pick<ProviderTransportOptions, 'dynamicImportFromUri'> | ||
): ProviderTransport { | ||
return lazyProvider( | ||
(dynamicImportFromUri | ||
? dynamicImportFromUri(moduleUrl) | ||
: import(/* @vite-ignore */ moduleUrl) | ||
).then(providerFromModule) | ||
) | ||
export async function fetchProviderSource(providerUri: string): Promise<string> { | ||
const resp = await fetch(providerUri) | ||
if (!resp.ok) { | ||
throw new Error( | ||
`OpenCtx remote provider module URL ${providerUri} responded with HTTP error ${resp.status} ${resp.statusText}` | ||
) | ||
} | ||
const contentType = resp.headers.get('Content-Type')?.trim()?.replace(/;.*$/, '') | ||
if ( | ||
!contentType || | ||
(contentType !== 'text/javascript' && | ||
contentType !== 'application/javascript' && | ||
contentType !== 'text/plain') | ||
) { | ||
throw new Error( | ||
`OpenCtx remote provider module URL ${providerUri} reported invalid Content-Type ${JSON.stringify( | ||
contentType | ||
)} (expected "text/javascript" or "text/plain")` | ||
) | ||
} | ||
const moduleSource = await resp.text() | ||
return moduleSource | ||
} | ||
@@ -68,26 +47,2 @@ | ||
async function importModuleFromString( | ||
uri: string, | ||
source: string, | ||
dynamicImportFromSource: ProviderTransportOptions['dynamicImportFromSource'] | ||
): Promise<ProviderModule> { | ||
if (dynamicImportFromSource) { | ||
return (await dynamicImportFromSource(uri, source)).exports | ||
} | ||
// Note: Used by VS Code Web. | ||
const url = `data:text/javascript;charset=utf-8;base64,${base64Encode(source)}` | ||
return import(/* @vite-ignore */ url) | ||
} | ||
/** | ||
* See https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem for why we need | ||
* something other than just `btoa` for base64 encoding. | ||
*/ | ||
function base64Encode(text: string): string { | ||
const bytes = new TextEncoder().encode(text) | ||
const binString = String.fromCodePoint(...bytes) | ||
return btoa(binString) | ||
} | ||
function providerFromModule(providerModule: ProviderModule): Provider { | ||
@@ -104,2 +59,3 @@ let impl = providerModule.default | ||
capabilities: async (params, settings) => (await provider).capabilities(params, settings), | ||
mentions: async (params, settings) => (await provider).mentions?.(params, settings) ?? [], | ||
items: async (params, settings) => (await provider).items?.(params, settings) ?? [], | ||
@@ -106,0 +62,0 @@ annotations: async (params, settings) => (await provider).annotations?.(params, settings) ?? [], |
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
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
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
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
121079
1866
+ Added@openctx/protocol@0.0.8(transitive)
+ Added@openctx/provider@0.0.8(transitive)
+ Added@openctx/schema@0.0.7(transitive)
- Removed@openctx/protocol@0.0.7(transitive)
- Removed@openctx/provider@0.0.7(transitive)
- Removed@openctx/schema@0.0.6(transitive)
Updated@openctx/protocol@0.0.8
Updated@openctx/provider@0.0.8
Updated@openctx/schema@0.0.7