domma-cms 1.10.22 → 1.10.23
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/CHANGELOG.md +4 -0
- package/docs/plugin-development.md +2 -1
- package/package.json +1 -1
- package/server/services/content.js +2 -15
- package/server/services/hooks.js +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.23] - 2026-10-25
|
|
4
|
+
|
|
5
|
+
- **One plugin's broken event listener no longer stops the others.** Every `hooks.on(...)` listener now runs on its own: one that throws, or an async one that rejects, is logged, and the listeners after it still hear the event. Before, the first throw stopped the rest and reached the code that emitted it.
|
|
6
|
+
|
|
3
7
|
## [1.10.22] - 2026-10-25
|
|
4
8
|
|
|
5
9
|
- Survey definition exports and imports preserve public or invitation-only access, supporting keyless public surveys in Surveys 1.8.0. Imported surveys still start as drafts.
|
|
@@ -375,7 +375,8 @@ export default async function myPlugin(fastify, options) {
|
|
|
375
375
|
### 1. Event Bus
|
|
376
376
|
|
|
377
377
|
Subscribe to fire-and-forget lifecycle events emitted by the CMS. Handlers receive a payload object and must not return
|
|
378
|
-
a value -
|
|
378
|
+
a value - an error thrown inside a handler (or a rejected promise from an async one) is logged, and the other
|
|
379
|
+
handlers still run.
|
|
379
380
|
|
|
380
381
|
```js
|
|
381
382
|
options.hooks.on('content:pageCreated', ({ page, urlPath }) => {
|
package/package.json
CHANGED
|
@@ -409,19 +409,6 @@ export async function listMedia() {
|
|
|
409
409
|
return stats.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
/**
|
|
413
|
-
* Emit a media event without letting a listener undo the work: the hooks are a plain
|
|
414
|
-
* EventEmitter, so a plugin's listener that throws would throw out of saveMedia after the
|
|
415
|
-
* file was written - the caller would report a failure and never clean the file up.
|
|
416
|
-
*/
|
|
417
|
-
function tellListeners(event, data) {
|
|
418
|
-
try {
|
|
419
|
-
hooks.emit(event, data);
|
|
420
|
-
} catch (err) {
|
|
421
|
-
console.warn(`[media] A ${event} listener failed: ${err?.message || err}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
412
|
/**
|
|
426
413
|
* Save an uploaded file to the media directory.
|
|
427
414
|
*
|
|
@@ -444,7 +431,7 @@ export async function saveMedia(filename, buffer, {exclusive = false} = {}) {
|
|
|
444
431
|
} else {
|
|
445
432
|
await fs.writeFile(filePath, buffer);
|
|
446
433
|
}
|
|
447
|
-
|
|
434
|
+
hooks.emit('content:mediaUploaded', {filename});
|
|
448
435
|
return { name: filename, url: `/media/${filename}` };
|
|
449
436
|
}
|
|
450
437
|
|
|
@@ -457,7 +444,7 @@ export async function saveMedia(filename, buffer, {exclusive = false} = {}) {
|
|
|
457
444
|
export async function deleteMedia(filename) {
|
|
458
445
|
const filePath = path.join(MEDIA_DIR, filename);
|
|
459
446
|
await fs.unlink(filePath);
|
|
460
|
-
|
|
447
|
+
hooks.emit('content:mediaDeleted', {filename});
|
|
461
448
|
}
|
|
462
449
|
|
|
463
450
|
/**
|
package/server/services/hooks.js
CHANGED
|
@@ -12,7 +12,30 @@ import {EventEmitter} from 'node:events';
|
|
|
12
12
|
// 1. Event bus
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The events are fire-and-forget, so one listener must not stop the others or reach the code
|
|
17
|
+
* that emitted: a plain EventEmitter stops at the first listener that throws and throws out of
|
|
18
|
+
* emit(). Each listener runs on its own here; a throw or a rejected promise is logged.
|
|
19
|
+
*/
|
|
20
|
+
class HookBus extends EventEmitter {
|
|
21
|
+
emit(event, ...args) {
|
|
22
|
+
if (event === 'error') return super.emit(event, ...args);
|
|
23
|
+
const listeners = this.rawListeners(event);
|
|
24
|
+
if (!listeners.length) return false;
|
|
25
|
+
const failed = (err) => console.warn(`[hooks] A ${String(event)} listener failed: ${err?.message || err}`);
|
|
26
|
+
for (const listener of listeners) {
|
|
27
|
+
try {
|
|
28
|
+
const result = listener.apply(this, args);
|
|
29
|
+
if (typeof result?.then === 'function') result.then(null, failed);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
failed(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const hooks = new HookBus();
|
|
16
39
|
hooks.on('error', (err) => console.error('[hooks]', err));
|
|
17
40
|
|
|
18
41
|
export {hooks};
|