Merge pull request #32743 from overleaf/ar-new-v1-api

[web] new v1 api client

GitOrigin-RevId: 7ba0deef0d10526198a2a6ba997c2dcff7b7e5a5
This commit is contained in:
Andrew Rumble
2026-04-23 16:06:56 +01:00
committed by Copybot
parent d3b60bcb46
commit 54d953cfff
3 changed files with 68 additions and 8 deletions
@@ -4,6 +4,7 @@ import settings from '@overleaf/settings'
import Errors from '../Errors/Errors.js'
import { promisifyMultiResult } from '@overleaf/promise-utils'
import OError from '@overleaf/o-error'
import Modules from '../../infrastructure/Modules.mjs'
// TODO: check what happens when these settings aren't defined
const DEFAULT_V1_PARAMS = {
@@ -16,6 +17,14 @@ const DEFAULT_V1_PARAMS = {
timeout: settings.apis.v1.timeout,
}
export async function makeV1Request(options) {
const results = await Modules.promises.hooks.fire('makeV1Request', options)
if (!Array.isArray(results) || results.length === 0) {
throw new Error('No module provides a makeV1Request implementation')
}
return results[0]
}
const v1Request = request.defaults(DEFAULT_V1_PARAMS)
function makeRequest(options, callback) {
+27
View File
@@ -0,0 +1,27 @@
import type { RequestInit, Response } from 'node-fetch'
export type V1RequestOptions = Omit<RequestInit, 'method'> & {
uri: string
qs?: Record<string, string | undefined>
expectJson?: boolean
expectedStatusCodes?: number[]
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
}
export interface KnownHookSignatures {
makeV1Request: (
options: V1RequestOptions
) => Promise<{ body: unknown; response: Response }>
}
export type HookName = keyof KnownHookSignatures | string
export type HookParameters<K extends HookName> =
K extends keyof KnownHookSignatures
? Parameters<KnownHookSignatures[K]>
: any[]
export type HookReturnType<K extends HookName> =
K extends keyof KnownHookSignatures
? Awaited<ReturnType<KnownHookSignatures[K]>>
: any
@@ -12,12 +12,16 @@ import Metrics from '@overleaf/metrics'
/** @import { WebModule } from "../../../types/web-module" */
/** @import { RequestHandler } from "express" */
/**
* @import { HookName, HookParameters, HookReturnType } from './Modules'
*/
const MODULE_BASE_PATH = Path.join(import.meta.dirname, '/../../../modules')
/** @type {WebModule[]} */
const _modules = []
let _modulesLoaded = false
/** @type {Record<string, any>} */
/** @type {Partial<Record<HookName, Function[]>>} */
const _hooks = {}
/** @type {Record<string, RequestHandler[]>} */
@@ -46,9 +50,20 @@ async function loadModulesImpl() {
await import(settingsCheckModule)
}
for (const moduleName of Settings.moduleImportSequence || []) {
const module = await import(
Path.join(MODULE_BASE_PATH, moduleName, 'index.mjs')
const typescriptModule = Path.join(
MODULE_BASE_PATH,
moduleName,
'index.mts'
)
let module
if (fs.existsSync(typescriptModule)) {
module = await import(typescriptModule)
} else {
module = await import(
Path.join(MODULE_BASE_PATH, moduleName, 'index.mjs')
)
}
/** @type {WebModule & {name: string}} */
const loadedModule = module.default || module
@@ -194,8 +209,9 @@ async function attachHooks() {
}
/**
* @param {any} name
* @param {any} method
* @template {HookName} K
* @param {K} name
* @param {(...args: HookParameters<K>) => HookReturnType<K>| Promise<HookReturnType<K>>} method
*/
function attachHook(name, method) {
if (_hooks[name] == null) {
@@ -221,8 +237,10 @@ async function attachMiddleware() {
}
/**
* @param {any} name
* @param {...any} args
* @template {HookName} K
* @param {K} name
* @param {HookParameters<K>} args
* @returns {Promise<HookReturnType<K>[]>}
*/
async function fireHook(name, ...args) {
// ensure that modules are loaded if we need to fire a hook
@@ -250,6 +268,10 @@ async function getMiddleware(name) {
return _middleware[name] || []
}
/**
* @typedef {<K extends HookName>(name: K, ...args: [...HookParameters<K>, (err: any, results?: HookReturnType<K>[]) => void]) => void} CallbackFireHook
*/
export default {
applyNonCsrfRouter,
applyRouter,
@@ -261,7 +283,9 @@ export default {
start,
hooks: {
attach: attachHook,
fire: callbackify(fireHook),
fire: /** @type {CallbackFireHook} */ (
/** @type {unknown} */ (callbackify(/** @type {any} */ (fireHook)))
),
},
middleware: getMiddleware,
promises: {