w-orm-reladb 1.1.7 → 1.1.9
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 +3 -1
- package/dist/w-orm-reladb.umd.js +2 -2
- package/dist/w-orm-reladb.umd.js.map +1 -1
- package/docs/WOrmReladb.html +14 -14
- package/docs/WOrmReladb.mjs.html +61 -24
- package/docs/cvFind.mjs.html +277 -32
- package/docs/global.html +41 -7
- package/docs/importModels.mjs.html +1 -1
- package/docs/index.html +1 -1
- package/package.json +1 -1
- package/src/WOrmReladb.mjs +60 -23
- package/src/cvFind.mjs +276 -31
- package/src/requireModel.js +13 -5
- package/test/api-dist.test.mjs +112 -0
- package/test/api-init.test.mjs +50 -0
- package/test/api-models.test.mjs +23 -0
- package/test/api-suite.mjs +525 -0
- package/test/unit-cvfind.test.mjs +806 -5
- package/toolg/gDistRollup.mjs +2 -0
package/src/requireModel.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const { createRequire } = require('module')
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//載入js格式之model檔並呼叫其定義函數
|
|
5
|
+
//註: 不可直接以require(fn)載入: 發布用dist經rollup之commonjs外掛打包時, 引數非字面值之require會被換成必拋[Could not dynamically require]之樁,
|
|
6
|
+
//故以Node內建module之createRequire依model檔之絕對路徑產生require函數載入, 其解析與模組快取皆同require
|
|
7
|
+
//註: fn須為絕對路徑, 呼叫端(本套件與w-orm-mdb之importModels)皆以path.resolve產生; 被w-orm-mdb等深層引用, 勿改簽章或路徑
|
|
8
|
+
function requireModel(fn, sequelize, DataTypes) {
|
|
9
|
+
let req = createRequire(fn)
|
|
10
|
+
return req(fn)(sequelize, DataTypes)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = requireModel
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import { execFile } from 'child_process'
|
|
5
|
+
import { createRequire } from 'module'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
//rulebook R10: 經package.json之main(dist)使用時, js型model之importModels與各函數須與經src使用時同樣可用
|
|
9
|
+
//修正前dist內src/requireModel.js之require(fn)被rollup之commonjs外掛換成必拋[Could not dynamically require]之樁, 而測試只跑src未曾發現
|
|
10
|
+
//作法: 以toolg/gDistRollup.mjs原檔之設定只換輸出夾, 由目前src建置至已安裝佈局(app/node_modules/w-orm-reladb/dist), 再由app以套件名require
|
|
11
|
+
//僅以sqlite驗證, 因model檔之載入與資料庫種類無關
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
let fd = './test/_tmp/api-dist'
|
|
15
|
+
let fdApp = `${fd}/app`
|
|
16
|
+
let fdPkg = `${fdApp}/node_modules/w-orm-reladb`
|
|
17
|
+
let fdDist = `${fdPkg}/dist`
|
|
18
|
+
let fdModels = `${fd}/models`
|
|
19
|
+
let fpBundle = `${fdDist}/w-orm-reladb.umd.js`
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
//buildDist, 複製gDistRollup.mjs並只把fdTar換成已安裝佈局之dist後, 以子行程於專案根目錄執行(同發布時之cwd, 故babel.config.js與package.json同發布)
|
|
23
|
+
let buildDist = () => {
|
|
24
|
+
let c = fs.readFileSync('./toolg/gDistRollup.mjs', 'utf8')
|
|
25
|
+
let from = `let fdTar = './dist'`
|
|
26
|
+
assert.strictEqual(c.split(from).length - 1, 1, `toolg/gDistRollup.mjs須恰有一處[${from}]供替換輸出夾`)
|
|
27
|
+
c = c.replace(from, `let fdTar = '${fdDist}'`)
|
|
28
|
+
let fpGen = `${fd}/gDistRollup.mjs`
|
|
29
|
+
fs.writeFileSync(fpGen, c, 'utf8')
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
execFile(process.execPath, [fpGen], { maxBuffer: 64 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
32
|
+
if (err) {
|
|
33
|
+
reject(new Error(`build dist failed: ${err.message}\n${stderr}`))
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
resolve(stdout)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
describe('dist (installed layout)', function() {
|
|
43
|
+
|
|
44
|
+
let wo = null
|
|
45
|
+
|
|
46
|
+
before(async function() {
|
|
47
|
+
|
|
48
|
+
fs.rmSync(fd, { recursive: true, force: true })
|
|
49
|
+
fs.mkdirSync(fdDist, { recursive: true })
|
|
50
|
+
|
|
51
|
+
//build
|
|
52
|
+
await buildDist()
|
|
53
|
+
|
|
54
|
+
//已安裝佈局: 套件夾內之package.json(main指向dist), 相依(sequelize等)沿上層node_modules解析
|
|
55
|
+
fs.copyFileSync('./package.json', `${fdPkg}/package.json`)
|
|
56
|
+
|
|
57
|
+
//models, 複製至暫存夾, 不動版控中之models
|
|
58
|
+
fs.mkdirSync(fdModels, { recursive: true })
|
|
59
|
+
fs.copyFileSync('./models/users.js', `${fdModels}/users.js`)
|
|
60
|
+
fs.copyFileSync('./models/users.json', `${fdModels}/users.json`)
|
|
61
|
+
|
|
62
|
+
//require, 由app以套件名解析
|
|
63
|
+
let req = createRequire(path.resolve(fdApp, 'index.js'))
|
|
64
|
+
assert.strictEqual(req.resolve('w-orm-reladb'), path.resolve(fpBundle), '套件名須經package.json之main解析至建出之dist')
|
|
65
|
+
wo = req('w-orm-reladb')
|
|
66
|
+
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
after(function() {
|
|
70
|
+
fs.rmSync(fd, { recursive: true, force: true })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('建出之dist不含rollup動態require之樁', function() {
|
|
74
|
+
//rulebook R10: 建置產物不得含[Could not dynamically require](含被打包之相依)
|
|
75
|
+
let code = fs.readFileSync(fpBundle, 'utf8')
|
|
76
|
+
assert.ok(code.length > 0)
|
|
77
|
+
assert.strictEqual(code.indexOf('Could not dynamically require'), -1)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('js型model: createStorage、insert、select、selectByPk、del皆成功且不發error事件', async function() {
|
|
81
|
+
//rulebook R10: 預設modelType為js, 經main使用時importModels須可載入js型model
|
|
82
|
+
assert.strictEqual(typeof wo, 'function')
|
|
83
|
+
let w = wo({ url: 'sqlite://:', db: 'worm', cl: 'users', fdModels, storage: `${fd}/js.sqlite` })
|
|
84
|
+
let errs = []
|
|
85
|
+
w.on('error', (mode, data, err) => {
|
|
86
|
+
errs.push([mode, err])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
assert.strictEqual(await w.createStorage(), 'created')
|
|
90
|
+
assert.deepStrictEqual(await w.insert({ id: 'a1', name: 'A', value: 1.5 }), { n: 1, nInserted: 1, ok: 1 })
|
|
91
|
+
assert.deepStrictEqual(await w.select({ id: 'a1' }), [{ id: 'a1', name: 'A', value: 1.5 }])
|
|
92
|
+
assert.deepStrictEqual(await w.selectByPk('a1'), { id: 'a1', name: 'A', value: 1.5 })
|
|
93
|
+
assert.deepStrictEqual(await w.del({ id: 'a1' }), [{ n: 1, nDeleted: 1, ok: 1 }])
|
|
94
|
+
assert.deepStrictEqual(await w.select(), [])
|
|
95
|
+
assert.deepStrictEqual(errs, [])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('json型model(對照): createStorage、insert、select皆成功且不發error事件', async function() {
|
|
99
|
+
//rulebook R10: 兩種modelType經main皆須可用; json型不經requireModel, 修正前即可用
|
|
100
|
+
let w = wo({ url: 'sqlite://:', db: 'worm', cl: 'users', fdModels, modelType: 'json', storage: `${fd}/json.sqlite` })
|
|
101
|
+
let errs = []
|
|
102
|
+
w.on('error', (mode, data, err) => {
|
|
103
|
+
errs.push([mode, err])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
assert.strictEqual(await w.createStorage(), 'created')
|
|
107
|
+
assert.deepStrictEqual(await w.insert({ id: 'b1', name: 'B', value: 2 }), { n: 1, nInserted: 1, ok: 1 })
|
|
108
|
+
assert.deepStrictEqual(await w.select({ id: 'b1' }), [{ id: 'b1', name: 'B', value: 2 }])
|
|
109
|
+
assert.deepStrictEqual(errs, [])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import wo from '../src/WOrmReladb.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//rulebook R7: 七函數之內部初始化(initSequelize)在主try內, 其建構期例外與其他整批性錯誤同樣於reject前發一次error事件(規格T10.3)
|
|
6
|
+
//觸發: 本套件不相依mysql2, 以mysql://建構sequelize時即拋[Please install mysql2 package manually]; 若環境另裝mysql2則前提不成立, 斷言訊息會指出
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
describe('initSequelize throws', function() {
|
|
10
|
+
|
|
11
|
+
it('七函數皆reject, 且於reject前各發一次error事件, mode為函數名、data為輸入數據或null、err與reject訊息一致', async function() {
|
|
12
|
+
let w = wo({ url: 'mysql://username:password@localhost:3306', db: 'worm', cl: 'users', fdModels: './models' })
|
|
13
|
+
let settled = false
|
|
14
|
+
let evs = []
|
|
15
|
+
w.on('error', (mode, data, err) => {
|
|
16
|
+
evs.push({ mode, data, err, settled })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
let cases = [
|
|
20
|
+
['select', null, () => w.select({ id: 'k1' })],
|
|
21
|
+
['selectByPk', null, () => w.selectByPk('k1')],
|
|
22
|
+
['insert', { id: 'k1', name: 'A' }, (d) => w.insert(d)],
|
|
23
|
+
['insertBulk', [{ id: 'k2', name: 'B' }], (d) => w.insertBulk(d)],
|
|
24
|
+
['save', [{ id: 'k1', name: 'Z' }], (d) => w.save(d)],
|
|
25
|
+
['del', [{ id: 'k1' }], (d) => w.del(d)],
|
|
26
|
+
['delAll', null, () => w.delAll({ id: 'k1' })],
|
|
27
|
+
]
|
|
28
|
+
for (let [mode, data, fn] of cases) {
|
|
29
|
+
settled = false
|
|
30
|
+
evs = []
|
|
31
|
+
let r = await fn(data)
|
|
32
|
+
.then((v) => ({ RESOLVE: v }), (err) => ({ REJECT: String(err.message || err) }))
|
|
33
|
+
.finally(() => {
|
|
34
|
+
settled = true
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
//整批性錯誤經Promise.reject(規格T4)
|
|
38
|
+
assert.ok(r.REJECT !== undefined, `${mode} 未reject, 實得 ${JSON.stringify(r.RESOLVE)}`)
|
|
39
|
+
assert.ok(r.REJECT.indexOf('Please install mysql2') >= 0, `${mode} 之reject非建構期例外(前提: 本機未裝mysql2): ${r.REJECT}`)
|
|
40
|
+
|
|
41
|
+
//規格T10.3: 整批性錯誤於reject之前發出一次, mode取函數名, err與reject之訊息一致, data為本次輸入數據(無輸入數據者為null)
|
|
42
|
+
assert.strictEqual(evs.length, 1, `${mode} 之error事件數 ${evs.length}`)
|
|
43
|
+
assert.strictEqual(evs[0].mode, mode)
|
|
44
|
+
assert.strictEqual(evs[0].err, r.REJECT)
|
|
45
|
+
assert.deepStrictEqual(evs[0].data, data)
|
|
46
|
+
assert.strictEqual(evs[0].settled, false, `${mode} 之error事件須於reject之前發出`)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
})
|
package/test/api-models.test.mjs
CHANGED
|
@@ -128,6 +128,29 @@ describe('models file', function() {
|
|
|
128
128
|
assert.deepStrictEqual((await wAll.select()).map((v) => v.id).sort(), ['a', 'b'])
|
|
129
129
|
})
|
|
130
130
|
|
|
131
|
+
it('model之defaultScope帶group/having、include或含Op.and之where時delAll拒絕且不刪除(select與destroy對這些選項之處置不同, 或scope之Op.and被cvFind頂層之Op.and覆寫)', async function() {
|
|
132
|
+
//rulebook R1: findAll套用scope之group/having與required include(只回部分列), destroy卻不套用而刪除全部符合where者(run05 sol F03、opus 疑慮(ii), sqlite實測 select=[2] 而 destroy=3);
|
|
133
|
+
//scope.where含Op.and者, sequelize以Object.assign合併而被cvFind輸出之頂層Op.and覆寫, scope條件消失(opus 疑慮(i))
|
|
134
|
+
let cases = [
|
|
135
|
+
['group+having', 'defaultScope: { group: [\'id\'], having: sequelize.literal(\'value > 0\') }'],
|
|
136
|
+
['required include', 'defaultScope: { include: [{ model: sequelize.define(\'uchild\', { id: { type: DataTypes.STRING, primaryKey: true } }, { tableName: \'uchild\' }), required: true }] }'],
|
|
137
|
+
['where含Op.and', 'defaultScope: { where: { [require(\'sequelize\').Op.and]: [{ name: \'x\' }] } }'],
|
|
138
|
+
]
|
|
139
|
+
for (let [i, [t, sc]] of cases.entries()) {
|
|
140
|
+
let fdm = path.join(fd, `m6${i}`)
|
|
141
|
+
fs.mkdirSync(fdm, { recursive: true })
|
|
142
|
+
fs.writeFileSync(path.join(fdm, 'users.js'), genModel(true).replace('tableName: \'users\'', `tableName: 'users', ${sc}`), 'utf8')
|
|
143
|
+
let st = path.join(fd, `m6${i}.sqlite`)
|
|
144
|
+
let w = wo({ url: 'sqlite://:', storage: st, db: 'worm', cl: 'users', fdModels: fdm })
|
|
145
|
+
await w.createStorage()
|
|
146
|
+
await w.insert([{ id: 'a', name: 'x', value: 1 }, { id: 'b', name: 'y', value: 0 }])
|
|
147
|
+
let r = await w.delAll({ value: { $gte: 0 } }).then(() => null).catch((err) => String(err.message || err))
|
|
148
|
+
assert.ok(r !== null && r.indexOf('defaultScope') >= 0, `${t}: 未reject: ${r}`)
|
|
149
|
+
let wAll = wo({ url: 'sqlite://:', storage: st, db: 'worm', cl: 'users', fdModels: path.join(fd, 'm1') })
|
|
150
|
+
assert.deepStrictEqual((await wAll.select()).map((v) => v.id).sort(), ['a', 'b'], `${t}: 資料被刪`)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
131
154
|
it('BIGINT主鍵: 超出安全整數之數值為型別不符, 精確值以標準整數寫法之字串給予', async function() {
|
|
132
155
|
//rulebook R8: 整數類主鍵之數值須為安全整數, 否則已失精度而會命中相鄰主鍵; 超出安全整數之標準整數寫法字串保留為字串(表外細化)
|
|
133
156
|
let fdm = path.join(fd, 'm5')
|