@discue/mongodb-resource-client
Advanced tools
Comparing version 0.40.0 to 1.0.0
@@ -1,32 +0,61 @@ | ||
export function EQUALS(field: string, target: unknown): any; | ||
export function EQUALS_ALL(object: any, { prefix }?: { | ||
prefix: any; | ||
}): any; | ||
export function EQUALS_ANY_OF(field: string, targets: any[]): any; | ||
export function EQUALS_WILDCARD(): any; | ||
export function LESS_THAN(field: string, target: unknown): any; | ||
export function LESS_THAN_OR_EQUAL(field: string, target: unknown): any; | ||
export function GREATER_THAN(field: string, target: unknown): any; | ||
export function GREATER_THAN_OR_EQUAL(field: string, target: unknown): any; | ||
export function LIMIT(amount: number): any; | ||
export function PROJECT(projection: any): any; | ||
export function SORT_BY_ASC(...field: string[]): any; | ||
export function SORT_BY_DESC(...field: string[]): any; | ||
export function SORT_BY_COUNT(field: string): any; | ||
export function UNWIND(field: string): any; | ||
export function COUNT(): any; | ||
export function LOOKUP({ from, as, localField, foreignField, pipeline }: LookupOptions): any; | ||
export function REDUCE({ input, initialValue, inExpression }: ReduceOptions): any; | ||
export function APPEND_OBJECTS(...expressions: any[]): any; | ||
export function LET({ vars, inExpression }: LetOptions): any; | ||
export function TO_OBJECT({ key, value }: ToObjectOptions): any; | ||
export function CONCAT_STRINGS(...strings: any[]): any; | ||
export function JOIN_STRINGS(separator: string, ...strings: any[]): any; | ||
export function CONCAT_ARRAYS(): any; | ||
export function ELEMENT_AT(arrayName: string, index: number): any; | ||
export function AS_ROOT(fieldName: string): any; | ||
export function EXISTS(fieldName: string): any; | ||
export function TO_LONG(fieldname: string): object; | ||
export function DATE_TRUNC(fieldname: string, { unit, timezone, binSize, startOfWeek }: string): any; | ||
export function INDEX_STATS(): any; | ||
export function EQUALS(field: string, target: string): Match; | ||
export function EQUALS_ALL(object: object, { prefix }?: { | ||
prefix: string; | ||
}): Match; | ||
export function EQUALS_ANY_OF(field: string, targets: any[]): Match; | ||
export function EQUALS_WILDCARD(): Match; | ||
export function LESS_THAN(field: string, target: string): Match; | ||
export function LESS_THAN_OR_EQUAL(field: string, target: string): Match; | ||
export function GREATER_THAN(field: string, target: string): Match; | ||
export function GREATER_THAN_OR_EQUAL(field: string, target: string): Match; | ||
export function LIMIT(amount: number): Limit; | ||
export function PROJECT(projection: object): Projection; | ||
export function SORT_BY_ASC(...field: string[]): Sort; | ||
export function SORT_BY_DESC(...field: string[]): Sort; | ||
export function SORT_BY_COUNT(field: string): SortByCount; | ||
export function UNWIND(field: string): Unwind; | ||
export function COUNT(): Count; | ||
export function LOOKUP({ from, as, localField, foreignField, pipeline }: LookupOptions): Lookup; | ||
export function REDUCE({ input, initialValue, inExpression }: ReduceOptions): Reduce; | ||
export function APPEND_OBJECTS(...expressions: any[]): AppendObjects; | ||
export function LET({ vars, inExpression }: LetOptions): Let; | ||
export function TO_OBJECT({ key, value }: ToObjectOptions): ToObject; | ||
export function CONCAT_STRINGS(...strings: any): Concat; | ||
export function JOIN_STRINGS(separator: string, ...strings: string[]): Concat; | ||
export function CONCAT_ARRAYS(): ConcatArray; | ||
export function ELEMENT_AT(arrayName: string, index: number): ArrayElementAt; | ||
export function AS_ROOT(fieldName: string): ReplaceRoot; | ||
export function EXISTS(fieldName: string): Match; | ||
export function TO_LONG(fieldname: string): Set; | ||
export function DATE_TRUNC(fieldname: string, { unit, timezone, binSize, startOfWeek }: { | ||
unit: string; | ||
binSize?: number; | ||
timezone?: string; | ||
startOfWeek?: string; | ||
}): DateTrunc; | ||
export function INDEX_STATS(): IndexStats; | ||
export type Match = { | ||
$match: object; | ||
}; | ||
export type Limit = { | ||
$limit: object; | ||
}; | ||
export type Projection = { | ||
$limit: object; | ||
}; | ||
export type Sort = { | ||
$sort: object; | ||
}; | ||
export type SortByCount = { | ||
$sortByCount: object; | ||
}; | ||
export type Unwind = { | ||
$unwind: object; | ||
}; | ||
export type Count = { | ||
$count: object; | ||
}; | ||
export type Lookup = { | ||
$lookup: object; | ||
}; | ||
export type LookupOptions = { | ||
@@ -50,6 +79,9 @@ /** | ||
/** | ||
* pipeline for aggregation of the lookup query | ||
* pipeline for of the lookup query | ||
*/ | ||
pipeline: any[]; | ||
}; | ||
export type Reduce = { | ||
$reduce: object; | ||
}; | ||
export type ReduceOptions = { | ||
@@ -67,4 +99,10 @@ /** | ||
*/ | ||
inExpression: any; | ||
inExpression: object; | ||
}; | ||
export type AppendObjects = { | ||
mergeObjects: object; | ||
}; | ||
export type Let = { | ||
$let: object; | ||
}; | ||
export type LetOptions = { | ||
@@ -74,8 +112,11 @@ /** | ||
*/ | ||
vars: any; | ||
vars: object; | ||
/** | ||
* any expression | ||
*/ | ||
inExpression: any; | ||
inExpression: object; | ||
}; | ||
export type ToObject = { | ||
arrayToObject: object; | ||
}; | ||
export type ToObjectOptions = { | ||
@@ -89,3 +130,26 @@ /** | ||
*/ | ||
expression: any; | ||
expression: object; | ||
}; | ||
export type Concat = { | ||
$concat: object; | ||
}; | ||
export type ConcatArray = { | ||
$concatArrays: Array<string>; | ||
}; | ||
export type ArrayElementAt = { | ||
$arrayElemAt: Array<string>; | ||
}; | ||
export type ReplaceRoot = { | ||
$replaceRoot: { | ||
newRoot: string; | ||
}; | ||
}; | ||
export type Set = { | ||
$set: object; | ||
}; | ||
export type DateTrunc = { | ||
$dateTrunc: object; | ||
}; | ||
export type IndexStats = { | ||
$indexStats: object; | ||
}; |
@@ -1,12 +0,38 @@ | ||
'use strict' | ||
/** | ||
* | ||
* @param {string} string | ||
* @returns {string} | ||
*/ | ||
function withLeadingDollar(string) { | ||
if (!string.startsWith('$')) { | ||
string = `$${string}` | ||
} | ||
return string | ||
} | ||
/** | ||
* Returns a $match aggregation stage | ||
* | ||
* @param {Array.<string>} array | ||
* @param {string} value | ||
*/ | ||
function reduce(array, value) { | ||
return array.reduce((context, element) => { | ||
return Object.assign(context, { | ||
[element]: value | ||
}) | ||
}, {}) | ||
} | ||
/** | ||
* @typedef Match | ||
* @property {object} $match | ||
*/ | ||
/** | ||
* | ||
* @param {String} field the target field name | ||
* @param {unknown} target the target value | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {string} target the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.EQUALS = (field, target) => { | ||
export const EQUALS = (field, target) => { | ||
return { | ||
@@ -20,9 +46,9 @@ $match: { | ||
/** | ||
* Returns a $match aggregation stage for all entries of the object | ||
* | ||
* @param {object} field the target field name | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {object} object an object containing they key value pairs to query for | ||
* @param {object} [options] options | ||
* @param {string} options.prefix a prefix to add to all keys | ||
* @returns {Match} | ||
*/ | ||
module.exports.EQUALS_ALL = (object, { prefix } = {}) => { | ||
export const EQUALS_ALL = (object, { prefix } = {}) => { | ||
const keyPrefix = prefix ? `${prefix}.` : '' | ||
@@ -37,10 +63,8 @@ const query = Object.entries(object).reduce((context, [key, value]) => { | ||
/** | ||
* Returns a $in aggregation stage | ||
* | ||
* @param {String} field the target field name | ||
* @param {Array} targets the target values | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {Array} targets the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.EQUALS_ANY_OF = (field, targets) => { | ||
export const EQUALS_ANY_OF = (field, targets) => { | ||
return { | ||
@@ -56,7 +80,6 @@ $match: { | ||
/** | ||
* Matches all documents of a collection | ||
* | ||
* @returns {Object} an object containing a MongoDb match object | ||
* | ||
* @returns {Match} | ||
*/ | ||
module.exports.EQUALS_WILDCARD = () => { | ||
export const EQUALS_WILDCARD = () => { | ||
return { | ||
@@ -67,12 +90,9 @@ $match: { _id: { $exists: true } } | ||
/** | ||
* Returns a $match aggregation stage | ||
* | ||
* @param {String} field the target field name | ||
* @param {unknown} target the target value | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {string} target the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.LESS_THAN = (field, target) => { | ||
export const LESS_THAN = (field, target) => { | ||
return { | ||
@@ -88,10 +108,8 @@ $match: { | ||
/** | ||
* Returns a $match aggregation stage | ||
* | ||
* @param {String} field the target field name | ||
* @param {unknown} target the target value | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {string} target the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.LESS_THAN_OR_EQUAL = (field, target) => { | ||
export const LESS_THAN_OR_EQUAL = (field, target) => { | ||
return { | ||
@@ -107,10 +125,8 @@ $match: { | ||
/** | ||
* Returns a $match aggregation stage | ||
* | ||
* @param {String} field the target field name | ||
* @param {unknown} target the target value | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {string} target the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.GREATER_THAN = (field, target) => { | ||
export const GREATER_THAN = (field, target) => { | ||
return { | ||
@@ -126,10 +142,8 @@ $match: { | ||
/** | ||
* Returns a $match aggregation stage | ||
* | ||
* @param {String} field the target field name | ||
* @param {unknown} target the target value | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field the name of the field | ||
* @param {string} target the target value | ||
* @returns {Match} | ||
*/ | ||
module.exports.GREATER_THAN_OR_EQUAL = (field, target) => { | ||
export const GREATER_THAN_OR_EQUAL = (field, target) => { | ||
return { | ||
@@ -145,9 +159,12 @@ $match: { | ||
/** | ||
* Returns a $limit aggregation stage | ||
* @typedef Limit | ||
* @property {object} $limit | ||
*/ | ||
/** | ||
* | ||
* @param {Number} amount the desired maximum number of elements the query should return | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {number} amount the amount of documents to return | ||
* @returns {Limit} | ||
*/ | ||
module.exports.LIMIT = (amount) => { | ||
export const LIMIT = (amount) => { | ||
return { | ||
@@ -159,9 +176,12 @@ $limit: amount | ||
/** | ||
* Returns a $sort aggregation stage | ||
* @typedef Projection | ||
* @property {object} $limit | ||
*/ | ||
/** | ||
* | ||
* @param {Object} projection | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {object} projection the projection object | ||
* @returns {Projection} | ||
*/ | ||
module.exports.PROJECT = (projection) => { | ||
export const PROJECT = (projection) => { | ||
return { | ||
@@ -173,9 +193,12 @@ $project: projection | ||
/** | ||
* Returns a $sort aggregation stage | ||
* @typedef Sort | ||
* @property {object} $sort | ||
*/ | ||
/** | ||
* | ||
* @param {...String} field the target field name | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {...string} field | ||
* @returns {Sort} | ||
*/ | ||
module.exports.SORT_BY_ASC = (...field) => { | ||
export const SORT_BY_ASC = (...field) => { | ||
return { | ||
@@ -187,9 +210,7 @@ $sort: reduce(field, 1) | ||
/** | ||
* Returns a $sort aggregation stage | ||
* | ||
* @param {...String} field the target field name | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {...string} field | ||
* @returns {Sort} | ||
*/ | ||
module.exports.SORT_BY_DESC = (...field) => { | ||
export const SORT_BY_DESC = (...field) => { | ||
return { | ||
@@ -201,9 +222,12 @@ $sort: reduce(field, -1) | ||
/** | ||
* Returns a $sortByCount aggregation stage. Will add required $ prefix to field if missing. | ||
* @typedef SortByCount | ||
* @property {object} $sortByCount | ||
*/ | ||
/** | ||
* | ||
* @param {String} field the target field name | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field | ||
* @returns {SortByCount} | ||
*/ | ||
module.exports.SORT_BY_COUNT = (field) => { | ||
export const SORT_BY_COUNT = (field) => { | ||
return { | ||
@@ -215,9 +239,12 @@ $sortByCount: withLeadingDollar(field) | ||
/** | ||
* Returns a $unwind aggregation stage. Will add required $ prefix to field if missing. | ||
* @typedef Unwind | ||
* @property {object} $unwind | ||
*/ | ||
/** | ||
* | ||
* @param {String} field the target field name | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @param {string} field | ||
* @returns {Unwind} | ||
*/ | ||
module.exports.UNWIND = (field) => { | ||
export const UNWIND = (field) => { | ||
return { | ||
@@ -229,8 +256,11 @@ $unwind: withLeadingDollar(field) | ||
/** | ||
* Returns a $count aggregation stage | ||
* @typedef Count | ||
* @property {object} $count | ||
*/ | ||
/** | ||
* | ||
* @returns {Object} an object containing a MongoDb projection object | ||
* | ||
* @returns {Count} | ||
*/ | ||
module.exports.COUNT = () => { | ||
export const COUNT = () => { | ||
return { | ||
@@ -242,8 +272,13 @@ $count: 'count' | ||
/** | ||
* @typedef Lookup | ||
* @property {object} $lookup | ||
*/ | ||
/** | ||
* @typedef LookupOptions | ||
* @property {String} from the collection to lookup from | ||
* @property {String} as name of the merged field | ||
* @property {String} localField local field name | ||
* @property {String} foreignField field name of the `from` collection | ||
* @property {Array} pipeline pipeline for aggregation of the lookup query | ||
* @property {string} from the collection to lookup from | ||
* @property {string} as name of the merged field | ||
* @property {string} localField local field name | ||
* @property {string} foreignField field name of the `from` collection | ||
* @property {Array} pipeline pipeline for of the lookup query | ||
*/ | ||
@@ -254,5 +289,5 @@ | ||
* @param {LookupOptions} options | ||
* @returns {Object} | ||
* @returns {Lookup} | ||
*/ | ||
module.exports.LOOKUP = ({ from, as, localField, foreignField = 'id', pipeline = [] }) => { | ||
export const LOOKUP = ({ from, as, localField, foreignField = 'id', pipeline = [] }) => { | ||
return { | ||
@@ -266,6 +301,11 @@ $lookup: { | ||
/** | ||
* @typedef Reduce | ||
* @property {object} $reduce | ||
*/ | ||
/** | ||
* @typedef ReduceOptions | ||
* @property {String} input any expression resolving to an array | ||
* @property {string} input any expression resolving to an array | ||
* @property {any} initialValue the initial value used for reduction | ||
* @property {Object} inExpression any expression applied to each element of the array | ||
* @property {object} inExpression any expression applied to each element of the array | ||
*/ | ||
@@ -276,5 +316,5 @@ | ||
* @param {ReduceOptions} options | ||
* @returns {Object} | ||
* @returns {Reduce} | ||
*/ | ||
module.exports.REDUCE = ({ input, initialValue = {}, inExpression }) => { | ||
export const REDUCE = ({ input, initialValue = {}, inExpression }) => { | ||
return { | ||
@@ -288,8 +328,13 @@ $reduce: { | ||
/** | ||
* @typedef AppendObjects | ||
* @property {object} mergeObjects | ||
*/ | ||
/** | ||
* Returns a $mergeObjects expression that appends the result of the given expressions to $$value | ||
* | ||
* @param {...any} expressions a list of expressions that evaluate to an object | ||
* @returns {Object} | ||
* @returns {AppendObjects} | ||
*/ | ||
module.exports.APPEND_OBJECTS = (...expressions) => { | ||
export const APPEND_OBJECTS = (...expressions) => { | ||
return { | ||
@@ -301,5 +346,10 @@ $mergeObjects: ['$$value', ...expressions] | ||
/** | ||
* @typedef Let | ||
* @property {object} $let | ||
*/ | ||
/** | ||
* @typedef LetOptions | ||
* @property {Object} vars an object defining additional variables for the expression | ||
* @property {Object} inExpression any expression | ||
* @property {object} vars an object defining additional variables for the expression | ||
* @property {object} inExpression any expression | ||
*/ | ||
@@ -310,5 +360,5 @@ | ||
* @param {LetOptions} options | ||
* @returns {Object} | ||
* @returns {Let} | ||
*/ | ||
module.exports.LET = ({ vars, inExpression }) => { | ||
export const LET = ({ vars, inExpression }) => { | ||
return { | ||
@@ -322,5 +372,10 @@ $let: { | ||
/** | ||
* @typedef ToObject | ||
* @property {object} arrayToObject | ||
*/ | ||
/** | ||
* @typedef ToObjectOptions | ||
* @property {String} key the object key | ||
* @property {Object} expression any expression | ||
* @property {string} key the object key | ||
* @property {object} expression any expression | ||
*/ | ||
@@ -331,5 +386,5 @@ | ||
* @param {ToObjectOptions} options | ||
* @returns {Object} | ||
* @returns {ToObject} | ||
*/ | ||
module.exports.TO_OBJECT = ({ key = '$$this', value }) => { | ||
export const TO_OBJECT = ({ key = '$$this', value }) => { | ||
return { | ||
@@ -348,7 +403,12 @@ $arrayToObject: [ | ||
/** | ||
* @typedef Concat | ||
* @property {object} $concat | ||
*/ | ||
/** | ||
* | ||
* @param {...any} strings strings to concat | ||
* @returns {Object} | ||
* @param {...strings} strings strings to concat | ||
* @returns {Concat} | ||
*/ | ||
module.exports.CONCAT_STRINGS = (...strings) => { | ||
export const CONCAT_STRINGS = (...strings) => { | ||
return { | ||
@@ -359,9 +419,10 @@ $concat: strings | ||
/** | ||
* | ||
* @param {String} separator separator to be used for joining given strings | ||
* @param {...any} strings strings to concat | ||
* @returns {Object} | ||
* @param {string} separator separator to be used for joining given strings | ||
* @param {...string} strings strings to concat | ||
* @returns {Concat} | ||
*/ | ||
module.exports.JOIN_STRINGS = (separator, ...strings) => { | ||
export const JOIN_STRINGS = (separator, ...strings) => { | ||
const array = strings.reduce((context, next) => { | ||
@@ -371,10 +432,15 @@ context.push(separator, next) | ||
}, []) | ||
return module.exports.CONCAT_STRINGS.apply(null, array) | ||
return CONCAT_STRINGS.apply(null, array) | ||
} | ||
/** | ||
* @typedef ConcatArray | ||
* @property {Array.<string>} $concatArrays | ||
*/ | ||
/** | ||
* | ||
* @returns {Object} | ||
* @returns {ConcatArray} | ||
*/ | ||
module.exports.CONCAT_ARRAYS = () => { | ||
export const CONCAT_ARRAYS = () => { | ||
return { | ||
@@ -389,8 +455,13 @@ $concatArrays: [ | ||
/** | ||
* @typedef ArrayElementAt | ||
* @property {Array.<string>} $arrayElemAt | ||
*/ | ||
/** | ||
* | ||
* @param {String} arrayName name of the array | ||
* @param {Number} index | ||
* @returns {Object} | ||
* @param {string} arrayName name of the array | ||
* @param {number} index | ||
* @returns {ArrayElementAt} | ||
*/ | ||
module.exports.ELEMENT_AT = (arrayName, index) => { | ||
export const ELEMENT_AT = (arrayName, index) => { | ||
return { | ||
@@ -402,7 +473,13 @@ $arrayElemAt: [withLeadingDollar(arrayName), index] | ||
/** | ||
* @typedef ReplaceRoot | ||
* @property {object} $replaceRoot | ||
* @property {string} $replaceRoot.newRoot | ||
*/ | ||
/** | ||
* | ||
* @param {String} fieldName the field to return as root | ||
* @returns {Object} | ||
* @param {string} fieldName the field to return as root | ||
* @returns {ReplaceRoot} | ||
*/ | ||
module.exports.AS_ROOT = (fieldName) => { | ||
export const AS_ROOT = (fieldName) => { | ||
return { | ||
@@ -417,6 +494,6 @@ $replaceRoot: { | ||
* | ||
* @param {String} fieldName the field to check for existence | ||
* @returns {Object} | ||
* @param {string} fieldName the target fieldname | ||
* @returns {Match} | ||
*/ | ||
module.exports.EXISTS = (fieldName) => { | ||
export const EXISTS = (fieldName) => { | ||
return { | ||
@@ -428,7 +505,12 @@ $match: { [fieldName]: { $exists: true } } | ||
/** | ||
* @typedef Set | ||
* @property {object} $set | ||
*/ | ||
/** | ||
* | ||
* @param {string} fieldname | ||
* @returns {object} | ||
* @param {string} fieldname the target fieldname | ||
* @returns {Set} | ||
*/ | ||
module.exports.TO_LONG = (fieldname) => { | ||
export const TO_LONG = (fieldname) => { | ||
return { | ||
@@ -444,13 +526,18 @@ $set: { | ||
/** | ||
* @typedef DateTrunc | ||
* @property {object} $dateTrunc | ||
*/ | ||
/** | ||
* | ||
* @param {string} fieldname | ||
* @param {string} options | ||
* @param {string} unit e.g. day, month, quarter, year. | ||
* @param {number} [binSize=1] specifies the amount units | ||
* @param {string} [timezone=Europe/Berlin] | ||
* @param {string} [startOfWeek=monday] | ||
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/ | ||
* @returns {Object} | ||
* @param {object} options | ||
* @param {string} options.unit e.g. day, month, quarter, year. | ||
* @param {number} [options.binSize=1] specifies the amount units | ||
* @param {string} [options.timezone=Europe/Berlin] | ||
* @param {string} [options.startOfWeek=monday] | ||
* @returns {DateTrunc} | ||
* @see https://www.mongodb.com/docs/manual/reference/operator//dateTrunc/ | ||
*/ | ||
module.exports.DATE_TRUNC = (fieldname, { unit, timezone = 'Europe/Berlin', binSize = 1, startOfWeek = 'monday' }) => { | ||
export const DATE_TRUNC = (fieldname, { unit, timezone = 'Europe/Berlin', binSize = 1, startOfWeek = 'monday' }) => { | ||
return { | ||
@@ -465,29 +552,14 @@ $dateTrunc: { | ||
/** | ||
* | ||
* @returns {Object} | ||
* @typedef IndexStats | ||
* @property {object} $indexStats | ||
*/ | ||
module.exports.INDEX_STATS = () => { | ||
return { | ||
'$indexStats': {} | ||
} | ||
} | ||
/** | ||
* | ||
* @param {string} string | ||
* @returns {string} | ||
* @returns {IndexStats} | ||
*/ | ||
function withLeadingDollar(string) { | ||
if (!string.startsWith('$')) { | ||
string = `$${string}` | ||
export const INDEX_STATS = () => { | ||
return { | ||
'$indexStats': {} | ||
} | ||
return string | ||
} | ||
function reduce(array, value) { | ||
return array.reduce((context, element) => { | ||
return Object.assign(context, { | ||
[element]: value | ||
}) | ||
}, {}) | ||
} |
@@ -1,102 +0,102 @@ | ||
export = exports; | ||
declare class exports { | ||
/** | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
constructor({ client, databaseName, collectionName }?: ConstructorOptions); | ||
/** @private */ private _mongoDbClient; | ||
/** @private */ private _databaseName; | ||
/** @private */ private _collectionName; | ||
/** @private */ private _transactionsEnabled; | ||
/** | ||
* @protected | ||
* @returns {import('mongodb').MongoClient} | ||
*/ | ||
protected _getConnectedClient(): import("mongodb").MongoClient; | ||
/** | ||
* @protected | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {import('mongodb').Db} | ||
*/ | ||
protected _getDb(givenClient?: import("mongodb").MongoClient): import("mongodb").Db; | ||
/** | ||
* @protected | ||
* @param {import('mongodb').MongoClient} [collectionName] | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {Promise.<Collection>} | ||
*/ | ||
protected _getCollection(collectionName?: import("mongodb").MongoClient, givenClient?: import("mongodb").MongoClient): Promise<Collection>; | ||
/** | ||
* @protected | ||
*/ | ||
protected _runWithActiveSpan(spanName: any, resourceIds: any, callback: any): Promise<any>; | ||
/** | ||
* @typedef {import('mongodb').ClientSession} ClientSession | ||
*/ | ||
/** | ||
* @callback WithSessionCallback | ||
* @param {ClientSession} clientSession | ||
*/ | ||
/** | ||
* @protected | ||
* @param {WithSessionCallback} callback | ||
*/ | ||
protected _withTransaction(callback: (clientSession: import("mongodb").ClientSession) => any): Promise<any>; | ||
/** | ||
* @protected | ||
* @param {object} options | ||
* @returns {object} | ||
*/ | ||
protected _passSessionIfTransactionEnabled(options: object): object; | ||
/** | ||
* @protected | ||
* @returns {object} | ||
*/ | ||
protected _createMetadata(): object; | ||
/** | ||
* @protected | ||
* @returns {object} | ||
*/ | ||
protected _createUpdateMetadata(): object; | ||
/** | ||
* Closes the database client | ||
* | ||
* @method close | ||
* @returns {void} | ||
*/ | ||
close(): void; | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, GetOptions, WithMongoClient, Collection, MongoClient }; | ||
} | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
declare const _default: { | ||
new ({ client, databaseName, collectionName }?: ConstructorOptions): { | ||
/** @private */ _mongoDbClient: any; | ||
/** @private */ _databaseName: any; | ||
/** @private */ _collectionName: any; | ||
/** @private */ _transactionsEnabled: boolean; | ||
/** @private */ _history: History; | ||
/** @protected */ _emitter: Emittery<Record<PropertyKey, any>, Record<PropertyKey, any> & import("emittery").OmnipresentEventData, import("emittery").DatalessEventNames<Record<PropertyKey, any>>>; | ||
/** @private */ _eventListenersCount: { | ||
create: number; | ||
update: number; | ||
delete: number; | ||
}; | ||
/** | ||
* @returns {import('mongodb').MongoClient} | ||
* @protected | ||
*/ | ||
_getConnectedClient(): import("mongodb").MongoClient; | ||
/** | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {import('mongodb').Db} | ||
* @protected | ||
*/ | ||
_getDb(givenClient?: import("mongodb").MongoClient): import("mongodb").Db; | ||
/** | ||
* @param {import('mongodb').MongoClient} [collectionName] | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {Promise.<Collection>} | ||
* @protected | ||
*/ | ||
_getCollection(collectionName?: import("mongodb").MongoClient, givenClient?: import("mongodb").MongoClient): Promise<Collection>; | ||
/** | ||
* @param {string} spanName | ||
* @param {string | Array.<string> }resourceIds | ||
* @param {Function} callback | ||
* @protected | ||
*/ | ||
_runWithActiveSpan(spanName: string, resourceIds: string | Array<string>, callback: Function): Promise<any>; | ||
/** | ||
* @typedef {import('mongodb').ClientSession} ClientSession | ||
*/ | ||
/** | ||
* @callback WithSessionCallback | ||
* @param {ClientSession} clientSession | ||
*/ | ||
/** | ||
* @param {WithSessionCallback} callback | ||
* @protected | ||
*/ | ||
_withTransaction(callback: (clientSession: mongodb.ClientSession) => any): Promise<any>; | ||
/** | ||
* @param {object} options | ||
* @returns {object} | ||
* @protected | ||
*/ | ||
_passSessionIfTransactionEnabled(options: object): object; | ||
/** | ||
* @returns {object} | ||
* @protected | ||
*/ | ||
_createMetadata(): object; | ||
/** | ||
* @returns {object} | ||
* @protected | ||
*/ | ||
_createUpdateMetadata(): object; | ||
/** | ||
* @callback AsyncFunction | ||
* @returns {Promise.<any>} | ||
*/ | ||
/** | ||
* @param {('create'|'update'|'delete'|'close')} eventName | ||
* @param {AsyncFunction} callback | ||
* @returns {Function} a function to unsubscribe the listener | ||
*/ | ||
on(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
/** | ||
* @param {('create'|'update'|'delete'|'close')} eventName | ||
* @param {AsyncFunction} callback | ||
* @returns {Function} a function to unsubscribe the listener | ||
*/ | ||
off(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
enableHistory(): void; | ||
disableHistory(): void; | ||
/** | ||
* Closes the database client | ||
* | ||
* @function close | ||
* @returns {void} | ||
*/ | ||
close(): void; | ||
}; | ||
}; | ||
type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
}; | ||
type WithMongoClient = { | ||
client: import("mongodb").MongoClient; | ||
}; | ||
type Collection = import("mongodb").Collection; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export default _default; | ||
export type ConstructorOptions = any; | ||
export type GetOptions = any; | ||
export type WithMongoClient = any; | ||
export type Collection = import("mongodb").Collection; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
import History from './history.js'; | ||
import Emittery from 'emittery'; | ||
import * as mongodb from 'mongodb'; |
@@ -1,17 +0,11 @@ | ||
'use strict' | ||
import Emittery from 'emittery' | ||
import * as mongodb from 'mongodb' | ||
import History from './history.js' | ||
import { withActiveSpan } from './tracer.js' | ||
const { Timestamp } = require('mongodb') | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { name } = require('../package.json') | ||
const { Timestamp } = mongodb | ||
/** | ||
* @protected | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
@@ -23,20 +17,20 @@ * @property {string} [databaseName=null] name of the mongodb database | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @protected | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef WithMongoClient | ||
* @name WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
* @protected | ||
* @typedef WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
*/ | ||
/** | ||
* @private | ||
* @typedef {import('mongodb').Collection} Collection | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
@@ -51,8 +45,8 @@ | ||
*/ | ||
module.exports = class { | ||
export default (class { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
@@ -62,15 +56,28 @@ constructor({ client, databaseName, collectionName } = {}) { | ||
/** @private */ this._mongoDbClient = client | ||
} else { | ||
} | ||
else { | ||
throw new Error('Configuration Error. `client` needs to be set.') | ||
} | ||
/** @private */ this._databaseName = process.env.DSQ_MONGODB_RESOURCE_CLIENT_DB_NAME || databaseName | ||
/** @private */ this._collectionName = collectionName | ||
/** @private */ this._transactionsEnabled = process.env.DSQ_MONGOD_ENABLE_TRANSACTIONS === 'true' | ||
/** @private */ this._history = null | ||
/** @private */ this._transactionsEnabled = process.env.DSQ_MONGOD_ENABLE_TRANSACTIONS === 'true' | ||
/** @protected */ this._emitter = new Emittery() | ||
/** @private */ this._eventListenersCount = { | ||
create: 0, | ||
update: 0, | ||
delete: 0 | ||
} | ||
this._emitter.on(Emittery.listenerAdded, ({ eventName }) => { | ||
this._eventListenersCount[eventName]++ | ||
}) | ||
this._emitter.on(Emittery.listenerRemoved, ({ eventName }) => { | ||
this._eventListenersCount[eventName]-- | ||
}) | ||
} | ||
/** | ||
* @returns {import('mongodb').MongoClient} | ||
* @protected | ||
* @returns {import('mongodb').MongoClient} | ||
*/ | ||
@@ -80,7 +87,6 @@ async _getConnectedClient() { | ||
} | ||
/** | ||
* @protected | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {import('mongodb').Db} | ||
* @protected | ||
*/ | ||
@@ -93,8 +99,7 @@ async _getDb(givenClient) { | ||
} | ||
/** | ||
* @protected | ||
* @param {import('mongodb').MongoClient} [collectionName] | ||
* @param {import('mongodb').MongoClient} [givenClient] | ||
* @returns {Promise.<Collection>} | ||
* @protected | ||
*/ | ||
@@ -111,8 +116,10 @@ async _getCollection(collectionName, givenClient) { | ||
} | ||
/** | ||
* @param {string} spanName | ||
* @param {string | Array.<string> }resourceIds | ||
* @param {Function} callback | ||
* @protected | ||
*/ | ||
async _runWithActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
return withActiveSpan(`${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
@@ -123,3 +130,2 @@ | ||
*/ | ||
/** | ||
@@ -129,6 +135,5 @@ * @callback WithSessionCallback | ||
*/ | ||
/** | ||
* @param {WithSessionCallback} callback | ||
* @protected | ||
* @param {WithSessionCallback} callback | ||
*/ | ||
@@ -144,3 +149,4 @@ async _withTransaction(callback) { | ||
}) | ||
} catch (e) { | ||
} | ||
catch (e) { | ||
if (session.transaction.isActive && !session.transaction.isCommitted) { | ||
@@ -150,11 +156,11 @@ await session.abortTransaction() | ||
throw e | ||
} finally { | ||
} | ||
finally { | ||
await session.endSession() | ||
} | ||
} | ||
/** | ||
* @param {object} options | ||
* @returns {object} | ||
* @protected | ||
* @param {object} options | ||
* @returns {object} | ||
*/ | ||
@@ -167,6 +173,5 @@ _passSessionIfTransactionEnabled(options) { | ||
} | ||
/** | ||
* @returns {object} | ||
* @protected | ||
* @returns {object} | ||
*/ | ||
@@ -180,6 +185,5 @@ _createMetadata() { | ||
} | ||
/** | ||
* @returns {object} | ||
* @protected | ||
* @returns {object} | ||
*/ | ||
@@ -194,10 +198,44 @@ _createUpdateMetadata() { | ||
/** | ||
* @callback AsyncFunction | ||
* @returns {Promise.<any>} | ||
*/ | ||
/** | ||
* @param {('create'|'update'|'delete'|'close')} eventName | ||
* @param {AsyncFunction} callback | ||
* @returns {Function} a function to unsubscribe the listener | ||
*/ | ||
on(eventName, callback) { | ||
return this._emitter.on(eventName, callback) | ||
} | ||
/** | ||
* @param {('create'|'update'|'delete'|'close')} eventName | ||
* @param {AsyncFunction} callback | ||
* @returns {Function} a function to unsubscribe the listener | ||
*/ | ||
off(eventName, callback) { | ||
return this._emitter.off(eventName, callback) | ||
} | ||
enableHistory() { | ||
this._history = new History({ client: this._mongoDbClient, databaseName: this._databaseName, collectionName: this._collectionName, storage: this }) | ||
} | ||
disableHistory() { | ||
this._history.disable() | ||
this._history = null | ||
} | ||
/** | ||
* Closes the database client | ||
* | ||
* @method close | ||
* | ||
* @function close | ||
* @returns {void} | ||
*/ | ||
async close() { | ||
await this._emitter.emit('close') | ||
this._emitter.clearListeners() | ||
return this._mongoDbClient.close() | ||
} | ||
} | ||
}) |
@@ -1,31 +0,47 @@ | ||
export = exports; | ||
declare class exports { | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @description Options for class constructor | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {import('./base-storage.js')} storage the target storage object. | ||
* @example | ||
* import { OneToFewResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const collectionName = 'api_clients' | ||
* const url = 'mongodb://127.0.0.1:27017' | ||
* | ||
* const storage = new OneToFewResourceStorage({ | ||
* url, | ||
* collectionName | ||
* }) | ||
* | ||
* storage.enableHistory() | ||
*/ | ||
/** | ||
* Simple resource class that will listen to storage events of the given storage object | ||
* | ||
* @name ResourceStorageHistory | ||
* @class | ||
*/ | ||
export default class _default { | ||
/** | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
constructor({ client, databaseName, collectionName, connectTimeout, usageEventPrefix, eventEmitter }?: ConstructorOptions); | ||
_eventEmitter: any; | ||
_collectionName: any; | ||
_usageEventPrefix: any; | ||
_resourceStorage: ResourceStorage; | ||
/** | ||
* Activate listening for storage events to monitor changes of target storage resource to | ||
* populate the history table. | ||
* | ||
* @public | ||
*/ | ||
public listenForStorageEvents(): void; | ||
constructor({ client, databaseName, collectionName, connectTimeout, storage }?: ConstructorOptions); | ||
/** @private */ private _collectionName; | ||
/** @private */ private _parentStorage; | ||
/** @private */ private _storage; | ||
/** @private */ private _eventHandlers; | ||
disable(): void; | ||
/** | ||
* @param {string} action | ||
* @param {object} event | ||
* @param {object} event.after | ||
* @param {string | Array.<string>} event.resourceIds | ||
* @param {string} event.collectionName | ||
* @private | ||
* @param {import('node:events').EventEmitter} eventEmitter | ||
*/ | ||
private _registerEventHandlers; | ||
/** | ||
* @private | ||
* @param {Object} event | ||
* @param {boolean} event.error | ||
* @param {Object} event.after | ||
* @param {Array.<String>} event.resourceIds | ||
*/ | ||
private _eventHandler; | ||
@@ -35,3 +51,3 @@ /** | ||
* | ||
* @method close | ||
* @function close | ||
* @returns {void} | ||
@@ -41,6 +57,2 @@ */ | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions }; | ||
} | ||
import ResourceStorage = require("./simple-resource-storage.js"); | ||
type ConstructorOptions = any; | ||
export type ConstructorOptions = any; |
@@ -1,18 +0,7 @@ | ||
'use strict' | ||
import ResourceStorage from './simple-resource-storage.js' | ||
import { withActiveSpan } from './tracer.js' | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const ResourceStorage = require('./simple-resource-storage.js') | ||
const { name } = require('../package.json') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @description Options for class constructor | ||
@@ -22,30 +11,19 @@ * @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {string} usageEventPrefix the usageEventPrefix of the target storage module | ||
* @property {import('node:events').EventEmitter} eventEmitter if provided, will trigger events base on resource creation, updates and deletion | ||
* | ||
* @property {import('./base-storage.js')} storage the target storage object. | ||
* @example | ||
* const { EventEmitter } = require('events') | ||
* const { OneToFewResourceStorage, ResourceStorageHistory } = require('@discue/mongodb-resource-client') | ||
* import { OneToFewResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const eventEmitter = new EventEmitter() | ||
* const collectionName = 'api_clients' | ||
* const url = 'mongodb://127.0.0.1:27017' | ||
* | ||
* const oneToFewResourceStorage = new OneToFewResourceStorage({ | ||
* const storage = new OneToFewResourceStorage({ | ||
* url, | ||
* collectionName, | ||
* eventEmitter | ||
* collectionName | ||
* }) | ||
* | ||
* const history = new ResourceStorageHistory({ | ||
* url, | ||
* collectionName, | ||
* usageEventPrefix: oneToFewResourceStorage.usageEventPrefix | ||
* eventEmitter | ||
* }) | ||
* history.listenForStorageEvents() | ||
* storage.enableHistory() | ||
*/ | ||
/** | ||
* Simple resource class that will listen to storage event of the given **eventEmitter** to populate a history collection / table. | ||
* Simple resource class that will listen to storage events of the given storage object | ||
* | ||
@@ -55,80 +33,69 @@ * @name ResourceStorageHistory | ||
*/ | ||
module.exports = class { | ||
export default class { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
constructor({ client, databaseName, collectionName, connectTimeout = 10_000, usageEventPrefix, eventEmitter } = {}) { | ||
this._eventEmitter = eventEmitter | ||
this._collectionName = collectionName | ||
this._usageEventPrefix = usageEventPrefix | ||
this._resourceStorage = new ResourceStorage({ client, databaseName, collectionName: this._collectionName, connectTimeout }) | ||
constructor({ client, databaseName, collectionName, connectTimeout = 10_000, storage } = {}) { | ||
/** @private */ this._collectionName = `${collectionName}_history` | ||
/** @private */ this._parentStorage = storage | ||
/** @private */ this._storage = new ResourceStorage({ client, databaseName, collectionName: this._collectionName, connectTimeout }) | ||
/** @private */ this._eventHandlers = { | ||
create: (event) => this._eventHandler('create', event), | ||
update: (event) => this._eventHandler('update', event), | ||
delete: (event) => this._eventHandler('delete', event) | ||
} | ||
this._parentStorage.on('create', this._eventHandlers['create']) | ||
this._parentStorage.on('update', this._eventHandlers['update']) | ||
this._parentStorage.on('delete', this._eventHandlers['delete']) | ||
} | ||
/** | ||
* Activate listening for storage events to monitor changes of target storage resource to | ||
* populate the history table. | ||
* | ||
* @public | ||
*/ | ||
listenForStorageEvents() { | ||
this._registerEventHandlers(this._eventEmitter) | ||
disable() { | ||
this._parentStorage.off('create', this._eventHandlers['create']) | ||
this._parentStorage.off('update', this._eventHandlers['update']) | ||
this._parentStorage.off('delete', this._eventHandlers['delete']) | ||
} | ||
/** | ||
* @param {string} action | ||
* @param {object} event | ||
* @param {object} event.after | ||
* @param {string | Array.<string>} event.resourceIds | ||
* @param {string} event.collectionName | ||
* @private | ||
* @param {import('node:events').EventEmitter} eventEmitter | ||
*/ | ||
_registerEventHandlers(eventEmitter) { | ||
eventEmitter.on(`${this._usageEventPrefix}.create`, (event) => this._eventHandler('create', event)) | ||
eventEmitter.on(`${this._usageEventPrefix}.update`, (event) => this._eventHandler('update', event)) | ||
eventEmitter.on(`${this._usageEventPrefix}.delete`, (event) => this._eventHandler('delete', event)) | ||
eventEmitter.on(`${this._usageEventPrefix}.close`, () => this.close()) | ||
} | ||
/** | ||
* @private | ||
* @param {Object} event | ||
* @param {boolean} event.error | ||
* @param {Object} event.after | ||
* @param {Array.<String>} event.resourceIds | ||
*/ | ||
async _eventHandler(action, { error, after, resourceIds, collectionName }) { | ||
if (!error) { | ||
return withActiveSpan(`${name}#handle-history-relevant-event`, { action, resourceIds }, async () => { | ||
if (action === 'create' && collectionName != this._collectionName) { | ||
await this._resourceStorage.create(resourceIds, { | ||
history: [{ | ||
async _eventHandler(action, { after, resourceIds, collectionName }) { | ||
return withActiveSpan(`handle-history-relevant-event`, { action, resourceIds }, async () => { | ||
if (action === 'create' && collectionName != this._collectionName) { | ||
await this._storage.create(resourceIds, { | ||
history: [{ | ||
timestamp: Date.now(), | ||
action, | ||
resource: after | ||
}] | ||
}) | ||
} | ||
else { | ||
await this._storage.update(resourceIds, { | ||
$push: { | ||
history: { | ||
timestamp: Date.now(), | ||
action, | ||
resource: after | ||
}] | ||
}) | ||
} else { | ||
await this._resourceStorage.update(resourceIds, { | ||
$push: { | ||
history: { | ||
timestamp: Date.now(), | ||
action, | ||
resource: after | ||
} | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
/** | ||
* Closes the database client | ||
* | ||
* @method close | ||
* | ||
* @function close | ||
* @returns {void} | ||
*/ | ||
close() { | ||
return this._resourceStorage.close() | ||
return this._storage.close() | ||
} | ||
} | ||
} |
@@ -1,7 +0,8 @@ | ||
export const SimpleResourceStorage: typeof import("./simple-resource-storage.js"); | ||
export const SimpleTimeseriesStorage: typeof import("./simple-timeseries-storage.js"); | ||
export const OneToFewRefStorage: typeof import("./one-to-few-ref-storage.js"); | ||
export const OneToFewResourceStorage: typeof import("./one-to-few-resource-storage.js"); | ||
export const OneToManyResourceStorage: typeof import("./one-to-many-resource-storage.js"); | ||
export const ResourceStorageHistory: typeof import("./history.js"); | ||
export const ResourceLock: typeof import("./locks.js"); | ||
import oneToFewRefStorage from './one-to-few-ref-storage.js'; | ||
import oneToFewResourceStorage from './one-to-few-resource-storage.js'; | ||
import oneToManyResourceStorage from './one-to-many-resource-storage.js'; | ||
import locks from './locks.js'; | ||
import history from './history.js'; | ||
import simpleResourceStorage from './simple-resource-storage.js'; | ||
import simpleTimeseriesStorage from './simple-timeseries-storage.js'; | ||
export { oneToFewRefStorage as OneToFewRefStorage, oneToFewResourceStorage as OneToFewResourceStorage, oneToManyResourceStorage as OneToManyResourceStorage, locks as ResourceLock, history as ResourceStorageHistory, simpleResourceStorage as SimpleResourceStorage, simpleTimeseriesStorage as SimpleTimeseriesStorage }; |
@@ -1,9 +0,10 @@ | ||
'use strict' | ||
import history from './history.js' | ||
import locks from './locks.js' | ||
import oneToFewRefStorage from './one-to-few-ref-storage.js' | ||
import oneToFewResourceStorage from './one-to-few-resource-storage.js' | ||
import oneToManyResourceStorage from './one-to-many-resource-storage.js' | ||
import simpleResourceStorage from './simple-resource-storage.js' | ||
import simpleTimeseriesStorage from './simple-timeseries-storage.js' | ||
module.exports.SimpleResourceStorage = require('./simple-resource-storage.js') | ||
module.exports.SimpleTimeseriesStorage = require('./simple-timeseries-storage.js') | ||
module.exports.OneToFewRefStorage = require('./one-to-few-ref-storage.js') | ||
module.exports.OneToFewResourceStorage = require('./one-to-few-resource-storage.js') | ||
module.exports.OneToManyResourceStorage = require('./one-to-many-resource-storage.js') | ||
module.exports.ResourceStorageHistory = require('./history.js') | ||
module.exports.ResourceLock = require('./locks.js') | ||
export { oneToFewRefStorage as OneToFewRefStorage, oneToFewResourceStorage as OneToFewResourceStorage, oneToManyResourceStorage as OneToManyResourceStorage, locks as ResourceLock, history as ResourceStorageHistory, simpleResourceStorage as SimpleResourceStorage, simpleTimeseriesStorage as SimpleTimeseriesStorage } | ||
@@ -1,6 +0,33 @@ | ||
export = exports; | ||
declare class exports { | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {import('mongodb').MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {number} [connectTimeout=10_000] the connect timeout of the mongo db client if client was not passed | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { ResourceLock } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const lock = new ResourceLock({ | ||
* client | ||
* }) | ||
* | ||
* await lock.doWhileLocked([123], () => { | ||
* // do important stuff while lock is being held for 5s by default | ||
* }) | ||
*/ | ||
/** | ||
* Creates lock documents and allows to execute functions with a lock. | ||
* | ||
* @name ResourceLock | ||
* @class | ||
*/ | ||
export default class _default { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -13,2 +40,3 @@ constructor({ client, databaseName, connectTimeout }?: ConstructorOptions); | ||
* | ||
* @name lock | ||
* @param {Array.<string>} resourceIds the resource ids | ||
@@ -22,2 +50,3 @@ */ | ||
* | ||
* @name unlock | ||
* @param {Array.<string>} resourceIds the resource ids | ||
@@ -31,8 +60,9 @@ */ | ||
* | ||
* @name doWhileLocked | ||
* @param {Array.<string>} resourceIds the resource ids | ||
* @param {Function} callback callback to execute with lock | ||
* @param {Object} options | ||
* @param {Number} [options.lockTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval=125] max time to wait in between retries | ||
* @param {object} options | ||
* @param {number} [options.lockTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
@@ -50,10 +80,10 @@ * @throws {Error} Lock interrupted by timeout - if callback did not return before lockTimeout | ||
* | ||
* @private | ||
* @param {Array.<string>} ids the resource ids | ||
* @param {any} span | ||
* @param {Function} callback callback to execute with lock | ||
* @param {Object} options | ||
* @param {Number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval=125] max time to wait in between retries | ||
* @param {object} options | ||
* @param {number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
* @private | ||
*/ | ||
@@ -65,9 +95,9 @@ private _doWhileLocked; | ||
* | ||
* @private | ||
* @param {Array.<string>} ids the resource ids | ||
* @param {any} span | ||
* @param {Object} options | ||
* @param {Number} [options.waitTimeout] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval] max time to wait in between retries | ||
* @param {object} options | ||
* @param {number} [options.waitTimeout] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval] max time to wait in between retries | ||
* @returns {Promise.<boolean>} | ||
* @private | ||
*/ | ||
@@ -78,3 +108,3 @@ private _ensureIsLocked; | ||
* | ||
* @method close | ||
* @function close | ||
* @returns {void} | ||
@@ -84,19 +114,3 @@ */ | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions }; | ||
} | ||
import ResourceStorage = require("./simple-resource-storage.js"); | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* the connect timeout of the mongo db client if client was not passed | ||
*/ | ||
connectTimeout?: number; | ||
}; | ||
export type ConstructorOptions = any; | ||
import ResourceStorage from './simple-resource-storage.js'; |
118
lib/locks.js
@@ -1,28 +0,22 @@ | ||
'use strict' | ||
import SpanStatusCode from '@discue/open-telemetry-tracing/status-codes' | ||
import ResourceStorage from './simple-resource-storage.js' | ||
import { withActiveSpan } from './tracer.js' | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const SpanStatusCode = require('@discue/open-telemetry-tracing/status-codes') | ||
const ResourceStorage = require('./simple-resource-storage.js') | ||
const { name } = require('../package.json') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {import('mongodb').MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {number} [connectTimeout=10_000] the connect timeout of the mongo db client if client was not passed | ||
* | ||
* @example | ||
* const { ResourceLock } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { ResourceLock } from '@discue/mongodb-resource-client' | ||
* | ||
* const url = 'mongodb://127.0.0.1:27017' | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const lock = new ResourceLock({ | ||
* url | ||
* client | ||
* }) | ||
@@ -41,10 +35,9 @@ * | ||
*/ | ||
module.exports = class { | ||
export default class { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
constructor({ client, databaseName, connectTimeout = 10_000 } = {}) { | ||
const collectionName = '_locks' | ||
@@ -60,11 +53,11 @@ this._resourceStorage = new ResourceStorage({ client, databaseName, collectionName, connectTimeout }) | ||
} | ||
/** | ||
* Creates an entry in the `locks` collection. The context param is a unique identifier. If context already | ||
* exists, the method will throw. | ||
* | ||
* | ||
* @name lock | ||
* @param {Array.<string>} resourceIds the resource ids | ||
*/ | ||
async lock(resourceIds) { | ||
return withActiveSpan(`${name}#lock-resource`, { resourceIds }, async () => { | ||
return withActiveSpan(`lock-resource`, { resourceIds }, async () => { | ||
return this._resourceStorage.create(resourceIds, { | ||
@@ -75,39 +68,37 @@ locked_at: Date.now() | ||
} | ||
/** | ||
* Deletes an entry from the `locks` collection unlocking the document. | ||
* Deletes an entry from the `locks` collection unlocking the document. | ||
* The context param is a unique identifier. If context has already been | ||
* removed, the method will throw. | ||
* | ||
* | ||
* @name unlock | ||
* @param {Array.<string>} resourceIds the resource ids | ||
*/ | ||
async unlock(resourceIds) { | ||
return withActiveSpan(`${name}#unlock-resource`, { resourceIds }, async () => { | ||
return withActiveSpan(`unlock-resource`, { resourceIds }, async () => { | ||
return this._resourceStorage._deleteUnsafe(resourceIds) | ||
}) | ||
} | ||
/** | ||
* Executes the callback only if the appropriate lock document has been created successfully. | ||
* Unlocks the document either after completion of the callback or after `lockTimeout` millis | ||
* have passed. | ||
* | ||
* have passed. | ||
* | ||
* @name doWhileLocked | ||
* @param {Array.<string>} resourceIds the resource ids | ||
* @param {Function} callback callback to execute with lock | ||
* @param {Object} options | ||
* @param {Number} [options.lockTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
* @param {object} options | ||
* @param {number} [options.lockTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
* @throws {Error} Lock interrupted by timeout - if callback did not return before lockTimeout | ||
*/ | ||
async doWhileLocked(resourceIds, callback, { lockTimeout = 5_000, waitTimeout = 5_000, retryInterval = 125 } = {}) { | ||
return withActiveSpan(`${name}#do-while-resource-locked`, { resourceIds, lockTimeout, waitTimeout, retryInterval }, async (span) => { | ||
return withActiveSpan(`do-while-resource-locked`, { resourceIds, lockTimeout, waitTimeout, retryInterval }, async (span) => { | ||
return new Promise((resolve, reject) => { | ||
let timeout | ||
// override the callback here. The lock timeout should only start | ||
// after the lock was established | ||
this._doWhileLocked(resourceIds, span, async () => { | ||
timeout = setTimeout(() => { | ||
@@ -117,10 +108,6 @@ this.unlock(resourceIds) | ||
span.addEvent('Timeout waiting for lock') | ||
reject(new Error('Lock interrupted by timeout')) | ||
}) | ||
}, lockTimeout) | ||
return callback() | ||
}, { lockTimeout, waitTimeout, retryInterval }) | ||
@@ -139,16 +126,14 @@ .then((result) => { | ||
} | ||
/** | ||
* Executes the callback only if the appropriate lock document has been created successfully. | ||
* Unlocks the document either after completion. | ||
* | ||
* @private | ||
* | ||
* @param {Array.<string>} ids the resource ids | ||
* @param {any} span | ||
* @param {Function} callback callback to execute with lock | ||
* @param {Object} options | ||
* @param {Number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
* @param {object} options | ||
* @param {number} [options.waitTimeout=5_000] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval=125] max time to wait in between retries | ||
* @throws {Error} Unable to establish lock - if unable to establish lock for a document | ||
* @private | ||
*/ | ||
@@ -163,23 +148,21 @@ async _doWhileLocked(ids, span, callback, { waitTimeout = 5_000, retryInterval = 125 } = {}) { | ||
return result | ||
} finally { | ||
} | ||
finally { | ||
await this.unlock(ids) | ||
} | ||
} | ||
/** | ||
* Creates the lock for the given id waiting `waitTimeout` milliseconds and retrying | ||
* Creates the lock for the given id waiting `waitTimeout` milliseconds and retrying | ||
* every `retryInterval` milliseconds. | ||
* | ||
* @private | ||
* | ||
* @param {Array.<string>} ids the resource ids | ||
* @param {any} span | ||
* @param {Object} options | ||
* @param {Number} [options.waitTimeout] max time to wait in milliseconds | ||
* @param {Number} [options.retryInterval] max time to wait in between retries | ||
* @returns {Promise.<boolean>} | ||
* @param {object} options | ||
* @param {number} [options.waitTimeout] max time to wait in milliseconds | ||
* @param {number} [options.retryInterval] max time to wait in between retries | ||
* @returns {Promise.<boolean>} | ||
* @private | ||
*/ | ||
async _ensureIsLocked(ids, span, { waitTimeout, retryInterval }) { | ||
const start = Date.now() | ||
while (Date.now() - start <= waitTimeout) { | ||
@@ -189,3 +172,4 @@ try { | ||
return true | ||
} catch (e) { | ||
} | ||
catch (e) { | ||
span.addEvent('Document still locked', { | ||
@@ -201,10 +185,8 @@ waitedFor: Date.now() - start, | ||
} | ||
return false | ||
} | ||
/** | ||
* Closes the database client. | ||
* | ||
* @method close | ||
* | ||
* @function close | ||
* @returns {void} | ||
@@ -215,2 +197,2 @@ */ | ||
} | ||
} | ||
} |
export function getSingleLookupPipeline({ rootId, childCollectionName, pipeline }: { | ||
rootId: string; | ||
childCollectionName: string; | ||
pipeline: any[]; | ||
}): any[]; | ||
rootId: any; | ||
childCollectionName: any; | ||
pipeline?: any[]; | ||
}): (import("./aggregations.js").Match | import("./aggregations.js").Projection | import("./aggregations.js").Lookup)[]; | ||
export function joinAndQueryChildResourcesPipeline({ parentCollectionName, childCollectionName, options }: { | ||
parentCollectionName: string; | ||
childCollectionName: string; | ||
options: GetOptions; | ||
}): any[]; | ||
export type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
/** | ||
* MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
match: any; | ||
}; | ||
parentCollectionName: any; | ||
childCollectionName: any; | ||
options: any; | ||
}): (import("./aggregations.js").Projection | import("./aggregations.js").Lookup)[]; |
@@ -1,14 +0,4 @@ | ||
'use strict' | ||
import { APPEND_OBJECTS, CONCAT_ARRAYS, EQUALS, EQUALS_ALL, JOIN_STRINGS, LET, LOOKUP, PROJECT, REDUCE, TO_OBJECT } from './aggregations.js' | ||
const { PROJECT, EQUALS, LOOKUP, REDUCE, CONCAT_ARRAYS, TO_OBJECT, LET, APPEND_OBJECTS, JOIN_STRINGS, EQUALS_ALL } = require('./aggregations.js') | ||
/** | ||
* | ||
* @param {object} options | ||
* @param {string} options.rootId the id of the root object | ||
* @param {string} options.childCollectionName the collectionName of the child resource | ||
* @param {Array} options.pipeline an array of other pipelines that should be executed after the lookup | ||
* @returns {Array} | ||
*/ | ||
module.exports.getSingleLookupPipeline = function ({ rootId, childCollectionName, pipeline = [] }) { | ||
export const getSingleLookupPipeline = function ({ rootId, childCollectionName, pipeline = [] }) { | ||
return [ | ||
@@ -26,20 +16,3 @@ EQUALS('id', rootId), | ||
/** | ||
* @name GetOptions | ||
* @private | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {Object} match MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* | ||
* @param {object} options | ||
* @param {string} options.parentCollectionName name of the parent collection | ||
* @param {string} options.childCollectionName the collectionName of the child resource | ||
* @param {GetOptions} options.options | ||
* @returns {Array} | ||
*/ | ||
module.exports.joinAndQueryChildResourcesPipeline = function ({ parentCollectionName, childCollectionName, options }) { | ||
export const joinAndQueryChildResourcesPipeline = function ({ parentCollectionName, childCollectionName, options }) { | ||
const lookupPipeline = [ | ||
@@ -51,11 +24,8 @@ PROJECT({ | ||
] | ||
if (options.match) { | ||
lookupPipeline.push(EQUALS_ALL(options.match)) | ||
} | ||
if (options.projection) { | ||
lookupPipeline.push(PROJECT(options.projection)) | ||
} | ||
const pipeline = [ | ||
@@ -65,8 +35,7 @@ PROJECT({ | ||
parent: `$${parentCollectionName}`, | ||
[childCollectionName]: | ||
REDUCE({ | ||
input: `$${parentCollectionName}.${childCollectionName}`, | ||
initialValue: [], | ||
inExpression: CONCAT_ARRAYS() | ||
}) | ||
[childCollectionName]: REDUCE({ | ||
input: `$${parentCollectionName}.${childCollectionName}`, | ||
initialValue: [], | ||
inExpression: CONCAT_ARRAYS() | ||
}) | ||
}), | ||
@@ -83,27 +52,18 @@ LOOKUP({ | ||
input: `$parent`, | ||
inExpression: APPEND_OBJECTS( | ||
LET({ | ||
vars: { | ||
parent: '$$this.id', | ||
children: `$$this.${childCollectionName}` | ||
}, | ||
inExpression: REDUCE({ | ||
input: '$$children', | ||
inExpression: | ||
APPEND_OBJECTS( | ||
TO_OBJECT({ | ||
value: JOIN_STRINGS('/', | ||
`${parentCollectionName}`, | ||
'$$parent', | ||
`${childCollectionName}`, | ||
'$$this') | ||
})) | ||
}) | ||
inExpression: APPEND_OBJECTS(LET({ | ||
vars: { | ||
parent: '$$this.id', | ||
children: `$$this.${childCollectionName}` | ||
}, | ||
inExpression: REDUCE({ | ||
input: '$$children', | ||
inExpression: APPEND_OBJECTS(TO_OBJECT({ | ||
value: JOIN_STRINGS('/', `${parentCollectionName}`, '$$parent', `${childCollectionName}`, '$$this') | ||
})) | ||
}) | ||
) | ||
})) | ||
}) | ||
}) | ||
] | ||
return pipeline | ||
} | ||
} |
@@ -1,6 +0,52 @@ | ||
export = exports; | ||
declare class exports extends Base { | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToFewRefstorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const oneToFewRefStorage = new OneToFewRefStorage({ | ||
* client, | ||
* collectionName: 'api_clients', | ||
* resourceName: 'queues' | ||
* }) | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').ObjectId} ObjectId | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
/** | ||
* Similar to @link OneToFewResourceStorage, but allows only managing of | ||
* references. Meaning: Instead of embedding objects in the target array | ||
* this class only stores references to other objects. | ||
* | ||
* <strong>Universities collection</strong> | ||
* ```js | ||
* { | ||
* name: 'University Munich', | ||
* students: [1828391, 9440201, 29930302] | ||
* } | ||
* { | ||
* name: 'University Stuttgart', | ||
* students: [551234, 115235, 4451515] | ||
* } | ||
* ``` | ||
* | ||
* @name OneToFewRefStorage | ||
* @class | ||
* @link https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
*/ | ||
export default class _default extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -12,59 +58,45 @@ constructor(options: ConstructorOptions); | ||
* | ||
* @method getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {Array.<Object>} | ||
* @function getAll | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {Array.<object>} | ||
*/ | ||
getAll(resourceIds: string | Array<string>): Array<any>; | ||
getAll(resourceIds: string | Array<string>): Array<object>; | ||
/** | ||
* @typedef WithMongoClient | ||
* @name WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
* @private | ||
*/ | ||
/** | ||
* Add a reference to a collection by ids. | ||
* | ||
* @method create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} ref the resource to be stored | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} ref the resource to be stored | ||
* @param {import('mongodb').UpdateOptions | WithMongoClient} [options=null] | ||
* @returns {Promise.<ObjectId>} | ||
*/ | ||
create(resourceIds: string | Array<string>, ref: any, options?: import("mongodb").UpdateOptions | WithMongoClient): Promise<ObjectId>; | ||
create(resourceIds: string | Array<string>, ref: object, options?: import("mongodb").UpdateOptions | any): Promise<ObjectId>; | ||
/** | ||
* Delete a reference | ||
* | ||
* @method find | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<Objecft>} references | ||
* @returns {Promise.<void>} | ||
*/ | ||
findReferences(resourceIds: string | Array<string>, references: string | Array<Objecft>): Promise<void>; | ||
* Delete a reference | ||
* | ||
* @function find | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<object>} references | ||
* @returns {Promise.<void>} | ||
*/ | ||
findReferences(resourceIds: string | Array<string>, references: string | Array<object>): Promise<void>; | ||
/** | ||
* Delete a reference | ||
* | ||
* @method delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @function delete | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {import('mongodb').FindOneAndUpdateOptions | WithMongoClient} [options=null] | ||
* @returns {Promise.<void>} | ||
*/ | ||
delete(resourceIds: string | Array<string>, options?: import("mongodb").FindOneAndUpdateOptions | WithMongoClient): Promise<void>; | ||
delete(resourceIds: string | Array<string>, options?: import("mongodb").FindOneAndUpdateOptions | any): Promise<void>; | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, ObjectId, MongoClient }; | ||
} | ||
import Base = require("./simple-resource-storage.js"); | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
/** | ||
* name of the resource e.g. users, customers, topics, shipments | ||
*/ | ||
resourceName: string; | ||
}; | ||
type ObjectId = import("mongodb").ObjectId; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export type ConstructorOptions = any; | ||
export type ObjectId = import("mongodb").ObjectId; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
import Base from './simple-resource-storage.js'; |
@@ -1,19 +0,8 @@ | ||
'use strict' | ||
import { EQUALS, EQUALS_ANY_OF } from './aggregations.js' | ||
import { toArrayAndClose } from './safe-cursor.js' | ||
import Base from './simple-resource-storage.js' | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { EQUALS_ANY_OF, EQUALS } = require('./aggregations.js') | ||
const Base = require('./simple-resource-storage.js') | ||
const { name } = require('../package.json') | ||
const { toArrayAndClose } = require('./safe-cursor.js') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
@@ -23,6 +12,5 @@ * @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* | ||
* @example | ||
* const { MongoClient } = require('mongodb') | ||
* const { OneToFewRefstorage } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToFewRefstorage } from '@discue/mongodb-resource-client' | ||
* | ||
@@ -41,5 +29,5 @@ * const client = new MongoClient(url, { | ||
/** | ||
* @private | ||
* @typedef {import('mongodb').ObjectId} ObjectId | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
@@ -64,11 +52,11 @@ | ||
* | ||
* @link https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
* @name OneToFewRefStorage | ||
* @class | ||
* @link https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
*/ | ||
module.exports = class extends Base { | ||
export default class extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -79,12 +67,11 @@ constructor(options) { | ||
} | ||
/** | ||
* Returns true if a resource with given ids exists. | ||
* | ||
* @method exists | ||
* @param {String|Array.<String>} resourceIds | ||
* | ||
* @function exists | ||
* @param {string | Array.<string>} resourceIds | ||
* @returns {boolean} | ||
*/ | ||
async exists(resourceIds) { | ||
return this._withActiveSpan('exists-one-to-few-ref-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('exists-one-to-few-ref-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
@@ -99,3 +86,2 @@ const parent = await collection.findOne({ | ||
}) | ||
return parent != null | ||
@@ -106,17 +92,10 @@ }) | ||
/** | ||
* @private | ||
*/ | ||
async _withActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
/** | ||
* Returns all references. | ||
* | ||
* @method getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {Array.<Object>} | ||
* | ||
* @function getAll | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {Array.<object>} | ||
*/ | ||
async getAll(resourceIds) { | ||
return this._withActiveSpan('get-all-one-to-few-refs-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('get-all-one-to-few-refs-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
@@ -126,7 +105,5 @@ const parent = await collection.findOne({ | ||
}) | ||
if (!parent) { | ||
return [] | ||
} | ||
return parent[`${this._resourceName}`] | ||
@@ -137,7 +114,14 @@ }) | ||
/** | ||
* @typedef WithMongoClient | ||
* @name WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
* @private | ||
*/ | ||
/** | ||
* Add a reference to a collection by ids. | ||
* | ||
* @method create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} ref the resource to be stored | ||
* | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} ref the resource to be stored | ||
* @param {import('mongodb').UpdateOptions | WithMongoClient} [options=null] | ||
@@ -147,3 +131,3 @@ * @returns {Promise.<ObjectId>} | ||
async create(resourceIds, ref, options) { | ||
return this._withActiveSpan('create-one-to-few-ref-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('create-one-to-few-ref-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection(options?.client) | ||
@@ -157,3 +141,2 @@ const result = await collection.updateOne({ | ||
}, this._passSessionIfTransactionEnabled(options)) | ||
const success = result.acknowledged === true && result.matchedCount === 1 | ||
@@ -163,19 +146,16 @@ if (!success) { | ||
} | ||
return ref | ||
}) | ||
} | ||
/** | ||
* Delete a reference | ||
* | ||
* @method find | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<Objecft>} references | ||
* @returns {Promise.<void>} | ||
*/ | ||
* Delete a reference | ||
* | ||
* @function find | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<object>} references | ||
* @returns {Promise.<void>} | ||
*/ | ||
async findReferences(resourceIds, references) { | ||
return this._withActiveSpan('find-one-to-few-refs-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('find-one-to-few-refs-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
const stages = [ | ||
@@ -185,3 +165,2 @@ EQUALS('id', resourceIds.at(0)), | ||
] | ||
const cursor = collection.aggregate(stages) | ||
@@ -191,8 +170,7 @@ return toArrayAndClose(cursor) | ||
} | ||
/** | ||
* Delete a reference | ||
* | ||
* @method delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* | ||
* @function delete | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {import('mongodb').FindOneAndUpdateOptions | WithMongoClient} [options=null] | ||
@@ -202,3 +180,3 @@ * @returns {Promise.<void>} | ||
async delete(resourceIds, options) { | ||
return this._withActiveSpan('delete-one-to-few-ref-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('delete-one-to-few-ref-by-id', resourceIds, async () => { | ||
const exists = await this.exists(resourceIds) | ||
@@ -208,3 +186,2 @@ if (!exists) { | ||
} | ||
const collection = await this._getCollection(options?.client) | ||
@@ -218,3 +195,2 @@ const result = await collection.findOneAndUpdate({ | ||
}, { includeResultMetadata: true, ...this._passSessionIfTransactionEnabled(options) }) | ||
const success = result.ok === 1 | ||
@@ -226,2 +202,2 @@ if (!success) { | ||
} | ||
} | ||
} |
@@ -1,6 +0,62 @@ | ||
export = exports; | ||
declare class exports extends Base { | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToFewResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const oneToFewResourceStorage = new OneToFewResourceStorage({ | ||
* client, | ||
* collectionName: 'api_clients', | ||
* resourceName: 'queues' | ||
* }) | ||
*/ | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
/** | ||
* Allows to manage a list of documents in another document to e.g. store a list of | ||
* students of each school. As the child documents will be embedded, it is easy | ||
* to retrieve them (only one query), but harder to get all students across e.g. | ||
* a country in various schools. | ||
* | ||
* <strong>Universities collection</strong> | ||
* ```js | ||
* { | ||
* name: 'University Munich', | ||
* students: [ | ||
* { | ||
* name: 'Stef', | ||
* city: 'Munich | ||
* }, | ||
* { | ||
* name: 'Frank', | ||
* city: 'Stuttgart | ||
* } | ||
* ] | ||
* } | ||
* ``` | ||
* | ||
* @name OneToFewResourceStorage | ||
* @class | ||
*/ | ||
export default class _default extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -13,7 +69,7 @@ constructor(options: ConstructorOptions); | ||
* @name get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Promise.<Object>} | ||
* @returns {Promise.<object>} | ||
*/ | ||
get(resourceIds: string | Array<string>, { withMetadata, projection }?: GetOptions): Promise<any>; | ||
get(resourceIds: string | Array<string>, { withMetadata, projection }?: GetOptions): Promise<object>; | ||
/** | ||
@@ -23,7 +79,7 @@ * Returns all resources. | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Promise.<Array.<Object>>} | ||
* @returns {Promise.<Array.<object>>} | ||
*/ | ||
getAll(resourceIds: string | Array<string>, { withMetadata, projection }?: GetOptions): Promise<Array<any>>; | ||
getAll(resourceIds: string | Array<string>, { withMetadata, projection }?: GetOptions): Promise<Array<object>>; | ||
/** | ||
@@ -33,7 +89,7 @@ * Add a resource to a collection by ids. | ||
* @name create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @returns {ObjectId} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @returns {string} the id of the new object | ||
*/ | ||
create(resourceIds: string | Array<string>, resource: any): ObjectId; | ||
create(resourceIds: string | Array<string>, resource: object): string; | ||
/** | ||
@@ -43,15 +99,17 @@ * Updates a resource by ids | ||
* @name update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @returns {void} | ||
*/ | ||
update(resourceIds: string | Array<string>, update: any): void; | ||
_createUpdateMetadata(): { | ||
[x: string]: Timestamp; | ||
}; | ||
update(resourceIds: string | Array<string>, update: object): void; | ||
/** | ||
* | ||
* @private | ||
*/ | ||
private _createUpdateMetadata; | ||
/** | ||
* Deletes a resource by ids | ||
* | ||
* @name delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {void} | ||
@@ -61,39 +119,5 @@ */ | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, GetOptions, MongoClient }; | ||
} | ||
import Base = require("./simple-resource-storage.js"); | ||
import { Timestamp } from "mongodb"; | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
/** | ||
* name of the resource e.g. users, customers, topics, shipments | ||
*/ | ||
resourceName: string; | ||
/** | ||
* if provided, will trigger events base on resource creation, updates and deletion | ||
*/ | ||
eventEmitter: import("node:events").EventEmitter; | ||
}; | ||
type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
}; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export type ConstructorOptions = any; | ||
export type GetOptions = any; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
import Base from './simple-resource-storage.js'; |
@@ -1,21 +0,11 @@ | ||
'use strict' | ||
import * as mongodb from 'mongodb' | ||
import { EQUALS, LIMIT, PROJECT } from './aggregations.js' | ||
import { getFirstAndClose, toArrayAndClose } from './safe-cursor.js' | ||
import Base from './simple-resource-storage.js' | ||
const { PROJECT, EQUALS, LIMIT } = require('./aggregations.js') | ||
const eventTrigger = require('./usage-event-trigger.js') | ||
const Base = require('./simple-resource-storage.js') | ||
const { Timestamp } = require('mongodb') | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { name } = require('../package.json') | ||
const { getFirstAndClose, toArrayAndClose } = require('./safe-cursor.js') | ||
const { Timestamp } = mongodb | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
@@ -25,7 +15,5 @@ * @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* @property {import('node:events').EventEmitter} eventEmitter if provided, will trigger events base on resource creation, updates and deletion | ||
* | ||
* @example | ||
* const { MongoClient } = require('mongodb') | ||
* const { OneToFewResourceStorage } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToFewResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
@@ -44,11 +32,11 @@ * const client = new MongoClient(url, { | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
*/ | ||
@@ -82,29 +70,23 @@ | ||
*/ | ||
module.exports = class extends Base { | ||
export default class extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
constructor(options) { | ||
super(options) | ||
/** @private */ this._resourceName = options.resourceName | ||
/** @private */ this._emitUsageEvent = eventTrigger(this.usageEventPrefix, options.resourceName, options.eventEmitter) | ||
} | ||
get usageEventPrefix() { | ||
return `one-to-many-resource.${this._resourceName}` | ||
} | ||
/** | ||
* Returns a resource by ids. | ||
* | ||
* | ||
* @name get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Promise.<Object>} | ||
* @returns {Promise.<object>} | ||
*/ | ||
async get(resourceIds, { withMetadata = false, projection } = {}) { | ||
return this._withActiveSpan('get-one-to-few-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('get-one-to-few-resource-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
@@ -118,11 +100,8 @@ const aggregationStages = [ | ||
] | ||
if (!withMetadata) { | ||
aggregationStages.push(PROJECT({ _meta_data: 0 })) | ||
} | ||
if (projection) { | ||
aggregationStages.push(PROJECT(projection)) | ||
} | ||
const cursor = collection.aggregate(aggregationStages) | ||
@@ -134,18 +113,11 @@ return getFirstAndClose(cursor) | ||
/** | ||
* @private | ||
*/ | ||
async _withActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
/** | ||
* Returns all resources. | ||
* | ||
* | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Promise.<Array.<Object>>} | ||
* @returns {Promise.<Array.<object>>} | ||
*/ | ||
async getAll(resourceIds, { withMetadata = false, projection } = {}) { | ||
return this._withActiveSpan('get-all-one-to-few-resources-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('get-all-one-to-few-resources-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
@@ -157,11 +129,8 @@ const aggregationStages = [ | ||
] | ||
if (!withMetadata) { | ||
aggregationStages.push(PROJECT({ _meta_data: 0 })) | ||
} | ||
if (projection) { | ||
aggregationStages.push(PROJECT(projection)) | ||
} | ||
const cursor = collection.aggregate(aggregationStages) | ||
@@ -171,13 +140,12 @@ return toArrayAndClose(cursor) | ||
} | ||
/** | ||
* Add a resource to a collection by ids. | ||
* | ||
* | ||
* @name create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @returns {ObjectId} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @returns {string} the id of the new object | ||
*/ | ||
async create(resourceIds, resource) { | ||
return this._withActiveSpan('create-one-to-few-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('create-one-to-few-resource-by-id', resourceIds, async () => { | ||
const exists = await this.exists(resourceIds) | ||
@@ -187,6 +155,4 @@ if (exists) { | ||
} | ||
const update = Object.assign({}, resource) | ||
update.id = resourceIds.at(-1) | ||
const collection = await this._getCollection() | ||
@@ -200,3 +166,2 @@ const result = await collection.updateOne({ | ||
}) | ||
const success = result.acknowledged === true && result.modifiedCount === 1 | ||
@@ -206,19 +171,16 @@ if (!success) { | ||
} | ||
this._emitUsageEvent({ context: 'create', after: resource, resourceIds }) | ||
this._emitter.emit('create', { after: resource, resourceIds }) | ||
return resourceIds.at(-1) | ||
}) | ||
} | ||
/** | ||
* Updates a resource by ids | ||
* | ||
* | ||
* @name update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @returns {void} | ||
*/ | ||
async update(resourceIds, update) { | ||
return this._withActiveSpan('update-one-to-few-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('update-one-to-few-resource-by-id', resourceIds, async () => { | ||
const beforeResource = await this.get(resourceIds) | ||
@@ -228,3 +190,2 @@ if (!beforeResource) { | ||
} | ||
const hasAtomicOperator = Object.keys(update).find(key => key.startsWith('$')) | ||
@@ -236,3 +197,2 @@ if (!hasAtomicOperator) { | ||
} | ||
const collection = await this._getCollection() | ||
@@ -249,3 +209,2 @@ const result = await collection.findOneAndUpdate({ | ||
}) | ||
const success = result.ok === 1 | ||
@@ -255,10 +214,11 @@ if (!success) { | ||
} | ||
const afterResource = await this.get(resourceIds) | ||
await this._emitUsageEvent({ context: 'update', before: beforeResource, after: afterResource, resourceIds }) | ||
await this._emitter.emit('update', { before: beforeResource, after: afterResource, resourceIds }) | ||
return result | ||
}) | ||
} | ||
/** | ||
* | ||
* @private | ||
*/ | ||
_createUpdateMetadata() { | ||
@@ -270,12 +230,11 @@ const timestamp = Timestamp.fromNumber(Date.now()) | ||
} | ||
/** | ||
* Deletes a resource by ids | ||
* | ||
* | ||
* @name delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns {void} | ||
*/ | ||
async delete(resourceIds) { | ||
return this._withActiveSpan('delete-one-to-few-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('delete-one-to-few-resource-by-id', resourceIds, async () => { | ||
const resource = await this.get(resourceIds) | ||
@@ -285,3 +244,2 @@ if (!resource) { | ||
} | ||
const collection = await this._getCollection() | ||
@@ -297,3 +255,2 @@ const result = await collection.findOneAndUpdate({ | ||
}, { includeResultMetadata: true }) | ||
const success = result.ok === 1 | ||
@@ -303,6 +260,5 @@ if (!success) { | ||
} | ||
await this._emitUsageEvent({ context: 'delete', before: resource, resourceIds }) | ||
await this._emitter.emit('delete', { before: resource, resourceIds }) | ||
}) | ||
} | ||
} | ||
} |
@@ -1,6 +0,80 @@ | ||
export = exports; | ||
declare class exports extends Base { | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* @property {string} [resourcePath=resourceName] slash separated path describing the hierarchy e.g. universities/teachers/subjects/exams. | ||
* @property {string} [hiddenResourcePath=null] slash separated path describing which path elements should not be returned to callers | ||
* @property {string} enableTwoWayReferences true if documents should also store references to their parents e.g. student have references to their schools | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToManyResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const oneToManyResourceStorage = new OneToManyResourceStorage({ | ||
* client, | ||
* collectionName: 'api_clients', | ||
* resourceName: 'listeners' | ||
* enableTwoWayReferences: true | ||
* }) | ||
*/ | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {boolean} addDocumentPath true if $path propety should be added to documents e.g. `$path=/countries/1/cities/2/companies` | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
/** | ||
* Manages relationships between entities in a more decoupled way by keep storing | ||
* entities in separate collections and using references to establish an relationship | ||
* between both. This way students can be queried independently of an university, | ||
* while all studies of a university can still be looked up via the stored reference. | ||
* | ||
* The references between both collections are kept up-to-date. Deleting a document, | ||
* causes the reference to be deleted in the other entity. Adding a document | ||
* causes a reference to be updated, too. | ||
* | ||
* <strong>Students collection</strong> | ||
* ```js | ||
* { | ||
* id: 1828391, | ||
* name: 'Miles Morales', | ||
* }, | ||
* { | ||
* id: 4451515, | ||
* name: 'Bryan Jenkins', | ||
* } | ||
* ``` | ||
* | ||
* <strong>Universities collection</strong> | ||
* ```js | ||
* { | ||
* name: 'University Munich', | ||
* students: [1828391] | ||
* } | ||
* { | ||
* name: 'University Stuttgart', | ||
* students: [4451515] | ||
* } | ||
* ``` | ||
* | ||
* @name OneToManyResourceStorage | ||
* @class | ||
* @link as described here: https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
*/ | ||
export default class _default extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -16,3 +90,2 @@ constructor(options: ConstructorOptions); | ||
/** @private */ private _resourceLevel; | ||
/** @private */ private _childEmitUsageEvent; | ||
/** | ||
@@ -22,14 +95,14 @@ * Returns a resource by ids. | ||
* @name get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} options | ||
* @returns {Object} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} options | ||
* @returns {object} | ||
*/ | ||
get(resourceIds: string | Array<string>, options: any): any; | ||
get(resourceIds: string | Array<string>, options: object): object; | ||
/** | ||
* @typedef FindOptions | ||
* @name FindOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} match MongoDB match object e.g. { id: 123 } | ||
* @private | ||
* @typedef FindOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {Object} match MongoDB match object e.g. { id: 123 } | ||
*/ | ||
@@ -40,43 +113,39 @@ /** | ||
* @name find | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {FindOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
*/ | ||
find(resourceIds: string | Array<string>, options: { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
/** | ||
* MongoDB match object e.g. { id: 123 } | ||
*/ | ||
match: any; | ||
}): any; | ||
find(resourceIds: string | Array<string>, options: any): object; | ||
/** | ||
* Returns a resource without safety checks and tracing | ||
* | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
* @private | ||
*/ | ||
_getResourceWithLookup(resourceIds: string | Array<string>, options?: GetOptions): any; | ||
_getParentCollection(): Promise<import("mongodb").Collection<import("bson").Document>>; | ||
private _getResourceWithLookup; | ||
/** | ||
* | ||
* @private | ||
*/ | ||
private _getParentCollection; | ||
/** | ||
* | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
* @private | ||
*/ | ||
_getNestedLookupStages(resourceIds: string | Array<string>, options: GetOptions): any; | ||
_getResourceIdsForHostStorage(resourceIds: any): any; | ||
private _getNestedLookupStages; | ||
/** | ||
* Returns resources based on return value of {@link findReferences}. | ||
* | ||
* @param {Array.<string>} resourceIds | ||
* @private | ||
*/ | ||
private _getResourceIdsForHostStorage; | ||
/** | ||
* | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
@@ -89,7 +158,7 @@ */ | ||
* @name create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @returns | ||
*/ | ||
create(resourceIds: string | Array<string>, resource: any): Promise<any>; | ||
create(resourceIds: string | Array<string>, resource: object): Promise<any>; | ||
/** | ||
@@ -99,7 +168,7 @@ * Updates a resource by ids | ||
* @name update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @returns | ||
*/ | ||
update(resourceIds: string | Array<string>, update: any): Promise<any>; | ||
update(resourceIds: string | Array<string>, update: object): Promise<any>; | ||
/** | ||
@@ -112,58 +181,5 @@ * Closes all clients. | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, GetOptions, MongoClient }; | ||
} | ||
import Base = require("./simple-resource-storage.js"); | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes | ||
*/ | ||
indexes?: Array<any>; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
/** | ||
* name of the resource e.g. users, customers, topics, shipments | ||
*/ | ||
resourceName: string; | ||
/** | ||
* slash separated path describing the hierarchy e.g. universities/teachers/subjects/exams. | ||
*/ | ||
resourcePath?: string; | ||
/** | ||
* slash separated path describing which path elements should not be returned to callers | ||
*/ | ||
hiddenResourcePath?: string; | ||
/** | ||
* true if documents should also store references to their parents e.g. student have references to their schools | ||
*/ | ||
enableTwoWayReferences: string; | ||
/** | ||
* if provided, will trigger events base on resource creation, updates and deletion | ||
*/ | ||
eventEmitter: import("node:events").EventEmitter; | ||
}; | ||
type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* true if $path propety should be added to documents e.g. `$path=/countries/1/cities/2/companies` | ||
*/ | ||
addDocumentPath: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
}; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export type ConstructorOptions = any; | ||
export type GetOptions = any; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
import Base from './simple-resource-storage.js'; |
@@ -1,27 +0,13 @@ | ||
'use strict' | ||
import SpanStatusCode from '@discue/open-telemetry-tracing/status-codes' | ||
import { AS_ROOT, EQUALS, EQUALS_ALL, LOOKUP, PROJECT, UNWIND } from './aggregations.js' | ||
import OneToFewStorage from './one-to-few-ref-storage.js' | ||
import { toArrayAndClose } from './safe-cursor.js' | ||
import Base from './simple-resource-storage.js' | ||
const Base = require('./simple-resource-storage.js') | ||
const OneToFewStorage = require('./one-to-few-ref-storage.js') | ||
const { PROJECT, EQUALS, LOOKUP, UNWIND, AS_ROOT, EQUALS_ALL } = require('./aggregations.js') | ||
const usageEventTrigger = require('./usage-event-trigger.js') | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { name } = require('../package.json') | ||
const SpanStatusCode = require('@discue/open-telemetry-tracing/status-codes') | ||
const { toArrayAndClose } = require('./safe-cursor.js') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename, | ||
name: 'mongodb-resource-client' | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {Array.<Object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
@@ -32,7 +18,5 @@ * @property {string} resourceName name of the resource e.g. users, customers, topics, shipments | ||
* @property {string} enableTwoWayReferences true if documents should also store references to their parents e.g. student have references to their schools | ||
* @property {import('node:events').EventEmitter} eventEmitter if provided, will trigger events base on resource creation, updates and deletion | ||
* | ||
* @example | ||
* const { MongoClient } = require('mongodb') | ||
* const { OneToManyResourceStorage } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { OneToManyResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
@@ -52,12 +36,12 @@ * const client = new MongoClient(url, { | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {boolean} addDocumentPath true if $path propety should be added to documents e.g. `$path=/countries/1/cities/2/companies` | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
*/ | ||
@@ -99,11 +83,11 @@ | ||
* | ||
* @link as described here: https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
* @name OneToManyResourceStorage | ||
* @class | ||
* @link as described here: https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design | ||
*/ | ||
module.exports = class extends Base { | ||
export default class extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @public | ||
* @param {ConstructorOptions} options | ||
*/ | ||
@@ -113,3 +97,2 @@ constructor(options) { | ||
baseOptions.collectionName = options.resourceName | ||
baseOptions.eventEmitter = null | ||
super(baseOptions) | ||
@@ -120,3 +103,2 @@ | ||
/** @private */ this._hostStorage = new OneToFewStorage(hostOptions) | ||
/** @private */ this._enableTwoWayReferences = options.enableTwoWayReferences | ||
@@ -129,20 +111,13 @@ /** @private */ this._resourceName = options.resourceName | ||
/** @private */ this._resourceLevel = this._resourcePath.length | ||
/** @private */ this._emitUsageEventEnabled = options.eventEmitter != null | ||
/** @private */ this._childEmitUsageEvent = usageEventTrigger(this.usageEventPrefix, options.resourceName, options.eventEmitter) | ||
} | ||
get usageEventPrefix() { | ||
return `one-to-many-resource.${this._resourceName}` | ||
} | ||
/** | ||
* Returns true if a resource with given ids exists. | ||
* | ||
* | ||
* @name exists | ||
* @param {String|Array.<String>} resourceIds | ||
* @param {string | Array.<string>} resourceIds | ||
* @returns {boolean} | ||
*/ | ||
async exists(resourceIds) { | ||
return this._withActiveSpan('exists-one-to-many-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('exists-one-to-many-resource-by-id', resourceIds, async () => { | ||
const resource = await this.get(resourceIds) | ||
@@ -154,18 +129,11 @@ return resource != null | ||
/** | ||
* @private | ||
*/ | ||
async _withActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
/** | ||
* Returns a resource by ids. | ||
* | ||
* | ||
* @name get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} options | ||
* @returns {Object} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} options | ||
* @returns {object} | ||
*/ | ||
async get(resourceIds, options) { | ||
return this._withActiveSpan('get-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('get-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length + 1)) { | ||
@@ -182,22 +150,20 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operation you need to provide resourceIds with length ${this._resourcePath.length + 1}` | ||
} | ||
/** | ||
* @typedef FindOptions | ||
* @name FindOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} match MongoDB match object e.g. { id: 123 } | ||
* @private | ||
* @typedef FindOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {Object} match MongoDB match object e.g. { id: 123 } | ||
*/ | ||
/** | ||
* Find a resource by via options.match query. | ||
* | ||
* | ||
* @name find | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {FindOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
*/ | ||
async find(resourceIds, options) { | ||
return this._withActiveSpan('find-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('find-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length)) { | ||
@@ -214,9 +180,9 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operations you need to provide a resourceId array with length ${this._resourcePath.length}.` | ||
} | ||
/** | ||
* Returns a resource without safety checks and tracing | ||
* | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
* @private | ||
*/ | ||
@@ -230,3 +196,6 @@ async _getResourceWithLookup(resourceIds, options = {}) { | ||
} | ||
/** | ||
* | ||
* @private | ||
*/ | ||
async _getParentCollection() { | ||
@@ -236,17 +205,14 @@ const parentCollection = this._resourcePath.at(0) | ||
} | ||
/** | ||
* | ||
* | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
* @private | ||
*/ | ||
_getNestedLookupStages(resourceIds, options) { | ||
const pipeline = [] | ||
for (let i = 0, n = resourceIds.length; i < n; i++) { | ||
pipeline.push(EQUALS('id', resourceIds.at(i))) | ||
pipeline.push(PROJECT({ _id: 0, _meta_data: 0 })) | ||
const childCollection = this._resourcePath.at(i + 1) ?? this._resourceName | ||
@@ -256,3 +222,4 @@ const lookupPipeline = [] | ||
lookupPipeline.push(EQUALS('id', resourceIds.at(i + 1))) | ||
} else if (options.match) { | ||
} | ||
else if (options.match) { | ||
lookupPipeline.push(EQUALS_ALL(options.match)) | ||
@@ -269,3 +236,2 @@ } | ||
pipeline.push(AS_ROOT(childCollection)) | ||
if (childCollection === this._resourceName) { | ||
@@ -275,29 +241,29 @@ break | ||
} | ||
if (options.withMetadata) { | ||
pipeline.push(PROJECT({ _id: 0 })) | ||
} else { | ||
} | ||
else { | ||
pipeline.push(PROJECT({ _id: 0, _meta_data: 0 })) | ||
} | ||
if (options.projection) { | ||
pipeline.push(PROJECT(options.projection)) | ||
} | ||
return pipeline | ||
} | ||
/** | ||
* | ||
* @param {Array.<string>} resourceIds | ||
* @private | ||
*/ | ||
_getResourceIdsForHostStorage(resourceIds) { | ||
return resourceIds.slice(this._resourceLevel - 1) | ||
} | ||
/** | ||
* Returns resources based on return value of {@link findReferences}. | ||
* | ||
* | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
*/ | ||
async getAll(resourceIds, { withMetadata = false, addDocumentPath, projection } = {}) { | ||
return this._withActiveSpan('get-all-one-to-many-resources-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('get-all-one-to-many-resources-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length)) { | ||
@@ -311,3 +277,2 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operations you need to provide a resourceId array with length ${this._resourcePath.length}.` | ||
} | ||
const collection = await this._getParentCollection() | ||
@@ -317,11 +282,8 @@ const aggregationStages = this._getNestedLookupStages(resourceIds, { withMetadata, projection }) | ||
const resources = await toArrayAndClose(cursor) | ||
if (!resources || resources.length === 0) { | ||
return [] | ||
} | ||
if (addDocumentPath) { | ||
resources.forEach((result) => { | ||
const { id } = result | ||
// not ideal but right now lets only support one reference per document | ||
@@ -340,17 +302,15 @@ // refsArray.forEach((refs) => { | ||
} | ||
return resources | ||
}) | ||
} | ||
/** | ||
* Add a resource to a collection by ids. | ||
* | ||
* | ||
* @name create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @returns | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @returns | ||
*/ | ||
async create(resourceIds, resource) { | ||
return this._withActiveSpan('create-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('create-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length + 1)) { | ||
@@ -365,5 +325,5 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operations you need to provide a resourceId array with length ${this._resourcePath.length + 1}.` | ||
return this._withTransaction(async (session, client) => { | ||
const newId = await super.create(resourceIds.slice(-1), resource, { session, client }) | ||
const newId = resourceIds.at(-1) | ||
await super._createUnsafe(newId, resource, { session, client }) | ||
await this._hostStorage.create(this._getResourceIdsForHostStorage(resourceIds), newId, { session, client }) | ||
if (this._enableTwoWayReferences) { | ||
@@ -374,6 +334,4 @@ await this._updateUnsafe(newId, { | ||
} | ||
await session.commitTransaction() | ||
await this._childEmitUsageEvent({ context: 'create', after: resource, resourceIds }) | ||
await this._emitter.emit('create', { after: resource, resourceIds }) | ||
return newId | ||
@@ -383,13 +341,12 @@ }) | ||
} | ||
/** | ||
* Updates a resource by ids | ||
* | ||
* | ||
* @name update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @returns | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @returns | ||
*/ | ||
async update(resourceIds, update) { | ||
return this._withActiveSpan('update-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('update-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length + 1)) { | ||
@@ -408,11 +365,8 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operation you need to provide resourceIds with length ${this._resourcePath.length + 1}` | ||
} | ||
const updateResult = await this._updateUnsafe(resourceIds.slice(-1), update, { session, client }) | ||
await session.commitTransaction() | ||
if (this._emitUsageEventEnabled) { | ||
{ | ||
const newResource = await this.get(resourceIds) | ||
await this._childEmitUsageEvent({ context: 'update', before: resource, after: newResource, resourceIds }) | ||
await this._emitter.emit('update', { before: resource, after: newResource, resourceIds }) | ||
} | ||
return updateResult | ||
@@ -422,12 +376,11 @@ }) | ||
} | ||
/** | ||
* Deletes a resource by ids | ||
* | ||
* | ||
* @name delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns | ||
*/ | ||
async delete(resourceIds) { | ||
return this._withActiveSpan('delete-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
return this._runWithActiveSpan('delete-one-to-many-resource-by-id', resourceIds, async (span) => { | ||
if (!Array.isArray(resourceIds) || !(resourceIds.length == this._resourcePath.length + 1)) { | ||
@@ -446,9 +399,6 @@ const errorMessage = `Given resourceIds ${resourceIds} and resourcePath ${this._resourcePath} dont match lengths. For this operation you need to provide resourceIds with length ${this._resourcePath.length + 1}` | ||
} | ||
const deleteResult = await this._hostStorage.delete(this._getResourceIdsForHostStorage(resourceIds), { session, client }) | ||
await this._deleteUnsafe(resourceIds.slice(-1), resource, { session, client }) | ||
await session.commitTransaction() | ||
await this._childEmitUsageEvent({ context: 'delete', before: resource, resourceIds }) | ||
await this._emitter.emit('delete', { before: resource, resourceIds }) | ||
return deleteResult | ||
@@ -458,6 +408,5 @@ }) | ||
} | ||
/** | ||
* Closes all clients. | ||
* | ||
* | ||
* @returns {Promise.<void>} | ||
@@ -474,2 +423,2 @@ */ | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
export function toArrayAndClose(cursor: import("mongodb").AbstractCursor): Promise<any>; | ||
export function getFirstAndClose(cursor: import("mongodb").AbstractCursor): Promise<any>; | ||
export function toArrayAndClose(cursor: import("mongodb").AbstractCursor): any[]; | ||
export function getFirstAndClose(cursor: import("mongodb").AbstractCursor): object; |
@@ -1,13 +0,12 @@ | ||
'use strict' | ||
/** | ||
* | ||
* @param {import('mongodb').AbstractCursor} cursor | ||
* @returns {Promise} | ||
* @returns {Array} | ||
*/ | ||
module.exports.toArrayAndClose = async function (cursor) { | ||
export const toArrayAndClose = async function (cursor) { | ||
try { | ||
const array = await cursor.toArray() | ||
return array | ||
} finally { | ||
} | ||
finally { | ||
await cursor.close() | ||
@@ -20,11 +19,12 @@ } | ||
* @param {import('mongodb').AbstractCursor} cursor | ||
* @returns {Promise} | ||
* @returns {object} | ||
*/ | ||
module.exports.getFirstAndClose = async function (cursor) { | ||
export const getFirstAndClose = async function (cursor) { | ||
try { | ||
const first = await cursor.next() | ||
return first | ||
} finally { | ||
} | ||
finally { | ||
await cursor.close() | ||
} | ||
} | ||
} |
@@ -1,27 +0,76 @@ | ||
export = exports; | ||
declare class exports extends Base { | ||
declare const _default_base: { | ||
new ({ client, databaseName, collectionName }?: import("./base-storage.js").ConstructorOptions): { | ||
_mongoDbClient: any; | ||
_databaseName: any; | ||
_collectionName: any; | ||
_transactionsEnabled: boolean; | ||
_history: import("./history.js").default; | ||
_emitter: import("emittery").default<Record<PropertyKey, any>, Record<PropertyKey, any> & import("emittery").OmnipresentEventData, import("emittery").DatalessEventNames<Record<PropertyKey, any>>>; | ||
_eventListenersCount: { | ||
create: number; | ||
update: number; | ||
delete: number; | ||
}; | ||
_getConnectedClient(): import("mongodb").MongoClient; | ||
_getDb(givenClient?: import("mongodb").MongoClient): import("mongodb").Db; | ||
_getCollection(collectionName?: import("mongodb").MongoClient, givenClient?: import("mongodb").MongoClient): Promise<import("./base-storage.js").Collection>; | ||
_runWithActiveSpan(spanName: string, resourceIds: string | Array<string>, callback: Function): Promise<any>; | ||
_withTransaction(callback: (clientSession: import("mongodb").ClientSession) => any): Promise<any>; | ||
_passSessionIfTransactionEnabled(options: object): object; | ||
_createMetadata(): object; | ||
_createUpdateMetadata(): object; | ||
on(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
off(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
enableHistory(): void; | ||
disableHistory(): void; | ||
close(): void; | ||
}; | ||
}; | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { SimpleResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const storage = new SimpleResourceStorage({ | ||
* client, | ||
* collectionName: 'api_clients', | ||
* }) | ||
*/ | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @private | ||
*/ | ||
/** | ||
* @typedef WithMongoClient | ||
* @name WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
* @private | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').Collection} Collection | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
/** | ||
* Simple resource class with crud operation methods to create, update, delete, and | ||
* get stored entities and documents. | ||
* | ||
* @name SimpleResourceStorage | ||
* @class | ||
*/ | ||
export default class _default extends _default_base { | ||
/** | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
constructor({ client, databaseName, collectionName, eventEmitter, indexes }?: ConstructorOptions); | ||
/** @private */ private _eventEmitter; | ||
/** @private */ private _emitUsageEventEnabled; | ||
/** @private */ private _emitUsageEvent; | ||
get usageEventPrefix(): string; | ||
/** | ||
* | ||
* @param {import('node:events').EventEmitter} eventEmitter | ||
*/ | ||
enableEventing(eventEmitter: import("node:events").EventEmitter): void; | ||
/** | ||
* | ||
*/ | ||
disableEventing(): void; | ||
/** | ||
* @private | ||
*/ | ||
private _withActiveSpan; | ||
/** | ||
* @callback WithSessionCallback | ||
@@ -31,5 +80,5 @@ * @param {import('mongodb').ClientSession} | ||
/** | ||
* @private | ||
* @param {Array.<string>} ids | ||
* @returns | ||
* @private | ||
*/ | ||
@@ -40,29 +89,16 @@ private _toStringIfArray; | ||
* | ||
* @method get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @function get | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
*/ | ||
get(resourceIds: string | Array<string>, { withMetadata, projection }?: { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
/** | ||
* MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
match: any; | ||
}): any; | ||
get(resourceIds: string | Array<string>, { withMetadata, projection }?: any): object; | ||
/** | ||
* Returns a resource by ids. | ||
* | ||
* @function get | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {object} | ||
* @private | ||
* @method get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
*/ | ||
@@ -73,32 +109,19 @@ private __getResource; | ||
* | ||
* @method getAll | ||
* @function getAll | ||
* @param {GetOptions} options | ||
* @returns {Array.<Object>} | ||
* @returns {Array.<object>} | ||
*/ | ||
getAll({ withMetadata, projection }?: { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
/** | ||
* MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
match: any; | ||
}): Array<any>; | ||
getAll({ withMetadata, projection }?: any): Array<object>; | ||
/** | ||
* @typedef GetOptions | ||
* @name GetChildrenOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} match MongoDB match object e.g. { id: 0, name: 0 } | ||
* @private | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {Object} match MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef ChildrenAndResourcePaths | ||
* @property {Array.<Object>} children | ||
* @property {Object} resourcePaths and object mapping child ids to their resource path e.g. { 4: '/queues/123/listeners/4'} | ||
* @property {Array.<object>} children | ||
* @property {object} resourcePaths and object mapping child ids to their resource path e.g. { 4: '/queues/123/listeners/4'} | ||
*/ | ||
@@ -111,4 +134,4 @@ /** | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<String>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {GetChildrenOptions} [options] | ||
@@ -118,7 +141,7 @@ * @returns {Promise.<ChildrenAndResourcePaths>} | ||
getAllChildren(resourceIds: string | Array<string>, childPath: string | Array<string>, { withMetadata, projection, match }?: GetChildrenOptions): Promise<{ | ||
children: Array<any>; | ||
children: Array<object>; | ||
/** | ||
* and object mapping child ids to their resource path e.g. { 4: '/queues/123/listeners/4'} | ||
*/ | ||
resourcePaths: any; | ||
resourcePaths: object; | ||
}>; | ||
@@ -131,4 +154,4 @@ /** | ||
* @name countAllChildren | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<String>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {GetChildrenOptions} [options] | ||
@@ -141,13 +164,13 @@ * @returns {Promise.<number>} | ||
* | ||
* @function find | ||
* @param {Array.<object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<object>} | ||
* @see {@link README_AGGREGATIONS.md} | ||
* @method find | ||
* @param {Array.<Object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<Object>} | ||
*/ | ||
find(aggregations?: Array<any>): Array<any>; | ||
find(aggregations?: Array<object>): Array<object>; | ||
/** | ||
* Returns true if a resource with given ids exists. | ||
* | ||
* @method exists | ||
* @param {String|Array.<String>} resourceIds | ||
* @function exists | ||
* @param {string | Array.<string>} resourceIds | ||
* @returns {boolean} | ||
@@ -159,10 +182,21 @@ */ | ||
* | ||
* @method create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
*/ | ||
create(resourceIds: string | Array<string>, resource: any, options?: import("mongodb").InsertOneOptions): Promise<any>; | ||
create(resourceIds: string | Array<string>, resource: object, options?: import("mongodb").InsertOneOptions): Promise<any>; | ||
/** | ||
* Adds a resource to a collection without any checks. | ||
* | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
* @protected | ||
*/ | ||
protected _createUnsafe(resourceIds: string | Array<string>, resource: object, options?: import("mongodb").InsertOneOptions): Promise<void>; | ||
/** | ||
* @typedef UpdateOptions | ||
@@ -174,9 +208,9 @@ * @property {boolean} [upsert=false] | ||
* | ||
* @method update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @function update | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @param {UpdateOptions} options | ||
* @returns | ||
*/ | ||
update(resourceIds: string | Array<string>, update: any, options: { | ||
update(resourceIds: string | Array<string>, update: object, options: { | ||
upsert?: boolean; | ||
@@ -187,7 +221,7 @@ }): Promise<any>; | ||
* | ||
* @private | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
* @private | ||
*/ | ||
@@ -198,4 +232,4 @@ private _updateUnsafe; | ||
* | ||
* @method delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @function delete | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns | ||
@@ -207,49 +241,14 @@ */ | ||
* | ||
* @private | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
* @private | ||
*/ | ||
private _deleteUnsafe; | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, GetOptions, WithMongoClient, Collection, MongoClient }; | ||
} | ||
import Base = require("./base-storage.js"); | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
/** | ||
* indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
*/ | ||
indexes?: Array<any>; | ||
/** | ||
* if provided, will trigger events base on resource creation, updates and deletion | ||
*/ | ||
eventEmitter: import("node:events").EventEmitter; | ||
}; | ||
type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
}; | ||
type WithMongoClient = { | ||
client: import("mongodb").MongoClient; | ||
}; | ||
type Collection = import("mongodb").Collection; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export type ConstructorOptions = any; | ||
export type GetOptions = any; | ||
export type WithMongoClient = any; | ||
export type Collection = import("mongodb").Collection; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
export {}; |
@@ -1,30 +0,16 @@ | ||
'use strict' | ||
import { EQUALS, LIMIT, PROJECT } from './aggregations.js' | ||
import Base from './base-storage.js' | ||
import { getSingleLookupPipeline, joinAndQueryChildResourcesPipeline } from './lookup-pipeline.js' | ||
import { getFirstAndClose, toArrayAndClose } from './safe-cursor.js' | ||
const Base = require('./base-storage.js') | ||
const { EQUALS, LIMIT, PROJECT } = require('./aggregations.js') | ||
const eventTrigger = require('./usage-event-trigger.js') | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { name } = require('../package.json') | ||
const { getSingleLookupPipeline, joinAndQueryChildResourcesPipeline } = require('./lookup-pipeline.js') | ||
const { toArrayAndClose, getFirstAndClose } = require('./safe-cursor.js') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {Array.<Object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @property {import('node:events').EventEmitter} eventEmitter if provided, will trigger events base on resource creation, updates and deletion | ||
* | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @example | ||
* const { MongoClient } = require('mongodb') | ||
* const { SimpleResourceStorage } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { SimpleResourceStorage } from '@discue/mongodb-resource-client' | ||
* | ||
@@ -42,20 +28,20 @@ * const client = new MongoClient(url, { | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @private | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef WithMongoClient | ||
* @name WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
* @private | ||
* @typedef WithMongoClient | ||
* @property {import('mongodb').MongoClient} client | ||
*/ | ||
/** | ||
* @private | ||
* @typedef {import('mongodb').Collection} Collection | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
@@ -70,17 +56,12 @@ | ||
*/ | ||
module.exports = class extends Base { | ||
export default class extends Base { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
constructor({ client, databaseName, collectionName, eventEmitter, indexes } = {}) { | ||
constructor({ client, databaseName, collectionName, indexes } = {}) { | ||
super({ client, databaseName, collectionName }) | ||
/** @private */ this._eventEmitter = eventEmitter | ||
/** @private */ this._emitUsageEventEnabled = eventEmitter != null | ||
/** @private */ this._emitUsageEvent = eventTrigger(this.usageEventPrefix, collectionName, eventEmitter) | ||
this._getCollection().then(async collection => { | ||
this._getCollection().then(async (collection) => { | ||
if (indexes?.length > 0) { | ||
@@ -90,3 +71,4 @@ const indexSpecifications = indexes.map((index) => { | ||
return index | ||
} else { | ||
} | ||
else { | ||
return { key: index } | ||
@@ -105,40 +87,10 @@ } | ||
get usageEventPrefix() { | ||
return `simple-resource.${this._collectionName}` | ||
} | ||
/** | ||
* | ||
* @param {import('node:events').EventEmitter} eventEmitter | ||
*/ | ||
enableEventing(eventEmitter) { | ||
this._emitUsageEventEnabled = eventEmitter != null | ||
this._emitUsageEvent = eventTrigger(this.usageEventPrefix, this._collectionName, eventEmitter) | ||
} | ||
/** | ||
* | ||
*/ | ||
disableEventing() { | ||
this._emitUsageEventEnabled = false | ||
this._emitUsageEvent = null | ||
} | ||
/** | ||
* @private | ||
*/ | ||
async _withActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
/** | ||
* @callback WithSessionCallback | ||
* @param {import('mongodb').ClientSession} | ||
*/ | ||
/** | ||
* @param {Array.<string>} ids | ||
* @returns | ||
* @private | ||
* @param {Array.<string>} ids | ||
* @returns | ||
*/ | ||
@@ -151,14 +103,14 @@ _toStringIfArray(ids) { | ||
return ids.join('#') | ||
} else { | ||
} | ||
else { | ||
return ids | ||
} | ||
} | ||
/** | ||
* Returns a resource by ids. | ||
* | ||
* @method get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* | ||
* @function get | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
* @returns {object} | ||
*/ | ||
@@ -168,14 +120,13 @@ async get(resourceIds, { withMetadata = false, projection } = {}) { | ||
} | ||
/** | ||
* Returns a resource by ids. | ||
* | ||
* | ||
* @function get | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {object} | ||
* @private | ||
* @method get | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {GetOptions} options | ||
* @returns {Object} | ||
*/ | ||
async __getResource(resourceIds, { withMetadata = false, projection } = {}) { | ||
return this._withActiveSpan('get-simple-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('get-simple-resource-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
@@ -186,13 +137,11 @@ const aggregationStages = [ | ||
] | ||
if (!withMetadata) { | ||
aggregationStages.push(PROJECT({ _id: 0, _meta_data: 0 })) | ||
} else { | ||
} | ||
else { | ||
aggregationStages.push(PROJECT({ _id: 0 })) | ||
} | ||
if (projection) { | ||
aggregationStages.push(PROJECT(projection)) | ||
} | ||
const cursor = collection.aggregate(aggregationStages) | ||
@@ -202,25 +151,22 @@ return getFirstAndClose(cursor) | ||
} | ||
/** | ||
* Returns all resources. | ||
* | ||
* @method getAll | ||
* | ||
* @function getAll | ||
* @param {GetOptions} options | ||
* @returns {Array.<Object>} | ||
* @returns {Array.<object>} | ||
*/ | ||
async getAll({ withMetadata = false, projection } = {}) { | ||
return this._withActiveSpan('get-all-simple-resources', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
return this._runWithActiveSpan('get-all-simple-resources', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
const collection = await this._getCollection() | ||
const aggregationStages = [] | ||
if (!withMetadata) { | ||
aggregationStages.push(PROJECT({ _id: 0, _meta_data: 0 })) | ||
} else { | ||
} | ||
else { | ||
aggregationStages.push(PROJECT({ _id: 0 })) | ||
} | ||
if (projection) { | ||
aggregationStages.push(PROJECT(projection)) | ||
} | ||
const cursor = collection.aggregate(aggregationStages) | ||
@@ -230,26 +176,23 @@ return toArrayAndClose(cursor) | ||
} | ||
/** | ||
* @typedef GetOptions | ||
* @name GetChildrenOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {object} match MongoDB match object e.g. { id: 0, name: 0 } | ||
* @private | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @property {Object} match MongoDB match object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @typedef ChildrenAndResourcePaths | ||
* @property {Array.<Object>} children | ||
* @property {Object} resourcePaths and object mapping child ids to their resource path e.g. { 4: '/queues/123/listeners/4'} | ||
* @property {Array.<object>} children | ||
* @property {object} resourcePaths and object mapping child ids to their resource path e.g. { 4: '/queues/123/listeners/4'} | ||
*/ | ||
/** | ||
* Returns all children of a certain type/collection. Imagine this method walking a tree and returning all leaves at a certain level. | ||
* | ||
* | ||
* Currently only supports trees with three levels. | ||
* | ||
* | ||
* @name getAll | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<String>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {GetChildrenOptions} [options] | ||
@@ -259,3 +202,3 @@ * @returns {Promise.<ChildrenAndResourcePaths>} | ||
async getAllChildren(resourceIds, childPath, { withMetadata = false, projection, match } = {}) { | ||
return this._withActiveSpan('get-all-simple-resource-children', resourceIds, async () => { | ||
return this._runWithActiveSpan('get-all-simple-resource-children', resourceIds, async () => { | ||
if (childPath.startsWith('/')) { | ||
@@ -268,9 +211,6 @@ childPath = childPath.substring(1) | ||
} | ||
const lookupPipeline = getSingleLookupPipeline({ rootId: this._toStringIfArray(resourceIds), childCollectionName: parent }) | ||
const childResourcesPipeline = joinAndQueryChildResourcesPipeline({ parentCollectionName: parent, childCollectionName: child, options: { withMetadata, projection, match } }) | ||
lookupPipeline.push(...childResourcesPipeline) | ||
const collection = await this._getCollection() | ||
const cursor = collection.aggregate(lookupPipeline) | ||
@@ -281,18 +221,15 @@ const result = await toArrayAndClose(cursor) | ||
} | ||
const children = result.at(0).children ?? [] | ||
const resourcePaths = result.at(0).resource_paths ?? [] | ||
return { children, resourcePaths } | ||
}) | ||
} | ||
/** | ||
* Returns the count of all children of a certain type/collection. | ||
* | ||
* Returns the count of all children of a certain type/collection. | ||
* | ||
* Currently only supports trees with three levels. | ||
* | ||
* | ||
* @name countAllChildren | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {String|Array.<String>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {string | Array.<string>} childPath the path of the children to query e.g. /api_clients/queues/messages | ||
* @param {GetChildrenOptions} [options] | ||
@@ -302,3 +239,3 @@ * @returns {Promise.<number>} | ||
async countAllChildren(resourceIds, childPath, { withMetadata = false, projection, match } = {}) { | ||
return this._withActiveSpan('count-all-simple-resource-children', resourceIds, async () => { | ||
return this._runWithActiveSpan('count-all-simple-resource-children', resourceIds, async () => { | ||
if (childPath.startsWith('/')) { | ||
@@ -311,3 +248,2 @@ childPath = childPath.substring(1) | ||
} | ||
const lookupPipeline = getSingleLookupPipeline({ rootId: this._toStringIfArray(resourceIds), childCollectionName: parent }) | ||
@@ -317,5 +253,3 @@ const childResourcesPipeline = joinAndQueryChildResourcesPipeline({ parentCollectionName: parent, childCollectionName: child, options: { withMetadata, projection, match } }) | ||
lookupPipeline.push(PROJECT({ count: { $size: '$children' } })) | ||
const collection = await this._getCollection() | ||
const cursor = collection.aggregate(lookupPipeline) | ||
@@ -326,19 +260,16 @@ const result = await toArrayAndClose(cursor) | ||
} | ||
return result.at(0).count | ||
}) | ||
} | ||
/** | ||
* Returns all resources that pass the given aggregation stages. | ||
* | ||
* | ||
* @function find | ||
* @param {Array.<object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<object>} | ||
* @see {@link README_AGGREGATIONS.md} | ||
* @method find | ||
* @param {Array.<Object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<Object>} | ||
*/ | ||
async find(aggregations = []) { | ||
return this._withActiveSpan('find-simple-resources', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
return this._runWithActiveSpan('find-simple-resources', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
const collection = await this._getCollection() | ||
const cursor = collection.aggregate(aggregations) | ||
@@ -348,12 +279,11 @@ return toArrayAndClose(cursor) | ||
} | ||
/** | ||
* Returns true if a resource with given ids exists. | ||
* | ||
* @method exists | ||
* @param {String|Array.<String>} resourceIds | ||
* | ||
* @function exists | ||
* @param {string | Array.<string>} resourceIds | ||
* @returns {boolean} | ||
*/ | ||
async exists(resourceIds) { | ||
return this._withActiveSpan('exists-simple-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('exists-simple-resource-by-id', resourceIds, async () => { | ||
const resource = await this.get(resourceIds) | ||
@@ -363,29 +293,15 @@ return resource != undefined | ||
} | ||
/** | ||
* Adds a resource to a collection by ids. | ||
* | ||
* @method create | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} resource the resource to be stored | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
* @returns | ||
*/ | ||
async create(resourceIds, resource, options) { | ||
return this._withActiveSpan('create-simple-resource-by-id', resourceIds, async () => { | ||
const collection = await this._getCollection() | ||
const result = await collection.insertOne(Object.assign(resource, { | ||
id: this._toStringIfArray(resourceIds), | ||
_meta_data: this._createMetadata() | ||
}), this._passSessionIfTransactionEnabled(options)) | ||
const success = result.acknowledged === true | ||
if (!success) { | ||
throw new Error(`Was not able to insert ${resourceIds} with resource ${resource}`) | ||
} | ||
if (this._emitUsageEventEnabled) { | ||
await this._emitUsageEvent({ context: 'create', after: resource, resourceIds }) | ||
} | ||
return this._runWithActiveSpan('create-simple-resource-by-id', resourceIds, async () => { | ||
await this._createUnsafe(resourceIds, resource, options) | ||
await this._emitter.emit('create', { after: resource, resourceIds }) | ||
return this._toStringIfArray(resourceIds) | ||
@@ -396,17 +312,38 @@ }) | ||
/** | ||
* Adds a resource to a collection without any checks. | ||
* | ||
* @function create | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} resource the resource to be stored | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
* @protected | ||
*/ | ||
async _createUnsafe(resourceIds, resource, options) { | ||
const collection = await this._getCollection() | ||
const result = await collection.insertOne(Object.assign(resource, { | ||
id: this._toStringIfArray(resourceIds), | ||
_meta_data: this._createMetadata() | ||
}), this._passSessionIfTransactionEnabled(options)) | ||
const success = result.acknowledged === true | ||
if (!success) { | ||
throw new Error(`Was not able to insert ${resourceIds} with resource ${resource}`) | ||
} | ||
} | ||
/** | ||
* @typedef UpdateOptions | ||
* @property {boolean} [upsert=false] | ||
*/ | ||
/** | ||
* Updates a resource by ids. | ||
* | ||
* @method update | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* | ||
* @function update | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @param {UpdateOptions} options | ||
* @returns | ||
* @returns | ||
*/ | ||
async update(resourceIds, update, options) { | ||
return this._withActiveSpan('update-simple-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('update-simple-resource-by-id', resourceIds, async () => { | ||
return this._withTransaction(async (session, client) => { | ||
@@ -420,11 +357,6 @@ let resource = null | ||
} | ||
const updateResult = await this._updateUnsafe(resourceIds, update, { session, client, upsert: options?.upsert }) | ||
await session.commitTransaction() | ||
if (this._emitUsageEventEnabled) { | ||
const newResource = await this.get(resourceIds) | ||
await this._emitUsageEvent({ context: 'update', before: resource, after: newResource, resourceIds }) | ||
} | ||
const newResource = await this.get(resourceIds) | ||
await this._emitter.emit('update', { before: resource, after: newResource, resourceIds }) | ||
return updateResult | ||
@@ -434,11 +366,10 @@ }) | ||
} | ||
/** | ||
* Updates a resource by ids without checking its existence | ||
* | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {object} update values that should be updated | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
* @private | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {Object} update values that should be updated | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
*/ | ||
@@ -453,3 +384,2 @@ async _updateUnsafe(resourceIds, update, options) { | ||
} | ||
const objectKeys = Object.keys(update) | ||
@@ -461,3 +391,4 @@ const isSetOperation = objectKeys.at(0) === '$set' | ||
} | ||
} else { | ||
} | ||
else { | ||
// let's not generically interfere with updates and in this case | ||
@@ -467,3 +398,2 @@ // update meta data with another operation | ||
} | ||
const collection = await this._getCollection(options.client) | ||
@@ -473,3 +403,2 @@ const result = await collection.updateOne({ | ||
}, update, this._passSessionIfTransactionEnabled(options)) | ||
const success = result.acknowledged === true | ||
@@ -479,3 +408,2 @@ if (!success) { | ||
} | ||
if (updateMetadataSeparately) { | ||
@@ -489,12 +417,11 @@ await collection.updateOne({ | ||
} | ||
/** | ||
* Deletes a resource by ids. | ||
* | ||
* @method delete | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns | ||
* | ||
* @function delete | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @returns | ||
*/ | ||
async delete(resourceIds) { | ||
return this._withActiveSpan('delete-simple-resource-by-id', resourceIds, async () => { | ||
return this._runWithActiveSpan('delete-simple-resource-by-id', resourceIds, async () => { | ||
return this._withTransaction(async (session, client) => { | ||
@@ -505,10 +432,5 @@ const resource = await this.get(resourceIds) | ||
} | ||
const deleteResult = await this._deleteUnsafe(resourceIds, resource, { session, client }) | ||
await session.commitTransaction() | ||
if (this._emitUsageEventEnabled) { | ||
await this._emitUsageEvent({ context: 'delete', before: resource, resourceIds }) | ||
} | ||
await this._emitter.emit('delete', { before: resource, resourceIds }) | ||
return deleteResult | ||
@@ -518,10 +440,9 @@ }) | ||
} | ||
/** | ||
* Deletes a resource by ids without checking its existence. | ||
* | ||
* | ||
* @param {string | Array.<string>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
* @private | ||
* @param {String|Array.<String>} resourceIds resource ids that will added to the resource path i.e. /users/${id}/documents/${id} | ||
* @param {import('mongodb').DeleteOptions | WithMongoClient} options | ||
* @returns | ||
*/ | ||
@@ -533,3 +454,2 @@ async _deleteUnsafe(resourceIds, options) { | ||
}, this._passSessionIfTransactionEnabled(options)) | ||
const success = result.acknowledged === true && result.deletedCount > 0 | ||
@@ -540,15 +460,2 @@ if (!success) { | ||
} | ||
/** | ||
* Closes the database client | ||
* | ||
* @method close | ||
* @returns {void} | ||
*/ | ||
async close() { | ||
if (this._emitUsageEventEnabled) { | ||
await this._emitUsageEvent({ context: 'close' }) | ||
} | ||
return super.close() | ||
} | ||
} | ||
} |
@@ -1,74 +0,92 @@ | ||
export = exports; | ||
declare class exports extends BaseStorage { | ||
/** | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
constructor({ client, databaseName, collectionName, eventEmitter, timeseries: { timeField, metaField, granularity } }?: ConstructorOptions); | ||
declare const _default_base: { | ||
new ({ client, databaseName, collectionName }?: import("./base-storage.js").ConstructorOptions): { | ||
_mongoDbClient: any; | ||
_databaseName: any; | ||
_collectionName: any; | ||
_transactionsEnabled: boolean; | ||
_history: import("./history.js").default; | ||
_emitter: import("emittery").default<Record<PropertyKey, any>, Record<PropertyKey, any> & import("emittery").OmnipresentEventData, import("emittery").DatalessEventNames<Record<PropertyKey, any>>>; | ||
_eventListenersCount: { | ||
create: number; | ||
update: number; | ||
delete: number; | ||
}; | ||
_getConnectedClient(): import("mongodb").MongoClient; | ||
_getDb(givenClient?: import("mongodb").MongoClient): import("mongodb").Db; | ||
_getCollection(collectionName?: import("mongodb").MongoClient, givenClient?: import("mongodb").MongoClient): Promise<import("./base-storage.js").Collection>; | ||
_runWithActiveSpan(spanName: string, resourceIds: string | Array<string>, callback: Function): Promise<any>; | ||
_withTransaction(callback: (clientSession: import("mongodb").ClientSession) => any): Promise<any>; | ||
_passSessionIfTransactionEnabled(options: object): object; | ||
_createMetadata(): object; | ||
_createUpdateMetadata(): object; | ||
on(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
off(eventName: ("create" | "update" | "delete" | "close"), callback: () => Promise<any>): Function; | ||
enableHistory(): void; | ||
disableHistory(): void; | ||
close(): void; | ||
}; | ||
}; | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @example | ||
* import { MongoClient } from 'mongodb' | ||
* import { SimpleTimeseriesStorage } from '@discue/mongodb-resource-client' | ||
* | ||
* const client = new MongoClient(url, { | ||
* serverApi: { version: '1', strict: true, deprecationErrors: true }, // https://www.mongodb.com/docs/manual/reference/stable-api/ | ||
* }) | ||
* | ||
* const storage = new SimpleTimeseriesStorage({ | ||
* client, | ||
* collectionName: 'api_access', | ||
* }) | ||
*/ | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @private | ||
*/ | ||
/** | ||
* @typedef {import('mongodb').Collection} Collection | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
/** | ||
* Simple resource class that allows appending to Timeseries | ||
* | ||
* @name SimpleTimeseriesStorage | ||
* @class | ||
*/ | ||
export default class _default extends _default_base { | ||
/** @private */ private _timeField; | ||
/** @private */ private _eventEmitter; | ||
/** @private */ private _emitUsageEventEnabled; | ||
/** @private */ private _emitUsageEvent; | ||
get usageEventPrefix(): string; | ||
/** | ||
* @private | ||
*/ | ||
private _withActiveSpan; | ||
/** | ||
* Returns all resources that pass the given aggregation stages. | ||
* | ||
* @function find | ||
* @param {Array.<object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<object>} | ||
* @see {@link README_AGGREGATIONS.md} | ||
* @method find | ||
* @param {Array.<Object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<Object>} | ||
*/ | ||
find(aggregations?: Array<any>): Array<any>; | ||
find(aggregations?: Array<object>): Array<object>; | ||
/** | ||
* Adds a resource to the timeseries. | ||
* | ||
* @method create | ||
* @param {Object} resource the resource to be stored. If timeField is missing, current timestamp will be added | ||
* @function create | ||
* @param {object} resource the resource to be stored. If timeField is missing, current timestamp will be added | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
*/ | ||
create(resource: any, options?: import("mongodb").InsertOneOptions): Promise<any>; | ||
create(resource: object, options?: import("mongodb").InsertOneOptions): Promise<any>; | ||
} | ||
declare namespace exports { | ||
export { ConstructorOptions, GetOptions, Collection, MongoClient }; | ||
} | ||
import BaseStorage = require("./base-storage.js"); | ||
type ConstructorOptions = { | ||
/** | ||
* configured mongo client to use. Can be null if url is set | ||
*/ | ||
client: MongoClient; | ||
/** | ||
* name of the mongodb database | ||
*/ | ||
databaseName?: string; | ||
/** | ||
* name of the mongodb collection used to store the resources | ||
*/ | ||
collectionName: string; | ||
/** | ||
* indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
*/ | ||
indexes?: Array<any>; | ||
/** | ||
* if provided, will trigger events base on resource creation, updates and deletion | ||
*/ | ||
eventEmitter: import("node:events").EventEmitter; | ||
}; | ||
type GetOptions = { | ||
/** | ||
* true if also meta data should be returned | ||
*/ | ||
withMetadata: boolean; | ||
/** | ||
* MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
projection: any; | ||
}; | ||
type Collection = import("mongodb").Collection; | ||
type MongoClient = import("mongodb").MongoClient; | ||
export type ConstructorOptions = any; | ||
export type GetOptions = any; | ||
export type Collection = import("mongodb").Collection; | ||
export type MongoClient = import("mongodb").MongoClient; | ||
export {}; |
@@ -1,28 +0,15 @@ | ||
'use strict' | ||
import BaseStorage from './base-storage.js' | ||
import { toArrayAndClose } from './safe-cursor.js' | ||
const BaseStorage = require('./base-storage.js') | ||
const { createTracer } = require('@discue/open-telemetry-tracing') | ||
const { name } = require('../package.json') | ||
const { toArrayAndClose } = require('./safe-cursor.js') | ||
const eventTrigger = require('./usage-event-trigger.js') | ||
/** | ||
* @private | ||
*/ | ||
const { withActiveSpan } = createTracer({ | ||
filepath: __filename | ||
}) | ||
/** | ||
* @typedef ConstructorOptions | ||
* @name ConstructorOptions | ||
* @typedef ConstructorOptions | ||
* @property {MongoClient} client configured mongo client to use. Can be null if url is set | ||
* @property {string} [databaseName=null] name of the mongodb database | ||
* @property {string} collectionName name of the mongodb collection used to store the resources | ||
* @property {Array.<Object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @property {import('node:events').EventEmitter} eventEmitter if provided, will trigger events base on resource creation, updates and deletion | ||
* | ||
* @property {Array.<object>} [indexes=null] indexes to be created on instantiation. Use format {key:1} for single indexes and {key1: 1, key:2} for compound indexes. See https://www.mongodb.com/docs/manual/reference/command/createIndexes/#command-fields | ||
* @example | ||
* const { MongoClient } = require('mongodb') | ||
* const { SimpleTimeseriesStorage } = require('@discue/mongodb-resource-client') | ||
* import { MongoClient } from 'mongodb' | ||
* import { SimpleTimeseriesStorage } from '@discue/mongodb-resource-client' | ||
* | ||
@@ -40,13 +27,13 @@ * const client = new MongoClient(url, { | ||
/** | ||
* @typedef GetOptions | ||
* @name GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
* @private | ||
* @typedef GetOptions | ||
* @property {boolean} withMetadata true if also meta data should be returned | ||
* @property {Object} projection MongoDB projection object e.g. { id: 0, name: 0 } | ||
*/ | ||
/** | ||
* @private | ||
* @typedef {import('mongodb').Collection} Collection | ||
* @typedef {import('mongodb').MongoClient} MongoClient | ||
* @private | ||
*/ | ||
@@ -60,24 +47,13 @@ | ||
*/ | ||
module.exports = class extends BaseStorage { | ||
export default class extends BaseStorage { | ||
/** | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
* @public | ||
* @param {ConstructorOptions} options | ||
* @returns | ||
*/ | ||
constructor( | ||
{ client, databaseName, collectionName, eventEmitter, timeseries: { | ||
timeField = 'timestamp', | ||
metaField = 'metadata', | ||
granularity = 'seconds' | ||
} = {} | ||
} = {}) { | ||
constructor({ client, databaseName, collectionName, timeseries: { timeField = 'timestamp', metaField = 'metadata', granularity = 'seconds' } = {} } = {}) { | ||
super({ client, databaseName, collectionName }) | ||
/** @private */ this._timeField = timeField | ||
/** @private */ this._eventEmitter = eventEmitter | ||
/** @private */ this._emitUsageEventEnabled = eventEmitter != null | ||
/** @private */ this._emitUsageEvent = eventTrigger(this.usageEventPrefix, collectionName, eventEmitter) | ||
super._getDb().then((db) => { | ||
@@ -88,25 +64,13 @@ db.createCollection(collectionName, { timeseries: { timeField, metaField, granularity } }).catch(() => { }) | ||
get usageEventPrefix() { | ||
return `simple-timeseries.${this._collectionName}` | ||
} | ||
/** | ||
* @private | ||
*/ | ||
async _withActiveSpan(spanName, resourceIds, callback) { | ||
return withActiveSpan(`${name}#${spanName}`, { 'peer.service': 'resource-client', resourceIds, resourceName: this._collectionName, databaseName: this._databaseName }, callback) | ||
} | ||
/** | ||
* Returns all resources that pass the given aggregation stages. | ||
* | ||
* | ||
* @function find | ||
* @param {Array.<object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<object>} | ||
* @see {@link README_AGGREGATIONS.md} | ||
* @method find | ||
* @param {Array.<Object>} [aggregations=[]] a list of valid aggregation objects | ||
* @returns {Array.<Object>} | ||
*/ | ||
async find(aggregations = []) { | ||
return this._withActiveSpan('find-timeseries-data', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
return this._runWithActiveSpan('find-timeseries-data', { resourceName: this._collectionName, databaseName: this._databaseName }, async () => { | ||
const collection = await super._getCollection() | ||
const cursor = collection.aggregate(aggregations) | ||
@@ -116,20 +80,17 @@ return toArrayAndClose(cursor) | ||
} | ||
/** | ||
* Adds a resource to the timeseries. | ||
* | ||
* @method create | ||
* @param {Object} resource the resource to be stored. If timeField is missing, current timestamp will be added | ||
* @function create | ||
* @param {object} resource the resource to be stored. If timeField is missing, current timestamp will be added | ||
* @param {import('mongodb').InsertOneOptions} [options=null] | ||
* @returns | ||
* @returns | ||
*/ | ||
async create(resource, options) { | ||
return this._withActiveSpan('add-timeseries-data', null, async () => { | ||
return this._runWithActiveSpan('add-timeseries-data', null, async () => { | ||
if (!resource[this._timeField]) { | ||
resource[this._timeField] = new Date() | ||
} | ||
const collection = await super._getCollection() | ||
const result = await collection.insertOne(resource, options) | ||
const success = result.acknowledged === true | ||
@@ -141,2 +102,2 @@ if (!success) { | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
export { Timestamp }; | ||
import { Timestamp } from "mongodb"; | ||
export const Timestamp: typeof mongodb.BSON.Timestamp; | ||
import * as mongodb from 'mongodb'; |
@@ -1,5 +0,5 @@ | ||
'use strict' | ||
import * as mongodb from 'mongodb' | ||
const { Timestamp } = require('mongodb') | ||
const { Timestamp } = mongodb | ||
export { Timestamp } | ||
module.exports.Timestamp = Timestamp |
@@ -5,5 +5,6 @@ { | ||
"license": "MIT", | ||
"version": "0.40.0", | ||
"version": "1.0.0", | ||
"description": "Simple wrapper around mongodb client allowing easier managing of resources", | ||
"main": "lib/index", | ||
"type": "module", | ||
"files": [ | ||
@@ -57,14 +58,19 @@ "lib" | ||
"devDependencies": { | ||
"@discue/open-telemetry-tracing": "^1.0.0", | ||
"@discue/open-telemetry-tracing": "^1.1.0", | ||
"@stylistic/eslint-plugin": "^2.6.2", | ||
"@types/chai": "^4.3.14", | ||
"@types/mocha": "^10.0.7", | ||
"@types/node": "^20.14.10", | ||
"@types/node": "^22.0.2", | ||
"chai": "4", | ||
"documentation": "^14.0.3", | ||
"eslint": "^9.6.0", | ||
"mocha": "^10.6.0", | ||
"eslint": "^9.8.0", | ||
"eslint-plugin-jsdoc": "^50.0.1", | ||
"mocha": "^10.7.0", | ||
"mongodb": "^6.8.0", | ||
"standard-version": "^9.5.0", | ||
"typescript": "^5.5.3" | ||
"typescript": "^5.5.4" | ||
}, | ||
"dependencies": { | ||
"emittery": "^1.0.3" | ||
} | ||
} |
@@ -52,11 +52,8 @@ | ||
## History / Auditing | ||
The module provides support for history / auditing tables to keep track of changes made to documents. The `ResourceStorageHistory` component can be used as an extension | ||
of a storage instance e.g. `SimpleResourceStorage`. An instance of `ResourceStorageHistory` can listen to storage events of another storage instance and populate a `${resourceName}_history` collection with timestamp, change type, and the full resource state. | ||
The module provides support for history / auditing tables to keep track of changes made to documents by calling the `enableHistory` method. | ||
```javascript | ||
const { MongoClient } = require('mongodb') | ||
const { EventEmiter } = require('events') | ||
const { OneToFewResourceStorage, ResourceStorageHistory } = require('@discue/mongodb-resource-client') | ||
import { MongoClient } from ('mongodb') | ||
import { OneToFewResourceStorage, ResourceStorageHistory } from ('@discue/mongodb-resource-client') | ||
const eventEmitter = new EventEmitter() | ||
const collectionName = 'api_clients' | ||
@@ -75,9 +72,3 @@ const url = 'mongodb://127.0.0.1:27017' | ||
const history = new ResourceStorageHistory({ | ||
client, | ||
collectionName, | ||
usageEventPrefix: oneToFewResourceStorage.usageEventPrefix | ||
eventEmitter | ||
}) | ||
history.listenForStorageEvents() | ||
oneToFewResourceStorage.enableHistory() | ||
``` | ||
@@ -84,0 +75,0 @@ |
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
141155
3569
0
Yes
1
13
31
100
+ Addedemittery@^1.0.3
+ Addedemittery@1.0.3(transitive)