mikser-io 6.12.0 → 6.17.0
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/package.json +1 -1
- package/src/api.js +35 -16
- package/src/plugins/api.js +10 -7
- package/src/plugins/data.js +34 -14
- package/src/plugins/layouts.js +26 -7
- package/src/plugins/resources.js +2 -2
- package/src/utils.js +2 -2
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -87,24 +87,41 @@ export function useRenderer(runtime, { defaultTimeout = 30_000 } = {}) {
|
|
|
87
87
|
* `runtime.process()` cycle — within that cycle, mikser's worker pool
|
|
88
88
|
* renders the batch in parallel.
|
|
89
89
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
90
|
+
* Two control flags mirror mikser's default-keep-everything behavior;
|
|
91
|
+
* both opt-out via strict `=== false`:
|
|
92
|
+
*
|
|
93
|
+
* - `catalog: true` (default) — keep the entity in the catalog after
|
|
94
|
+
* the render. Pass `catalog: false` to prune the catalog row;
|
|
95
|
+
* useful for on-demand renders where the metadata row would just
|
|
96
|
+
* accumulate.
|
|
97
|
+
* - `save: true` (default) — write the rendered output to disk at
|
|
98
|
+
* `<outputFolder>/<entity.destination>`. Pass `save: false` to
|
|
99
|
+
* skip the final disk write; the bytes still come back via
|
|
100
|
+
* `output.result` for you to pipe wherever you want (HTTP
|
|
101
|
+
* response, S3, …). For layouts with a postprocessor (e.g.
|
|
102
|
+
* `*.html-pdf.*`), the intermediate file is still written so the
|
|
103
|
+
* postprocessor can consume it; only the FINAL output is skipped.
|
|
104
|
+
*
|
|
105
|
+
* The rendered output's bytes are always returned in `output.result`
|
|
106
|
+
* regardless of either flag — `save` only affects whether they also
|
|
107
|
+
* end up on disk.
|
|
96
108
|
*
|
|
97
109
|
* @param {object} entity - any entity-shaped object
|
|
98
110
|
* @param {object} [opts]
|
|
99
111
|
* @param {number} [opts.timeout] - override the default timeout
|
|
100
|
-
* @param {boolean} [opts.catalog=
|
|
112
|
+
* @param {boolean} [opts.catalog=true] - keep the catalog row after render
|
|
113
|
+
* @param {boolean} [opts.save=true] - write the rendered output to disk
|
|
101
114
|
* @returns {Promise<{output, entity}>}
|
|
102
115
|
*/
|
|
103
|
-
async function render(entity, { timeout = defaultTimeout, catalog =
|
|
116
|
+
async function render(entity, { timeout = defaultTimeout, catalog = true, save = true } = {}) {
|
|
104
117
|
const result = await new Promise((resolve, reject) => {
|
|
105
118
|
const correlationId = randomUUID()
|
|
119
|
+
const stamped = { ...entity, _correlationId: correlationId }
|
|
120
|
+
// Only stamp _save when explicitly opting out — keeps the
|
|
121
|
+
// entity object clean for the common case.
|
|
122
|
+
if (save === false) stamped._save = false
|
|
106
123
|
pending.push({
|
|
107
|
-
entity:
|
|
124
|
+
entity: stamped,
|
|
108
125
|
correlationId,
|
|
109
126
|
timeout,
|
|
110
127
|
resolve,
|
|
@@ -114,13 +131,15 @@ export function useRenderer(runtime, { defaultTimeout = 30_000 } = {}) {
|
|
|
114
131
|
if (!cycleRunning) setImmediate(runBatch)
|
|
115
132
|
})
|
|
116
133
|
|
|
117
|
-
if (
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// product. We deliberately bypass the
|
|
121
|
-
// here (which would also unlink the file
|
|
122
|
-
// manifest cleanup) and splice the entity
|
|
123
|
-
// in-memory catalog directly.
|
|
134
|
+
if (catalog === false) {
|
|
135
|
+
// Explicit opt-out: prune the catalog row so it doesn't
|
|
136
|
+
// accumulate. The rendered output file stays on disk — the
|
|
137
|
+
// bytes are the work product. We deliberately bypass the
|
|
138
|
+
// journal/DELETE path here (which would also unlink the file
|
|
139
|
+
// via engine.js's manifest cleanup) and splice the entity
|
|
140
|
+
// out of the in-memory catalog directly. Strict equality so
|
|
141
|
+
// ambiguous inputs (null, "false", 0) fall through to the
|
|
142
|
+
// default of keeping the row.
|
|
124
143
|
const entities = runtime.catalog?.data?.entities
|
|
125
144
|
if (entities) {
|
|
126
145
|
const idx = entities.findIndex(e => e.id === result.entity.id)
|
package/src/plugins/api.js
CHANGED
|
@@ -148,13 +148,16 @@ export default ({
|
|
|
148
148
|
|
|
149
149
|
router.post('/render', auth, async (req, res) => {
|
|
150
150
|
try {
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
// Body shape mirrors the JS API: entity fields at top
|
|
152
|
+
// level, control flags grouped under `options`. Forwarded
|
|
153
|
+
// straight to render(entity, options). Defaults match
|
|
154
|
+
// mikser's lifecycle (save and keep the catalog row);
|
|
155
|
+
// strict opt-outs via the literal `false`:
|
|
156
|
+
// options.catalog: false → prune the catalog row
|
|
157
|
+
// options.save: false → skip the final disk write
|
|
158
|
+
// (bytes still in the response)
|
|
159
|
+
const { options = {}, ...entityShape } = req.body
|
|
160
|
+
const { output, entity } = await render(entityShape, options)
|
|
158
161
|
await sendRenderOutput(res, output, entity)
|
|
159
162
|
} catch (err) {
|
|
160
163
|
logger.error('Api render error: %s', err.message)
|
package/src/plugins/data.js
CHANGED
|
@@ -36,9 +36,18 @@ export default ({
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
for (let entitiesName in entitiesConfig) {
|
|
39
|
+
// Per-config namespacing token. When set, this entity-writer's
|
|
40
|
+
// files land under <dataFolder>/<token>/ instead of straight
|
|
41
|
+
// in <dataFolder>. Each named entity config can declare its
|
|
42
|
+
// own token, so they can target different namespaces (useful
|
|
43
|
+
// for multi-tenant exports into shared storage).
|
|
44
|
+
const token = entitiesConfig[entitiesName].token
|
|
45
|
+
const targetFolder = token
|
|
46
|
+
? path.join(runtime.options.dataFolder, token)
|
|
47
|
+
: runtime.options.dataFolder
|
|
39
48
|
const {
|
|
40
49
|
query,
|
|
41
|
-
map,
|
|
50
|
+
map: mapEntity = entity => entity,
|
|
42
51
|
pick,
|
|
43
52
|
save: saveEntity = async entity => {
|
|
44
53
|
if (!entity.name) {
|
|
@@ -46,12 +55,12 @@ export default ({
|
|
|
46
55
|
return
|
|
47
56
|
}
|
|
48
57
|
const dump = JSON.stringify(normalize(entity))
|
|
49
|
-
const entityFile = path.join(
|
|
58
|
+
const entityFile = path.join(targetFolder, `${entity.name}.${entitiesName}.json`)
|
|
50
59
|
await mkdir(path.dirname(entityFile), { recursive: true })
|
|
51
60
|
await writeFile(entityFile, dump, 'utf8')
|
|
52
61
|
},
|
|
53
62
|
delete: deleteEntity = async entity => {
|
|
54
|
-
const entityFile = path.join(
|
|
63
|
+
const entityFile = path.join(targetFolder, `${entity.name}.json`)
|
|
55
64
|
await unlink(entityFile)
|
|
56
65
|
}
|
|
57
66
|
} = entitiesConfig[entitiesName]
|
|
@@ -62,12 +71,12 @@ export default ({
|
|
|
62
71
|
case OPERATION.CREATE:
|
|
63
72
|
case OPERATION.UPDATE:
|
|
64
73
|
logger.debug('Data export entity %s %s: %s', entity.collection, operation, entity.id)
|
|
65
|
-
await saveEntity(
|
|
74
|
+
await saveEntity(({
|
|
66
75
|
refId: ('/' + entity.name.replaceAll('\\', '/')).replace(/\/index$/g, '/'),
|
|
67
76
|
name: entity.name,
|
|
68
77
|
date: new Date(entity.time),
|
|
69
|
-
data: _.pick(entity, pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
70
|
-
})
|
|
78
|
+
data: _.pick(await mapEntity(entity), pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
79
|
+
}))
|
|
71
80
|
break
|
|
72
81
|
case OPERATION.DELETE:
|
|
73
82
|
await deleteEntity(entity)
|
|
@@ -90,14 +99,19 @@ export default ({
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
for (let contextName in contextConfig) {
|
|
102
|
+
// Per-config namespacing token — see the entities loop above.
|
|
103
|
+
const token = contextConfig[contextName].token
|
|
104
|
+
const targetFolder = token
|
|
105
|
+
? path.join(runtime.options.dataFolder, token)
|
|
106
|
+
: runtime.options.dataFolder
|
|
93
107
|
const {
|
|
94
108
|
query,
|
|
95
|
-
map,
|
|
109
|
+
map: mapEntityContext = (entity, context) => context,
|
|
96
110
|
pick,
|
|
97
111
|
save: saveConext = async (entity, context) => {
|
|
98
112
|
if (context?.data) {
|
|
99
113
|
const entityName = entity.name
|
|
100
|
-
const contextFile = path.join(
|
|
114
|
+
const contextFile = path.join(targetFolder, `${entityName}.${contextName}.json`)
|
|
101
115
|
await mkdir(path.dirname(contextFile), { recursive: true })
|
|
102
116
|
await writeFile(contextFile, JSON.stringify(context), 'utf8')
|
|
103
117
|
}
|
|
@@ -107,7 +121,7 @@ export default ({
|
|
|
107
121
|
for await (let { entity, context } of useJournal('Data context', [OPERATION.RENDER])) {
|
|
108
122
|
if (query(entity)) {
|
|
109
123
|
logger.debug('Data export context: %s', entity.name)
|
|
110
|
-
await saveConext(entity,
|
|
124
|
+
await saveConext(entity, _.pick(await mapEntityContext(entity, context), pick || ['data']))
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
}
|
|
@@ -116,23 +130,29 @@ export default ({
|
|
|
116
130
|
onFinalize(async () => {
|
|
117
131
|
const logger = useLogger()
|
|
118
132
|
for (let catalogName in runtime.config.data?.catalog || {}) {
|
|
133
|
+
// Per-config namespacing token — see the entities loop above.
|
|
134
|
+
const token = runtime.config.data?.catalog[catalogName].token
|
|
135
|
+
const targetFolder = token
|
|
136
|
+
? path.join(runtime.options.dataFolder, token)
|
|
137
|
+
: runtime.options.dataFolder
|
|
119
138
|
const {
|
|
120
139
|
query: queryEntities = entity => entity.type == 'document',
|
|
121
|
-
map,
|
|
140
|
+
map: mapEntity = entity => entity,
|
|
122
141
|
pick,
|
|
123
142
|
save: saveEntities = async entities => {
|
|
124
|
-
const entitiesFile = path.join(
|
|
143
|
+
const entitiesFile = path.join(targetFolder, `${catalogName}.json`)
|
|
144
|
+
await mkdir(path.dirname(entitiesFile), { recursive: true })
|
|
125
145
|
logger.debug('Data export catalog %s %s: %s', catalogName, entities.length, entitiesFile)
|
|
126
146
|
await writeFile(entitiesFile, JSON.stringify(entities), 'utf8')
|
|
127
147
|
}
|
|
128
148
|
} = runtime.config.data?.catalog[catalogName]
|
|
129
149
|
const entities = await findEntities(queryEntities)
|
|
130
|
-
await saveEntities(await pMap(entities, async entity =>
|
|
150
|
+
await saveEntities(await pMap(entities, async entity => ({
|
|
131
151
|
refId: ('/' + entity.name.replaceAll('\\', '/')).replace(/\/index$/g, '/'),
|
|
132
152
|
name: entity.name,
|
|
133
153
|
date: new Date(entity.time),
|
|
134
|
-
data: _.pick(entity, pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
135
|
-
}))
|
|
154
|
+
data: _.pick(await mapEntity(entity), pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
155
|
+
})))
|
|
136
156
|
}
|
|
137
157
|
})
|
|
138
158
|
}
|
package/src/plugins/layouts.js
CHANGED
|
@@ -370,13 +370,32 @@ export default ({
|
|
|
370
370
|
onComplete(async ({ entity, options, output }) => {
|
|
371
371
|
const logger = useLogger()
|
|
372
372
|
if (entity.layout && !options?.ignore && output.result != null) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
373
|
+
// `_save: false` (stamped by useRenderer when called with
|
|
374
|
+
// { save: false }) opts out of writing the final output to
|
|
375
|
+
// disk. The bytes still come back to the caller via
|
|
376
|
+
// output.result. Strict equality — only the literal `false`
|
|
377
|
+
// opts out, matching the catalog-flag pattern.
|
|
378
|
+
//
|
|
379
|
+
// The intermediate file (when a postprocessor will run next)
|
|
380
|
+
// must still be written so the postprocessor can consume it,
|
|
381
|
+
// so we only honour `_save: false` for the FINAL output —
|
|
382
|
+
// detected as either "no postprocessor configured" or "we're
|
|
383
|
+
// running on the postprocess side already" (origin set).
|
|
384
|
+
const isFinal = !entity.layout.postprocessor || entity.origin != null
|
|
385
|
+
const skipWrite = entity._save === false && isFinal
|
|
386
|
+
|
|
387
|
+
if (!skipWrite) {
|
|
388
|
+
const destinationFile = path.join(runtime.options.outputFolder, entity.destination)
|
|
389
|
+
await mkdir(path.dirname(destinationFile), { recursive: true })
|
|
390
|
+
try {
|
|
391
|
+
await unlink(destinationFile)
|
|
392
|
+
} catch { }
|
|
393
|
+
await writeFile(destinationFile, output.result)
|
|
394
|
+
logger.debug('Layout render finished: %s', entity.destination.replace(runtime.options.workingFolder, ''))
|
|
395
|
+
} else {
|
|
396
|
+
logger.debug('Layout render finished (save:false, bytes only): %s', entity.id)
|
|
397
|
+
}
|
|
398
|
+
|
|
380
399
|
if (entity.origin && entity.origin !== entity.destination) {
|
|
381
400
|
// Don't unlink the origin if it was the same path we just
|
|
382
401
|
// wrote to (post plugins that produce the same extension as
|
package/src/plugins/resources.js
CHANGED
|
@@ -24,6 +24,7 @@ export default ({
|
|
|
24
24
|
trackProgress,
|
|
25
25
|
updateProgress,
|
|
26
26
|
updateEntry,
|
|
27
|
+
matchEntity,
|
|
27
28
|
constants: { OPERATION },
|
|
28
29
|
}) => {
|
|
29
30
|
const collection = 'resources'
|
|
@@ -65,8 +66,7 @@ export default ({
|
|
|
65
66
|
_.eachDeep(entity.meta, resource => {
|
|
66
67
|
if (typeof resource == 'string') {
|
|
67
68
|
for (let library in resourceLib) {
|
|
68
|
-
|
|
69
|
-
if (resource.match(match)) {
|
|
69
|
+
if (matchEntity(resource, library)) {
|
|
70
70
|
resourceMap[entity.id].push({ library, resource, entity })
|
|
71
71
|
}
|
|
72
72
|
}
|
package/src/utils.js
CHANGED
|
@@ -49,9 +49,9 @@ export function matchEntity(entity, match) {
|
|
|
49
49
|
if (typeof match == 'function') return match(entity)
|
|
50
50
|
else if (typeof match == 'string') {
|
|
51
51
|
if (match.substring(0, 2) == '@/') {
|
|
52
|
-
return minimatch(entity.name, match.substring(2))
|
|
52
|
+
return minimatch(typeof entity == 'string' ? entity : entity.name, match.substring(2))
|
|
53
53
|
} else {
|
|
54
|
-
return minimatch(entity.id, match)
|
|
54
|
+
return minimatch(typeof entity == 'string' ? entity : entity.id, match)
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
else if (typeof match == 'object') return _.isMatch(entity, match)
|