npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

w-orm-mongodb

v1.1.54

Published

An operator for mongodb in nodejs.

Readme

w-orm-mongodb

An operator for mongodb in nodejs.

language npm version license npm download npm download jsdelivr download

Documentation

To view documentation or get support, visit docs.

Installation

Using npm(ES6 module):

npm i w-orm-mongodb

Example for collection

Link: [dev source code]

import WOrm from './src/WOrmMongodb.mjs'
//import WOrm from './dist/w-orm-mongodb.umd.js'

let opt = {
    url: 'mongodb://username:[email protected]:27017',
    db: 'worm',
    cl: 'users',
}

let rs = [
    {
        id: 'id-peter',
        name: 'peter',
        value: 123,
    },
    {
        id: 'id-rosemary',
        name: 'rosemary',
        value: 123.456,
    },
    {
        id: '',
        name: 'kettle',
        value: 456,
    },
]

let rsm = [
    {
        id: 'id-peter',
        name: 'peter(modify)'
    },
    {
        id: 'id-rosemary',
        name: 'rosemary(modify)'
    },
    {
        id: '',
        name: 'kettle(modify)'
    },
]

async function test() {

    //wo
    let wo = WOrm(opt)

    //on change, 資料實際異動成功後發出
    wo.on('change', function(mode, data, res) {
        console.log('change', mode)
    })

    //on error, 操作發生錯誤時發出, 整批性錯誤於reject前、逐筆失敗於該筆定案後
    //註: 事件僅為附加通知, 錯誤仍可由reject或逐筆之err欄位取得; 正常結果不會發出
    wo.on('error', function(mode, data, err) {
        console.log('error', mode, err)
    })

    //delAll
    await wo.delAll()
        .then(function(msg) {
            console.log('delAll then', msg)
        })
        .catch(function(msg) {
            console.log('delAll catch', msg)
        })

    //insert
    await wo.insert(rs)
        .then(function(msg) {
            console.log('insert then', msg)
        })
        .catch(function(msg) {
            console.log('insert catch', msg)
        })

    //save
    await wo.save(rsm, { autoInsert: false })
        .then(function(msg) {
            console.log('save then', msg)
        })
        .catch(function(msg) {
            console.log('save catch', msg)
        })

    //select all
    let ss = await wo.select()
    console.log('select all', ss)

    //select
    let so = await wo.select({ id: 'id-rosemary' })
    console.log('select', so)

    //select by $and, $gt, $lt
    let spa = await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
    console.log('select by $and, $gt, $lt', spa)

    //select by $or, $gte, $lte
    let spb = await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
    console.log('select by $or, $gte, $lte', spb)

    //select by $or, $and, $ne, $in, $nin
    let spc = await wo.select({ '$or': [{ '$and': [{ value: { '$ne': 123 } }, { value: { '$in': [123, 321, 123.456, 456] } }, { value: { '$nin': [456, 654] } }] }, { '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 400 } }] }] })
    console.log('select by $or, $and, $ne, $in, $nin', spc)

    //select by regex, $options之合法flag僅有i、m、x、s
    let sr = await wo.select({ name: { $regex: 'PeT', $options: 'i' } })
    console.log('selectReg', sr)

    //selectByPk, 由id直接查找單筆, 不需如select提取全部符合數據再處理
    let sbi = await wo.selectByPk('id-rosemary')
    console.log('selectByPk', sbi)

    //selectByPk by id not existed
    let sbn = await wo.selectByPk('id-not-existed')
    console.log('selectByPk by id not existed', sbn)

    //del
    let d = ss.filter(function(v) {
        return v.name === 'kettle'
    })
    await wo.del(d)
        .then(function(msg) {
            console.log('del then', msg)
        })
        .catch(function(msg) {
            console.log('del catch', msg)
        })

}
test()
// change delAll
// delAll then { n: 0, nDeleted: 0, ok: 1 }
// change insert
// insert then { n: 3, nInserted: 3, ok: 1 }
// change save
// save then [
//   { n: 1, nInserted: 0, nModified: 1, ok: 1 },
//   { n: 1, nInserted: 0, nModified: 1, ok: 1 },
//   { n: 0, nInserted: 0, nModified: 0, ok: 1 }
// ]
// select all [
//   { id: 'id-peter', name: 'peter(modify)', value: 123 },
//   { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
//   {
//     id: {random id},
//     name: 'kettle',
//     value: 456
//   }
// ]
// select [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
// select by $and, $gt, $lt [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
// select by $or, $gte, $lte [
//   {
//     id: {random id},
//     name: 'kettle',
//     value: 456
//   }
// ]
// select by $or, $and, $ne, $in, $nin [
//   {
//     id: 'id-rosemary',
//     name: 'rosemary(modify)',
//     value: 123.456
//   },
//   {
//     id: {random id},
//     name: 'kettle',
//     value: 456
//   }
// ]
// selectReg [ { id: 'id-peter', name: 'peter(modify)', value: 123 } ]
// selectByPk { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 }
// selectByPk by id not existed null
// change del
// del then [ { n: 1, nDeleted: 1, ok: 1 } ]

Return values

本套件屬w-orm-*系列,七個函數selectselectByPkinsertinsertBulksavedeldelAll之回傳結構依系列統一規格:

select(find)        → [ {...}, {...} ]                    無符合為 []
selectByPk(pk)      → {...} | null

insert(data, option)              → { n, nInserted, ok }
insert(data, {returnList: true})  → [ { n, nInserted, ok }, ... ]   與輸入等長保序
insertBulk(data)                  → { n, nInserted, ok }            衝突即整批reject且不寫入任何一筆
save(data, option)  → [ { n, nInserted, nModified, ok }, ... ]
del(data)           → [ { n, nDeleted, ok }, ... ]
delAll(find)        → { n, nDeleted, ok }

單筆失敗            → { ..., ok: 0, err: '...' }          僅 save、del
整批失敗            → Promise.reject(err)

各計數欄位之語義:

| 欄位 | 語義 | |---|---| | n | insert為輸入筆數;savedel為id命中筆數(01);delAll為實際刪除筆數 | | nInserted | 實際插入筆數 | | nModified | 實際更新筆數。合併後內容與現值相同而未寫入者為0 | | nDeleted | 實際刪除筆數 | | ok | 1成功、0該筆失敗 | | err | 失敗訊息,僅於ok0時出現 |

判讀準則:

| 要判斷什麼 | 看什麼 | |---|---| | 這批有沒有撞到既有主鍵 | insertBulk是否reject | | 這批有幾筆是新資料 | insertnInserted | | 這批是哪幾筆是新資料 | insert(data, {returnList: true})之各元素nInserted === 1 | | 這筆是不是新資料 | savenInserted === 1 | | 這筆內容有沒有實際寫入 | savenModified === 1 | | 這筆有沒有真的被刪 | nDeleted | | 整批有沒有失敗 | Promise是否reject | | 個別筆有沒有失敗 | 逐筆之ok === 0,訊息取err |

save之「內容相同」判定基準為將待儲存物件合併進現值之後,結果與現值相同,由MongoDB於伺服器端逐欄位比對。故僅給部份欄位且該些欄位值皆與現值相同時,nModified亦為0

del對未帶有效id者不送查詢條件,直接回ok: 0並附err,以免undefined經序列化為null而誤刪idnull之數據。

insert 的 returnList

聚合的nInserted回答「有幾筆是新的」,回答不了「是哪幾筆」——而後者正是去重的產出物(下游只對新資料做昂貴動作)。option.returnList把函數內部本就算出的逐筆判定交出來,免得呼叫端為了知道哪幾筆而把批次退化成單筆呼叫。

| option.returnList | 回傳 | |---|---| | false(預設) | { n, nInserted, ok } | | true | 與輸入等長且保序之陣列,元素為{ n, nInserted, ok } |

逐筆元素:已插入者nInserted1,主鍵已存在而跳過者(含同批重複之非首筆)為0nok恆為1——insert的任何錯誤都是整批性錯誤而reject,故逐筆元素不出現ok: 0err

let rs = await wo.insert([
    { id: 'a', name: 'x' },
    { id: 'b', name: 'y' },  //假設b已存在
    { id: 'c', name: 'z' },
], { returnList: true })
// => [ {n:1,nInserted:1,ok:1}, {n:1,nInserted:0,ok:1}, {n:1,nInserted:1,ok:1} ]

//取出本次真正新增的那幾筆
let rsNew = data.filter((v, k) => rs[k].nInserted === 1)

不變式:陣列長度等於輸入筆數,且rs.filter(v => v.nInserted === 1).length等於預設模式的nInserted。輸入無效時回[]

回傳形式的切換是靜態的——呼叫點寫死取值即知回傳形狀。兩種取值是兩份契約,共用的結果處理程式碼不要跨不同取值的呼叫點混用。

savedel不提供此選項,因為它們本來就回等長保序的逐筆陣列,資訊已經在裡面(rs[i].nInserted === 1rs[i].nModified === 1);insertBulk也不提供,因為成功時逐筆恆為已插入(零資訊量)、失敗時整批reject而無部分結果。

insert vs insertBulk

兩者衝突政策不同,不是同一個操作的加速版

| 情形 | insert | insertBulk | |---|---|---| | 主鍵已存在 | 跳過該筆,整批ok: 1 | 整批reject,且不寫入任何一筆 | | 同批重複主鍵 | 僅首筆計入nInserted | 視為衝突,整批reject | | nInserted | 實際插入筆數,0 ≤ nInserted ≤ n | 成功時恆等於n | | 適用場景 | 一般寫入,資料表可能已有既有資料 | 批次匯入,本即預期無衝突 |

確無衝突時兩者的可觀察結果完全相同(皆回{ n, nInserted: n, ok: 1 }),差異只在有衝突時顯現。需要逐筆處置者用insertinsertBulk不提供逐筆結果,故不出現ok: 0err

本套件的insertBulk不會比insert——insert本來就是一次往返且計數精確。提供它是為了語義(全有全無)與跨套件的可替換性。

全有全無的達成方式

依部署而異,由套件於執行期以hello回應自動判定(setNamemsgisdbgrid),每個實例判定一次:

| 部署 | 作法 | |---|---| | replica set / 分片叢集 | 以交易包覆insertMany,失敗即回滾 | | standalone | 無交易可用,改以補償動作:偵測到衝突後刪除本次已寫入者,再reject |

補償動作刪除的依據是本次由驅動在用戶端產生的_id,與呼叫前既有資料的_id必不相同,故縱使該筆的id已存在也不會誤刪既有資料。

standalone 的限制:補償動作於正常運作下可使狀態回復如初,但行程若於補償途中中止(斷電、強制終止),已寫入的部分可能殘留。這是無交易可用的固有限制。需要嚴格保證者請部署 replica set(單成員即可啟用交易)。

Events

操作物件為EventEmitter,發出changeerror兩種事件,供於單一處集中觀察資料異動與失敗,不必在每個呼叫點各自包裝。

change  (mode, data, res)   資料實際異動成功後,整批一次
error   (mode, data, err)   整批性錯誤於 reject 前;逐筆失敗於該筆定案後,每筆一次

mode    函數名稱,如 'insert'、'save'、'del'、'delAll'、'select'、'selectByPk'、'insertGfs' 等
data    輸入數據,無輸入數據者(如 delAll、select、selectByPk)為 null
err     錯誤訊息字串
let wo = WOrm(opt)

wo.on('change', function(mode, data, res) {
    console.log('change', mode, res)
})

wo.on('error', function(mode, data, err) {
    console.log('error', mode, err)
})

事件僅為附加通知,不承擔任何回傳義務。 凡經由事件送出之資訊,必同時經由正規管道送達——整批性錯誤經Promise.reject,逐筆失敗經該筆之err欄位,操作結果經 resolve 值。把全部事件移除之後,呼叫端仍能取得完整資訊。

操作行為不因監聽者之有無而改變。 同一份程式碼、同一組輸入,於有註冊監聽與未註冊監聽兩種情況下,回傳值與 resolve/reject 之選擇完全相同。本套件之EventEmittereventemitter3wsemievem()),其於'error'無監聽者時僅回傳false而不拋出;Node 內建之events.EventEmitter具「'error'無監聽者時將錯誤拋出」之語義,故不採用。

訂閱函數自身拋錯亦不影響本次操作之結果——每一處emit皆以 try/catch 包覆。

正常結果不發出error insert全數已存在、save合併後內容相同而未寫入、del主鍵未命中、delAll條件無命中、selectByPk查無數據,皆為正常結果而非錯誤。

收到error不表示該次呼叫失敗。 逐筆失敗時整批仍 resolve,欲判斷整批成敗仍依上方「判讀準則」。

save於逐筆插入時另發出mode'insert'change事件,供區辨新增與更新。讀取函數(selectselectByPkselectByPkGfs)不發出change

Primary key

本套件之主鍵欄位固定為id,尚未支援由呼叫端指定。id為無業務語義之識別碼。

主鍵由誰產生,由建構設定opt.autoGenPk決定:

| opt.autoGenPk | 行為 | |---|---| | true(預設) | insertsaveinsertGfs於輸入未帶有效id時,由套件以genIDSeq()自動產生(UUIDv7,時間有序) | | false | 套件一律不產生idid須由呼叫端於寫入前自備 |

autoGenPk: false之定位為依賴注入id的產生規則改由呼叫端掌握(如採用外部發號器、以業務欄位組合、沿用上游系統既有識別碼),套件不介入。採用此設定後,id之唯一性、格式與是否與既有資料衝突,皆由呼叫端自負

autoGenPk為建構層設定,不得於insertsaveoption逐次覆寫——主鍵由誰產生是整個資料表的政策,若逐次可改,同一資料表將混入兩種來源之id而難以追溯。

autoGenPk: false而輸入未帶有效id時,以Promise.reject拋出,屬整批性錯誤而不進入逐筆結果。主鍵檢查於任何寫入之前一次完成,故整批reject同批之有效筆數亦不會被寫入

del不受autoGenPk影響,於任一設定下皆不補值——未帶有效id者視為該筆無法處理,回ok: 0並附err

//預設: 未帶id者自動產生
let wo = WOrm({ url, db: 'worm', cl: 'users' })
await wo.insert({ name: 'peter' }) // => { n: 1, nInserted: 1, ok: 1 }

//關閉: id須自備
let woOff = WOrm({ url, db: 'worm', cl: 'users', autoGenPk: false })
await woOff.insert({ id: 'id-peter', name: 'peter' }) // => { n: 1, nInserted: 1, ok: 1 }
await woOff.insert({ name: 'peter' })                 // => reject: invalid data[0].id, autoGenPk is false

Upgrading

本套件會於id欄位建立唯一索引,此為insert之「已存在則跳過」與save之「不遺失更新」所必需,無法關閉。若既有資料表內已存在重複id,建立索引會失敗,insertsave會直接reject 升級前請先清除重複數據:

// 找出重複id
db.users.aggregate([
    { $group: { _id: '$id', n: { $sum: 1 } } },
    { $match: { n: { $gt: 1 } } },
])

清除重複數據後即可正常使用。索引僅於首次寫入時建立一次,已存在同樣索引時MongoDB不會重建亦不報錯。

GridFS同理,insertGfs會於<cl>.filesfilename欄位建立唯一索引:

// 找出重複id
db['usersGfs.files'].aggregate([
    { $group: { _id: '$filename', n: { $sum: 1 } } },
    { $match: { n: { $gt: 1 } } },
])

selectByPkGfsdelGfsdelAllGfs皆不建立索引,故既有數據縱使尚存重複id亦可正常查詢與清除,delGfs會將同一id之多筆一併刪除並如實回報nDeleted

Example for unique id and concurrency

Link: [dev source code]

import WOrm from './src/WOrmMongodb.mjs'
//import WOrm from './dist/w-orm-mongodb.umd.js'

let opt = {
    url: 'mongodb://username:[email protected]:27017',
    db: 'worm',
    cl: 'usersUnique',
}

async function test() {

    //wo
    let wo = WOrm(opt)

    //delAll
    await wo.delAll()

    //insert, 同批含重複id時僅首筆成功
    let ri = await wo.insert([
        { id: 'id-dup', name: 'dup-1' },
        { id: 'id-dup', name: 'dup-2' },
        { id: 'id-uniq', name: 'uniq' },
    ])
    console.log('insert with duplicated id', ri)
    console.log('selectByPk(id-dup)', await wo.selectByPk('id-dup'))

    //insert, 對已存在id再插入則跳過而不覆寫
    let re = await wo.insert({ id: 'id-dup', name: 'dup-3' })
    console.log('insert existed id', re)
    console.log('selectByPk(id-dup)', await wo.selectByPk('id-dup'))

    //insert, 併發對同一id插入10次, nInserted總和為1
    let rc = await Promise.all(Array.from({ length: 10 }, (v, k) => {
        return wo.insert({ id: 'id-race', k })
    }))
    console.log('sum of nInserted by 10 concurrent insert', rc.reduce((sum, v) => sum + v.nInserted, 0))
    console.log('records of id-race', (await wo.select({ id: 'id-race' })).length)

    //save, 併發對同一全新id儲存不同欄位, 僅一次為插入, 各欄位皆保留
    let rs = await Promise.all(Array.from({ length: 5 }, (v, k) => {
        return wo.save({ id: 'id-new', [`f${k}`]: k })
    }))
    console.log('count of nInserted===1 by 5 concurrent save', rs.filter((v) => v[0].nInserted === 1).length)
    console.log('records of id-new', (await wo.select({ id: 'id-new' })).length)
    console.log('selectByPk(id-new)', await wo.selectByPk('id-new'))

}
test()
// insert with duplicated id { n: 3, nInserted: 2, ok: 1 }
// selectByPk(id-dup) { id: 'id-dup', name: 'dup-1' }
// insert existed id { n: 1, nInserted: 0, ok: 1 }
// selectByPk(id-dup) { id: 'id-dup', name: 'dup-1' }
// sum of nInserted by 10 concurrent insert 1
// records of id-race 1
// count of nInserted===1 by 5 concurrent save 1
// records of id-new 1
// selectByPk(id-new) { id: 'id-new', f0: 0, f1: 1, f2: 2, f4: 4, f3: 3 }
// 註: 併發儲存之各欄位皆會保留, 惟欄位順序取決於各次儲存之完成順序, 故每次執行不盡相同

Example for GridFS

Link: [dev source code]

GridFS函數之參數與回傳形狀比照一般操作,數據物件為{ id, u8a }

| GridFS函數 | 對應一般函數 | 回傳 | |---|---|---| | selectByPkGfs(id) | selectByPk | { id, u8a }null | | insertGfs(data) | insert | { n, nInserted, ok } | | delGfs(data) | del | [ { n, nDeleted, ok }, ... ] | | delAllGfs(find) | delAll | { n, nDeleted, ok } |

insertGfs同樣具備「已存在則跳過」語義,以<cl>.filesfilename唯一索引達成,升級前提與一般操作相同。GridFS無法於單一原子操作內取代既有內容,故不提供saveGfs,更新請以delGfs後再insertGfs完成。

import WOrm from './src/WOrmMongodb.mjs'
//import WOrm from './dist/w-orm-mongodb.umd.js'


//GridFS函數之參數與回傳形狀比照一般操作:
//數據物件為{ id, u8a }, insertGfs收物件或陣列, delGfs收物件或陣列並回傳等長陣列
let opt = {
    url: 'mongodb://username:[email protected]:27017',
    db: 'worm',
    cl: 'usersGfs',
}

//genU8a, 產生內容可複現之測試數據
function genU8a(n) {
    let u8a = new Uint8Array(n)
    for (let i = 0; i < n; i++) {
        u8a[i] = i % 256
    }
    return u8a
}

async function test() {

    //wo
    let wo = WOrm(opt)

    //on change, 資料實際異動成功後發出
    wo.on('change', function(mode, data, res) {
        console.log('change', mode)
    })

    //on error, 操作發生錯誤時發出, 整批性錯誤於reject前、逐筆失敗於該筆定案後
    //註: 事件僅為附加通知, 錯誤仍可由reject或逐筆之err欄位取得; 正常結果不會發出
    wo.on('error', function(mode, data, err) {
        console.log('error', mode, err)
    })

    //u8a, 亦可為瀏覽器或nodejs取得之任何Uint8Array
    let u8a = genU8a(1000)

    //delAllGfs
    await wo.delAllGfs()
        .then(function(msg) {
            console.log('delAllGfs then', msg)
        })
        .catch(function(msg) {
            console.log('delAllGfs catch', msg)
        })

    //insertGfs
    let gi = await wo.insertGfs({ id: 'id-file', u8a })
    console.log('insertGfs', gi)

    //insertGfs, 已存在id者跳過且不覆寫
    let gr = await wo.insertGfs({ id: 'id-file', u8a: genU8a(50) })
    console.log('insertGfs existed id', gr)

    //selectByPkGfs
    let gs = await wo.selectByPkGfs('id-file')
    console.log('selectByPkGfs id', gs.id)
    console.log('selectByPkGfs u8a.length', gs.u8a.length)
    console.log('selectByPkGfs u8a[0..3]', gs.u8a[0], gs.u8a[1], gs.u8a[2], gs.u8a[3])

    //selectByPkGfs by id not existed
    let gn = await wo.selectByPkGfs('id-not-existed')
    console.log('selectByPkGfs by id not existed', gn)

    //insertGfs, 一次插入多筆
    let gm = await wo.insertGfs([
        { id: 'id-a', u8a: genU8a(10) },
        { id: 'id-b', u8a: genU8a(20) },
    ])
    console.log('insertGfs multi', gm)

    //delGfs
    let gd = await wo.delGfs({ id: 'id-file' })
    console.log('delGfs', gd)

    //delGfs by id not existed
    let gdn = await wo.delGfs({ id: 'id-not-existed' })
    console.log('delGfs by id not existed', gdn)

    //delAllGfs
    let gda = await wo.delAllGfs()
    console.log('delAllGfs', gda)

}
test()
// change delAllGfs
// delAllGfs then { n: 0, nDeleted: 0, ok: 1 }
// change insertGfs
// insertGfs { n: 1, nInserted: 1, ok: 1 }
// change insertGfs
// insertGfs existed id { n: 1, nInserted: 0, ok: 1 }
// selectByPkGfs id id-file
// selectByPkGfs u8a.length 1000
// selectByPkGfs u8a[0..3] 0 1 2 3
// selectByPkGfs by id not existed null
// change insertGfs
// insertGfs multi { n: 2, nInserted: 2, ok: 1 }
// change delGfs
// delGfs [ { n: 1, nDeleted: 1, ok: 1 } ]
// change delGfs
// delGfs by id not existed [ { n: 0, nDeleted: 0, ok: 1 } ]
// change delAllGfs
// delAllGfs { n: 2, nDeleted: 2, ok: 1 }