UNPKG

@thisisagile/easy-mongo

Version:
566 lines (556 loc) • 23 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc2) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { AtlasSearchGateway: () => AtlasSearchGateway, Collection: () => Collection, FilterBuilder: () => FilterBuilder, IncludeBuilder: () => IncludeBuilder, MongoGateway: () => MongoGateway, MongoProvider: () => MongoProvider, SortBuilder: () => SortBuilder, asc: () => asc, desc: () => desc, lucene: () => lucene, stages: () => stages, toFilters: () => toFilters, toMongoType: () => toMongoType }); module.exports = __toCommonJS(src_exports); // src/Stages.ts var import_easy2 = require("@thisisagile/easy"); // src/Utils.ts var import_easy = require("@thisisagile/easy"); var toMongoType = (input) => (0, import_easy.choose)(input).type(import_easy.isIsoDateString, (i) => new import_easy.DateTime(i).toDate()).type(import_easy.isDateTime, (i) => i.toDate()).type(import_easy.isArray, (a) => a.map((i) => toMongoType(i))).type(import_easy.isDate, (d) => d).type( import_easy.isObject, (o) => Object.fromEntries( (0, import_easy.meta)(o).entries().map(([k, i]) => [k, toMongoType(i)]) ) ).else(input); // src/Stages.ts var asc = 1; var desc = -1; var FilterBuilder = class { constructor(filters) { this.filters = filters; } from = (q = {}) => stages.match.match( (0, import_easy2.meta)(q).entries().reduce((acc, [key, value]) => ({ ...acc, ...(0, import_easy2.ifDefined)(this.filters[key], (f) => f(value)) }), {}) ); }; var SortBuilder = class { constructor(sorts) { this.sorts = sorts; } get keys() { return Object.keys(this.sorts); } from = (s = {}, alt) => stages.sort.sort(this.sorts[s?.s ?? ""] ?? this.sorts[alt ?? ""]); }; var IncludeBuilder = class { constructor(includes) { this.includes = includes; } get keys() { return Object.keys(this.includes); } from = (i = {}, alt) => stages.project.include(...this.includes[i?.i ?? ""] ?? this.includes[alt ?? ""] ?? []); }; var escapeRegex = (s) => s.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); var stages = { root: "$$ROOT", current: "$$CURRENT", id: "_id", decode: { object: (f) => (0, import_easy2.use)(Object.entries(f)[0], ([k, v]) => (0, import_easy2.ofGet)(v, k)), fields: (f) => Object.entries(f).reduce((res, [k, v]) => (0, import_easy2.on)(res, (r) => (0, import_easy2.ifDefined)((0, import_easy2.ofGet)(v, k), (nv) => r[k] = nv)), {}), fieldsArrays: (f) => Object.entries(f).reduce((res, [k, v]) => (0, import_easy2.on)(res, (r) => r[k] = (0, import_easy2.use)((0, import_easy2.toArray)(v), (vs) => vs.map((v2) => (0, import_easy2.ofGet)(v2, k)))), {}), id: (f) => (0, import_easy2.isString)(f) ? `$${(0, import_easy2.asString)(f)}` : (0, import_easy2.isPrimitive)(f) ? f : Object.entries(f).map(([k, v]) => (0, import_easy2.ofGet)(v, k))[0] }, match: { match: (f) => ({ $match: stages.decode.fields(f) }), filter: (filters) => new FilterBuilder(filters), or: (...filters) => ({ $or: (0, import_easy2.toArray)(filters).map((f) => stages.decode.object(f)) }), gt: (value) => ({ $gt: value }), gte: (value) => ({ $gte: value }), lt: (value) => ({ $lt: value }), lte: (value) => ({ $lte: value }), isIn: (value, separator = ",") => ({ $in: (0, import_easy2.isString)(value) ? value.split(separator) : value }), notIn: (value, separator = ",") => ({ $nin: (0, import_easy2.isString)(value) ? value.split(separator) : value }), after: (date) => stages.match.gte(toMongoType(date)), before: (date) => stages.match.lt(toMongoType(date)), anywhere: (q) => ({ $regex: escapeRegex(q), $options: "i" }), money: (currency, value) => (key) => ({ [`${key}.currency`]: currency.id, ...stages.decode.fields({ [`${key}.value`]: value }) }) }, sort: { sort: ($sort) => (0, import_easy2.isPresent)($sort) ? { $sort } : void 0, sorter: (sorts) => new SortBuilder(sorts), asc: (key) => stages.sort.sort({ [key]: asc }), desc: (key) => stages.sort.sort({ [key]: desc }) }, group: { group: (fields) => ({ by: (by) => ({ $group: Object.assign({ _id: stages.decode.id(by) }, stages.decode.fields(fields)) }) }), date: (format = "%Y-%m-%d") => (key) => ({ $dateToString: { date: `$${key}`, format } }), count: () => ({ $count: {} }), sum: (from) => (0, import_easy2.isDefined)(from) ? { $sum: `$${from}` } : { $sum: 1 }, avg: (from) => ({ $avg: `$${from}` }), multiply: (...multiply) => ({ $multiply: multiply.map((m) => `$${m}`) }), first: (from) => ({ $first: `$${from}` }), last: (from) => ({ $last: `$${from}` }), min: (from) => ({ $min: `$${from}` }), max: (from) => ({ $max: `$${from}` }), addToSet: (from) => ({ $addToSet: `$${from}` }), push: (from = "$ROOT") => ({ $push: `$${from}` }), size: (from) => ({ $size: `$${from}` }) }, search: { search: (f) => (0, import_easy2.ifDefined)(stages.decode.id(f), ($search) => ({ $search })), auto: (value) => (key) => (0, import_easy2.ifDefined)(value, (v) => ({ autocomplete: { path: key, query: [v] } })), fuzzy: (value, maxEdits = 1) => (key) => (0, import_easy2.ifDefined)(value, (v) => ({ text: { query: v, path: key === "wildcard" ? { wildcard: "*" } : key, fuzzy: { maxEdits } } })) }, set: { set: (f) => ({ $set: stages.decode.fields(f) }), score: () => ({ $meta: "searchScore" }) }, skip: { skip: (o = {}) => (0, import_easy2.ifDefined)(o.skip, { $skip: (0, import_easy2.asNumber)(o.skip) }), take: (o = {}) => (0, import_easy2.ifDefined)(o.take, { $limit: (0, import_easy2.asNumber)(o.take) }) }, project: { include: (...includes) => (0, import_easy2.ifNotEmpty)(includes, (es) => ({ $project: es.reduce((a, b) => ({ ...a, ...(0, import_easy2.isString)(b) ? { [b]: 1 } : b }), {}) })), exclude: (...excludes) => (0, import_easy2.ifNotEmpty)(excludes, (es) => ({ $project: es.reduce((a, b) => ({ ...a, ...(0, import_easy2.isString)(b) ? { [b]: 0 } : b }), {}) })), includes: (includes) => new IncludeBuilder(includes), project: (project) => (0, import_easy2.ifDefined)(project, ($project) => ({ $project })), date: (key, format) => ({ $toDate: `$${key}`, ...(0, import_easy2.ifDefined)(format, { format }) }), duration: (from, to) => ({ $divide: [{ $subtract: [stages.project.date(from), stages.project.date(to)] }, 1e3] }) }, replaceWith: { replaceWith: (f) => (0, import_easy2.ifDefined)(f, { $replaceWith: f }), merge: (...objects) => (0, import_easy2.ifNotEmpty)(objects, (os) => ({ $mergeObjects: os })), rootAnd: (...objects) => stages.replaceWith.merge(stages.root, ...objects), currentAnd: (...objects) => stages.replaceWith.merge(stages.current, ...objects), reroot: (prop) => ({ $replaceRoot: { newRoot: `$${prop}` } }), concat: (...props) => (0, import_easy2.ifNotEmpty)(props, (ps) => ({ $concatArrays: ps.map((p) => `$${p}`) })) }, facet: { facet: (f) => ({ $facet: stages.decode.fieldsArrays(f) }), unwind: (from) => (f) => ({ $unwind: `$${from ?? f}` }), count: (from) => (f) => ({ $sortByCount: `$${from ?? f}` }), data: () => [] }, unwind: { unwind: (prop) => ({ $unwind: `$${prop}` }) } }; // src/MongoGateway.ts var import_easy3 = require("@thisisagile/easy"); var MongoGateway = class { constructor(collection, provider = collection.provider) { this.collection = collection; this.provider = provider; } all(options) { return this.provider.all(options).then((js) => js.map((j) => this.collection.in(j))); } byId(id) { return this.provider.byId(id).then((j) => (0, import_easy3.ifDefined)(j, this.collection.in(j))); } by(key, value, options) { return this.provider.by(key, value, options).then((js) => js.map((j) => this.collection.in(j))); } byIds(...ids) { return this.find(this.collection.id.isIn(...ids)); } find(q, options) { return this.provider.find((0, import_easy3.asJson)(q), options).then((js) => js.map((j) => this.collection.in(j))); } search(q, options) { return this.find(this.collection.google(q), options); } filter(options) { return this.all(options); } exists(id) { return this.provider.byId(id).then((i) => (0, import_easy3.isDefined)(i)); } aggregate(...filters) { return this.provider.aggregate((0, import_easy3.toArray)(...filters).filter(import_easy3.isPresent)); } count(...filters) { return this.aggregate(...filters, { $count: "total" }).then((d) => d.first()?.total ?? 0); } match(f) { return this.aggregate(stages.match.match(f)); } add(item) { return this.provider.add(this.collection.out(item)).then((j) => this.collection.in(j)); } update(item) { return this.provider.update(this.collection.out(item)).then((j) => this.collection.in(j)); } remove(id) { return this.provider.remove(id); } }; // src/Lucene.ts var import_easy4 = require("@thisisagile/easy"); var should = (query, def) => (0, import_easy4.entries)(query).mapDefined(([k, v]) => def[k]?.(v, query)?.should); var must = (query, def) => (0, import_easy4.entries)(query).mapDefined(([k, v]) => def[k]?.(v, query)?.must); var mustNot = (query, def) => (0, import_easy4.entries)(query).mapDefined(([k, v]) => def[k]?.(v, query)?.mustNot); var filter = (query, def) => (0, import_easy4.entries)(query).mapDefined(([k, v]) => def[k]?.(v, query)?.filter); var lucene = { clause: (c) => (0, import_easy4.entries)(c).reduce((res, [k, v]) => res.add((0, import_easy4.isFunction)(v) ? v(k) : v), (0, import_easy4.toList)()), clauses: (cs) => (0, import_easy4.toArray)(cs).flatMap((c) => lucene.clause(c)), compound: (query, def, wildcard = true) => (0, import_easy4.ifNotEmpty)( (0, import_easy4.entries)({ should: should(query, def), filter: filter(query, def), mustNot: mustNot(query, def), must: must(query, def) }).filter(([_, v]) => v.length > 0), (e) => e.reduce((res, [k, v]) => (0, import_easy4.on)(res, (r) => r[k] = lucene.clauses(v)), should(query, def).length > 0 ? { minimumShouldMatch: 1 } : {}), () => (0, import_easy4.ifTrue)(wildcard, () => ({ should: lucene.clauses([{ r: { exists: { path: "id" } } }]), minimumShouldMatch: 0 })) ), search: (c, index) => ({ $search: { ...(0, import_easy4.ifDefined)(index, { index }), compound: (0, import_easy4.entries)(c).reduce((res, [k, v]) => (0, import_easy4.on)(res, (r) => r[k] = lucene.clauses(v)), {}) } }), searchWithDef: (query, options, count = "total", index) => { const sort = (0, import_easy4.entries)(query).mapDefined(([k, v]) => options[k]?.(v, query)?.sort).first(); return { $search: { ...(0, import_easy4.ifDefined)(index, { index }), compound: lucene.compound(query, options), ...(0, import_easy4.ifDefined)(sort, { sort }), count: { type: count } } }; }, searchMeta: (query, def, count = "total", index) => ({ $searchMeta: { ...(0, import_easy4.ifDefined)(index, { index }), ...(0, import_easy4.ifTrue)( !(0, import_easy4.isEmptyObject)(lucene.facets(def)), { facet: { operator: { compound: lucene.compound(query, def) }, facets: lucene.facets(def) } }, { compound: lucene.compound(query, def) } ), count: { type: count } } }), exists: () => (path) => ({ exists: { path } }), equals: (value) => (path) => ({ equals: { path, value: value === 1 } }), text: (value, fuzzy) => (path) => (0, import_easy4.ifDefined)(value, (v) => ({ text: { path: path === "wildcard" ? { wildcard: "*" } : path, query: v, ...(0, import_easy4.ifDefined)(fuzzy, { fuzzy }) } })), wildcard: (value, allowAnalyzedField = true) => (path) => ({ wildcard: { path: path === "wildcard" ? { wildcard: "*" } : path, query: (0, import_easy4.ifDefined)(value, value, "*"), allowAnalyzedField } }), lt: (value) => (path) => (0, import_easy4.ifDefined)(value, (lt) => ({ range: { path, lt } })), lte: (value) => (path) => (0, import_easy4.ifDefined)(value, (lte) => ({ range: { path, lte } })), gt: (value) => (path) => (0, import_easy4.ifDefined)(value, (gt) => ({ range: { path, gt } })), gte: (value) => (path) => (0, import_easy4.ifDefined)(value, (gte) => ({ range: { path, gte } })), after: (date) => lucene.gte(toMongoType(date)), before: (date) => lucene.lt(toMongoType(date)), between: (after, before, includeLimit) => (path) => { const upperLimit = includeLimit ? { lte: toMongoType(before) } : { lt: toMongoType(before) }; return { range: { path, gte: toMongoType(after), ...upperLimit } }; }, facets: (def) => (0, import_easy4.entries)(def).filter(([k, v]) => (0, import_easy4.isDefined)(v(k)?.facet)).map(([k, v]) => ({ [k]: v(k)?.facet })).reduce((acc, v) => ({ ...acc, ...v }), {}), facet: { string: (path, numBuckets = 1e3) => ({ type: "string", path, numBuckets }), number: (path, boundaries, alt) => ({ type: "number", path, boundaries, ...(0, import_easy4.ifDefined)(alt, (a) => ({ default: a })) }), date: (path, boundaries, alt) => ({ type: "date", path, boundaries: boundaries.mapDefined((b) => b.toDate()), ...(0, import_easy4.ifDefined)(alt, (a) => ({ default: a })) }) } }; // src/AtlasSearchGateway.ts var import_easy5 = require("@thisisagile/easy"); var { skip, take } = stages.skip; var { replaceWith } = stages.replaceWith; var { searchWithDef, searchMeta, facets } = lucene; var toFilters = (facets2) => (0, import_easy5.entries)(facets2).map(([k, fs]) => ({ label: (0, import_easy5.text)(k).title.toString(), field: k, values: fs.map((f) => toValue(f)) })); var toValue = (f) => ({ label: f._id ?? "unknown", value: f._id ?? "unknown", count: f.count }); var AtlasSearchGateway = class extends MongoGateway { constructor(collection, searchDef, sortDef = {}, provider = collection.provider) { super(collection, provider); this.searchDef = searchDef; this.sortDef = sortDef; } query(query, additionalStages = []) { return (0, import_easy5.tuple2)( this.aggregate( searchWithDef(query, this.searchDef), skip({ skip: query?.skip ?? 0 }), take({ take: query?.take ?? 250 }), ...additionalStages ), this.aggregate( searchMeta(query, this.searchDef), replaceWith({ total: "$count.total", facets: Object.keys(facets(this.searchDef)).reduce((acc, k) => ({ ...acc, [k]: `$facet.${k}.buckets` }), {}) }) ) ).then(([data, meta3]) => ({ data, meta: meta3.first() })).then( ({ data, meta: meta3 }) => (0, import_easy5.toPageList)(data, { total: (0, import_easy5.asNumber)(meta3?.total, 0), skip: (0, import_easy5.asNumber)(query?.skip, 0), take: (0, import_easy5.asNumber)(query?.take, 250), sorts: Object.keys(this.sortDef), filters: toFilters(meta3.facets) }) ); } }; // src/Collection.ts var import_easy7 = require("@thisisagile/easy"); // src/MongoProvider.ts var import_easy6 = require("@thisisagile/easy"); var import_mongodb = require("mongodb"); var omitId = (j) => import_easy6.json.delete(j, "_id"); function omitOptions(obj) { const { maxTimeMS, ...rest } = obj ?? {}; return rest; } var MongoProvider = class _MongoProvider { constructor(coll) { this.coll = coll; } static clients = {}; static destroyAll() { return Promise.all((0, import_easy6.entries)(_MongoProvider.clients).map(([u, c]) => c.then((c2) => c2.close()).then(() => delete _MongoProvider.clients[u]))).then( () => void 0 ); } static connect(u, db) { return import_mongodb.MongoClient.connect(u, { auth: { username: (0, import_easy6.asString)(db.options?.user), password: (0, import_easy6.asString)(db.options?.password) }, ...db.options?.maxPoolSize && { maxPoolSize: db.options?.maxPoolSize }, ...db.options?.minPoolSize && { minPoolSize: db.options?.minPoolSize }, ...db.options?.maxIdleTimeMS && { maxIdleTimeMS: db.options?.maxIdleTimeMS }, ...db.options?.socketTimeoutMS && { socketTimeoutMS: db.options?.socketTimeoutMS } }).then((c) => { c.on("error", () => delete _MongoProvider.clients[u]); c.on("close", () => delete _MongoProvider.clients[u]); return c; }).catch((err) => { delete _MongoProvider.clients[u]; return Promise.reject(err); }); } cluster() { return (0, import_easy6.use)( this.coll.db, (db) => (0, import_easy6.when)(db.options?.cluster).not.isDefined.reject(import_easy6.Exception.IsNotValid.because("Missing cluster in database options.")).then((c) => _MongoProvider.clients[c] ?? (_MongoProvider.clients[c] = _MongoProvider.connect(c, db))) ); } collection() { return this.cluster().then((c) => c.db(this.coll.db.name)).then((db) => db.collection((0, import_easy6.asString)(this.coll))); } toMongoJson(query) { return toMongoType((0, import_easy6.asJson)(query)); } withTimeout(options) { return { ...options, maxTimeMS: options?.maxTimeMS ?? this.coll.db?.options?.queryTimeoutMS ?? 3e4 }; } find(query, options) { return (0, import_easy6.tuple3)(this.collection(), this.toMongoJson(query), this.toFindOptions(options)).then( ([c, q, o]) => (0, import_easy6.tuple2)( c.find(q, o), (0, import_easy6.ifTrue)(o.total, () => c.countDocuments(q, { maxTimeMS: this.withTimeout(options).maxTimeMS })) ) ).then(([res, total]) => this.toArray(res, { ...omitOptions(options), total })); } all(options) { return this.find({}, options); } byId(id, options) { return this.collection().then((c) => c.findOne(this.toMongoJson({ id }), this.toFindOptions(options))); } by(key, value, options) { return this.find({ [key]: value }, options); } group(qs, options) { return this.aggregate(qs, options); } aggregate(qs, options) { return this.collection().then( (c) => c.aggregate( qs.map((q) => this.toMongoJson(q)), this.withTimeout(options) ) ).then((res) => this.toArray(res)); } add(item) { return this.collection().then((c) => c.insertOne(omitId(item))).then(() => omitId(item)); } update(item) { return this.collection().then((c) => c.updateOne(this.toMongoJson({ id: item.id }), { $set: omitId(item) })).then(() => this.byId(item.id)); } remove(id) { return this.collection().then((c) => c.deleteOne(this.toMongoJson({ id }))).then((d) => d.acknowledged); } count(query, options) { return this.collection().then((c) => c.countDocuments(this.toMongoJson(query ?? {}), this.withTimeout(options))); } createIndex(indexes, options) { return this.collection().then((c) => c.createIndex(this.toIndexSpecification(indexes), this.toCreateIndexesOptions(options))); } createPartialIndex(indexes, filter2, options) { return this.createIndex(indexes, { ...options, filter: filter2 }); } createTextIndex(indexes, options) { const ii = (0, import_easy6.toArray)(indexes).reduce((i, f) => ({ ...i, [(0, import_easy6.asString)(f)]: "text" }), {}); return this.createIndex(ii, { unique: false, ...options }); } toFindOptions(options) { return { limit: (0, import_easy6.asNumber)(options?.take ?? 250), ...options?.skip && { skip: (0, import_easy6.asNumber)(options?.skip) }, ...options?.sorts && { sort: options?.sorts } || options?.sort && { sort: this.coll.sort(...options?.sort ?? []) }, total: (0, import_easy6.isDefined)(options?.skip) || (0, import_easy6.isDefined)(options?.take), projection: options?.projection ?? { _id: 0 }, maxTimeMS: this.withTimeout(options).maxTimeMS }; } toIndexSpecification(index) { return (0, import_easy6.choose)(index).type(import_easy6.isField, (f) => f.property).type(import_easy6.isSortCondition, (s) => s.toJSON()).type(import_easy6.isArray, (aa) => aa.map((a) => this.toIndexSpecification(a))).else((i) => i); } toCreateIndexesOptions(options) { return { unique: options?.unique ?? true, ...options?.languageOverride && { language_override: options.languageOverride }, ...options?.languageDefault && { default_language: options.languageDefault }, ...options?.filter && { partialFilterExpression: toMongoType((0, import_easy6.asJson)(options.filter)) } }; } toArray(cursor, options) { return cursor.toArray().then((r) => (0, import_easy6.toPageList)(r, options)); } }; // src/Collection.ts var Collection = class extends import_easy7.Mapper { map = { ...import_easy7.mappings, field: (name, options) => new import_easy7.Field(name, options) }; id = this.map.field("id", { dflt: import_easy7.toUuid }); constructor(options = { startFrom: "source" }) { super(options); } get db() { return import_easy7.Database.Default; } get provider() { return new MongoProvider(this); } where = (...conditions) => new import_easy7.LogicalCondition( "and", conditions.map((c) => (0, import_easy7.ofGet)(c, this)) ).toJSON(); match = (condition) => ({ $match: (0, import_easy7.ofGet)(condition, this).toJSON() }); group = (...conditions) => new import_easy7.LogicalCondition( "group", conditions.map((c) => (0, import_easy7.ofGet)(c, this)) ).toJSON(); google = (value) => (0, import_easy7.toCondition)("$text", "search", value); search = (key) => this.map.field((0, import_easy7.asString)(key)); sort = (...conditions) => conditions.reduce((cs, c) => { cs[c.key] = c.value; return cs; }, {}); out(to = {}) { return toMongoType(super.out(to)); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AtlasSearchGateway, Collection, FilterBuilder, IncludeBuilder, MongoGateway, MongoProvider, SortBuilder, asc, desc, lucene, stages, toFilters, toMongoType }); //# sourceMappingURL=index.js.map