bajo-extra 0.3.0 → 0.3.1
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/bajo/config.json +0 -13
- package/bajo/helper/fetch-and-save.js +2 -4
- package/bajoCli/tool.js +1 -8
- package/package.json +3 -11
- package/bajo/helper/export-to.js +0 -106
- package/bajo/helper/import-from.js +0 -69
- package/bajoCli/tool/export-to.js +0 -57
- package/bajoCli/tool/import-from.js +0 -53
- package/lib/ndjson-csv-xlsx.js +0 -35
package/bajo/config.json
CHANGED
|
@@ -51,13 +51,11 @@ async function handler (rec, bulk) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
async function fetchAndSave ({ url, bulk, save = {}, opts = {} } = {}) {
|
|
54
|
-
const {
|
|
54
|
+
const { startPlugin } = this.bajo.helper
|
|
55
55
|
const { fetchBulk } = this.bajoExtra.helper
|
|
56
56
|
const { merge } = this.bajo.helper._
|
|
57
57
|
merge(bulk, { handler, save })
|
|
58
|
-
|
|
59
|
-
const start = await importModule(`${cfgDb.dir.pkg}/bajo/start.js`)
|
|
60
|
-
await start.call(this, 'all')
|
|
58
|
+
await startPlugin('bajoDb')
|
|
61
59
|
|
|
62
60
|
await fetchBulk(url, bulk, opts)
|
|
63
61
|
}
|
package/bajoCli/tool.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const { currentLoc } = this.bajo.helper
|
|
3
|
-
const { runToolMethod } = this.bajoCli.helper
|
|
4
|
-
const options = { demonize: ['shell'] }
|
|
5
|
-
await runToolMethod({ path, args, dir: `${currentLoc(import.meta).dir}/tool`, options })
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default tool
|
|
1
|
+
export default 'defCliHandler'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bajo-extra",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Extra package for Bajo Framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,21 +26,13 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/ardhi/bajo-extra#readme",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@atomictech/xlsx-write-stream": "^2.0.2",
|
|
30
29
|
"async": "^3.2.4",
|
|
31
30
|
"axios": "^1.4.0",
|
|
32
31
|
"bcrypt": "^5.1.1",
|
|
33
32
|
"email-addresses": "^5.0.0",
|
|
34
|
-
"fast-
|
|
35
|
-
"fast-jwt": "^3.2.0",
|
|
36
|
-
"fast-xml-parser": "^4.3.2",
|
|
37
|
-
"ndjson": "^2.0.0",
|
|
33
|
+
"fast-xml-parser": "^4.3.6",
|
|
38
34
|
"numbro": "^2.5.0",
|
|
39
35
|
"performant-array-to-tree": "^1.11.0",
|
|
40
|
-
"query-string": "^8.1.0"
|
|
41
|
-
"scramjet": "^4.36.9",
|
|
42
|
-
"stream-chain": "^2.2.5",
|
|
43
|
-
"stream-json": "^1.8.0",
|
|
44
|
-
"xlsx-parse-stream": "^1.1.0"
|
|
36
|
+
"query-string": "^8.1.0"
|
|
45
37
|
}
|
|
46
38
|
}
|
package/bajo/helper/export-to.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import scramjet from 'scramjet'
|
|
3
|
-
import format from '../../lib/ndjson-csv-xlsx.js'
|
|
4
|
-
import { createGzip } from 'zlib'
|
|
5
|
-
|
|
6
|
-
const { json, ndjson, csv, xlsx } = format
|
|
7
|
-
const { DataStream } = scramjet
|
|
8
|
-
|
|
9
|
-
const supportedExt = ['.json', '.jsonl', '.ndjson', '.csv', '.xlsx', '.tsv']
|
|
10
|
-
|
|
11
|
-
async function getFile (dest, ensureDir) {
|
|
12
|
-
const { fs, importPkg, error, getPluginDataDir } = this.bajo.helper
|
|
13
|
-
const increment = await importPkg('add-filename-increment')
|
|
14
|
-
let file
|
|
15
|
-
if (path.isAbsolute(dest)) file = dest
|
|
16
|
-
else {
|
|
17
|
-
file = `${getPluginDataDir('bajoExtra')}/export/${dest}`
|
|
18
|
-
fs.ensureDirSync(path.dirname(file))
|
|
19
|
-
}
|
|
20
|
-
file = increment(file, { fs: true })
|
|
21
|
-
const dir = path.dirname(file)
|
|
22
|
-
if (!fs.existsSync(dir)) {
|
|
23
|
-
if (ensureDir) fs.ensureDirSync(dir)
|
|
24
|
-
else throw error('Directory \'%s\' doesn\'t exist', dir)
|
|
25
|
-
}
|
|
26
|
-
let compress = false
|
|
27
|
-
let ext = path.extname(file)
|
|
28
|
-
if (ext === '.gz') {
|
|
29
|
-
compress = true
|
|
30
|
-
ext = path.extname(path.basename(file).replace('.gz', ''))
|
|
31
|
-
// file = file.slice(0, file.length - 3)
|
|
32
|
-
}
|
|
33
|
-
if (!supportedExt.includes(ext)) throw error('Unsupported format \'%s\'', ext.slice(1))
|
|
34
|
-
return { file, ext, compress }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function getData ({ source, filter, count, stream, progressFn }) {
|
|
38
|
-
let cnt = count ?? 0
|
|
39
|
-
const { recordFind } = this.bajoDb.helper
|
|
40
|
-
for (;;) {
|
|
41
|
-
const batchStart = new Date()
|
|
42
|
-
const { data, page } = await recordFind(source, filter, { dataOnly: false })
|
|
43
|
-
if (data.length === 0) break
|
|
44
|
-
cnt += data.length
|
|
45
|
-
await stream.pull(data)
|
|
46
|
-
if (progressFn) await progressFn.call(this, { batchNo: page, data, batchStart, batchEnd: new Date() })
|
|
47
|
-
filter.page++
|
|
48
|
-
}
|
|
49
|
-
await stream.end()
|
|
50
|
-
return cnt
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function exportTo (source, dest, { filter = {}, ensureDir, useHeader = true, batch = 500, progressFn } = {}, opts = {}) {
|
|
54
|
-
const { fs, error, getConfig } = this.bajo.helper
|
|
55
|
-
const cfg = getConfig('bajoExtra')
|
|
56
|
-
if (!this.bajoDb) throw error('Bajo DB isn\'t loaded')
|
|
57
|
-
filter.page = 1
|
|
58
|
-
batch = parseInt(batch) ?? 500
|
|
59
|
-
if (batch > cfg.stream.export.maxBatch) batch = cfg.stream.export.maxBatch
|
|
60
|
-
if (batch < 0) batch = 1
|
|
61
|
-
filter.limit = batch
|
|
62
|
-
const { merge } = this.bajo.helper._
|
|
63
|
-
|
|
64
|
-
return new Promise((resolve, reject) => {
|
|
65
|
-
const { getInfo } = this.bajoDb.helper
|
|
66
|
-
let count = 0
|
|
67
|
-
let file
|
|
68
|
-
let ext
|
|
69
|
-
let stream
|
|
70
|
-
let compress
|
|
71
|
-
let writer
|
|
72
|
-
getInfo(source)
|
|
73
|
-
.then(res => {
|
|
74
|
-
return getFile.call(this, dest, ensureDir)
|
|
75
|
-
})
|
|
76
|
-
.then(res => {
|
|
77
|
-
file = res.file
|
|
78
|
-
ext = res.ext
|
|
79
|
-
compress = res.compress
|
|
80
|
-
writer = fs.createWriteStream(file)
|
|
81
|
-
writer.on('error', err => {
|
|
82
|
-
reject(err)
|
|
83
|
-
})
|
|
84
|
-
writer.on('finish', () => {
|
|
85
|
-
resolve({ file, count })
|
|
86
|
-
})
|
|
87
|
-
stream = new DataStream()
|
|
88
|
-
stream = stream.flatMap(items => (items))
|
|
89
|
-
const pipes = []
|
|
90
|
-
if (ext === '.json') pipes.push(json.stringify(opts))
|
|
91
|
-
else if (['.ndjson', '.jsonl'].includes(ext)) pipes.push(ndjson.stringify(opts))
|
|
92
|
-
else if (ext === '.csv') pipes.push(csv.stringify(merge({}, { headers: useHeader }, opts)))
|
|
93
|
-
else if (ext === '.tsv') pipes.push(csv.stringify(merge({}, { headers: useHeader }, merge({}, opts, { delimiter: '\t' }))))
|
|
94
|
-
else if (ext === '.xlsx') pipes.push(xlsx.stringify(merge({}, { header: useHeader }, opts)))
|
|
95
|
-
if (compress) pipes.push(createGzip())
|
|
96
|
-
DataStream.pipeline(stream, ...pipes).pipe(writer)
|
|
97
|
-
return getData.call(this, { source, filter, count, stream, progressFn })
|
|
98
|
-
})
|
|
99
|
-
.then(cnt => {
|
|
100
|
-
count = cnt
|
|
101
|
-
})
|
|
102
|
-
.catch(reject)
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default exportTo
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import scramjet from 'scramjet'
|
|
3
|
-
import format from '../../lib/ndjson-csv-xlsx.js'
|
|
4
|
-
import { createGunzip } from 'zlib'
|
|
5
|
-
|
|
6
|
-
const { json, ndjson, csv, xlsx } = format
|
|
7
|
-
const { DataStream } = scramjet
|
|
8
|
-
const supportedExt = ['.json', '.jsonl', '.ndjson', '.csv', '.xlsx', '.tsv']
|
|
9
|
-
|
|
10
|
-
async function importFrom (source, dest, { trashOld = true, batch = 1, progressFn, converterFn, useHeader = true, fileType, createOpts = {} } = {}, opts = {}) {
|
|
11
|
-
const { fs, error, getConfig, getPluginDataDir } = this.bajo.helper
|
|
12
|
-
if (dest !== false) {
|
|
13
|
-
if (!this.bajoDb) throw error('Bajo DB isn\'t loaded')
|
|
14
|
-
await this.bajoDb.helper.getInfo(dest)
|
|
15
|
-
}
|
|
16
|
-
const { merge } = this.bajo.helper._
|
|
17
|
-
const cfg = getConfig('bajoExtra')
|
|
18
|
-
|
|
19
|
-
let file
|
|
20
|
-
if (path.isAbsolute(source)) file = source
|
|
21
|
-
else {
|
|
22
|
-
file = `${getPluginDataDir('bajoExtra')}/import/${source}`
|
|
23
|
-
fs.ensureDirSync(path.dirname(file))
|
|
24
|
-
}
|
|
25
|
-
if (!fs.existsSync(file)) throw error('Source file \'%s\' doesn\'t exist', file)
|
|
26
|
-
let ext = fileType ? `.${fileType}` : path.extname(file)
|
|
27
|
-
let decompress = false
|
|
28
|
-
if (ext === '.gz') {
|
|
29
|
-
ext = path.extname(path.basename(file, '.gz'))
|
|
30
|
-
decompress = true
|
|
31
|
-
}
|
|
32
|
-
if (!supportedExt.includes(ext)) throw error('Unsupported format \'%s\'', ext.slice(1))
|
|
33
|
-
if (trashOld && dest !== false) await this.bajoDb.helper.recordClear(dest)
|
|
34
|
-
const reader = fs.createReadStream(file)
|
|
35
|
-
batch = parseInt(batch) || 100
|
|
36
|
-
if (batch > cfg.stream.import.maxBatch) batch = cfg.stream.import.maxBatch
|
|
37
|
-
if (batch < 0) batch = 1
|
|
38
|
-
let count = 0
|
|
39
|
-
const pipes = [reader]
|
|
40
|
-
if (decompress) pipes.push(createGunzip())
|
|
41
|
-
if (ext === '.json') pipes.push(json.parse(opts))
|
|
42
|
-
else if (['.ndjson', '.jsonl'].includes(ext)) pipes.push(ndjson.parse(opts))
|
|
43
|
-
else if (ext === '.csv') pipes.push(csv.parse(merge({}, { headers: useHeader }, opts)))
|
|
44
|
-
else if (ext === '.tsv') pipes.push(csv.parse(merge({}, { headers: useHeader }, merge({}, opts, { delimiter: '\t' }))))
|
|
45
|
-
else if (ext === '.xlsx') pipes.push(xlsx.parse(merge({}, { header: useHeader }, opts)))
|
|
46
|
-
|
|
47
|
-
const stream = DataStream.pipeline(...pipes)
|
|
48
|
-
let batchNo = 1
|
|
49
|
-
const data = []
|
|
50
|
-
await stream
|
|
51
|
-
.batch(batch)
|
|
52
|
-
.map(async items => {
|
|
53
|
-
if (items.length === 0) return null
|
|
54
|
-
const batchStart = new Date()
|
|
55
|
-
for (let item of items) {
|
|
56
|
-
count++
|
|
57
|
-
item = converterFn ? await converterFn.call(this, item) : item
|
|
58
|
-
if (dest !== false) await this.bajoDb.helper.recordCreate(dest, item, createOpts)
|
|
59
|
-
else data.push(item)
|
|
60
|
-
}
|
|
61
|
-
if (progressFn) await progressFn.call(this, { batchNo, data: items, batchStart, batchEnd: new Date() })
|
|
62
|
-
batchNo++
|
|
63
|
-
})
|
|
64
|
-
.run()
|
|
65
|
-
|
|
66
|
-
return dest === false ? data : { file, count }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default importFrom
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import Path from 'path'
|
|
2
|
-
|
|
3
|
-
const batch = 100
|
|
4
|
-
|
|
5
|
-
function makeProgress (spin) {
|
|
6
|
-
return async function ({ batchNo, data, batchStart, batchEnd } = {}) {
|
|
7
|
-
const { secToHms } = this.bajo.helper
|
|
8
|
-
if (data.length === 0) return
|
|
9
|
-
spin.setText('Batch #%d (%s)', batchNo, secToHms(batchEnd.toTime() - batchStart.toTime(), true))
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function exportTo ({ path, args }) {
|
|
14
|
-
const { importPkg, print, dayjs, getConfig, importModule, spinner } = this.bajo.helper
|
|
15
|
-
const { isEmpty, map } = this.bajo.helper._
|
|
16
|
-
const [input, select] = await importPkg('bajoCli:@inquirer/input',
|
|
17
|
-
'bajo-cli:@inquirer/select')
|
|
18
|
-
const config = getConfig()
|
|
19
|
-
if (!this.bajoDb) return print.fail('Bajo DB isn\'t loaded', { exit: config.tool })
|
|
20
|
-
const schemas = map(this.bajoDb.schemas, 'name')
|
|
21
|
-
if (isEmpty(schemas)) return print.fail('No schema found!', { exit: config.tool })
|
|
22
|
-
let [coll, dest, query] = args
|
|
23
|
-
if (isEmpty(coll)) {
|
|
24
|
-
coll = await select({
|
|
25
|
-
message: print.__('Please choose collection:'),
|
|
26
|
-
choices: map(schemas, s => ({ value: s }))
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
if (isEmpty(dest)) {
|
|
30
|
-
dest = await input({
|
|
31
|
-
message: print.__('Please enter destination file:'),
|
|
32
|
-
default: `${coll}-${dayjs().format('YYYYMMDD')}.ndjson`,
|
|
33
|
-
validate: (item) => !isEmpty(item)
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
if (isEmpty(query)) {
|
|
37
|
-
query = await input({
|
|
38
|
-
message: print.__('Please enter a query (if any):')
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
const spin = spinner().start('Exporting...')
|
|
42
|
-
const progressFn = makeProgress.call(this, spin)
|
|
43
|
-
const cfg = getConfig('bajoDb', { full: true })
|
|
44
|
-
const start = await importModule(`${cfg.dir.pkg}/bajo/start.js`)
|
|
45
|
-
const { connection } = await this.bajoDb.helper.getInfo(coll)
|
|
46
|
-
await start.call(this, connection.name)
|
|
47
|
-
try {
|
|
48
|
-
const filter = { query }
|
|
49
|
-
const result = await this.bajoExtra.helper.exportTo(coll, dest, { filter, batch, progressFn })
|
|
50
|
-
spin.succeed('%d records successfully exported to \'%s\'', result.count, Path.resolve(result.file))
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.log(err)
|
|
53
|
-
spin.fail('Error: %s', err.message, { exit: config.tool })
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default exportTo
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import Path from 'path'
|
|
2
|
-
|
|
3
|
-
const batch = 100
|
|
4
|
-
|
|
5
|
-
function makeProgress (spin) {
|
|
6
|
-
const { secToHms } = this.bajo.helper
|
|
7
|
-
return async function ({ batchNo, data, batchStart, batchEnd } = {}) {
|
|
8
|
-
spin.setText('Batch #%d (%s)', batchNo, secToHms(batchEnd.toTime() - batchStart.toTime(), true))
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function importFrom ({ path, args }) {
|
|
13
|
-
const { importPkg, print, importModule, getConfig, spinner } = this.bajo.helper
|
|
14
|
-
const { isEmpty, map } = this.bajo.helper._
|
|
15
|
-
const [input, select, confirm] = await importPkg('bajoCli:@inquirer/input',
|
|
16
|
-
'bajoCli:@inquirer/select', 'bajoCli:@inquirer/confirm')
|
|
17
|
-
const config = getConfig()
|
|
18
|
-
if (!this.bajoDb) return print.fail('Bajo DB isn\'t loaded', { exit: config.tool })
|
|
19
|
-
const schemas = map(this.bajoDb.schemas, 'name')
|
|
20
|
-
if (isEmpty(schemas)) return print.fail('No schema found!', { exit: config.tool })
|
|
21
|
-
let [dest, coll] = args
|
|
22
|
-
if (isEmpty(dest)) {
|
|
23
|
-
dest = await input({
|
|
24
|
-
message: print.__('Please enter source file:'),
|
|
25
|
-
validate: (item) => !isEmpty(item)
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
if (isEmpty(coll)) {
|
|
29
|
-
coll = await select({
|
|
30
|
-
message: print.__('Please choose collection:'),
|
|
31
|
-
choices: map(schemas, s => ({ value: s }))
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
const answer = await confirm({
|
|
35
|
-
message: print.__('You\'re about to replace ALL records with the new ones. Are you really sure?'),
|
|
36
|
-
default: false
|
|
37
|
-
})
|
|
38
|
-
if (!answer) return print.fail('Aborted!', { exit: config.tool })
|
|
39
|
-
const spin = spinner({ showCounter: true }).start('Importing...')
|
|
40
|
-
const progressFn = makeProgress.call(this, spin)
|
|
41
|
-
const cfg = getConfig('bajoDb', { full: true })
|
|
42
|
-
const start = await importModule(`${cfg.dir.pkg}/bajo/start.js`)
|
|
43
|
-
const { connection } = await this.bajoDb.helper.getInfo(coll)
|
|
44
|
-
await start.call(this, connection.name)
|
|
45
|
-
try {
|
|
46
|
-
const result = await this.bajoExtra.helper.importFrom(dest, coll, { batch, progressFn })
|
|
47
|
-
spin.succeed('%d records successfully imported from \'%s\'', result.count, Path.resolve(result.file))
|
|
48
|
-
} catch (err) {
|
|
49
|
-
spin.fail('Error: %s', err.message, { exit: config.tool })
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default importFrom
|
package/lib/ndjson-csv-xlsx.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// Borrowed from: https://github.com/fanlia/ndjson-csv-xlsx/blob/main/index.js
|
|
2
|
-
|
|
3
|
-
import ndjson from 'ndjson'
|
|
4
|
-
import csv from 'fast-csv'
|
|
5
|
-
import xlsxparse from 'xlsx-parse-stream'
|
|
6
|
-
import XLSXWriteStream from '@atomictech/xlsx-write-stream'
|
|
7
|
-
import StreamArray from 'stream-json/streamers/StreamArray.js'
|
|
8
|
-
import stringer from 'stream-json/Stringer.js'
|
|
9
|
-
import disassembler from 'stream-json/Disassembler.js'
|
|
10
|
-
import chain from 'stream-chain'
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
ndjson: {
|
|
14
|
-
parse: (...args) => ndjson.parse(...args),
|
|
15
|
-
stringify: (...args) => ndjson.stringify(...args)
|
|
16
|
-
},
|
|
17
|
-
csv: {
|
|
18
|
-
parse: (...args) => csv.parse(...args),
|
|
19
|
-
stringify: (...args) => csv.format(...args)
|
|
20
|
-
},
|
|
21
|
-
xlsx: {
|
|
22
|
-
parse: (...args) => xlsxparse(...args),
|
|
23
|
-
stringify: (...args) => new XLSXWriteStream(...args)
|
|
24
|
-
},
|
|
25
|
-
json: {
|
|
26
|
-
parse: (...args) => chain([
|
|
27
|
-
StreamArray.withParser(...args),
|
|
28
|
-
data => data.value
|
|
29
|
-
]),
|
|
30
|
-
stringify: (options, ...args) => chain([
|
|
31
|
-
disassembler(),
|
|
32
|
-
stringer({ ...options, makeArray: true })
|
|
33
|
-
])
|
|
34
|
-
}
|
|
35
|
-
}
|