w-orm-mongodb 1.1.37 → 1.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,288 +1,471 @@
1
- # w-orm-mongodb
2
- An operator for mongodb in nodejs.
3
-
4
- ![language](https://img.shields.io/badge/language-JavaScript-orange.svg)
5
- [![npm version](http://img.shields.io/npm/v/w-orm-mongodb.svg?style=flat)](https://npmjs.org/package/w-orm-mongodb)
6
- [![license](https://img.shields.io/npm/l/w-orm-mongodb.svg?style=flat)](https://npmjs.org/package/w-orm-mongodb)
7
- [![npm download](https://img.shields.io/npm/dt/w-orm-mongodb.svg)](https://npmjs.org/package/w-orm-mongodb)
8
- [![npm download](https://img.shields.io/npm/dm/w-orm-mongodb.svg)](https://npmjs.org/package/w-orm-mongodb)
9
- [![jsdelivr download](https://img.shields.io/jsdelivr/npm/hm/w-orm-mongodb.svg)](https://www.jsdelivr.com/package/npm/w-orm-mongodb)
10
-
11
- ## Documentation
12
- To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-orm-mongodb/WOrm.html).
13
-
14
- ## Installation
15
-
16
- ### Using npm(ES6 module):
17
- ```alias
18
- npm i w-orm-mongodb
19
- ```
20
-
21
- #### Example for collection
22
- > **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-basic.mjs)]
23
- ```alias
24
- import WOrm from './src/WOrmMongodb.mjs'
25
- //import WOrm from './dist/w-orm-mongodb.umd.js'
26
-
27
- let opt = {
28
- url: 'mongodb://username:password@127.0.0.1:27017',
29
- db: 'worm',
30
- cl: 'users',
31
- }
32
-
33
- let rs = [
34
- {
35
- id: 'id-peter',
36
- name: 'peter',
37
- value: 123,
38
- },
39
- {
40
- id: 'id-rosemary',
41
- name: 'rosemary',
42
- value: 123.456,
43
- },
44
- {
45
- id: '',
46
- name: 'kettle',
47
- value: 456,
48
- },
49
- ]
50
-
51
- let rsm = [
52
- {
53
- id: 'id-peter',
54
- name: 'peter(modify)'
55
- },
56
- {
57
- id: 'id-rosemary',
58
- name: 'rosemary(modify)'
59
- },
60
- {
61
- id: '',
62
- name: 'kettle(modify)'
63
- },
64
- ]
65
-
66
- async function test() {
67
-
68
- //wo
69
- let wo = WOrm(opt)
70
-
71
- //on
72
- wo.on('change', function(mode, data, res) {
73
- console.log('change', mode)
74
- })
75
-
76
- //delAll
77
- await wo.delAll()
78
- .then(function(msg) {
79
- console.log('delAll then', msg)
80
- })
81
- .catch(function(msg) {
82
- console.log('delAll catch', msg)
83
- })
84
-
85
- //insert
86
- await wo.insert(rs)
87
- .then(function(msg) {
88
- console.log('insert then', msg)
89
- })
90
- .catch(function(msg) {
91
- console.log('insert catch', msg)
92
- })
93
-
94
- //save
95
- await wo.save(rsm, { autoInsert: false })
96
- .then(function(msg) {
97
- console.log('save then', msg)
98
- })
99
- .catch(function(msg) {
100
- console.log('save catch', msg)
101
- })
102
-
103
- //select all
104
- let ss = await wo.select()
105
- console.log('select all', ss)
106
-
107
- //select
108
- let so = await wo.select({ id: 'id-rosemary' })
109
- console.log('select', so)
110
-
111
- //select by $and, $gt, $lt
112
- let spa = await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
113
- console.log('select by $and, $gt, $lt', spa)
114
-
115
- //select by $or, $gte, $lte
116
- let spb = await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
117
- console.log('select by $or, $gte, $lte', spb)
118
-
119
- //select by $or, $and, $ne, $in, $nin
120
- 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 } }] }] })
121
- console.log('select by $or, $and, $ne, $in, $nin', spc)
122
-
123
- //select by regex
124
- let sr = await wo.select({ name: { $regex: 'PeT', $options: '$i' } })
125
- console.log('selectReg', sr)
126
-
127
- //del
128
- let d = ss.filter(function(v) {
129
- return v.name === 'kettle'
130
- })
131
- await wo.del(d)
132
- .then(function(msg) {
133
- console.log('del then', msg)
134
- })
135
- .catch(function(msg) {
136
- console.log('del catch', msg)
137
- })
138
-
139
- }
140
- test()
141
- // change delAll
142
- // delAll then { n: 2, nDeleted: 2, ok: 1 }
143
- // change insert
144
- // insert then { n: 3, nInserted: 3, ok: 1 }
145
- // change save
146
- // save then [
147
- // { n: 1, nModified: 1, ok: 1 },
148
- // { n: 1, nModified: 1, ok: 1 },
149
- // { n: 0, nModified: 0, ok: 1 }
150
- // ]
151
- // select all [
152
- // { id: 'id-peter', name: 'peter(modify)', value: 123 },
153
- // { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
154
- // {
155
- // id: {random id},
156
- // name: 'kettle',
157
- // value: 456
158
- // }
159
- // ]
160
- // select [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
161
- // select by $and, $gt, $lt [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
162
- // select by $or, $gte, $lte [
163
- // {
164
- // id: {random id},
165
- // name: 'kettle',
166
- // value: 456
167
- // }
168
- // ]
169
- // select by $or, $and, $ne, $in, $nin [
170
- // {
171
- // id: 'id-rosemary',
172
- // name: 'rosemary(modify)',
173
- // value: 123.456
174
- // },
175
- // {
176
- // id: {random id},
177
- // name: 'kettle',
178
- // value: 456
179
- // }
180
- // ]
181
- // selectReg [ { id: 'id-peter', name: 'peter(modify)', value: 123 } ]
182
- // change del
183
- // del then [ { n: 1, nDeleted: 1, ok: 1 } ]
184
- ```
185
-
186
- #### Example for GridFS
187
- > **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-gfs.mjs)]
188
- ```alias
189
- import path from 'path'
190
- import fs from 'fs'
191
- import WOrm from './src/WOrmMongodb.mjs'
192
- //import WOrm from './dist/w-orm-mongodb.umd.js'
193
-
194
- let opt = {
195
- url: 'mongodb://username:password@127.0.0.1:27017',
196
- db: 'worm',
197
- cl: 'usersGfs',
198
- }
199
-
200
- async function test() {
201
-
202
- //wo
203
- let wo = WOrm(opt)
204
-
205
- //on
206
- wo.on('change', function(mode, data, res) {
207
- console.log('change', mode)
208
- })
209
-
210
- //fn_in, fn_out
211
- let fn_in = path.resolve('../', './_data', 'data(in).dat')
212
- let fn_out = path.resolve('../', './_data', 'data(out).dat')
213
- // console.log('fn_in', fn_in)
214
- // console.log('fn_out', fn_out)
215
-
216
- //unlinkSync
217
- try {
218
- fs.unlinkSync(fn_out)
219
- }
220
- catch (err) {}
221
-
222
- //u8a
223
- let b = await fs.readFileSync(fn_in)
224
- let u8a = new Uint8Array(b)
225
- // let u8a = new Uint8Array([66, 97, 115]) //Uint8Array data from nodejs or browser
226
- console.log('u8a', u8a)
227
-
228
- //delAllGfs
229
- await wo.delAllGfs()
230
- .then(function(msg) {
231
- console.log('delAllGfs then', msg)
232
- })
233
- .catch(function(msg) {
234
- console.log('delAllGfs catch', msg)
235
- })
236
-
237
- //insertGfs
238
- let gi = await wo.insertGfs(u8a)
239
- console.log('insertGfs', gi)
240
-
241
- //selectGfs
242
- let gs = await wo.selectGfs(gi.id)
243
- console.log('selectGfs', gs)
244
- console.log('gs[0]', gs[0], gs[0] === 0)
245
- console.log('gs[1]', gs[1], gs[1] === 0)
246
- console.log('gs[2]', gs[2], gs[2] === 0)
247
- console.log('gs[3]', gs[3], gs[3] === 24)
248
- console.log('gs[4]', gs[4], gs[4] === 102)
249
- console.log('gs.length', gs.length, gs.length === 47381362)
250
- fs.writeFileSync(fn_out, gs)
251
-
252
- //delGfs
253
- let gd = await wo.delGfs(gi.id)
254
- console.log('delGfs', gd)
255
-
256
- }
257
- test()
258
- // u8a Uint8Array(47381362) [
259
- // 0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50,
260
- // 0, 0, 0, 0, 105, 115, 111, 109, 109, 112, 52, 50,
261
- // 0, 2, 14, 73, 109, 111, 111, 118, 0, 0, 0, 108,
262
- // 109, 118, 104, 100, 0, 0, 0, 0, 214, 15, 24, 167,
263
- // 214, 15, 24, 167, 0, 1, 95, 144, 1, 106, 95, 88,
264
- // 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
265
- // 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
266
- // 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
267
- // 0, 0, 0, 0,
268
- // ... 47381262 more items
269
- // ]
270
- // change delAllGfs
271
- // delAllGfs then { n: 0, ok: 1 }
272
- // change insertGfs
273
- // insertGfs { n: 1, ok: 1, id: {random id} }
274
- // selectGfs Uint8Array(47381362) [
275
- // 0, 0, 0, 24, 102, 116, 121, 112, 109, 112, 52, 50,
276
- // 0, 0, 0, 0, 105, 115, 111, 109, 109, 112, 52, 50,
277
- // 0, 2, 14, 73, 109, 111, 111, 118, 0, 0, 0, 108,
278
- // 109, 118, 104, 100, 0, 0, 0, 0, 214, 15, 24, 167,
279
- // 214, 15, 24, 167, 0, 1, 95, 144, 1, 106, 95, 88,
280
- // 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
281
- // 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
282
- // 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
283
- // 0, 0, 0, 0,
284
- // ... 47381262 more items
285
- // ]
286
- // change delGfs
287
- // delGfs { n: 1, nDeleted: 1, ok: 1 }
288
- ```
1
+ # w-orm-mongodb
2
+ An operator for mongodb in nodejs.
3
+
4
+ ![language](https://img.shields.io/badge/language-JavaScript-orange.svg)
5
+ [![npm version](http://img.shields.io/npm/v/w-orm-mongodb.svg?style=flat)](https://npmjs.org/package/w-orm-mongodb)
6
+ [![license](https://img.shields.io/npm/l/w-orm-mongodb.svg?style=flat)](https://npmjs.org/package/w-orm-mongodb)
7
+ [![npm download](https://img.shields.io/npm/dt/w-orm-mongodb.svg)](https://npmjs.org/package/w-orm-mongodb)
8
+ [![npm download](https://img.shields.io/npm/dm/w-orm-mongodb.svg)](https://npmjs.org/package/w-orm-mongodb)
9
+ [![jsdelivr download](https://img.shields.io/jsdelivr/npm/hm/w-orm-mongodb.svg)](https://www.jsdelivr.com/package/npm/w-orm-mongodb)
10
+
11
+ ## Documentation
12
+ To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-orm-mongodb/WOrmMongodb.html).
13
+
14
+ ## Installation
15
+
16
+ ### Using npm(ES6 module):
17
+ ```alias
18
+ npm i w-orm-mongodb
19
+ ```
20
+
21
+ #### Example for collection
22
+ > **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-basic.mjs)]
23
+ ```alias
24
+ import WOrm from './src/WOrmMongodb.mjs'
25
+ //import WOrm from './dist/w-orm-mongodb.umd.js'
26
+
27
+ let opt = {
28
+ url: 'mongodb://username:password@127.0.0.1:27017',
29
+ db: 'worm',
30
+ cl: 'users',
31
+ }
32
+
33
+ let rs = [
34
+ {
35
+ id: 'id-peter',
36
+ name: 'peter',
37
+ value: 123,
38
+ },
39
+ {
40
+ id: 'id-rosemary',
41
+ name: 'rosemary',
42
+ value: 123.456,
43
+ },
44
+ {
45
+ id: '',
46
+ name: 'kettle',
47
+ value: 456,
48
+ },
49
+ ]
50
+
51
+ let rsm = [
52
+ {
53
+ id: 'id-peter',
54
+ name: 'peter(modify)'
55
+ },
56
+ {
57
+ id: 'id-rosemary',
58
+ name: 'rosemary(modify)'
59
+ },
60
+ {
61
+ id: '',
62
+ name: 'kettle(modify)'
63
+ },
64
+ ]
65
+
66
+ async function test() {
67
+
68
+ //wo
69
+ let wo = WOrm(opt)
70
+
71
+ //on
72
+ wo.on('change', function(mode, data, res) {
73
+ console.log('change', mode)
74
+ })
75
+
76
+ //delAll
77
+ await wo.delAll()
78
+ .then(function(msg) {
79
+ console.log('delAll then', msg)
80
+ })
81
+ .catch(function(msg) {
82
+ console.log('delAll catch', msg)
83
+ })
84
+
85
+ //insert
86
+ await wo.insert(rs)
87
+ .then(function(msg) {
88
+ console.log('insert then', msg)
89
+ })
90
+ .catch(function(msg) {
91
+ console.log('insert catch', msg)
92
+ })
93
+
94
+ //save
95
+ await wo.save(rsm, { autoInsert: false })
96
+ .then(function(msg) {
97
+ console.log('save then', msg)
98
+ })
99
+ .catch(function(msg) {
100
+ console.log('save catch', msg)
101
+ })
102
+
103
+ //select all
104
+ let ss = await wo.select()
105
+ console.log('select all', ss)
106
+
107
+ //select
108
+ let so = await wo.select({ id: 'id-rosemary' })
109
+ console.log('select', so)
110
+
111
+ //select by $and, $gt, $lt
112
+ let spa = await wo.select({ '$and': [{ value: { '$gt': 123 } }, { value: { '$lt': 200 } }] })
113
+ console.log('select by $and, $gt, $lt', spa)
114
+
115
+ //select by $or, $gte, $lte
116
+ let spb = await wo.select({ '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 200 } }] })
117
+ console.log('select by $or, $gte, $lte', spb)
118
+
119
+ //select by $or, $and, $ne, $in, $nin
120
+ 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 } }] }] })
121
+ console.log('select by $or, $and, $ne, $in, $nin', spc)
122
+
123
+ //select by regex, $options之合法flag僅有i、m、x、s
124
+ let sr = await wo.select({ name: { $regex: 'PeT', $options: 'i' } })
125
+ console.log('selectReg', sr)
126
+
127
+ //selectById, 由id直接查找單筆, 不需如select提取全部符合數據再處理
128
+ let sbi = await wo.selectById('id-rosemary')
129
+ console.log('selectById', sbi)
130
+
131
+ //selectById by id not existed
132
+ let sbn = await wo.selectById('id-not-existed')
133
+ console.log('selectById by id not existed', sbn)
134
+
135
+ //del
136
+ let d = ss.filter(function(v) {
137
+ return v.name === 'kettle'
138
+ })
139
+ await wo.del(d)
140
+ .then(function(msg) {
141
+ console.log('del then', msg)
142
+ })
143
+ .catch(function(msg) {
144
+ console.log('del catch', msg)
145
+ })
146
+
147
+ }
148
+ test()
149
+ // change delAll
150
+ // delAll then { n: 0, nDeleted: 0, ok: 1 }
151
+ // change insert
152
+ // insert then { n: 3, nInserted: 3, ok: 1 }
153
+ // change save
154
+ // save then [
155
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
156
+ // { n: 1, nInserted: 0, nModified: 1, ok: 1 },
157
+ // { n: 0, nInserted: 0, nModified: 0, ok: 1 }
158
+ // ]
159
+ // select all [
160
+ // { id: 'id-peter', name: 'peter(modify)', value: 123 },
161
+ // { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 },
162
+ // {
163
+ // id: {random id},
164
+ // name: 'kettle',
165
+ // value: 456
166
+ // }
167
+ // ]
168
+ // select [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
169
+ // select by $and, $gt, $lt [ { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 } ]
170
+ // select by $or, $gte, $lte [
171
+ // {
172
+ // id: {random id},
173
+ // name: 'kettle',
174
+ // value: 456
175
+ // }
176
+ // ]
177
+ // select by $or, $and, $ne, $in, $nin [
178
+ // {
179
+ // id: 'id-rosemary',
180
+ // name: 'rosemary(modify)',
181
+ // value: 123.456
182
+ // },
183
+ // {
184
+ // id: {random id},
185
+ // name: 'kettle',
186
+ // value: 456
187
+ // }
188
+ // ]
189
+ // selectReg [ { id: 'id-peter', name: 'peter(modify)', value: 123 } ]
190
+ // selectById { id: 'id-rosemary', name: 'rosemary(modify)', value: 123.456 }
191
+ // selectById by id not existed null
192
+ // change del
193
+ // del then [ { n: 1, nDeleted: 1, ok: 1 } ]
194
+ ```
195
+
196
+ ## Return values
197
+
198
+ 本套件屬`w-orm-*`系列,六個函數`select`、`selectById`、`insert`、`save`、`del`、`delAll`之回傳結構依系列統一規格:
199
+
200
+ ```alias
201
+ select(find) → [ {...}, {...} ] 無符合為 []
202
+ selectById(id) → {...} | null
203
+
204
+ insert(data) → { n, nInserted, ok }
205
+ save(data, option) → [ { n, nInserted, nModified, ok }, ... ]
206
+ del(data) → [ { n, nDeleted, ok }, ... ]
207
+ delAll(find) → { n, nDeleted, ok }
208
+
209
+ 單筆失敗 → { ..., ok: 0, err: '...' } 僅 save、del
210
+ 整批失敗 → Promise.reject(err)
211
+ ```
212
+
213
+ 各計數欄位之語義:
214
+
215
+ | 欄位 | 語義 |
216
+ |---|---|
217
+ | `n` | `insert`為輸入筆數;`save`與`del`為id命中筆數(`0`或`1`);`delAll`為實際刪除筆數 |
218
+ | `nInserted` | 實際插入筆數 |
219
+ | `nModified` | 實際更新筆數。合併後內容與現值相同而未寫入者為`0` |
220
+ | `nDeleted` | 實際刪除筆數 |
221
+ | `ok` | `1`成功、`0`該筆失敗 |
222
+ | `err` | 失敗訊息,僅於`ok`為`0`時出現 |
223
+
224
+ 判讀準則:
225
+
226
+ | 要判斷什麼 | 看什麼 |
227
+ |---|---|
228
+ | 這批有幾筆是新資料 | `insert`之`nInserted` |
229
+ | 這筆是不是新資料 | `save`之`nInserted === 1` |
230
+ | 這筆內容有沒有實際寫入 | `save`之`nModified === 1` |
231
+ | 這筆有沒有真的被刪 | `nDeleted` |
232
+ | 整批有沒有失敗 | Promise是否`reject` |
233
+ | 個別筆有沒有失敗 | 逐筆之`ok === 0`,訊息取`err` |
234
+
235
+ `save`之「內容相同」判定基準為**將待儲存物件合併進現值之後,結果與現值相同**,由MongoDB於伺服器端逐欄位比對。故僅給部份欄位且該些欄位值皆與現值相同時,`nModified`亦為`0`。
236
+
237
+ `del`對未帶有效`id`者不送查詢條件,直接回`ok: 0`並附`err`,以免`undefined`經序列化為`null`而誤刪`id`為`null`之數據。
238
+
239
+ ## Concurrency
240
+
241
+ `insert`之「已存在則跳過」與`save`之「不遺失更新」,由MongoDB於單一文件操作內原子完成,**不須開啟transaction**(transaction另須replica set,standalone不支援)。
242
+
243
+ | 適用範圍 | 是否保證 |
244
+ |---|---|
245
+ | 單一行程內併發 | |
246
+ | 跨行程併發 | |
247
+
248
+ 原子性全由MongoDB伺服器端提供,本套件每次操作各自建立連線、不持有跨呼叫狀態,故兩種範圍之保證相同。
249
+
250
+ 實測依據(Windows 11 / Node.js 24.19.0 / mongodb driver 7.5.0 / MongoDB 8 單機):
251
+
252
+ - 單一行程內對同一`id`併發`insert` 10次,`nInserted`總和為`1`,資料表僅`1`筆。
253
+ - 單一行程內對同一全新`id`併發`save` 5次,`nInserted === 1`者恰`1`筆,資料表僅`1`筆,5次所給欄位全數保留。
254
+ - 跨行程(2個獨立node行程)各自對相同20個`id`執行`insert`,`nInserted`總和為`20`,資料表恰`20`筆。
255
+ - 跨行程(2個獨立node行程)各自對同一既有`id`寫入20個不同欄位,40個欄位全數保留且原欄位未遺失,資料表恰`1`筆。
256
+
257
+ GridFS亦同(`test/api-gfs.test.mjs`):
258
+
259
+ - 對同一`id`併發`insertGfs` 10次,`nInserted`總和為`1`,僅新增1筆files且**未殘留孤兒chunks**。
260
+
261
+ 以上皆為測試案例,可以`npm test`複現。
262
+
263
+ `delAll`之`nDeleted`於併發下反映該次`deleteMany`實際刪除筆數,不保證等於呼叫當下符合條件之筆數。
264
+
265
+ ## Upgrading
266
+
267
+ 本套件會於`id`欄位建立唯一索引,此為`insert`之「已存在則跳過」與`save`之「不遺失更新」所必需,無法關閉。**若既有資料表內已存在重複`id`,建立索引會失敗,`insert`與`save`會直接`reject`。** 升級前請先清除重複數據:
268
+
269
+ ```alias
270
+ // 找出重複id
271
+ db.users.aggregate([
272
+ { $group: { _id: '$id', n: { $sum: 1 } } },
273
+ { $match: { n: { $gt: 1 } } },
274
+ ])
275
+ ```
276
+
277
+ 清除重複數據後即可正常使用。索引僅於首次寫入時建立一次,已存在同樣索引時MongoDB不會重建亦不報錯。
278
+
279
+ GridFS同理,`insertGfs`會於`<cl>.files`之`filename`欄位建立唯一索引:
280
+
281
+ ```alias
282
+ // 找出重複id
283
+ db['usersGfs.files'].aggregate([
284
+ { $group: { _id: '$filename', n: { $sum: 1 } } },
285
+ { $match: { n: { $gt: 1 } } },
286
+ ])
287
+ ```
288
+
289
+ `selectByIdGfs`、`delGfs`、`delAllGfs`皆不建立索引,故既有數據縱使尚存重複`id`亦可正常查詢與清除,`delGfs`會將同一`id`之多筆一併刪除並如實回報`nDeleted`。
290
+
291
+ #### Example for unique id and concurrency
292
+ > **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-unique.mjs)]
293
+
294
+ ```alias
295
+ import WOrm from './src/WOrmMongodb.mjs'
296
+ //import WOrm from './dist/w-orm-mongodb.umd.js'
297
+
298
+ let opt = {
299
+ url: 'mongodb://username:password@127.0.0.1:27017',
300
+ db: 'worm',
301
+ cl: 'usersUnique',
302
+ }
303
+
304
+ async function test() {
305
+
306
+ //wo
307
+ let wo = WOrm(opt)
308
+
309
+ //delAll
310
+ await wo.delAll()
311
+
312
+ //insert, 同批含重複id時僅首筆成功
313
+ let ri = await wo.insert([
314
+ { id: 'id-dup', name: 'dup-1' },
315
+ { id: 'id-dup', name: 'dup-2' },
316
+ { id: 'id-uniq', name: 'uniq' },
317
+ ])
318
+ console.log('insert with duplicated id', ri)
319
+ console.log('selectById(id-dup)', await wo.selectById('id-dup'))
320
+
321
+ //insert, 對已存在id再插入則跳過而不覆寫
322
+ let re = await wo.insert({ id: 'id-dup', name: 'dup-3' })
323
+ console.log('insert existed id', re)
324
+ console.log('selectById(id-dup)', await wo.selectById('id-dup'))
325
+
326
+ //insert, 併發對同一id插入10次, nInserted總和為1
327
+ let rc = await Promise.all(Array.from({ length: 10 }, (v, k) => {
328
+ return wo.insert({ id: 'id-race', k })
329
+ }))
330
+ console.log('sum of nInserted by 10 concurrent insert', rc.reduce((sum, v) => sum + v.nInserted, 0))
331
+ console.log('records of id-race', (await wo.select({ id: 'id-race' })).length)
332
+
333
+ //save, 併發對同一全新id儲存不同欄位, 僅一次為插入, 各欄位皆保留
334
+ let rs = await Promise.all(Array.from({ length: 5 }, (v, k) => {
335
+ return wo.save({ id: 'id-new', [`f${k}`]: k })
336
+ }))
337
+ console.log('count of nInserted===1 by 5 concurrent save', rs.filter((v) => v[0].nInserted === 1).length)
338
+ console.log('records of id-new', (await wo.select({ id: 'id-new' })).length)
339
+ console.log('selectById(id-new)', await wo.selectById('id-new'))
340
+
341
+ }
342
+ test()
343
+ // insert with duplicated id { n: 3, nInserted: 2, ok: 1 }
344
+ // selectById(id-dup) { id: 'id-dup', name: 'dup-1' }
345
+ // insert existed id { n: 1, nInserted: 0, ok: 1 }
346
+ // selectById(id-dup) { id: 'id-dup', name: 'dup-1' }
347
+ // sum of nInserted by 10 concurrent insert 1
348
+ // records of id-race 1
349
+ // count of nInserted===1 by 5 concurrent save 1
350
+ // records of id-new 1
351
+ // selectById(id-new) { id: 'id-new', f0: 0, f1: 1, f2: 2, f4: 4, f3: 3 }
352
+ // 註: 併發儲存之各欄位皆會保留, 惟欄位順序取決於各次儲存之完成順序, 故每次執行不盡相同
353
+ ```
354
+
355
+ #### Example for GridFS
356
+ > **Link:** [[dev source code](https://github.com/yuda-lyu/w-orm-mongodb/blob/master/g-gfs.mjs)]
357
+
358
+ GridFS函數之參數與回傳形狀比照一般操作,數據物件為`{ id, u8a }`:
359
+
360
+ | GridFS函數 | 對應一般函數 | 回傳 |
361
+ |---|---|---|
362
+ | `selectByIdGfs(id)` | `selectById` | `{ id, u8a }` 或 `null` |
363
+ | `insertGfs(data)` | `insert` | `{ n, nInserted, ok }` |
364
+ | `delGfs(data)` | `del` | `[ { n, nDeleted, ok }, ... ]` |
365
+ | `delAllGfs(find)` | `delAll` | `{ n, nDeleted, ok }` |
366
+
367
+ `insertGfs`同樣具備「已存在則跳過」語義,以`<cl>.files`之`filename`唯一索引達成,升級前提與一般操作相同。GridFS無法於單一原子操作內取代既有內容,故不提供`saveGfs`,更新請以`delGfs`後再`insertGfs`完成。
368
+
369
+ ```alias
370
+ import WOrm from './src/WOrmMongodb.mjs'
371
+ //import WOrm from './dist/w-orm-mongodb.umd.js'
372
+
373
+
374
+ //GridFS函數之參數與回傳形狀比照一般操作:
375
+ //數據物件為{ id, u8a }, insertGfs收物件或陣列, delGfs收物件或陣列並回傳等長陣列
376
+ let opt = {
377
+ url: 'mongodb://username:password@127.0.0.1:27017',
378
+ db: 'worm',
379
+ cl: 'usersGfs',
380
+ }
381
+
382
+ //genU8a, 產生內容可複現之測試數據
383
+ function genU8a(n) {
384
+ let u8a = new Uint8Array(n)
385
+ for (let i = 0; i < n; i++) {
386
+ u8a[i] = i % 256
387
+ }
388
+ return u8a
389
+ }
390
+
391
+ async function test() {
392
+
393
+ //wo
394
+ let wo = WOrm(opt)
395
+
396
+ //on
397
+ wo.on('change', function(mode, data, res) {
398
+ console.log('change', mode)
399
+ })
400
+
401
+ //u8a, 亦可為瀏覽器或nodejs取得之任何Uint8Array
402
+ let u8a = genU8a(1000)
403
+
404
+ //delAllGfs
405
+ await wo.delAllGfs()
406
+ .then(function(msg) {
407
+ console.log('delAllGfs then', msg)
408
+ })
409
+ .catch(function(msg) {
410
+ console.log('delAllGfs catch', msg)
411
+ })
412
+
413
+ //insertGfs
414
+ let gi = await wo.insertGfs({ id: 'id-file', u8a })
415
+ console.log('insertGfs', gi)
416
+
417
+ //insertGfs, 已存在id者跳過且不覆寫
418
+ let gr = await wo.insertGfs({ id: 'id-file', u8a: genU8a(50) })
419
+ console.log('insertGfs existed id', gr)
420
+
421
+ //selectByIdGfs
422
+ let gs = await wo.selectByIdGfs('id-file')
423
+ console.log('selectByIdGfs id', gs.id)
424
+ console.log('selectByIdGfs u8a.length', gs.u8a.length)
425
+ console.log('selectByIdGfs u8a[0..3]', gs.u8a[0], gs.u8a[1], gs.u8a[2], gs.u8a[3])
426
+
427
+ //selectByIdGfs by id not existed
428
+ let gn = await wo.selectByIdGfs('id-not-existed')
429
+ console.log('selectByIdGfs by id not existed', gn)
430
+
431
+ //insertGfs, 一次插入多筆
432
+ let gm = await wo.insertGfs([
433
+ { id: 'id-a', u8a: genU8a(10) },
434
+ { id: 'id-b', u8a: genU8a(20) },
435
+ ])
436
+ console.log('insertGfs multi', gm)
437
+
438
+ //delGfs
439
+ let gd = await wo.delGfs({ id: 'id-file' })
440
+ console.log('delGfs', gd)
441
+
442
+ //delGfs by id not existed
443
+ let gdn = await wo.delGfs({ id: 'id-not-existed' })
444
+ console.log('delGfs by id not existed', gdn)
445
+
446
+ //delAllGfs
447
+ let gda = await wo.delAllGfs()
448
+ console.log('delAllGfs', gda)
449
+
450
+ }
451
+ test()
452
+ // change delAllGfs
453
+ // delAllGfs then { n: 0, nDeleted: 0, ok: 1 }
454
+ // change insertGfs
455
+ // insertGfs { n: 1, nInserted: 1, ok: 1 }
456
+ // change insertGfs
457
+ // insertGfs existed id { n: 1, nInserted: 0, ok: 1 }
458
+ // selectByIdGfs id id-file
459
+ // selectByIdGfs u8a.length 1000
460
+ // selectByIdGfs u8a[0..3] 0 1 2 3
461
+ // selectByIdGfs by id not existed null
462
+ // change insertGfs
463
+ // insertGfs multi { n: 2, nInserted: 2, ok: 1 }
464
+ // change delGfs
465
+ // delGfs [ { n: 1, nDeleted: 1, ok: 1 } ]
466
+ // change delGfs
467
+ // delGfs by id not existed [ { n: 0, nDeleted: 0, ok: 1 } ]
468
+ // change delAllGfs
469
+ // delAllGfs { n: 2, nDeleted: 2, ok: 1 }
470
+
471
+ ```