mikser-io 6.9.3 → 6.12.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 +2 -1
- package/src/api.js +31 -11
- package/src/journal.js +7 -1
- package/src/lifecycle.js +9 -0
- package/src/plugins/api.js +12 -3
- package/src/tracking.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mikser-io",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.12.0",
|
|
4
4
|
"description": "<p align=\"center\"> <img src=\"mikser-lockup-stacked.svg\" alt=\"mikser\" width=\"198\" /> </p>",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"fluent-ffmpeg": "^2.1.3",
|
|
59
59
|
"mikser-io-post-mjml": "file:../mikser-io-post-mjml",
|
|
60
|
+
"mikser-io-render-eta": "file:../mikser-io-render-eta",
|
|
60
61
|
"mikser-io-render-liquid": "file:../mikser-io-render-liquid",
|
|
61
62
|
"mikser-io-render-markdown": "file:../mikser-io-render-markdown",
|
|
62
63
|
"sharp": "^0.34.5"
|
package/src/api.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { randomUUID } from 'node:crypto'
|
|
6
6
|
import { writeFile, unlink, mkdir } from 'node:fs/promises'
|
|
7
7
|
import path from 'node:path'
|
|
8
|
-
import
|
|
8
|
+
import './lifecycle.js' // side-effect: attaches runtime.create / update / delete
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Bind to the runtime and return an on-demand renderer that pipelines
|
|
@@ -20,14 +20,10 @@ import { updateEntity as defaultUpdateEntity } from './lifecycle.js'
|
|
|
20
20
|
*
|
|
21
21
|
* @param {object} runtime - the mikser runtime singleton
|
|
22
22
|
* @param {object} [opts]
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {number} [opts.defaultTimeout] - per-render timeout in ms (default 30_000)
|
|
23
|
+
* @param {number} [opts.defaultTimeout] - per-render timeout in ms (default 30_000)
|
|
25
24
|
* @returns {{ render: (entity, { timeout? }?) => Promise<{ output, entity }> }}
|
|
26
25
|
*/
|
|
27
|
-
export function useRenderer(runtime, {
|
|
28
|
-
updateEntity = defaultUpdateEntity,
|
|
29
|
-
defaultTimeout = 30_000,
|
|
30
|
-
} = {}) {
|
|
26
|
+
export function useRenderer(runtime, { defaultTimeout = 30_000 } = {}) {
|
|
31
27
|
let pending = []
|
|
32
28
|
let cycleRunning = false
|
|
33
29
|
|
|
@@ -61,7 +57,7 @@ export function useRenderer(runtime, {
|
|
|
61
57
|
|
|
62
58
|
try {
|
|
63
59
|
for (const item of batch) {
|
|
64
|
-
await
|
|
60
|
+
await runtime.update(item.entity).catch(item.reject)
|
|
65
61
|
}
|
|
66
62
|
await runtime.process()
|
|
67
63
|
} catch (err) {
|
|
@@ -91,13 +87,21 @@ export function useRenderer(runtime, {
|
|
|
91
87
|
* `runtime.process()` cycle — within that cycle, mikser's worker pool
|
|
92
88
|
* renders the batch in parallel.
|
|
93
89
|
*
|
|
90
|
+
* By default the entity is **not** kept in the catalog: after the
|
|
91
|
+
* render resolves, the catalog row is pruned so it doesn't accumulate.
|
|
92
|
+
* The rendered output file is always kept on disk — the bytes are the
|
|
93
|
+
* work product. Pass `catalog: true` to also keep the catalog row,
|
|
94
|
+
* useful if you'll re-render the same entity later or query it via
|
|
95
|
+
* `findEntities`.
|
|
96
|
+
*
|
|
94
97
|
* @param {object} entity - any entity-shaped object
|
|
95
98
|
* @param {object} [opts]
|
|
96
|
-
* @param {number}
|
|
99
|
+
* @param {number} [opts.timeout] - override the default timeout
|
|
100
|
+
* @param {boolean} [opts.catalog=false] - keep the catalog row after render
|
|
97
101
|
* @returns {Promise<{output, entity}>}
|
|
98
102
|
*/
|
|
99
|
-
function render(entity, { timeout = defaultTimeout } = {}) {
|
|
100
|
-
|
|
103
|
+
async function render(entity, { timeout = defaultTimeout, catalog = false } = {}) {
|
|
104
|
+
const result = await new Promise((resolve, reject) => {
|
|
101
105
|
const correlationId = randomUUID()
|
|
102
106
|
pending.push({
|
|
103
107
|
entity: { ...entity, _correlationId: correlationId },
|
|
@@ -109,6 +113,22 @@ export function useRenderer(runtime, {
|
|
|
109
113
|
})
|
|
110
114
|
if (!cycleRunning) setImmediate(runBatch)
|
|
111
115
|
})
|
|
116
|
+
|
|
117
|
+
if (!catalog) {
|
|
118
|
+
// Prune the catalog row so it doesn't accumulate, but LEAVE
|
|
119
|
+
// the rendered output file on disk — the bytes are the work
|
|
120
|
+
// product. We deliberately bypass the journal/DELETE path
|
|
121
|
+
// here (which would also unlink the file via engine.js's
|
|
122
|
+
// manifest cleanup) and splice the entity out of the
|
|
123
|
+
// in-memory catalog directly.
|
|
124
|
+
const entities = runtime.catalog?.data?.entities
|
|
125
|
+
if (entities) {
|
|
126
|
+
const idx = entities.findIndex(e => e.id === result.entity.id)
|
|
127
|
+
if (idx >= 0) entities.splice(idx, 1)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return result
|
|
112
132
|
}
|
|
113
133
|
|
|
114
134
|
return { render }
|
package/src/journal.js
CHANGED
|
@@ -69,7 +69,13 @@ export async function* useJournal(name, operations, signal) {
|
|
|
69
69
|
export async function clearJournal(aborted) {
|
|
70
70
|
await journal('operations').del()
|
|
71
71
|
if (!aborted) {
|
|
72
|
-
|
|
72
|
+
// Tear down the sqlite connection only when this is genuinely a
|
|
73
|
+
// one-shot run that's about to exit. Watch mode and any plugin
|
|
74
|
+
// that keeps the process alive (e.g. the API plugin's HTTP server)
|
|
75
|
+
// sets `persistent` so subsequent cycles can still write to the
|
|
76
|
+
// journal — without this, the second /render request would crash
|
|
77
|
+
// with "Unable to acquire a connection".
|
|
78
|
+
if (runtime.options.watch !== true && runtime.options.persistent !== true) {
|
|
73
79
|
journal.destroy()
|
|
74
80
|
}
|
|
75
81
|
}
|
package/src/lifecycle.js
CHANGED
|
@@ -34,6 +34,15 @@ export async function updateEntity(entity) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Expose the three entity operations as methods on the runtime so library
|
|
38
|
+
// code (useRenderer, custom plugins) can call `runtime.update(entity)`
|
|
39
|
+
// instead of importing and threading the standalone functions. The
|
|
40
|
+
// imports above already pulled the runtime singleton into scope; this is
|
|
41
|
+
// just a single assignment per name.
|
|
42
|
+
runtime.create = createEntity
|
|
43
|
+
runtime.update = updateEntity
|
|
44
|
+
runtime.delete = deleteEntity
|
|
45
|
+
|
|
37
46
|
export async function postprocessEntity(entity, options = {}, context = {}) {
|
|
38
47
|
const logger = useLogger()
|
|
39
48
|
const entry = { operation: OPERATION.POSTPROCESS, entity, options, context }
|
package/src/plugins/api.js
CHANGED
|
@@ -68,7 +68,6 @@ export default ({
|
|
|
68
68
|
runtime,
|
|
69
69
|
onLoaded,
|
|
70
70
|
useLogger,
|
|
71
|
-
updateEntity,
|
|
72
71
|
findEntities,
|
|
73
72
|
}) => {
|
|
74
73
|
onLoaded(async () => {
|
|
@@ -95,7 +94,6 @@ export default ({
|
|
|
95
94
|
// library entry point and the Api endpoints share the exact same
|
|
96
95
|
// batching/timeouts/error semantics.
|
|
97
96
|
const { render } = useRenderer(runtime, {
|
|
98
|
-
updateEntity,
|
|
99
97
|
defaultTimeout: runtime.config.api?.renderTimeout ?? 30_000,
|
|
100
98
|
})
|
|
101
99
|
|
|
@@ -150,7 +148,13 @@ export default ({
|
|
|
150
148
|
|
|
151
149
|
router.post('/render', auth, async (req, res) => {
|
|
152
150
|
try {
|
|
153
|
-
|
|
151
|
+
// Pull `catalog` out as a control flag; everything else
|
|
152
|
+
// is treated as the entity. Default is NOT to keep the
|
|
153
|
+
// catalog row (the rendered output stays on disk either
|
|
154
|
+
// way). Send `catalog: true` to keep the row, e.g. if
|
|
155
|
+
// you'll re-render or query the entity later.
|
|
156
|
+
const { catalog, ...entityShape } = req.body
|
|
157
|
+
const { output, entity } = await render(entityShape, { catalog })
|
|
154
158
|
await sendRenderOutput(res, output, entity)
|
|
155
159
|
} catch (err) {
|
|
156
160
|
logger.error('Api render error: %s', err.message)
|
|
@@ -160,6 +164,11 @@ export default ({
|
|
|
160
164
|
}
|
|
161
165
|
})
|
|
162
166
|
|
|
167
|
+
// The HTTP server keeps the process alive across many process()
|
|
168
|
+
// cycles. Tell the journal layer not to tear down the sqlite
|
|
169
|
+
// connection at the end of each cycle.
|
|
170
|
+
runtime.options.persistent = true
|
|
171
|
+
|
|
163
172
|
const base = runtime.config.api?.base ?? '/api'
|
|
164
173
|
app.use(base, router)
|
|
165
174
|
|
package/src/tracking.js
CHANGED
|
@@ -27,6 +27,7 @@ onInitialized(() => {
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
export function trackProgress(name, total) {
|
|
30
|
+
if (!name || !total) return
|
|
30
31
|
const logger = useLogger()
|
|
31
32
|
logger.debug('%s started: %d', name, total)
|
|
32
33
|
progress.bar?.stop()
|
|
@@ -54,7 +55,7 @@ export function stopProgress() {
|
|
|
54
55
|
logger.warn('%s unfinished: %d', name, total - value)
|
|
55
56
|
} else {
|
|
56
57
|
const time = Math.round((Date.now() - progress.stamp) / 1000)
|
|
57
|
-
logger.info('%s: %s', name, formatTime(time, { autopaddingChar: '' }))
|
|
58
|
+
logger.info('%s finished: %d %s', name, total, formatTime(time, { autopaddingChar: '' }))
|
|
58
59
|
}
|
|
59
60
|
progress = {}
|
|
60
61
|
}
|