UNPKG

mongoku

Version:

[![CI](https://github.com/huggingface/Mongoku/actions/workflows/ci.yml/badge.svg)](https://github.com/huggingface/Mongoku/actions/workflows/ci.yml)

1,503 lines (1,494 loc) 46.5 kB
import { T as get_request_store, w as with_request_store } from './root-otUAnOAR.js'; import { p as private_env } from './shared-server-BmU87nph.js'; import './utils-BQzn9ikS.js'; import { M as MUTATIVE_METHODS, y as stringify_remote_arg, n as noop, c as create_remote_key, z as unfriendly_hydratable, q as stringify, e as handle_error_and_jsonify } from './shared-DlqhoNLb.js'; import { e as error, H as HttpError, S as SvelteKitError } from './index-NcxaM188.js'; import { p as parseJSON, v as validateAggregationPipeline, i as isEmptyObject } from './jsonParser-C3QUcODD.js'; import { J as JsonEncoder } from './JsonEncoder-Dgtqxb_U.js'; import { l as logger } from './logger-PfH_grbh.js'; import { g as getMongo } from './mongo-B92d7zNj.js'; import { a as auditSchemaCompliance } from './schema-BZonjzNJ.js'; import { ObjectId, ReadPreference } from 'mongodb'; import { z } from 'zod'; /** @import { RemoteInternals } from 'types' */ /** @type {RemoteInternals['type'][]} */ const types = ['command', 'form', 'prerender', 'query', 'query_batch', 'query_live']; /** * @param {Record<string, any>} module * @param {string} file * @param {string} hash */ function init_remote_functions(module, file, hash) { if (module.default) { throw new Error( `Cannot export \`default\` from a remote module (${file}) — please use named exports instead` ); } for (const [name, fn] of Object.entries(module)) { if (!types.includes(fn?.__?.type)) { throw new Error( `\`${name}\` exported from ${file} is invalid — all exports from this file must be remote functions` ); } fn.__.id = `${hash}/${name}`; fn.__.name = name; } } function create_validator(validate_or_fn, maybe_fn) { if (!maybe_fn) { return (arg) => { if (arg !== void 0) { error(400, "Bad Request"); } }; } if (validate_or_fn === "unchecked") { return (arg) => arg; } if ("~standard" in validate_or_fn) { return async (arg) => { const { event, state } = get_request_store(); const result = await validate_or_fn["~standard"].validate(arg); if (result.issues) { error( 400, await state.handleValidationError({ issues: result.issues, event }) ); } return result.value; }; } throw new Error( 'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)' ); } async function get_response(internals, payload, state, get_result) { await 0; const cache = get_cache(internals, state); const entry = cache[payload] ??= { serialize: false, data: get_result() }; entry.serialize ||= !!state.is_in_universal_load; if (state.is_in_render && internals.id) { const remote_key = create_remote_key(internals.id, payload); Promise.resolve(entry.data).then((value) => { void unfriendly_hydratable(remote_key, () => stringify(value, state.transport)); }).catch(noop); } return entry.data; } function derive_remote_function_event(event, state, allow_cookies) { return { event: { ...event, setHeaders: () => { throw new Error("setHeaders is not allowed in remote functions"); }, cookies: { ...event.cookies, set: (name, value, opts) => { if (!allow_cookies) { throw new Error("Cannot set cookies in `query` or `prerender` functions"); } if (opts.path && !opts.path.startsWith("/")) { throw new Error("Cookies set in remote functions must have an absolute path"); } return event.cookies.set(name, value, opts); }, delete: (name, opts) => { if (!allow_cookies) { throw new Error("Cannot delete cookies in `query` or `prerender` functions"); } if (opts.path && !opts.path.startsWith("/")) { throw new Error("Cookies deleted in remote functions must have an absolute path"); } return event.cookies.delete(name, opts); } } }, state: { ...state, is_in_remote_function: true } }; } async function run_remote_function(event, state, allow_cookies, get_input, fn) { const store = derive_remote_function_event(event, state, allow_cookies); const input = await with_request_store(store, get_input); return with_request_store(store, () => fn(input)); } async function* run_remote_generator(event, state, allow_cookies, get_input, fn, name) { const store = derive_remote_function_event(event, state, allow_cookies); const input = await with_request_store(store, get_input); const source = await with_request_store(store, () => fn(input)); const iterator = to_iterator(source, name); let done = false; try { while (true) { const result = await with_request_store(store, () => iterator.next()); if (result.done) { done = true; return result.value; } yield result.value; } } finally { if (!done && typeof iterator.return === "function") { await with_request_store(store, () => iterator.return?.(void 0)); } } } function to_iterator(source, name) { if ("next" in source && typeof source.next === "function") { return source; } if (Symbol.asyncIterator in source && typeof source[Symbol.asyncIterator] === "function") { return source[Symbol.asyncIterator](); } if (Symbol.iterator in source && typeof source[Symbol.iterator] === "function") { return source[Symbol.iterator](); } throw new Error( `query.live '${name}' must return an Iterator, Iterable, AsyncIterator or AsyncIterable` ); } function get_cache(internals, state = get_request_store().state) { let cache = state.remote.data?.get(internals); if (cache === void 0) { cache = {}; (state.remote.data ??= /* @__PURE__ */ new Map()).set(internals, cache); } return cache; } // @__NO_SIDE_EFFECTS__ function command(validate_or_fn, maybe_fn) { const fn = maybe_fn ?? validate_or_fn; const validate = create_validator(validate_or_fn, maybe_fn); const __ = { type: "command", id: "", name: "" }; const wrapper = (arg) => { const { event, state } = get_request_store(); if (!MUTATIVE_METHODS.includes(event.request.method)) { throw new Error( `Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) from a ${event.request.method} handler` ); } if (state.is_in_render) { throw new Error( `Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) during server-side rendering` ); } state.remote.refreshes ??= /* @__PURE__ */ new Map(); state.remote.reconnects ??= /* @__PURE__ */ new Map(); const promise = Promise.resolve( run_remote_function(event, state, true, () => validate(arg), fn) ); promise.updates = () => { throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`); }; return ( /** @type {ReturnType<RemoteCommand<Input, Output>>} */ promise ); }; Object.defineProperty(wrapper, "__", { value: __ }); Object.defineProperty(wrapper, "pending", { get: () => 0 }); return wrapper; } // @__NO_SIDE_EFFECTS__ function query(validate_or_fn, maybe_fn) { const fn = maybe_fn ?? validate_or_fn; const validate = create_validator(validate_or_fn, maybe_fn); const __ = { type: "query", id: "", name: "", validate, bind(payload, validated_arg) { const { event, state } = get_request_store(); return create_query_resource( __, payload, state, () => run_remote_function(event, state, false, () => validated_arg, fn) ); } }; const wrapper = (arg) => { const { event, state } = get_request_store(); const payload = stringify_remote_arg(arg, state.transport); return create_query_resource( __, payload, state, () => run_remote_function(event, state, false, () => validate(arg), fn) ); }; Object.defineProperty(wrapper, "__", { value: __ }); return wrapper; } // @__NO_SIDE_EFFECTS__ function live(validate_or_fn, maybe_fn) { const fn = maybe_fn ?? validate_or_fn; const validate = create_validator(validate_or_fn, maybe_fn); const run = (event, state, get_input) => run_remote_generator(event, state, false, get_input, fn, __.name); const first_value = async (generator) => { try { const { value, done } = await generator.next(); if (done) { throw new Error(`query.live '${__.name}' did not yield a value`); } return value; } finally { await generator.return(void 0); } }; const __ = { type: "query_live", id: "", name: "", run: (event, state, arg) => run(event, state, () => validate(arg)), validate, bind(payload, validated_arg) { const { event, state } = get_request_store(); return create_live_query_resource( __, payload, state, () => first_value(run(event, state, () => validated_arg)) ); } }; const wrapper = (arg) => { const { event, state } = get_request_store(); const payload = stringify_remote_arg(arg, state.transport); return create_live_query_resource( __, payload, state, () => first_value(run(event, state, () => validate(arg))) ); }; Object.defineProperty(wrapper, "__", { value: __ }); return wrapper; } // @__NO_SIDE_EFFECTS__ function batch(validate_or_fn, maybe_fn) { const fn = maybe_fn ?? validate_or_fn; const validate = create_validator(validate_or_fn, maybe_fn); const enqueue = (payload, get_validated) => { const { event, state } = get_request_store(); return new Promise((resolve, reject) => { const batches = state.remote.batches ??= /** @type {NonNullable<typeof state.remote.batches>} */ /* @__PURE__ */ new Map(); let batched = batches.get(__.id); if (!batched) { batched = /* @__PURE__ */ new Map(); batches.set(__.id, batched); } const entry = batched.get(payload); if (entry) { entry.resolvers.push({ resolve, reject }); return; } batched.set(payload, { get_validated, resolvers: [{ resolve, reject }] }); if (batched.size > 1) return; setTimeout(async () => { batches.delete(__.id); const entries = Array.from(batched.values()); try { return await run_remote_function( event, state, false, async () => Promise.all(entries.map((entry2) => entry2.get_validated())), async (input) => { const get_result = await fn(input); for (let i = 0; i < entries.length; i++) { try { const result = get_result(input[i], i); for (const resolver of entries[i].resolvers) { resolver.resolve(result); } } catch (error2) { for (const resolver of entries[i].resolvers) { resolver.reject(error2); } } } } ); } catch (error2) { for (const entry2 of batched.values()) { for (const resolver of entry2.resolvers) { resolver.reject(error2); } } } }, 0); }); }; const __ = { type: "query_batch", id: "", name: "", validate, run: async (args, options) => { const { event, state } = get_request_store(); return run_remote_function( event, state, false, async () => Promise.all(args.map(validate)), async (input) => { const get_result = await fn(input); return Promise.all( input.map(async (arg, i) => { try { const data = get_result(arg, i); return { type: "result", data: stringify(data, state.transport) }; } catch (error2) { return { type: "error", error: await handle_error_and_jsonify(event, state, options, error2), status: error2 instanceof HttpError || error2 instanceof SvelteKitError ? error2.status : 500 }; } }) ); } ); }, bind(payload, validated_arg) { const { state } = get_request_store(); return create_query_resource(__, payload, state, () => enqueue(payload, () => validated_arg)); } }; const wrapper = (arg) => { const { state } = get_request_store(); const payload = stringify_remote_arg(arg, state.transport); return create_query_resource( __, payload, state, () => ( // Collect all the calls to the same query in the same macrotask, // then execute them as one backend request. enqueue(payload, () => validate(arg)) ) ); }; Object.defineProperty(wrapper, "__", { value: __ }); return wrapper; } function create_query_resource(__, payload, state, fn) { let promise = null; const get_promise = () => { return promise ??= get_response(__, payload, state, fn); }; const populate_hydratable = () => { void (__.id && state.is_in_render && get_promise()); }; return { /** @type {Promise<any>['catch']} */ catch(onrejected) { return get_promise().catch(onrejected); }, get current() { populate_hydratable(); return void 0; }, get error() { populate_hydratable(); return void 0; }, /** @type {Promise<any>['finally']} */ finally(onfinally) { return get_promise().finally(onfinally); }, get loading() { populate_hydratable(); return true; }, get ready() { populate_hydratable(); return false; }, refresh() { const { event } = get_request_store(); if (!event.isRemoteRequest) { return Promise.resolve(); } const refresh_context = get_refresh_context(__, "refresh", payload); const is_immediate_refresh = !refresh_context.cache[refresh_context.payload]; const value = is_immediate_refresh ? get_promise() : fn(); return update_refresh_value(refresh_context, value, is_immediate_refresh); }, run() { if (!state.is_in_universal_load) { throw new Error( "On the server, .run() can only be called in universal `load` functions. Anywhere else, just await the query directly" ); } return get_response(__, payload, state, fn); }, /** @param {any} value */ set(value) { return update_refresh_value(get_refresh_context(__, "set", payload), value); }, /** @type {Promise<any>['then']} */ then(onfulfilled, onrejected) { return get_promise().then(onfulfilled, onrejected); }, withOverride() { throw new Error(`Cannot call '${__.name}.withOverride()' on the server`); }, get [Symbol.toStringTag]() { return "QueryResource"; } }; } function create_live_query_resource(__, payload, state, get_first_value) { let promise = null; const get_promise = () => { return promise ??= get_response(__, payload, state, get_first_value); }; const populate_hydratable = () => { void (__.id && state.is_in_render && get_promise()); }; return { /** @type {Promise<any>['catch']} */ catch(onrejected) { return get_promise().catch(onrejected); }, get current() { populate_hydratable(); return void 0; }, get error() { populate_hydratable(); return void 0; }, /** @type {Promise<any>['finally']} */ finally(onfinally) { return get_promise().finally(onfinally); }, get done() { populate_hydratable(); return false; }, get loading() { populate_hydratable(); return true; }, get ready() { populate_hydratable(); return false; }, get connected() { populate_hydratable(); return false; }, reconnect() { const reconnects = state.remote.reconnects; if (!reconnects) { throw new Error( `Cannot call reconnect on query.live '${__.name}' because it is not executed in the context of a command/form remote function` ); } reconnects.set(create_remote_key(__.id, payload), get_promise()); return Promise.resolve(); }, run() { throw new Error("Cannot call .run() on a live query on the server"); }, /** @type {Promise<any>['then']} */ then(onfulfilled, onrejected) { return get_promise().then(onfulfilled, onrejected); }, get [Symbol.toStringTag]() { return "LiveQueryResource"; } }; } Object.defineProperty(query, "batch", { value: batch, enumerable: true }); Object.defineProperty(query, "live", { value: live, enumerable: true }); function get_refresh_context(__, action, payload) { const { state } = get_request_store(); const { refreshes } = state.remote; if (!refreshes) { const name = __.type === "query_batch" ? `query.batch '${__.name}'` : `query '${__.name}'`; throw new Error( `Cannot call ${action} on ${name} because it is not executed in the context of a command/form remote function` ); } const cache = get_cache(__, state); const refreshes_key = create_remote_key(__.id, payload); return { __, state, refreshes, refreshes_key, cache, payload }; } function update_refresh_value({ __, refreshes, refreshes_key, cache, payload }, value, is_immediate_refresh = false) { const promise = Promise.resolve(value); if (!is_immediate_refresh) { cache[payload] = { serialize: true, data: promise }; } if (__.id) { refreshes.set(refreshes_key, promise); } promise.catch(noop); return Promise.resolve(); } const m = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, get addServer() { return addServer; }, get auditSchema() { return auditSchema; }, get countDocuments() { return countDocuments; }, get countDocumentsByTimeRange() { return countDocumentsByTimeRange; }, get createIndex() { return createIndex; }, get deleteDocument() { return deleteDocument; }, get deleteMany() { return deleteMany; }, get detectPrimaryNode() { return detectPrimaryNode; }, get dropCollection() { return dropCollection; }, get dropDatabase() { return dropDatabase; }, get dropIndex() { return dropIndex; }, get explainQuery() { return explainQuery; }, get fetchMappedDocument() { return fetchMappedDocument; }, get getIndexStatsFromNodes() { return getIndexStatsFromNodes; }, get getIndexStatsWithReadPreference() { return getIndexStatsWithReadPreference; }, get getServerNodes() { return getServerNodes; }, get hideIndex() { return hideIndex; }, get insertDocument() { return insertDocument; }, get loadDocuments() { return loadDocuments; }, get removeServer() { return removeServer; }, get retryConnection() { return retryConnection; }, get unhideIndex() { return unhideIndex; }, get updateDocument() { return updateDocument; }, get updateMany() { return updateMany; } }, Symbol.toStringTag, { value: "Module" })); function checkReadOnly() { if (private_env.MONGOKU_READ_ONLY_MODE === "true") { error(403, "Read-only mode is enabled"); } } function sanitizeMongoUrl(url) { try { const urlObj = new URL(url.startsWith("mongodb") ? url : `mongodb://${url}`); if (urlObj.username || urlObj.password) { urlObj.username = "***"; urlObj.password = "***"; } return urlObj.toString(); } catch { return "***"; } } const addServer = command( z.object({ url: z.string() }), async ({ url }) => { logger.log("addServer called with payload:", { url: sanitizeMongoUrl(url) }); const mongo = await getMongo(); await mongo.addServer(url); return { ok: true }; } ); const removeServer = command(z.string(), async (serverName) => { logger.log("removeServer called with payload:", { serverName }); const mongo = await getMongo(); await mongo.removeServer(serverName); return { ok: true }; }); const updateDocument = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), document: z.string(), value: z.unknown(), partial: z.boolean().optional().default(false), upsert: z.boolean().optional().default(false) }), async ({ server, database, collection, document, value, partial, upsert }) => { logger.log("updateDocument called with payload:", { server, database, collection, document, partial, upsert }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const newValue = JsonEncoder.decode(value); const _id = /^[0-9a-fA-F]{24}$/.test(document) ? new ObjectId(document) : document; if (partial) { await coll.updateOne( { _id }, { $set: newValue }, { upsert } ); } else { await coll.replaceOne( { _id }, JsonEncoder.decode(newValue), { upsert } ); } if (collection === "mongoku.mappings") { client.clearMappingsCache(database, document); } return { ok: true, update: JsonEncoder.encode(newValue) }; } ); const insertDocument = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), document: z.string().nullable().optional(), value: z.unknown() }), async ({ server, database, collection, document, value }) => { logger.log("insertDocument called with payload:", { server, database, collection, document }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const newValue = JsonEncoder.decode(value); if (document !== null && document !== void 0) { const _id = /^[0-9a-fA-F]{24}$/.test(document) ? new ObjectId(document) : document; newValue._id = _id; } const res = await coll.insertOne(newValue); if (collection === "mongoku.mappings" && typeof res.insertedId === "string") { client.clearMappingsCache(database, res.insertedId); } return { ok: true, insert: JsonEncoder.encode(newValue) }; } ); const deleteDocument = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), document: z.string() }), async ({ server, database, collection, document }) => { logger.log("deleteDocument called with payload:", { server, database, collection, document }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const _id = /^[0-9a-fA-F]{24}$/.test(document) ? new ObjectId(document) : document; await client.db(database).collection(collection).deleteOne({ _id }); if (collection === "mongoku.mappings") { client.clearMappingsCache(database, document); } return { ok: true }; } ); const updateMany = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), filter: z.string(), update: z.string() }), async ({ server, database, collection, filter, update }) => { logger.log("updateMany called with payload:", { server, database, collection, filter, update }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const filterDoc = JsonEncoder.decode(parseJSON(filter)); const updateDoc = JsonEncoder.decode(parseJSON(update, { allowArray: true })); const result = await coll.updateMany(filterDoc, updateDoc); return { ok: true, matchedCount: result.matchedCount, modifiedCount: result.modifiedCount }; } ); const countDocuments = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), filter: z.string() }), async ({ server, database, collection, filter }) => { const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const filterDoc = JsonEncoder.decode(parseJSON(filter)); try { const count = await coll.countDocuments(filterDoc, { maxTimeMS: mongo.getCountTimeout() }); return { data: count, error: null }; } catch (err) { logger.error("Error counting documents:", err); return { data: 0, error: `Failed to count documents: ${err instanceof Error ? err.message : String(err)}` }; } } ); const deleteMany = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), filter: z.string() }), async ({ server, database, collection, filter }) => { logger.log("deleteMany called with payload:", { server, database, collection, filter }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const filterDoc = JsonEncoder.decode(parseJSON(filter)); const result = await coll.deleteMany(filterDoc); return { ok: true, deletedCount: result.deletedCount }; } ); const hideIndex = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), index: z.string() }), async ({ server, database, collection, index }) => { logger.log("hideIndex called with payload:", { server, database, collection, index }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); await client.db(database).command({ collMod: collection, index: { name: index, hidden: true } }); return { ok: true }; } ); const unhideIndex = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), index: z.string() }), async ({ server, database, collection, index }) => { logger.log("unhideIndex called with payload:", { server, database, collection, index }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); await client.db(database).command({ collMod: collection, index: { name: index, hidden: false } }); return { ok: true }; } ); const createIndex = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), keys: z.string(), name: z.string().optional(), unique: z.boolean().optional(), sparse: z.boolean().optional(), partialFilterExpression: z.string().optional(), expireAfterSeconds: z.number().optional(), background: z.boolean().optional() }), async ({ server, database, collection, keys, name, unique, sparse, partialFilterExpression, expireAfterSeconds, background }) => { logger.log("createIndex called with payload:", { server, database, collection, keys, name, unique, sparse }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); const keysDoc = JsonEncoder.decode(parseJSON(keys)); const options = {}; if (name) { options.name = name; } if (unique) { options.unique = unique; } if (sparse) { options.sparse = sparse; } if (partialFilterExpression) { options.partialFilterExpression = JsonEncoder.decode(parseJSON(partialFilterExpression)); } if (expireAfterSeconds !== void 0) { options.expireAfterSeconds = expireAfterSeconds; } if (background) { options.background = background; } await coll.createIndex(keysDoc, options); return { ok: true }; } ); const dropIndex = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), index: z.string() }), async ({ server, database, collection, index }) => { logger.log("dropIndex called with payload:", { server, database, collection, index }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); await client.db(database).command({ dropIndexes: collection, index }); return { ok: true }; } ); const dropCollection = command( z.object({ server: z.string(), database: z.string(), collection: z.string() }), async ({ server, database, collection }) => { logger.log("dropCollection called with payload:", { server, database, collection }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); const db = client.db(database); await db.dropCollection(collection); return { ok: true }; } ); const dropDatabase = command( z.object({ server: z.string(), database: z.string() }), async ({ server, database }) => { logger.log("dropDatabase called with payload:", { server, database }); checkReadOnly(); const mongo = await getMongo(); const client = mongo.getClient(server); await client.db(database).dropDatabase(); return { ok: true }; } ); const retryConnection = command(z.string(), async (serverId) => { logger.log("retryConnection called with payload:", { serverId }); const mongo = await getMongo(); await mongo.reconnectClient(serverId); return { ok: true }; }); const loadDocuments = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), query: z.string().default("{}"), sort: z.string().default("{}"), project: z.string().default("{}"), skip: z.number().int().default(0), limit: z.number().int().default(20), mode: z.enum(["query", "distinct", "aggregation"]).default("query"), field: z.string().optional() }), async ({ server, database, collection, query: queryStr, sort, project, skip, limit, mode, field }) => { let queryDoc; try { queryDoc = parseJSON(queryStr, { allowArray: true }); } catch (err) { error(400, `Invalid query: ${err}`); } try { parseJSON(sort); } catch (err) { error(400, `Invalid sort: ${err}`); } try { parseJSON(project); } catch (err) { error(400, `Invalid project: ${err}`); } const sortDoc = parseJSON(sort); const projectDoc = parseJSON(project); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); if (mode === "distinct") { if (!field) { error(400, "Invalid distinct query: field name is required"); } try { const results = await coll.distinct(field, JsonEncoder.decode(queryDoc), { maxTimeMS: mongo.getQueryTimeout() }); return { data: results.map((value) => JsonEncoder.encode({ value })), error: null, isAggregation: false, isDistinct: true }; } catch (err) { logger.error("Error executing distinct:", err); error(500, `Failed to execute distinct: ${err instanceof Error ? err.message : String(err)}`); } } if (mode === "aggregation" && Array.isArray(queryDoc)) { try { validateAggregationPipeline(queryDoc); } catch (err) { error(400, `Invalid aggregation pipeline: ${err instanceof Error ? err.message : String(err)}`); } const pipeline = JsonEncoder.decode(queryDoc); try { const results = await coll.aggregate( [ ...pipeline, ...isEmptyObject(projectDoc) ? [] : [{ $project: projectDoc }], ...isEmptyObject(sortDoc) ? [] : [{ $sort: sortDoc }], { $limit: limit }, { $skip: skip } ], { maxTimeMS: mongo.getQueryTimeout() } ).map((obj) => JsonEncoder.encode(obj)).toArray(); return { data: results, error: null, isAggregation: true, isDistinct: false }; } catch (err) { logger.error("Error executing aggregation:", err); error(500, `Failed to execute aggregation: ${err instanceof Error ? err.message : String(err)}`); } } try { const results = await coll.find(JsonEncoder.decode(queryDoc), { maxTimeMS: mongo.getQueryTimeout() }).project(projectDoc).sort(JsonEncoder.decode(sortDoc)).limit(limit).skip(skip).map((obj) => JsonEncoder.encode(obj)).toArray(); return { data: results, error: null, isAggregation: false, isDistinct: false }; } catch (err) { logger.error("Error fetching query results:", err); error(500, `Failed to fetch query results: ${err instanceof Error ? err.message : String(err)}`); } } ); const fetchMappedDocument = query( z.object({ server: z.string(), database: z.string(), mappings: z.array( z.union([ z.object({ type: z.literal("document"), collection: z.string(), on: z.string() }), z.object({ type: z.literal("url"), template: z.string() }), // Legacy format (backwards compatibility) z.object({ collection: z.string(), on: z.string() }) ]) ), value: z.unknown() }), async ({ server, database, mappings, value }) => { const mongo = await getMongo(); const client = mongo.getClient(server); const decodedValue = JsonEncoder.decode(value); const documentMappings = mappings.filter((m2) => { if ("type" in m2) { return m2.type === "document"; } return "collection" in m2 && "on" in m2; }); for (const mapping of documentMappings) { try { if ("type" in mapping && mapping.type !== "document") { continue; } const collection = "collection" in mapping ? mapping.collection : ""; const on = "on" in mapping ? mapping.on : "_id"; const coll = client.db(database).collection(collection); const query2 = { [on]: decodedValue }; const document = await coll.findOne(query2, { maxTimeMS: mongo.getQueryTimeout() }); if (document) { return { data: JsonEncoder.encode(document), collection, error: null }; } } catch (err) { const collection = "collection" in mapping ? mapping.collection : "unknown"; const on = "on" in mapping ? mapping.on : "_id"; logger.error(`Error fetching mapped document from ${collection}.${on}:`, err); continue; } } return { data: null, collection: null, error: "Document not found in any mapped collection" }; } ); const getIndexStatsWithReadPreference = query( z.object({ server: z.string(), database: z.string(), collection: z.string(), readPreferenceMode: z.string().optional(), readPreferenceTags: z.string().optional() }), async ({ server, database, collection, readPreferenceMode, readPreferenceTags }) => { const mongo = await getMongo(); const client = mongo.getClient(server); try { let tags; if (readPreferenceTags) { try { tags = parseJSON(readPreferenceTags); if (typeof tags !== "object" || tags === null) { error(400, "Invalid read preference tags format"); } if (!Array.isArray(tags)) { tags = [tags]; } } catch (err) { error(400, `Invalid read preference tags JSON: ${err}`); } } let readPreference; if (readPreferenceMode || tags) { const mode = readPreferenceMode || "nearest"; readPreference = tags ? new ReadPreference(mode, tags) : new ReadPreference(mode); } const coll = client.db(database).collection(collection); const aggregateOptions = readPreference ? { readPreference } : {}; const statsResult = await coll.aggregate([{ $indexStats: {} }], aggregateOptions).toArray(); const indexStats = Object.fromEntries( statsResult.map((stat) => [ stat.name, { ops: stat.accesses?.ops || 0, since: stat.accesses?.since || /* @__PURE__ */ new Date(), host: stat.host || "unknown" } ]) ); return { data: JsonEncoder.encode(indexStats), error: null }; } catch (err) { logger.error("Error fetching index stats with read preference:", err); return { data: {}, error: `Failed to fetch index stats: ${err instanceof Error ? err.message : String(err)}` }; } } ); const getServerNodes = query( z.object({ server: z.string() }), async ({ server }) => { const mongo = await getMongo(); try { const nodes = await mongo.getServerNodes(server); return { data: nodes, error: null }; } catch (err) { logger.error("Error getting server nodes:", err); return { data: [], error: `Failed to get server nodes: ${err instanceof Error ? err.message : String(err)}` }; } } ); const detectPrimaryNode = query( z.object({ server: z.string(), database: z.string(), collection: z.string() }), async ({ server, database, collection }) => { const mongo = await getMongo(); const client = mongo.getClient(server); try { const coll = client.db(database).collection(collection); const statsResult = await coll.aggregate([{ $indexStats: {} }], { readPreference: new ReadPreference("primary") }).toArray(); const primaryHost = statsResult.length > 0 ? statsResult[0].host : null; return { data: primaryHost, error: null }; } catch (err) { logger.error("Error detecting primary node:", err); return { data: null, error: `Failed to detect primary: ${err instanceof Error ? err.message : String(err)}` }; } } ); const getIndexStatsFromNodes = query( z.object({ server: z.string(), database: z.string(), collection: z.string(), nodes: z.array(z.string()) }), async ({ server, database, collection, nodes }) => { const mongo = await getMongo(); try { const results = await Promise.allSettled( nodes.map(async (node) => { const stats = await mongo.getIndexStatsFromNode(server, node, database, collection); return { node, stats }; }) ); const mergedStats = {}; const errors = []; let i = 0; for (const result of results) { if (result.status === "fulfilled") { const { stats } = result.value; for (const [indexName, indexStats] of Object.entries(stats)) { const key = `${indexName}::${indexStats.host}`; mergedStats[key] = indexStats; } } else { logger.error(`Error fetching index stats from node ${nodes[i]}:`, result.reason); errors.push(result.reason?.message || String(result.reason)); } i++; } return { data: JsonEncoder.encode(mergedStats), error: errors.length > 0 ? errors.join("; ") : null }; } catch (err) { logger.error("Error fetching index stats from nodes:", err); return { data: {}, error: `Failed to fetch index stats: ${err instanceof Error ? err.message : String(err)}` }; } } ); const explainQuery = query( z.object({ server: z.string(), database: z.string(), collection: z.string(), query: z.string().default("{}"), sort: z.string().default("{}"), project: z.string().default("{}"), skip: z.number().int().default(0), limit: z.number().int().default(20), mode: z.enum(["query", "aggregation"]).default("query"), verbosity: z.enum(["queryPlanner", "executionStats", "allPlansExecution"]).default("executionStats") }), async ({ server, database, collection, query: queryStr, sort, project, skip, limit, mode, verbosity }) => { const queryDoc = (() => { try { return parseJSON(queryStr, { allowArray: true }); } catch (err) { error(400, `Invalid query: ${err}`); } })(); try { parseJSON(sort); } catch (err) { error(400, `Invalid sort: ${err}`); } try { parseJSON(project); } catch (err) { error(400, `Invalid project: ${err}`); } const sortDoc = parseJSON(sort); const projectDoc = parseJSON(project); const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); try { let explainResult; if (mode === "aggregation" && Array.isArray(queryDoc)) { try { validateAggregationPipeline(queryDoc); } catch (err) { error(400, `Invalid aggregation pipeline: ${err instanceof Error ? err.message : String(err)}`); } const pipeline = JsonEncoder.decode(queryDoc); const fullPipeline = [ ...pipeline, ...isEmptyObject(projectDoc) ? [] : [{ $project: projectDoc }], ...isEmptyObject(sortDoc) ? [] : [{ $sort: sortDoc }], { $skip: skip }, { $limit: limit } ]; explainResult = await coll.aggregate(fullPipeline).explain(verbosity); } else { explainResult = await coll.find(JsonEncoder.decode(queryDoc)).project(projectDoc).sort(JsonEncoder.decode(sortDoc)).skip(skip).limit(limit).explain(verbosity); } const plainResult = JSON.parse(JSON.stringify(explainResult)); return { data: plainResult, error: null }; } catch (err) { logger.error("Error explaining query:", err); return { data: null, error: `Failed to explain query: ${err instanceof Error ? err.message : String(err)}` }; } } ); const countDocumentsByTimeRange = query( z.object({ server: z.string(), database: z.string(), collection: z.string(), days: z.number(), query: z.string().optional() }), async ({ server, database, collection, days, query: queryStr }) => { const mongo = await getMongo(); const client = mongo.getClient(server); const coll = client.db(database).collection(collection); let baseQuery = {}; if (queryStr) { try { const parsed = parseJSON(queryStr); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { baseQuery = JsonEncoder.encode(parsed); } } catch { } } let useCreatedAt = false; try { const sample = await coll.findOne({}, { projection: { _id: 1, createdAt: 1 }, maxTimeMS: 5e3 }); if (sample) { const idIsObjectId = sample._id instanceof ObjectId; if (!idIsObjectId) { if (sample.createdAt instanceof Date && await client.hasIndexOnField(database, collection, "createdAt")) { useCreatedAt = true; } else { return { count: null, error: "Cannot determine document age (no ObjectId or createdAt field)" }; } } } } catch { } const dateThreshold = /* @__PURE__ */ new Date(); dateThreshold.setDate(dateThreshold.getDate() - days); let filter; if (useCreatedAt) { filter = { ...baseQuery, createdAt: { $gte: dateThreshold } }; } else { const objectIdThreshold = ObjectId.createFromTime(Math.floor(dateThreshold.getTime() / 1e3)); filter = { ...baseQuery, _id: { $gte: objectIdThreshold } }; } try { const count = await coll.countDocuments(filter, { maxTimeMS: mongo.getCountTimeout() }); return { count, error: null }; } catch (err) { logger.error(`Error counting documents for ${days} days:`, err); const errorMsg = err instanceof Error ? err.message : String(err); return { count: null, error: errorMsg }; } } ); const auditSchema = command( z.object({ server: z.string(), database: z.string(), collection: z.string(), readPreferenceMode: z.string().optional(), readPreferenceTags: z.string().optional() }), async ({ server, database, collection, readPreferenceMode, readPreferenceTags }) => { const mongo = await getMongo(); const client = mongo.getClient(server); try { let readPreference; if (readPreferenceMode) { let tags; if (readPreferenceTags) { try { const parsed = parseJSON(readPreferenceTags, { allowArray: true }); if (Array.isArray(parsed)) { tags = parsed; } else if (parsed && typeof parsed === "object") { tags = [parsed]; } } catch { } } readPreference = tags ? new ReadPreference( readPreferenceMode, tags ) : new ReadPreference( readPreferenceMode ); } const result = await auditSchemaCompliance(client, database, collection, { readPreference, maxTimeMS: mongo.getQueryTimeout() }); return { data: JsonEncoder.encode(result), error: null }; } catch (err) { logger.error("Error auditing schema compliance:", err); return { data: null, error: `Failed to audit schema: ${err instanceof Error ? err.message : String(err)}` }; } } ); init_remote_functions(m, "src/api/servers.remote.ts", "xxtqbu"); for (const [name, fn] of Object.entries(m)) { fn.__.id = "xxtqbu/" + name; fn.__.name = name; } export { countDocuments as c, deleteDocument as d, explainQuery as e, fetchMappedDocument as f, loadDocuments as l, m, updateDocument as u }; //# sourceMappingURL=servers.remote-CGenkEHX.js.map