mikser-io 6.0.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/LICENSE +21 -0
- package/README.md +49 -0
- package/app.js +7 -0
- package/index.js +11 -0
- package/mikser-lockup-stacked.svg +14 -0
- package/package.json +64 -0
- package/src/catalog.js +56 -0
- package/src/config.js +37 -0
- package/src/constants.js +20 -0
- package/src/engine.js +317 -0
- package/src/journal.js +108 -0
- package/src/lifecycle.js +299 -0
- package/src/manager.js +119 -0
- package/src/plugins/api.js +176 -0
- package/src/plugins/assets.js +346 -0
- package/src/plugins/commands.js +75 -0
- package/src/plugins/data.js +138 -0
- package/src/plugins/documents.js +96 -0
- package/src/plugins/files.js +153 -0
- package/src/plugins/front-matter.js +24 -0
- package/src/plugins/json.js +20 -0
- package/src/plugins/layouts.js +368 -0
- package/src/plugins/mapper.js +29 -0
- package/src/plugins/post/pdf.js +60 -0
- package/src/plugins/render/asset.js +11 -0
- package/src/plugins/render/file.js +16 -0
- package/src/plugins/render/hbs.js +52 -0
- package/src/plugins/render/href.js +45 -0
- package/src/plugins/render/preset.js +13 -0
- package/src/plugins/render/resource.js +17 -0
- package/src/plugins/resources.js +216 -0
- package/src/plugins/rest.js +143 -0
- package/src/plugins/shares.js +40 -0
- package/src/plugins/validator.js +19 -0
- package/src/plugins/yaml.js +26 -0
- package/src/plugins.js +54 -0
- package/src/postprocess.js +47 -0
- package/src/render.js +71 -0
- package/src/runtime.js +153 -0
- package/src/tracking.js +74 -0
- package/src/utils.js +65 -0
package/src/journal.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import runtime from './runtime.js'
|
|
2
|
+
import { onLoaded, onCancelled, onFinalized } from './lifecycle.js'
|
|
3
|
+
import { unlink } from 'fs/promises'
|
|
4
|
+
import knex from 'knex'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { stopProgress, trackProgress, updateProgress } from './tracking.js'
|
|
7
|
+
import { AbortError } from './utils.js'
|
|
8
|
+
|
|
9
|
+
let journal
|
|
10
|
+
|
|
11
|
+
export async function addEntry({ entity, operation, context, options }) {
|
|
12
|
+
await journal('operations').insert([{ entity, operation, context, options }])
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function addEntries(entries) {
|
|
16
|
+
await journal.batchInsert('operations', entries.map(({ entity, operation, context, options }) => ({
|
|
17
|
+
entity: JSON.stringify(entity),
|
|
18
|
+
operation,
|
|
19
|
+
context: JSON.stringify(context),
|
|
20
|
+
options: JSON.stringify(options)
|
|
21
|
+
})), 10)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function updateEntry({ id, entity, output }) {
|
|
25
|
+
const data = {}
|
|
26
|
+
if (entity) data.entity = JSON.stringify(entity)
|
|
27
|
+
if (output) data.output = JSON.stringify(output)
|
|
28
|
+
await journal('operations').where({ id }).update(data)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function* useJournal(name, operations, signal) {
|
|
32
|
+
let query = journal('operations')
|
|
33
|
+
if (operations?.length) {
|
|
34
|
+
query.whereIn('operation', operations)
|
|
35
|
+
}
|
|
36
|
+
let [total] = await query.clone().count()
|
|
37
|
+
total = total['count(*)']
|
|
38
|
+
if (!total) return
|
|
39
|
+
|
|
40
|
+
trackProgress(name, total)
|
|
41
|
+
|
|
42
|
+
let offset = 0
|
|
43
|
+
const limit = 1000
|
|
44
|
+
let count = 0
|
|
45
|
+
do {
|
|
46
|
+
count = 0
|
|
47
|
+
const entries = await query.clone().orderBy('id').select().offset(offset).limit(limit)
|
|
48
|
+
for (let { id, entity, operation, context, options, output } of entries) {
|
|
49
|
+
if (signal?.aborted) {
|
|
50
|
+
stopProgress()
|
|
51
|
+
throw new AbortError()
|
|
52
|
+
}
|
|
53
|
+
count++
|
|
54
|
+
|
|
55
|
+
updateProgress()
|
|
56
|
+
yield {
|
|
57
|
+
id,
|
|
58
|
+
entity: JSON.parse(entity),
|
|
59
|
+
operation,
|
|
60
|
+
context: JSON.parse(context),
|
|
61
|
+
options: JSON.parse(options),
|
|
62
|
+
output: JSON.parse(output)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
offset += limit
|
|
66
|
+
} while (count == limit)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function clearJournal(aborted) {
|
|
70
|
+
await journal('operations').del()
|
|
71
|
+
if (!aborted) {
|
|
72
|
+
if (runtime.options.watch !== true) {
|
|
73
|
+
journal.destroy()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onLoaded(async () => {
|
|
79
|
+
const filename = path.join(runtime.options.runtimeFolder, `journal.db`)
|
|
80
|
+
try {
|
|
81
|
+
await unlink(filename)
|
|
82
|
+
} catch { }
|
|
83
|
+
|
|
84
|
+
journal = knex({
|
|
85
|
+
client: 'sqlite3',
|
|
86
|
+
connection: {
|
|
87
|
+
filename
|
|
88
|
+
},
|
|
89
|
+
useNullAsDefault: true
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await journal.schema.createTable('operations', table => {
|
|
93
|
+
table.increments('id')
|
|
94
|
+
table.string('operation').index()
|
|
95
|
+
table.json('entity')
|
|
96
|
+
table.json('context')
|
|
97
|
+
table.json('options')
|
|
98
|
+
table.json('output')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
onFinalized(async (signal) => {
|
|
103
|
+
await clearJournal(signal.aborted)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
onCancelled(async () => {
|
|
107
|
+
await clearJournal(true)
|
|
108
|
+
})
|
package/src/lifecycle.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import runtime from './runtime.js'
|
|
2
|
+
import { OPERATION } from './constants.js'
|
|
3
|
+
import { useLogger } from './engine.js'
|
|
4
|
+
import { addEntry, addEntries } from './journal.js'
|
|
5
|
+
|
|
6
|
+
export async function createEntity(entity) {
|
|
7
|
+
const logger = useLogger()
|
|
8
|
+
entity.stamp = runtime.stamp
|
|
9
|
+
entity.time = Date.now()
|
|
10
|
+
const entry = { operation: OPERATION.CREATE, entity }
|
|
11
|
+
if (await runtime.validate(entry)) {
|
|
12
|
+
logger.debug('Create %s entity: %s', entity.collection, entity.id)
|
|
13
|
+
await addEntry(entry)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function deleteEntity({ id, collection, type }) {
|
|
18
|
+
const logger = useLogger()
|
|
19
|
+
const entry = { operation: OPERATION.DELETE, entity: { id, type, collection } }
|
|
20
|
+
if (await runtime.validate(entry)) {
|
|
21
|
+
logger.debug('Delete %s entity: %s %s', collection, type, id)
|
|
22
|
+
await addEntry(entry)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function updateEntity(entity) {
|
|
27
|
+
const logger = useLogger()
|
|
28
|
+
entity.stamp = runtime.stamp
|
|
29
|
+
entity.time = Date.now()
|
|
30
|
+
const entry = { operation: OPERATION.UPDATE, entity }
|
|
31
|
+
if (await runtime.validate(entry)) {
|
|
32
|
+
logger.debug('Update %s entity: %s', entity.collection, entity.id)
|
|
33
|
+
await addEntry(entry)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function postprocessEntity(entity, options = {}, context = {}) {
|
|
38
|
+
const logger = useLogger()
|
|
39
|
+
const entry = { operation: OPERATION.POSTPROCESS, entity, options, context }
|
|
40
|
+
logger.debug('Postprocess %s entity: [%s] %s → %s', entity.collection, options.postprocessor, entity.id, entity.destination)
|
|
41
|
+
await addEntry(entry)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function postprocessEntities(tasks) {
|
|
45
|
+
const logger = useLogger()
|
|
46
|
+
if (!tasks.length) return
|
|
47
|
+
const entries = []
|
|
48
|
+
for (let { entity, options = {}, context = {} } of tasks) {
|
|
49
|
+
const entry = { operation: OPERATION.POSTPROCESS, entity, options, context }
|
|
50
|
+
logger.debug('Postprocess %s entity: [%s] %s → %s', entity.collection, options.postprocessor, entity.id, entity.destination)
|
|
51
|
+
entries.push(entry)
|
|
52
|
+
}
|
|
53
|
+
await addEntries(entries)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function renderEntities(tasks) {
|
|
57
|
+
const logger = useLogger()
|
|
58
|
+
if (!tasks.length) return
|
|
59
|
+
const entries = []
|
|
60
|
+
for (let { entity, options = {}, context = {} } of tasks) {
|
|
61
|
+
const entry = { operation: OPERATION.RENDER, entity, options, context, }
|
|
62
|
+
if (options.ignore) {
|
|
63
|
+
logger.trace('Render %s entity: [%s] %s → %s %s', entity.collection, options.renderer, entity.id, entity.destination, !options.ignore)
|
|
64
|
+
} else {
|
|
65
|
+
logger.debug('Render %s entity: [%s] %s → %s %s', entity.collection, options.renderer, entity.id, entity.destination, !options.ignore)
|
|
66
|
+
}
|
|
67
|
+
entries.push(entry)
|
|
68
|
+
}
|
|
69
|
+
await addEntries(entries)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function renderEntity(entity, options = {}, context = {}) {
|
|
73
|
+
const logger = useLogger()
|
|
74
|
+
const entry = { operation: OPERATION.RENDER, entity, options, context, }
|
|
75
|
+
if (options.ignore) {
|
|
76
|
+
logger.trace('Render %s entity: [%s] %s → %s %s', entity.collection, options.renderer, entity.id, entity.destination, !options.ignore)
|
|
77
|
+
} else {
|
|
78
|
+
logger.debug('Render %s entity: [%s] %s → %s %s', entity.collection, options.renderer, entity.id, entity.destination, !options.ignore)
|
|
79
|
+
}
|
|
80
|
+
await addEntry(entry)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function onInitialize(callback) {
|
|
84
|
+
runtime.hooks.initialize.push(callback)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function onInitialized(callback) {
|
|
88
|
+
runtime.hooks.initialized.push(callback)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function onLoad(callback) {
|
|
92
|
+
runtime.hooks.load.push(callback)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function onLoaded(callback) {
|
|
96
|
+
runtime.hooks.loaded.push(callback)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function onImport(callback) {
|
|
100
|
+
runtime.hooks.import.push(callback)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function onImported(callback) {
|
|
104
|
+
runtime.hooks.imported.push(callback)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function onProcess(callback, once) {
|
|
108
|
+
if (!once) runtime.hooks.process.push(callback)
|
|
109
|
+
else {
|
|
110
|
+
let called = false
|
|
111
|
+
runtime.hooks.process.push((signal) => {
|
|
112
|
+
if (!called) {
|
|
113
|
+
called = true
|
|
114
|
+
callback(signal)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function onProcessed(callback, once) {
|
|
121
|
+
if (!once) runtime.hooks.processed.push(callback)
|
|
122
|
+
else {
|
|
123
|
+
let called = false
|
|
124
|
+
runtime.hooks.processed.push((signal) => {
|
|
125
|
+
if (!called) {
|
|
126
|
+
called = true
|
|
127
|
+
callback(signal)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function onPersist(callback, once) {
|
|
134
|
+
if (!once) runtime.hooks.persist.push(callback)
|
|
135
|
+
else {
|
|
136
|
+
let called = false
|
|
137
|
+
runtime.hooks.persist.push((signal) => {
|
|
138
|
+
if (!called) {
|
|
139
|
+
called = true
|
|
140
|
+
callback(signal)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function onPersisted(callback, once) {
|
|
147
|
+
if (!once) runtime.hooks.persisted.push(callback)
|
|
148
|
+
else {
|
|
149
|
+
let called = false
|
|
150
|
+
runtime.hooks.persisted.push((signal) => {
|
|
151
|
+
if (!called) {
|
|
152
|
+
called = true
|
|
153
|
+
callback(signal)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function onCancel(callback) {
|
|
160
|
+
runtime.hooks.cancel.push(callback)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function onCancelled(callback) {
|
|
164
|
+
runtime.hooks.cancelled.push(callback)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function onBeforeRender(callback, once) {
|
|
168
|
+
if (!once) runtime.hooks.beforeRender.push(callback)
|
|
169
|
+
else {
|
|
170
|
+
let called = false
|
|
171
|
+
runtime.hooks.beforeRender.push((signal) => {
|
|
172
|
+
if (!called) {
|
|
173
|
+
called = true
|
|
174
|
+
callback(signal)
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function onRender(callback, once) {
|
|
181
|
+
if (!once) runtime.hooks.render.push(callback)
|
|
182
|
+
else {
|
|
183
|
+
let called = false
|
|
184
|
+
runtime.hooks.render.push((signal) => {
|
|
185
|
+
if (!called) {
|
|
186
|
+
called = true
|
|
187
|
+
callback(signal)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function onAfterRender(callback, once) {
|
|
194
|
+
if (!once) runtime.hooks.afterRender.push(callback)
|
|
195
|
+
else {
|
|
196
|
+
let called = false
|
|
197
|
+
runtime.hooks.afterRender.push((signal) => {
|
|
198
|
+
if (!called) {
|
|
199
|
+
called = true
|
|
200
|
+
callback(signal)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function onBeforePostprocess(callback, once) {
|
|
207
|
+
if (!once) runtime.hooks.beforePostprocess.push(callback)
|
|
208
|
+
else {
|
|
209
|
+
let called = false
|
|
210
|
+
runtime.hooks.beforePostprocess.push((signal) => {
|
|
211
|
+
if (!called) {
|
|
212
|
+
called = true
|
|
213
|
+
callback(signal)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function onPostprocess(callback, once) {
|
|
220
|
+
if (!once) runtime.hooks.postprocess.push(callback)
|
|
221
|
+
else {
|
|
222
|
+
let called = false
|
|
223
|
+
runtime.hooks.postprocess.push((signal) => {
|
|
224
|
+
if (!called) {
|
|
225
|
+
called = true
|
|
226
|
+
callback(signal)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function onAfterPostprocess(callback, once) {
|
|
233
|
+
if (!once) runtime.hooks.afterPostprocess.push(callback)
|
|
234
|
+
else {
|
|
235
|
+
let called = false
|
|
236
|
+
runtime.hooks.afterPostprocess.push((signal) => {
|
|
237
|
+
if (!called) {
|
|
238
|
+
called = true
|
|
239
|
+
callback(signal)
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function onFinalize(callback, once) {
|
|
246
|
+
if (!once) runtime.hooks.finalize.push(callback)
|
|
247
|
+
else {
|
|
248
|
+
let called = false
|
|
249
|
+
runtime.hooks.finalize.push((signal) => {
|
|
250
|
+
if (!called) {
|
|
251
|
+
called = true
|
|
252
|
+
callback(signal)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function onFinalized(callback, once) {
|
|
259
|
+
if (!once) runtime.hooks.finalized.push(callback)
|
|
260
|
+
else {
|
|
261
|
+
let called = false
|
|
262
|
+
runtime.hooks.finalized.push((signal) => {
|
|
263
|
+
if (!called) {
|
|
264
|
+
called = true
|
|
265
|
+
callback(signal)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function onSync(name, callback) {
|
|
272
|
+
runtime.hooks.sync.push(async (operation) => {
|
|
273
|
+
if (operation.name == name) {
|
|
274
|
+
return await callback(operation)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function onValidate(operations, callback) {
|
|
280
|
+
const logger = useLogger()
|
|
281
|
+
runtime.validators.push(async (entry) => {
|
|
282
|
+
if (operations.indexOf(entry.operation) != -1) {
|
|
283
|
+
try {
|
|
284
|
+
const message = await callback(entry)
|
|
285
|
+
if (message) {
|
|
286
|
+
logger.warn('Validation problem: [%s] %s %s', entry.operation, entry.entity.name, message)
|
|
287
|
+
}
|
|
288
|
+
return true
|
|
289
|
+
} catch (err) {
|
|
290
|
+
logger.error('Validation error: [%s] %s %s', entry.operation, entry.entity.name, err.message)
|
|
291
|
+
return false
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function onComplete(callback) {
|
|
298
|
+
runtime.hooks.completed.push(callback)
|
|
299
|
+
}
|
package/src/manager.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import runtime from './runtime.js'
|
|
2
|
+
import chokidar from 'chokidar'
|
|
3
|
+
import cron from 'node-cron'
|
|
4
|
+
import { onProcess, onFinalized } from './lifecycle.js'
|
|
5
|
+
import { useLogger } from './engine.js'
|
|
6
|
+
import { ACTION } from './constants.js'
|
|
7
|
+
|
|
8
|
+
const tasks = []
|
|
9
|
+
|
|
10
|
+
export async function createdHook(name, context) {
|
|
11
|
+
if (!runtime.started) return
|
|
12
|
+
|
|
13
|
+
const synced = await runtime.sync({
|
|
14
|
+
action: ACTION.CREATE,
|
|
15
|
+
name,
|
|
16
|
+
context
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (synced) {
|
|
20
|
+
clearTimeout(runtime.runtime.processTimeout)
|
|
21
|
+
runtime.runtime.processTimeout = setTimeout(() => runtime.process(), 1000)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function updatedHook(name, context) {
|
|
26
|
+
if (!runtime.started) return
|
|
27
|
+
|
|
28
|
+
const synced = await runtime.sync({
|
|
29
|
+
action: ACTION.UPDATE,
|
|
30
|
+
name,
|
|
31
|
+
context
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (synced) {
|
|
35
|
+
clearTimeout(runtime.runtime.processTimeout)
|
|
36
|
+
runtime.runtime.processTimeout = setTimeout(() => runtime.process(), 1000)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function triggeredHook(name, context) {
|
|
41
|
+
if (!runtime.started) return
|
|
42
|
+
|
|
43
|
+
const synced = await runtime.sync({
|
|
44
|
+
action: ACTION.TRIGGER,
|
|
45
|
+
name,
|
|
46
|
+
context
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (synced) {
|
|
50
|
+
clearTimeout(runtime.runtime.processTimeout)
|
|
51
|
+
runtime.runtime.processTimeout = setTimeout(() => runtime.process(), 1000)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function deletedHook(name, context) {
|
|
56
|
+
if (!runtime.started) return
|
|
57
|
+
|
|
58
|
+
const synced = await runtime.sync({
|
|
59
|
+
action: ACTION.DELETE,
|
|
60
|
+
name,
|
|
61
|
+
context
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (synced) {
|
|
65
|
+
clearTimeout(runtime.runtime.processTimeout)
|
|
66
|
+
runtime.runtime.processTimeout = setTimeout(() => runtime.process(), 1000)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function watch(name, folder, options = { interval: 1000, binaryInterval: 3000, ignored: /[\/\\]\./, ignoreInitial: true }) {
|
|
71
|
+
if (runtime.options.watch !== true) return
|
|
72
|
+
|
|
73
|
+
chokidar.watch(folder, options)
|
|
74
|
+
.on('all', () => {
|
|
75
|
+
clearTimeout(runtime.runtime.processTimeout)
|
|
76
|
+
})
|
|
77
|
+
.on('add', async fullPath => {
|
|
78
|
+
const relativePath = fullPath.replace(`${folder}/`, '')
|
|
79
|
+
createdHook(name, { relativePath })
|
|
80
|
+
})
|
|
81
|
+
.on('change', async fullPath => {
|
|
82
|
+
const relativePath = fullPath.replace(`${folder}/`, '')
|
|
83
|
+
updatedHook(name, { relativePath })
|
|
84
|
+
})
|
|
85
|
+
.on('unlink', async fullPath => {
|
|
86
|
+
const relativePath = fullPath.replace(`${folder}/`, '')
|
|
87
|
+
deletedHook(name, { relativePath })
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function schedule(name, expression, context) {
|
|
92
|
+
if (runtime.options.watch !== true) return
|
|
93
|
+
const logger = useLogger()
|
|
94
|
+
const taks = cron.schedule(expression, async () => {
|
|
95
|
+
logger.info('Scheduled task executed: %s %s', name, expression)
|
|
96
|
+
triggeredHook(name, context)
|
|
97
|
+
}, {
|
|
98
|
+
scheduled: false
|
|
99
|
+
})
|
|
100
|
+
tasks.push(taks)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onProcess(() => {
|
|
104
|
+
if (!tasks.length) return
|
|
105
|
+
const logger = useLogger()
|
|
106
|
+
logger.debug('Stopping scheduled tasks: %d', tasks.length)
|
|
107
|
+
for (let task of tasks) {
|
|
108
|
+
task.stop()
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
onFinalized(() => {
|
|
113
|
+
if (!tasks.length) return
|
|
114
|
+
const logger = useLogger()
|
|
115
|
+
logger.debug('Starting scheduled tasks: %d', tasks.length)
|
|
116
|
+
for (let task of tasks) {
|
|
117
|
+
task.start()
|
|
118
|
+
}
|
|
119
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { hash } from 'hasha'
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
|
|
5
|
+
export default ({
|
|
6
|
+
runtime,
|
|
7
|
+
useLogger,
|
|
8
|
+
onImport,
|
|
9
|
+
onLoaded,
|
|
10
|
+
onSync,
|
|
11
|
+
createEntity,
|
|
12
|
+
updateEntity,
|
|
13
|
+
deleteEntity,
|
|
14
|
+
findEntity,
|
|
15
|
+
findEntities,
|
|
16
|
+
schedule,
|
|
17
|
+
normalize,
|
|
18
|
+
trackProgress,
|
|
19
|
+
updateProgress,
|
|
20
|
+
}) => {
|
|
21
|
+
const format = 'api'
|
|
22
|
+
|
|
23
|
+
async function syncEntities(apiName) {
|
|
24
|
+
const logger = useLogger()
|
|
25
|
+
const syncTime = Date.now()
|
|
26
|
+
const {
|
|
27
|
+
collection = apiName,
|
|
28
|
+
type = 'document',
|
|
29
|
+
readMany,
|
|
30
|
+
uri = ''
|
|
31
|
+
} = runtime.config.api[apiName]
|
|
32
|
+
|
|
33
|
+
let synced = 0
|
|
34
|
+
let removed = 0
|
|
35
|
+
try {
|
|
36
|
+
const recent = new Set()
|
|
37
|
+
const entities = await readMany(runtime)
|
|
38
|
+
trackProgress(`Api sync ${apiName}`, entities.length)
|
|
39
|
+
for (let meta of entities) {
|
|
40
|
+
if (collection && type && meta.id) {
|
|
41
|
+
const name = path.join(collection, meta.name || meta.id.toString())
|
|
42
|
+
const id = path.join('/api', collection, meta.id.toString())
|
|
43
|
+
if (recent.has(id)) {
|
|
44
|
+
logger.error(meta, 'Duplicate entity found: %s', id)
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
recent.add(id)
|
|
48
|
+
const entity = normalize({
|
|
49
|
+
id,
|
|
50
|
+
uri: `${uri}/${meta.id}`,
|
|
51
|
+
name,
|
|
52
|
+
collection,
|
|
53
|
+
type,
|
|
54
|
+
format,
|
|
55
|
+
meta
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
entity.checksum = await hash(JSON.stringify(entity.meta), { algorithm: 'md5' })
|
|
59
|
+
const current = await findEntity({ id })
|
|
60
|
+
if (current) {
|
|
61
|
+
if (entity.checksum != current.checksum) {
|
|
62
|
+
await updateEntity(entity)
|
|
63
|
+
synced++
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
await createEntity(entity)
|
|
67
|
+
synced++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
updateProgress()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const entitiesToRemove = await findEntities(entity =>
|
|
74
|
+
entity.type == type &&
|
|
75
|
+
entity.format == format &&
|
|
76
|
+
entity.collection == collection &&
|
|
77
|
+
entity.time < syncTime &&
|
|
78
|
+
!recent.has(entity.id)
|
|
79
|
+
)
|
|
80
|
+
if (entitiesToRemove.length) trackProgress(`Api remove ${apiName}`, entitiesToRemove.length)
|
|
81
|
+
for (let entity of entitiesToRemove) {
|
|
82
|
+
deleteEntity(entity)
|
|
83
|
+
removed++
|
|
84
|
+
updateProgress()
|
|
85
|
+
}
|
|
86
|
+
if (synced || removed) {
|
|
87
|
+
logger.debug('Syncing api [%s] synced: %d, removed: %d', collection, synced, removed)
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
logger.error('Api sync [%s] error: %s', collection, err.message)
|
|
91
|
+
}
|
|
92
|
+
return synced > 0 || removed > 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function syncEntity(apiName, apiId) {
|
|
96
|
+
const logger = useLogger()
|
|
97
|
+
const {
|
|
98
|
+
collection = apiName,
|
|
99
|
+
type = 'document',
|
|
100
|
+
readOne,
|
|
101
|
+
uri = ''
|
|
102
|
+
} = runtime.config.api[apiName]
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const id = path.join('/api', collection, apiId.toString())
|
|
106
|
+
const current = await findEntity({ id })
|
|
107
|
+
const meta = await readOne(apiId, runtime)
|
|
108
|
+
if (meta?.id) {
|
|
109
|
+
const name = path.join(collection, meta.name || meta.id.toString())
|
|
110
|
+
const entity = normalize({
|
|
111
|
+
id,
|
|
112
|
+
uri: `${uri}/${meta.id}`,
|
|
113
|
+
name,
|
|
114
|
+
collection,
|
|
115
|
+
type,
|
|
116
|
+
format,
|
|
117
|
+
meta
|
|
118
|
+
})
|
|
119
|
+
entity.checksum = await hash(JSON.stringify(entity.meta), { algorithm: 'md5' })
|
|
120
|
+
if (current) {
|
|
121
|
+
if (entity.checksum != current.checksum) {
|
|
122
|
+
logger.info('Api update: %s', id)
|
|
123
|
+
await updateEntity(entity)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
logger.info('Api create: %s', id)
|
|
127
|
+
await createEntity(entity)
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
if (current) {
|
|
131
|
+
logger.info('Api delete: %s', id)
|
|
132
|
+
await deleteEntity(entity)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.error('Api sync entity [%s] error: %s', collection, err.message)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onLoaded(async () => {
|
|
141
|
+
const logger = useLogger()
|
|
142
|
+
for (let apiName in runtime.config.api || {}) {
|
|
143
|
+
const { cron } = runtime.config.api[apiName]
|
|
144
|
+
if (cron) {
|
|
145
|
+
logger.info('Schedule api: [%s] %s', apiName, cron)
|
|
146
|
+
schedule(apiName, cron)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
onSync(apiName, async ({ context }) => {
|
|
150
|
+
if (context?.id) {
|
|
151
|
+
return syncEntity(apiName, context.id)
|
|
152
|
+
} else {
|
|
153
|
+
return syncEntities(apiName)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const { origin } = new URL(runtime.config.api[apiName].uri)
|
|
158
|
+
onSync(origin, async ({ context }) => {
|
|
159
|
+
if (context.uri) {
|
|
160
|
+
logger.info('Syncing api: [%s] %s', apiName, context.uri)
|
|
161
|
+
return syncEntities(apiName)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
onImport(async () => {
|
|
168
|
+
for (let apiName in runtime.config.api || {}) {
|
|
169
|
+
await syncEntities(apiName)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
format
|
|
175
|
+
}
|
|
176
|
+
}
|