w-serv-orm 1.1.16 → 1.1.18
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 +110 -0
- package/dist/w-serv-orm.umd.js +2 -2
- package/dist/w-serv-orm.umd.js.map +1 -1
- package/docs/WServOrm.html +11 -4
- package/docs/WServOrm.mjs.html +96 -3
- package/docs/baseline.mjs.html +624 -0
- package/docs/global.html +1786 -0
- package/docs/index.html +2 -2
- package/docs/isOrmFail.mjs.html +102 -0
- package/package.json +1 -1
- package/src/WServOrm.mjs +94 -1
- package/src/baseline.mjs +552 -0
- package/src/genTestData.mjs +6 -0
- package/src/getDb.mjs +1 -1
- package/src/getDbBackupAndRecover.mjs +44 -0
- package/src/getMapOrm.mjs +229 -12
- package/src/getProc.mjs +13 -4
- package/src/isOrmFail.mjs +30 -0
- package/test/api-guard.test.mjs +401 -0
- package/test/api-keepuser.test.mjs +131 -0
- package/test/api-procOrm.test.mjs +142 -0
- package/test/tools/memOrm.mjs +122 -0
- package/test/unit-baseline.test.mjs +766 -0
- package/test/unit-getDb.test.mjs +78 -0
- package/test/unit-guard.test.mjs +952 -0
- package/test/unit-isOrmFail.test.mjs +35 -0
- package/test/unit-keepuser.test.mjs +307 -0
- package/test/unit-mapOrm.test.mjs +213 -0
- package/test/unit-ormsites.test.mjs +61 -0
|
@@ -92,3 +92,145 @@ describe('procOrm接w-orm-lowdb', function() {
|
|
|
92
92
|
})
|
|
93
93
|
|
|
94
94
|
})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
//R-PERITEM-01 規則4(orm-run03、run04): 包一層真ORM(w-orm-lowdb), 令insert於injectInsert為true時第0筆不寫入、其餘筆照常寫入, 並回傳聚合物件而ok為0; 為'array'時回傳逐筆陣列而第0筆ok為0(模擬違約之ORM, w-orm-lowdb實際不會如此); 其餘操作原樣
|
|
98
|
+
let fdIns = './test/_tmp/api-procOrm-insertfail'
|
|
99
|
+
let injectInsert = false
|
|
100
|
+
//R-ORM-RESULT-01(orm-run05): injectOther為'save'、'del'或'delAll'時, 該操作照常執行後回傳聚合物件而ok為0(非契約形狀, 模擬違約之ORM)
|
|
101
|
+
let injectOther = null
|
|
102
|
+
function WOrmInsertFail(...args) {
|
|
103
|
+
let wo = WOrm(...args)
|
|
104
|
+
for (let m of ['save', 'del', 'delAll']) {
|
|
105
|
+
let orig = wo[m]
|
|
106
|
+
wo[m] = async (...a) => {
|
|
107
|
+
let r = await orig(...a)
|
|
108
|
+
return injectOther === m ? { n: 1, ok: 0, err: 'E-agg' } : r
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let insertOrig = wo.insert
|
|
112
|
+
wo.insert = async (data) => {
|
|
113
|
+
if (!injectInsert) {
|
|
114
|
+
return insertOrig(data)
|
|
115
|
+
}
|
|
116
|
+
let rows = Array.isArray(data) ? data : [data]
|
|
117
|
+
let rest = rows.slice(1)
|
|
118
|
+
if (rest.length > 0) {
|
|
119
|
+
await insertOrig(rest)
|
|
120
|
+
}
|
|
121
|
+
if (injectInsert === 'array') {
|
|
122
|
+
return rows.map((v, k) => (k === 0 ? { n: 1, nInserted: 0, ok: 0, err: 'E-ins' } : { n: 1, nInserted: 1, ok: 1 }))
|
|
123
|
+
}
|
|
124
|
+
return { n: rows.length, nInserted: rest.length, ok: 0, err: 'E-ins' }
|
|
125
|
+
}
|
|
126
|
+
return wo
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
describe('procOrm接insert回報ok為0之w-orm-lowdb', function() {
|
|
131
|
+
|
|
132
|
+
let so = null
|
|
133
|
+
let proc = (mode, input) => so.procOrm('u1', 'tests', mode, input)
|
|
134
|
+
let getIds = async () => (await so.woItems.tests.select({})).map((v) => v.id).sort()
|
|
135
|
+
|
|
136
|
+
before(function() {
|
|
137
|
+
fs.rmSync(fdIns, { recursive: true, force: true })
|
|
138
|
+
fs.mkdirSync(fdIns, { recursive: true })
|
|
139
|
+
so = WServOrm(ds, WOrmInsertFail, `${fdIns}/db.json`, 'worm')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
beforeEach(async function() {
|
|
143
|
+
injectInsert = false
|
|
144
|
+
injectOther = null
|
|
145
|
+
await so.woItems.tests.delAll({})
|
|
146
|
+
await so.woItems.tests.insert([{ id: 'x1' }, { id: 'x2' }, { id: 'x3' }])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
after(async function() {
|
|
150
|
+
injectInsert = false
|
|
151
|
+
injectOther = null
|
|
152
|
+
await so.closeAll()
|
|
153
|
+
fs.rmSync(fdIns, { recursive: true, force: true })
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('F01 insert之ORM回報ok為0時reject而不回成功', async function() {
|
|
157
|
+
//規則4: 聚合結果ok為0視為失敗; 已寫入之筆無從回復(ORM違約, 第1筆已寫入、第0筆未寫入), 但呼叫端得知失敗
|
|
158
|
+
injectInsert = true
|
|
159
|
+
let err = await getReject(proc('insert', [{ id: 'n1' }, { id: 'n2' }]))
|
|
160
|
+
assert.strict.equal(err, 'failed to insert data')
|
|
161
|
+
assert.strict.deepEqual(await getIds(), ['n2', 'x1', 'x2', 'x3'])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('F02 mix之add之ORM回報ok為0時reject且後續del不執行', async function() {
|
|
165
|
+
//規則4: add為首步, 聚合結果ok為0即立即reject, del步驟不執行(x3仍在庫內)
|
|
166
|
+
injectInsert = true
|
|
167
|
+
let err = await getReject(proc('mix', { add: [{ id: 'n1' }, { id: 'n2' }], del: [{ id: 'x3' }] }))
|
|
168
|
+
assert.strict.equal(err, 'failed to insert data')
|
|
169
|
+
assert.strict.deepEqual(await getIds(), ['n2', 'x1', 'x2', 'x3'])
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('F04 insert之ORM回報陣列形逐筆ok為0時reject而不回成功', async function() {
|
|
173
|
+
//規則4(orm-run04): 陣列形任一筆ok為0同聚合形處置; 其餘筆已寫入(n2)無從回復
|
|
174
|
+
injectInsert = 'array'
|
|
175
|
+
let err = await getReject(proc('insert', [{ id: 'n1' }, { id: 'n2' }]))
|
|
176
|
+
assert.strict.equal(err, 'failed to insert data')
|
|
177
|
+
assert.strict.deepEqual(await getIds(), ['n2', 'x1', 'x2', 'x3'])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('F05 mix之add之ORM回報陣列形逐筆ok為0時reject且後續del不執行', async function() {
|
|
181
|
+
//規則4(orm-run04): add為首步, 立即reject, del步驟不執行(x3仍在庫內)
|
|
182
|
+
injectInsert = 'array'
|
|
183
|
+
let err = await getReject(proc('mix', { add: [{ id: 'n1' }, { id: 'n2' }], del: [{ id: 'x3' }] }))
|
|
184
|
+
assert.strict.equal(err, 'failed to insert data')
|
|
185
|
+
assert.strict.deepEqual(await getIds(), ['n2', 'x1', 'x2', 'x3'])
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('F06 delAll之ORM回報聚合物件ok為0時reject而不回成功', async function() {
|
|
189
|
+
//R-ORM-RESULT-01(orm-run05): ORM已執行刪除而回報ok為0(違約), 呼叫端得知失敗
|
|
190
|
+
injectOther = 'delAll'
|
|
191
|
+
let err = await getReject(proc('delAll', { id: 'x1' }))
|
|
192
|
+
assert.strict.equal(err, 'failed to delete data')
|
|
193
|
+
injectOther = null
|
|
194
|
+
assert.strict.deepEqual(await getIds(), ['x2', 'x3'])
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('F07 save與del之ORM回報聚合物件ok為0時reject而不回成功', async function() {
|
|
198
|
+
//R-ORM-RESULT-01(orm-run05): 契約為逐筆陣列, 聚合形(非契約形狀)無從逐筆列出, 固定字串reject
|
|
199
|
+
injectOther = 'save'
|
|
200
|
+
let err = await getReject(proc('save', [{ id: 'x1', isActive: 'n' }]))
|
|
201
|
+
assert.strict.equal(err, 'failed to save data')
|
|
202
|
+
injectOther = 'del'
|
|
203
|
+
err = await getReject(proc('del', [{ id: 'x2' }]))
|
|
204
|
+
assert.strict.equal(err, 'failed to delete data')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('F08 mix之del回報聚合物件ok為0時立即reject且diff不執行', async function() {
|
|
208
|
+
//R-ORM-RESULT-01(orm-run05): del為第2步, 聚合形失敗即整批性, diff步驟不執行(x1之isActive仍為預設而未被改為n)
|
|
209
|
+
injectOther = 'del'
|
|
210
|
+
let err = await getReject(proc('mix', { del: [{ id: 'x3' }], diff: [{ id: 'x1', isActive: 'n' }] }))
|
|
211
|
+
assert.strict.equal(err, 'failed to delete data')
|
|
212
|
+
injectOther = null
|
|
213
|
+
let rs = await so.woItems.tests.select({ id: 'x1' })
|
|
214
|
+
assert.ok(rs.length === 1 && rs[0].isActive !== 'n', JSON.stringify(rs))
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('F09 契約內之ORM(delAll恆ok為1)行為不變', async function() {
|
|
218
|
+
//R-ORM-RESULT-01: 未違約時delAll、save、del照常resolve
|
|
219
|
+
let r = await proc('delAll', { id: 'x1' })
|
|
220
|
+
assert.strict.deepEqual(r, { n: 1, nDeleted: 1, ok: 1 })
|
|
221
|
+
let r2 = await proc('save', [{ id: 'x2', isActive: 'n' }])
|
|
222
|
+
assert.strict.deepEqual(r2, [{ id: 'x2' }])
|
|
223
|
+
let r3 = await proc('del', [{ id: 'x3' }])
|
|
224
|
+
assert.strict.deepEqual(r3, [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('F03 契約內之ORM(insert恆ok為1)行為不變', async function() {
|
|
228
|
+
//規則4: 未違約時insert與mix照常resolve並寫入; 略過既有主鍵(n為0)亦照常
|
|
229
|
+
let r = await proc('insert', [{ id: 'n1' }, { id: 'x1' }])
|
|
230
|
+
assert.strict.deepEqual(r, [{ id: 'n1' }, { id: 'x1' }])
|
|
231
|
+
let r2 = await proc('mix', { add: [{ id: 'n2' }], del: [{ id: 'x3' }] })
|
|
232
|
+
assert.strict.deepEqual(r2, [{ id: 'n2' }, { id: 'x3' }])
|
|
233
|
+
assert.strict.deepEqual(await getIds(), ['n1', 'n2', 'x1', 'x2'])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//記憶體ORM(供 unit-guard 使用): 有狀態之替身, 回傳形狀依w-orm系列規格(save、del回與輸入等長之逐筆陣列, insert與delAll回聚合物件)
|
|
2
|
+
//與 ormStub.mjs 之 createStub 不同: 本工具真的存取資料(Map), 並支援select之{ id: { $in } }、save之autoInsert選項、各操作之人為延遲與逐項覆寫(plan),
|
|
3
|
+
//用以驗證只改不增、刪除未命中與表鎖原子性; 每次createMemOrm各自持有tables、calls與plan, 不共用模組層狀態
|
|
4
|
+
function createMemOrm() {
|
|
5
|
+
let tables = {} //cl -> Map(id -> row)
|
|
6
|
+
let calls = [] //{ cl, fn, args }
|
|
7
|
+
let plan = {
|
|
8
|
+
delay: 0, //每個操作開始前之延遲毫秒
|
|
9
|
+
select: null, //function(args, state)覆寫回傳或拋錯
|
|
10
|
+
save: null,
|
|
11
|
+
del: null,
|
|
12
|
+
}
|
|
13
|
+
let clone = (v) => JSON.parse(JSON.stringify(v))
|
|
14
|
+
let wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
15
|
+
|
|
16
|
+
function WOrmMem({ cl }) {
|
|
17
|
+
if (!tables[cl]) {
|
|
18
|
+
tables[cl] = new Map()
|
|
19
|
+
}
|
|
20
|
+
let tb = tables[cl]
|
|
21
|
+
let ev = {}
|
|
22
|
+
|
|
23
|
+
ev.select = async (find) => {
|
|
24
|
+
calls.push({ cl, fn: 'select', args: [find] })
|
|
25
|
+
if (plan.delay > 0) {
|
|
26
|
+
await wait(plan.delay)
|
|
27
|
+
}
|
|
28
|
+
if (plan.select) {
|
|
29
|
+
return plan.select([find], tb)
|
|
30
|
+
}
|
|
31
|
+
let rows = [...tb.values()].map(clone)
|
|
32
|
+
if (find && find.id && Array.isArray(find.id.$in)) {
|
|
33
|
+
//同各w-orm-*共用之條件檢核(wsemi cvOrmFind): $in之元素數上限10000, 超過整批reject
|
|
34
|
+
if (find.id.$in.length > 10000) {
|
|
35
|
+
return Promise.reject(new Error('invalid find: array is too large (> 10000) at [find.id.$in]'))
|
|
36
|
+
}
|
|
37
|
+
let ids = new Set(find.id.$in)
|
|
38
|
+
rows = rows.filter((r) => ids.has(r.id))
|
|
39
|
+
}
|
|
40
|
+
return rows
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ev.insert = async (data) => {
|
|
44
|
+
calls.push({ cl, fn: 'insert', args: [data] })
|
|
45
|
+
if (plan.delay > 0) {
|
|
46
|
+
await wait(plan.delay)
|
|
47
|
+
}
|
|
48
|
+
let n = 0
|
|
49
|
+
for (let v of (Array.isArray(data) ? data : [data])) {
|
|
50
|
+
if (!tb.has(v.id)) {
|
|
51
|
+
tb.set(v.id, clone(v))
|
|
52
|
+
n++
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { n, nInserted: n, ok: 1 }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ev.save = async (data, option) => {
|
|
59
|
+
calls.push({ cl, fn: 'save', args: option === undefined ? [data] : [data, option] })
|
|
60
|
+
if (plan.delay > 0) {
|
|
61
|
+
await wait(plan.delay)
|
|
62
|
+
}
|
|
63
|
+
if (plan.save) {
|
|
64
|
+
return plan.save([data, option], tb)
|
|
65
|
+
}
|
|
66
|
+
let autoInsert = !(option && option.autoInsert === false)
|
|
67
|
+
return (Array.isArray(data) ? data : [data]).map((v) => {
|
|
68
|
+
if (tb.has(v.id)) {
|
|
69
|
+
tb.set(v.id, { ...tb.get(v.id), ...clone(v) })
|
|
70
|
+
return { n: 1, nInserted: 0, nModified: 1, ok: 1 }
|
|
71
|
+
}
|
|
72
|
+
if (autoInsert) {
|
|
73
|
+
tb.set(v.id, clone(v))
|
|
74
|
+
return { n: 1, nInserted: 1, nModified: 0, ok: 1 }
|
|
75
|
+
}
|
|
76
|
+
return { n: 0, nInserted: 0, nModified: 0, ok: 1 }
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ev.del = async (data) => {
|
|
81
|
+
calls.push({ cl, fn: 'del', args: [data] })
|
|
82
|
+
if (plan.delay > 0) {
|
|
83
|
+
await wait(plan.delay)
|
|
84
|
+
}
|
|
85
|
+
if (plan.del) {
|
|
86
|
+
return plan.del([data], tb)
|
|
87
|
+
}
|
|
88
|
+
return (Array.isArray(data) ? data : [data]).map((v) => {
|
|
89
|
+
let hit = tb.delete(v.id)
|
|
90
|
+
return { n: hit ? 1 : 0, nDeleted: hit ? 1 : 0, ok: 1 }
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
ev.delAll = async (find) => {
|
|
95
|
+
calls.push({ cl, fn: 'delAll', args: [find] })
|
|
96
|
+
let n = tb.size
|
|
97
|
+
tb.clear()
|
|
98
|
+
return { n, nDeleted: n, ok: 1 }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return ev
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
WOrmMem,
|
|
106
|
+
tables,
|
|
107
|
+
calls,
|
|
108
|
+
plan,
|
|
109
|
+
rows: (cl) => [...tables[cl].values()].map(clone),
|
|
110
|
+
callsOf: (fn) => calls.filter((c) => c.fn === fn),
|
|
111
|
+
reset: () => {
|
|
112
|
+
calls.length = 0
|
|
113
|
+
plan.delay = 0
|
|
114
|
+
plan.select = null
|
|
115
|
+
plan.save = null
|
|
116
|
+
plan.del = null
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
export { createMemOrm }
|