From e3099febaaecc452c9c02f80809399c585be4eb7 Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Sun, 4 Jun 2023 22:09:46 +0000 Subject: [PATCH] refactor: utils, test harness, and mod to TS. Beef up coverage #31 #32 --- adapter.js | 1 - mod.js | 14 --------- mod.test.ts | 56 ++++++++++++++++++++++++++++++++++++ mod.ts | 42 +++++++++++++++++++++++++++ port_name.js => port_name.ts | 2 +- test/hyper.js | 15 ---------- test/hyper.ts | 16 +++++++++++ utils.js | 36 ----------------------- utils.ts | 40 ++++++++++++++------------ 9 files changed, 136 insertions(+), 86 deletions(-) delete mode 100644 mod.js create mode 100644 mod.test.ts create mode 100644 mod.ts rename port_name.js => port_name.ts (71%) delete mode 100644 test/hyper.js create mode 100644 test/hyper.ts delete mode 100644 utils.js diff --git a/adapter.js b/adapter.js index 16f7ab9..eefd3eb 100644 --- a/adapter.js +++ b/adapter.js @@ -215,7 +215,6 @@ export function adapter(client) { * @returns {Promise} */ async function queryDocuments({ db, query }) { - console.log(queryOptions(query)) try { const m = client.database(db).collection(db) const docs = await m.find(query.selector, { diff --git a/mod.js b/mod.js deleted file mode 100644 index 3764aca..0000000 --- a/mod.js +++ /dev/null @@ -1,14 +0,0 @@ -import { MongoClient } from './deps.js' -import { adapter } from './adapter.js' -import PORT_NAME from './port_name.js' - -export default (mongoUrl) => ({ - id: 'mongodb', - port: PORT_NAME, - load: async () => { - const client = new MongoClient() - await client.connect(mongoUrl) - return await client - }, // load env - link: (env) => (_) => adapter(env), // link adapter -}) diff --git a/mod.test.ts b/mod.test.ts new file mode 100644 index 0000000..234fda5 --- /dev/null +++ b/mod.test.ts @@ -0,0 +1,56 @@ +import { assert, assertRejects, dataPort, pluginFactory } from './dev_deps.ts' + +import { MongoClient as AtlasClient } from './clients/atlas.ts' + +import factory from './mod.ts' + +Deno.test('mod', async (t) => { + const happy = { + url: 'https://data.mongodb-api.com/app/foo/endpoint/data/v1', + options: { + atlas: { + dataSource: 'foo', + auth: { + apiKey: 'secret', + }, + }, + }, + } + await t.step('should implement the factory schema', () => { + assert(pluginFactory(factory(happy))) + }) + + await t.step('load', async (t) => { + await t.step('should construct an Atlas Data client', async () => { + assert((await factory(happy).load()) instanceof AtlasClient) + }) + + await t.step('should throw if the provided url is not valid', async () => { + await assertRejects(() => + factory({ + url: 'foobar://user:pass@cluster0.abcde.mongodb.net/test', + }).load() + ) + }) + + await t.step('should throw if no atlas options are provided', async () => { + await assertRejects(() => + factory({ + ...happy, + options: { + atlas: undefined, + }, + }).load() + ) + }) + }) + + await t.step('link', async (t) => { + await t.step('should return a data adapter', async () => { + const plugin = factory(happy) + const adapter = plugin.link(await plugin.load())({}) + + assert(dataPort(adapter)) + }) + }) +}) diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..b16616d --- /dev/null +++ b/mod.ts @@ -0,0 +1,42 @@ +import type { AdapterConfig } from './types.ts' +import PORT_NAME from './port_name.ts' + +import { MongoClient as AtlasClient } from './clients/atlas.ts' +import { MongoClient as NativeClient } from './clients/native.ts' + +import { adapter } from './adapter.js' + +const isNative = (url: URL) => /^mongodb/.test(url.protocol) +const isAtlas = (url: URL) => /^https/.test(url.protocol) + +export default (config: AdapterConfig) => ({ + id: 'mongodb', + port: PORT_NAME, + load: async () => { + const url = new URL(config.url) + let client: NativeClient | AtlasClient + + if (isNative(url)) { + client = new NativeClient(config.url) + await client.connect() + } else if (isAtlas(url)) { + if (!config.options?.atlas) { + throw new Error( + 'options.atlas is required when using an Atlas Data url', + ) + } + client = new AtlasClient({ + ...config.options.atlas, + endpoint: config.url, + fetch, + }) + } else { + throw new Error( + `provided url is not a valid MongoDB connection string or Atlas Data url`, + ) + } + + return Promise.resolve(client) + }, // load env + link: (env: NativeClient | AtlasClient) => (_: unknown) => adapter(env), // link adapter +}) diff --git a/port_name.js b/port_name.ts similarity index 71% rename from port_name.js rename to port_name.ts index a3d9051..4763fb3 100644 --- a/port_name.js +++ b/port_name.ts @@ -1,4 +1,4 @@ /** * @type {"cache" | "storage" | "data" | "search" | "hooks" | "queue"} */ -export default 'data' +export default 'data' as const diff --git a/test/hyper.js b/test/hyper.js deleted file mode 100644 index 472f9bc..0000000 --- a/test/hyper.js +++ /dev/null @@ -1,15 +0,0 @@ -// Harness deps -import { default as appOpine } from 'https://x.nest.land/hyper-app-opine@1.2.7/mod.js' -import { default as core } from 'https://x.nest.land/hyper@2.0.0/mod.js' - -import mongo from '../mod.js' -import PORT_NAME from '../port_name.js' - -const hyperConfig = { - app: appOpine, - adapters: [ - { port: PORT_NAME, plugins: [mongo('mongodb://127.0.0.1:27017')] }, - ], -} - -core(hyperConfig) diff --git a/test/hyper.ts b/test/hyper.ts new file mode 100644 index 0000000..2984710 --- /dev/null +++ b/test/hyper.ts @@ -0,0 +1,16 @@ +// Harness deps +import { default as hyper } from 'https://raw.githubusercontent.com/hyper63/hyper/hyper%40v4.1.0/packages/core/mod.ts' +import { default as app } from 'https://raw.githubusercontent.com/hyper63/hyper/hyper-app-express%40v1.1.0/packages/app-express/mod.ts' + +import mongo from '../mod.ts' +import PORT_NAME from '../port_name.ts' + +const hyperConfig = { + app, + middleware: [], + adapters: [ + { port: PORT_NAME, plugins: [mongo({ url: 'mongodb://127.0.0.1:27017' })] }, + ], +} + +hyper(hyperConfig) diff --git a/utils.js b/utils.js deleted file mode 100644 index 2929776..0000000 --- a/utils.js +++ /dev/null @@ -1,36 +0,0 @@ -import { R } from './deps.js' - -const { assoc, map, omit, keys, values } = R - -export const formatDocs = map((d) => { - if (d._deleted) { - return { deleteOne: { filter: { _id: d._id } } } - } else if (d._update) { - return { - replaceOne: { filter: { _id: d._id }, replacement: omit(['_update'], d) }, - } - } else { - return { insert: { document: d } } - } -}) - -export const queryOptions = ({ limit, fields, sort }) => { - let options = {} - options = limit ? assoc('limit', Number(limit), options) : options - options = fields - ? assoc('projection', fields.reduce((a, v) => assoc(v, 1, a), {}), options) - : options - options = sort - ? assoc( - 'sort', - sort.reduce( - (a, v) => { - return ({ ...a, [keys(v)[0]]: values(v)[0] === 'DESC' ? -1 : 1 }) - }, - {}, - ), - options, - ) - : options - return options -} diff --git a/utils.ts b/utils.ts index efa14f6..183ff07 100644 --- a/utils.ts +++ b/utils.ts @@ -63,25 +63,27 @@ export const queryOptions = ({ * * TL;DR: 1 is ascending, -1 is descending */ - // deno-lint-ignore ban-ts-comment - // @ts-ignore - ? sort.reduce((acc, cur) => { - /** - * The default order is ascending, if only the field name is provided - */ - if (typeof cur === 'string') return { ...acc, [cur]: 1 } - if (typeof cur === 'object') { - const key = Object.keys(cur)[0] - return { ...acc, [key]: cur[key] === 'DESC' ? -1 : 1 } - } - /** - * ignore the invalid sort value - * - * This should never happen because the wrapping zod schema would catch it - * but just to be explicit - */ - return acc - }, {}) + ? { + // deno-lint-ignore ban-ts-comment + // @ts-ignore + sort: sort.reduce((acc, cur) => { + /** + * The default order is ascending, if only the field name is provided + */ + if (typeof cur === 'string') return { ...acc, [cur]: 1 } + if (typeof cur === 'object') { + const key = Object.keys(cur)[0] + return { ...acc, [key]: cur[key] === 'DESC' ? -1 : 1 } + } + /** + * ignore the invalid sort value + * + * This should never happen because the wrapping zod schema would catch it + * but just to be explicit + */ + return acc + }, {}), + } : {}), }