@nocobase/server 1.8.0-beta.12 → 1.8.0-beta.13
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/lib/application.d.ts +5 -0
- package/lib/application.js +6 -0
- package/lib/event-queue.d.ts +89 -0
- package/lib/event-queue.js +328 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/package.json +15 -15
package/lib/application.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ import AesEncryptor from './aes-encryptor';
|
|
|
35
35
|
import { AuditManager } from './audit-manager';
|
|
36
36
|
import { Environment } from './environment';
|
|
37
37
|
import { ServiceContainer } from './service-container';
|
|
38
|
+
import { EventQueue, EventQueueOptions } from './event-queue';
|
|
38
39
|
export type PluginType = string | typeof Plugin;
|
|
39
40
|
export type PluginConfiguration = PluginType | [PluginType, any];
|
|
40
41
|
export interface ResourceManagerOptions {
|
|
@@ -55,6 +56,7 @@ export interface AppTelemetryOptions extends TelemetryOptions {
|
|
|
55
56
|
enabled?: boolean;
|
|
56
57
|
}
|
|
57
58
|
export interface ApplicationOptions {
|
|
59
|
+
instanceId?: string;
|
|
58
60
|
database?: IDatabaseOptions | Database;
|
|
59
61
|
cacheManager?: CacheManagerOptions;
|
|
60
62
|
/**
|
|
@@ -82,6 +84,7 @@ export interface ApplicationOptions {
|
|
|
82
84
|
authManager?: AuthManagerOptions;
|
|
83
85
|
auditManager?: AuditManager;
|
|
84
86
|
lockManager?: LockManagerOptions;
|
|
87
|
+
eventQueue?: EventQueueOptions;
|
|
85
88
|
/**
|
|
86
89
|
* @internal
|
|
87
90
|
*/
|
|
@@ -128,6 +131,7 @@ export type MaintainingCommandStatus = {
|
|
|
128
131
|
};
|
|
129
132
|
export declare class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
|
|
130
133
|
options: ApplicationOptions;
|
|
134
|
+
readonly instanceId: string;
|
|
131
135
|
/**
|
|
132
136
|
* @internal
|
|
133
137
|
*/
|
|
@@ -174,6 +178,7 @@ export declare class Application<StateT = DefaultState, ContextT = DefaultContex
|
|
|
174
178
|
private _actionCommand;
|
|
175
179
|
container: ServiceContainer;
|
|
176
180
|
lockManager: LockManager;
|
|
181
|
+
eventQueue: EventQueue;
|
|
177
182
|
constructor(options: ApplicationOptions);
|
|
178
183
|
private static staticCommands;
|
|
179
184
|
static addCommand(callback: (app: Application) => void): void;
|
package/lib/application.js
CHANGED
|
@@ -56,6 +56,7 @@ var import_glob = __toESM(require("glob"));
|
|
|
56
56
|
var import_koa = __toESM(require("koa"));
|
|
57
57
|
var import_koa_compose = __toESM(require("koa-compose"));
|
|
58
58
|
var import_lodash = __toESM(require("lodash"));
|
|
59
|
+
var import_nanoid = require("nanoid");
|
|
59
60
|
var import_path = __toESM(require("path"));
|
|
60
61
|
var import_semver = __toESM(require("semver"));
|
|
61
62
|
var import_acl = require("./acl");
|
|
@@ -81,10 +82,12 @@ var import_aes_encryptor = __toESM(require("./aes-encryptor"));
|
|
|
81
82
|
var import_audit_manager = require("./audit-manager");
|
|
82
83
|
var import_environment = require("./environment");
|
|
83
84
|
var import_service_container = require("./service-container");
|
|
85
|
+
var import_event_queue = require("./event-queue");
|
|
84
86
|
const _Application = class _Application extends import_koa.default {
|
|
85
87
|
constructor(options) {
|
|
86
88
|
super();
|
|
87
89
|
this.options = options;
|
|
90
|
+
this.instanceId = options.instanceId || (0, import_nanoid.nanoid)();
|
|
88
91
|
this.context.reqId = (0, import_crypto.randomUUID)();
|
|
89
92
|
this.rawOptions = this.name == "main" ? import_lodash.default.cloneDeep(options) : {};
|
|
90
93
|
this.init();
|
|
@@ -92,6 +95,7 @@ const _Application = class _Application extends import_koa.default {
|
|
|
92
95
|
this._appSupervisor.addApp(this);
|
|
93
96
|
}
|
|
94
97
|
}
|
|
98
|
+
instanceId;
|
|
95
99
|
/**
|
|
96
100
|
* @internal
|
|
97
101
|
*/
|
|
@@ -131,6 +135,7 @@ const _Application = class _Application extends import_koa.default {
|
|
|
131
135
|
_actionCommand;
|
|
132
136
|
container = new import_service_container.ServiceContainer();
|
|
133
137
|
lockManager;
|
|
138
|
+
eventQueue;
|
|
134
139
|
static addCommand(callback) {
|
|
135
140
|
this.staticCommands.push(callback);
|
|
136
141
|
}
|
|
@@ -855,6 +860,7 @@ const _Application = class _Application extends import_koa.default {
|
|
|
855
860
|
this._i18n = (0, import_helper.createI18n)(options);
|
|
856
861
|
this.pubSubManager = (0, import_pub_sub_manager.createPubSubManager)(this, options.pubSubManager);
|
|
857
862
|
this.syncMessageManager = new import_sync_message_manager.SyncMessageManager(this, options.syncMessageManager);
|
|
863
|
+
this.eventQueue = new import_event_queue.EventQueue(this, options.eventQueue);
|
|
858
864
|
this.lockManager = new import_lock_manager.LockManager({
|
|
859
865
|
defaultAdapter: process.env.LOCK_ADAPTER_DEFAULT,
|
|
860
866
|
...options.lockManager
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import Application from './application';
|
|
10
|
+
export type QueueCallbackOptions = {
|
|
11
|
+
id?: string;
|
|
12
|
+
retried?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
};
|
|
15
|
+
export type QueueCallback = (message: any, options: QueueCallbackOptions) => Promise<void> | void;
|
|
16
|
+
export type QueueEventOptions = {
|
|
17
|
+
interval?: number;
|
|
18
|
+
concurrency?: number;
|
|
19
|
+
idle(): boolean;
|
|
20
|
+
process: QueueCallback;
|
|
21
|
+
};
|
|
22
|
+
export type QueueMessageOptions = {
|
|
23
|
+
timeout?: number;
|
|
24
|
+
maxRetries?: number;
|
|
25
|
+
retried?: number;
|
|
26
|
+
timestamp?: number;
|
|
27
|
+
};
|
|
28
|
+
export interface IEventQueueAdapter {
|
|
29
|
+
isConnected(): boolean;
|
|
30
|
+
connect(): Promise<void> | void;
|
|
31
|
+
close(): Promise<void> | void;
|
|
32
|
+
subscribe(channel: string, event: QueueEventOptions): void;
|
|
33
|
+
unsubscribe(channel: string): void;
|
|
34
|
+
publish(channel: string, message: any, options: QueueMessageOptions): Promise<void> | void;
|
|
35
|
+
}
|
|
36
|
+
export interface EventQueueOptions {
|
|
37
|
+
channelPrefix?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const QUEUE_DEFAULT_INTERVAL = 1000;
|
|
40
|
+
export declare const QUEUE_DEFAULT_CONCURRENCY = 1;
|
|
41
|
+
export declare const QUEUE_DEFAULT_ACK_TIMEOUT = 15000;
|
|
42
|
+
export declare class MemoryEventQueueAdapter implements IEventQueueAdapter {
|
|
43
|
+
private options;
|
|
44
|
+
private connected;
|
|
45
|
+
private emitter;
|
|
46
|
+
private reading;
|
|
47
|
+
private events;
|
|
48
|
+
protected queues: Map<string, {
|
|
49
|
+
id: string;
|
|
50
|
+
content: any;
|
|
51
|
+
options?: QueueMessageOptions;
|
|
52
|
+
}[]>;
|
|
53
|
+
get processing(): Promise<void[]>;
|
|
54
|
+
get storagePath(): string;
|
|
55
|
+
listen: (channel: string) => Promise<void>;
|
|
56
|
+
constructor(options: {
|
|
57
|
+
appName: string;
|
|
58
|
+
});
|
|
59
|
+
isConnected(): boolean;
|
|
60
|
+
private loadFromStorage;
|
|
61
|
+
private saveToStorage;
|
|
62
|
+
connect(): Promise<void>;
|
|
63
|
+
close(): Promise<void>;
|
|
64
|
+
subscribe(channel: string, options: QueueEventOptions): void;
|
|
65
|
+
unsubscribe(channel: string): void;
|
|
66
|
+
publish(channel: string, content: any, options?: QueueMessageOptions): void;
|
|
67
|
+
read(channel: string): Promise<void>;
|
|
68
|
+
process(channel: any, { id, message }: {
|
|
69
|
+
id: any;
|
|
70
|
+
message: any;
|
|
71
|
+
}): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare class EventQueue {
|
|
74
|
+
protected app: Application;
|
|
75
|
+
protected options: EventQueueOptions;
|
|
76
|
+
protected adapter: IEventQueueAdapter;
|
|
77
|
+
protected events: Map<string, QueueEventOptions>;
|
|
78
|
+
get channelPrefix(): string;
|
|
79
|
+
constructor(app: Application, options?: EventQueueOptions);
|
|
80
|
+
getFullChannel(channel: string): string;
|
|
81
|
+
setAdapter<A extends IEventQueueAdapter>(adapter: A): void;
|
|
82
|
+
isConnected(): boolean;
|
|
83
|
+
connect(): Promise<void>;
|
|
84
|
+
close(): Promise<void>;
|
|
85
|
+
subscribe(channel: string, options: QueueEventOptions): void;
|
|
86
|
+
unsubscribe(channel: string): void;
|
|
87
|
+
publish(channel: string, message: any, options?: QueueMessageOptions): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
export default EventQueue;
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var event_queue_exports = {};
|
|
39
|
+
__export(event_queue_exports, {
|
|
40
|
+
EventQueue: () => EventQueue,
|
|
41
|
+
MemoryEventQueueAdapter: () => MemoryEventQueueAdapter,
|
|
42
|
+
QUEUE_DEFAULT_ACK_TIMEOUT: () => QUEUE_DEFAULT_ACK_TIMEOUT,
|
|
43
|
+
QUEUE_DEFAULT_CONCURRENCY: () => QUEUE_DEFAULT_CONCURRENCY,
|
|
44
|
+
QUEUE_DEFAULT_INTERVAL: () => QUEUE_DEFAULT_INTERVAL,
|
|
45
|
+
default: () => event_queue_default
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(event_queue_exports);
|
|
48
|
+
var import_crypto = require("crypto");
|
|
49
|
+
var import_events = require("events");
|
|
50
|
+
var import_path = __toESM(require("path"));
|
|
51
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
52
|
+
const QUEUE_DEFAULT_INTERVAL = 1e3;
|
|
53
|
+
const QUEUE_DEFAULT_CONCURRENCY = 1;
|
|
54
|
+
const QUEUE_DEFAULT_ACK_TIMEOUT = 15e3;
|
|
55
|
+
const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.emitter.setMaxListeners(0);
|
|
59
|
+
}
|
|
60
|
+
connected = false;
|
|
61
|
+
emitter = new import_events.EventEmitter();
|
|
62
|
+
reading = /* @__PURE__ */ new Map();
|
|
63
|
+
events = /* @__PURE__ */ new Map();
|
|
64
|
+
queues = /* @__PURE__ */ new Map();
|
|
65
|
+
get processing() {
|
|
66
|
+
const processing = Array.from(this.reading.values());
|
|
67
|
+
if (processing.length > 0) {
|
|
68
|
+
return Promise.all(processing);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
get storagePath() {
|
|
73
|
+
return import_path.default.resolve(process.cwd(), "storage", "apps", this.options.appName, "event-queue.json");
|
|
74
|
+
}
|
|
75
|
+
listen = /* @__PURE__ */ __name(async (channel) => {
|
|
76
|
+
if (!this.connected) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (this.reading.has(channel)) {
|
|
80
|
+
console.debug(`memory queue (${channel}) is already reading, waiting last reading to end...`);
|
|
81
|
+
await this.reading.get(channel);
|
|
82
|
+
}
|
|
83
|
+
const event = this.events.get(channel);
|
|
84
|
+
if (!event) {
|
|
85
|
+
console.warn(`memory queue (${channel}) not found, skipping...`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!event.idle()) {
|
|
89
|
+
console.debug(`memory queue (${channel}) is not idle, skipping...`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const reading = this.read(channel);
|
|
93
|
+
this.reading.set(channel, reading);
|
|
94
|
+
await reading;
|
|
95
|
+
}, "listen");
|
|
96
|
+
isConnected() {
|
|
97
|
+
return this.connected;
|
|
98
|
+
}
|
|
99
|
+
async loadFromStorage() {
|
|
100
|
+
let queues = {};
|
|
101
|
+
let exists = false;
|
|
102
|
+
try {
|
|
103
|
+
await import_promises.default.stat(this.storagePath);
|
|
104
|
+
exists = true;
|
|
105
|
+
} catch (ex) {
|
|
106
|
+
console.info(`memory queue storage file not found, skip`);
|
|
107
|
+
}
|
|
108
|
+
if (exists) {
|
|
109
|
+
try {
|
|
110
|
+
const queueJson = await import_promises.default.readFile(this.storagePath);
|
|
111
|
+
queues = JSON.parse(queueJson.toString());
|
|
112
|
+
console.debug("memory queue loaded from storage", queues);
|
|
113
|
+
await import_promises.default.unlink(this.storagePath);
|
|
114
|
+
} catch (ex) {
|
|
115
|
+
console.error("failed to load queue from storage", ex);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.queues = new Map(Object.entries(queues));
|
|
119
|
+
}
|
|
120
|
+
async saveToStorage() {
|
|
121
|
+
const queues = Array.from(this.queues.entries()).reduce((acc, [channel, queue]) => {
|
|
122
|
+
if (queue == null ? void 0 : queue.length) {
|
|
123
|
+
acc[channel] = queue;
|
|
124
|
+
}
|
|
125
|
+
return acc;
|
|
126
|
+
}, {});
|
|
127
|
+
if (Object.keys(queues).length) {
|
|
128
|
+
await import_promises.default.mkdir(import_path.default.dirname(this.storagePath), { recursive: true });
|
|
129
|
+
await import_promises.default.writeFile(this.storagePath, JSON.stringify(queues));
|
|
130
|
+
console.debug("memory queue saved to storage", queues);
|
|
131
|
+
} else {
|
|
132
|
+
console.debug("memory queue empty, no need to save to storage");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async connect() {
|
|
136
|
+
if (this.connected) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await this.loadFromStorage();
|
|
140
|
+
this.connected = true;
|
|
141
|
+
setImmediate(() => {
|
|
142
|
+
for (const channel of this.queues.keys()) {
|
|
143
|
+
const queue = this.queues.get(channel);
|
|
144
|
+
if (!(queue == null ? void 0 : queue.length)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
this.emitter.emit(channel, channel);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async close() {
|
|
152
|
+
this.connected = false;
|
|
153
|
+
if (this.processing) {
|
|
154
|
+
console.info("memory queue waiting for processing job...");
|
|
155
|
+
await this.processing;
|
|
156
|
+
console.info("memory queue job cleaned");
|
|
157
|
+
}
|
|
158
|
+
console.log("memory queue gracefully shutting down...");
|
|
159
|
+
await this.saveToStorage();
|
|
160
|
+
}
|
|
161
|
+
subscribe(channel, options) {
|
|
162
|
+
if (this.events.has(channel)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.events.set(channel, options);
|
|
166
|
+
if (!this.queues.has(channel)) {
|
|
167
|
+
this.queues.set(channel, []);
|
|
168
|
+
}
|
|
169
|
+
this.emitter.on(channel, this.listen);
|
|
170
|
+
}
|
|
171
|
+
unsubscribe(channel) {
|
|
172
|
+
if (!this.events.has(channel)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.events.delete(channel);
|
|
176
|
+
}
|
|
177
|
+
publish(channel, content, options = { timestamp: Date.now() }) {
|
|
178
|
+
const event = this.events.get(channel);
|
|
179
|
+
if (!event) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!this.queues.get(channel)) {
|
|
183
|
+
this.queues.set(channel, []);
|
|
184
|
+
}
|
|
185
|
+
const queue = this.queues.get(channel);
|
|
186
|
+
queue.push({ id: (0, import_crypto.randomUUID)(), content, options });
|
|
187
|
+
console.debug(`memory queue (${channel}) published message`, content);
|
|
188
|
+
setImmediate(() => {
|
|
189
|
+
this.emitter.emit(channel, channel);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async read(channel) {
|
|
193
|
+
const event = this.events.get(channel);
|
|
194
|
+
if (!event) {
|
|
195
|
+
this.reading.delete(channel);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const queue = this.queues.get(channel);
|
|
199
|
+
while (queue == null ? void 0 : queue.length) {
|
|
200
|
+
const messages = queue.slice(0, event.concurrency || QUEUE_DEFAULT_CONCURRENCY);
|
|
201
|
+
console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
|
|
202
|
+
queue.splice(0, messages.length);
|
|
203
|
+
const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message }));
|
|
204
|
+
await Promise.all(batch);
|
|
205
|
+
}
|
|
206
|
+
this.reading.delete(channel);
|
|
207
|
+
}
|
|
208
|
+
async process(channel, { id, message }) {
|
|
209
|
+
const event = this.events.get(channel);
|
|
210
|
+
const { content, options: { timeout = QUEUE_DEFAULT_ACK_TIMEOUT, maxRetries = 0, retried = 0 } = {} } = message;
|
|
211
|
+
try {
|
|
212
|
+
console.debug(`memory queue (${channel}) processing message (${id})...`, content);
|
|
213
|
+
await event.process(content, {
|
|
214
|
+
id,
|
|
215
|
+
retried,
|
|
216
|
+
signal: AbortSignal.timeout(timeout)
|
|
217
|
+
});
|
|
218
|
+
console.debug(`memory queue (${channel}) consumed message (${id})`);
|
|
219
|
+
} catch (ex) {
|
|
220
|
+
if (maxRetries > 0 && retried < maxRetries) {
|
|
221
|
+
const currentRetry = retried + 1;
|
|
222
|
+
console.warn(
|
|
223
|
+
`memory queue (${channel}) consum message (${id}) failed, retrying (${currentRetry} / ${maxRetries})...`,
|
|
224
|
+
ex
|
|
225
|
+
);
|
|
226
|
+
setImmediate(() => {
|
|
227
|
+
this.publish(channel, content, { timeout, maxRetries, retried: currentRetry, timestamp: Date.now() });
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
console.error(ex);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
__name(_MemoryEventQueueAdapter, "MemoryEventQueueAdapter");
|
|
236
|
+
let MemoryEventQueueAdapter = _MemoryEventQueueAdapter;
|
|
237
|
+
const _EventQueue = class _EventQueue {
|
|
238
|
+
constructor(app, options = {}) {
|
|
239
|
+
this.app = app;
|
|
240
|
+
this.options = options;
|
|
241
|
+
this.events = /* @__PURE__ */ new Map();
|
|
242
|
+
this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name }));
|
|
243
|
+
app.on("afterStart", async () => {
|
|
244
|
+
await this.connect();
|
|
245
|
+
});
|
|
246
|
+
app.on("beforeStop", async () => {
|
|
247
|
+
app.logger.info("[queue] gracefully shuting down...");
|
|
248
|
+
await this.close();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
adapter;
|
|
252
|
+
events = /* @__PURE__ */ new Map();
|
|
253
|
+
get channelPrefix() {
|
|
254
|
+
var _a;
|
|
255
|
+
return (_a = this.options) == null ? void 0 : _a.channelPrefix;
|
|
256
|
+
}
|
|
257
|
+
getFullChannel(channel) {
|
|
258
|
+
return [this.app.name, this.channelPrefix, channel].filter(Boolean).join(".");
|
|
259
|
+
}
|
|
260
|
+
setAdapter(adapter) {
|
|
261
|
+
this.adapter = adapter;
|
|
262
|
+
}
|
|
263
|
+
isConnected() {
|
|
264
|
+
if (!this.adapter) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return this.adapter.isConnected();
|
|
268
|
+
}
|
|
269
|
+
async connect() {
|
|
270
|
+
if (!this.adapter) {
|
|
271
|
+
throw new Error("no adapter set, cannot connect");
|
|
272
|
+
}
|
|
273
|
+
await this.adapter.connect();
|
|
274
|
+
for (const [channel, event] of this.events.entries()) {
|
|
275
|
+
this.adapter.subscribe(this.getFullChannel(channel), event);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async close() {
|
|
279
|
+
if (!this.adapter) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
await this.adapter.close();
|
|
283
|
+
for (const channel of this.events.keys()) {
|
|
284
|
+
this.adapter.unsubscribe(this.getFullChannel(channel));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
subscribe(channel, options) {
|
|
288
|
+
if (this.events.has(channel)) {
|
|
289
|
+
this.app.logger.warn(`event queue already subscribed on channel "${channel}", new subscription will be ignored`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.events.set(channel, options);
|
|
293
|
+
if (this.isConnected()) {
|
|
294
|
+
this.adapter.subscribe(this.getFullChannel(channel), options);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
unsubscribe(channel) {
|
|
298
|
+
if (!this.events.has(channel)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.events.delete(channel);
|
|
302
|
+
if (this.isConnected()) {
|
|
303
|
+
this.adapter.unsubscribe(this.getFullChannel(channel));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async publish(channel, message, options = {}) {
|
|
307
|
+
if (!this.adapter) {
|
|
308
|
+
throw new Error("no adapter set, cannot publish");
|
|
309
|
+
}
|
|
310
|
+
if (!this.isConnected()) {
|
|
311
|
+
throw new Error("event queue not connected, cannot publish");
|
|
312
|
+
}
|
|
313
|
+
const c = this.getFullChannel(channel);
|
|
314
|
+
this.app.logger.debug("event queue publishing:", { channel: c, message });
|
|
315
|
+
await this.adapter.publish(c, message, { timeout: QUEUE_DEFAULT_ACK_TIMEOUT, ...options, timestamp: Date.now() });
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
__name(_EventQueue, "EventQueue");
|
|
319
|
+
let EventQueue = _EventQueue;
|
|
320
|
+
var event_queue_default = EventQueue;
|
|
321
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
322
|
+
0 && (module.exports = {
|
|
323
|
+
EventQueue,
|
|
324
|
+
MemoryEventQueueAdapter,
|
|
325
|
+
QUEUE_DEFAULT_ACK_TIMEOUT,
|
|
326
|
+
QUEUE_DEFAULT_CONCURRENCY,
|
|
327
|
+
QUEUE_DEFAULT_INTERVAL
|
|
328
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './migration';
|
|
|
17
17
|
export * from './plugin';
|
|
18
18
|
export * from './plugin-manager';
|
|
19
19
|
export * from './pub-sub-manager';
|
|
20
|
+
export * from './event-queue';
|
|
20
21
|
export declare const OFFICIAL_PLUGIN_PREFIX = "@nocobase/plugin-";
|
|
21
22
|
export { appendToBuiltInPlugins, findAllPlugins, findBuiltInPlugins, findLocalPlugins, packageNameTrim, } from './plugin-manager/findPackageNames';
|
|
22
23
|
export { runPluginStaticImports } from './run-plugin-static-imports';
|
package/lib/index.js
CHANGED
|
@@ -59,6 +59,7 @@ __reExport(src_exports, require("./migration"), module.exports);
|
|
|
59
59
|
__reExport(src_exports, require("./plugin"), module.exports);
|
|
60
60
|
__reExport(src_exports, require("./plugin-manager"), module.exports);
|
|
61
61
|
__reExport(src_exports, require("./pub-sub-manager"), module.exports);
|
|
62
|
+
__reExport(src_exports, require("./event-queue"), module.exports);
|
|
62
63
|
var import_findPackageNames = require("./plugin-manager/findPackageNames");
|
|
63
64
|
var import_run_plugin_static_imports = require("./run-plugin-static-imports");
|
|
64
65
|
const OFFICIAL_PLUGIN_PREFIX = "@nocobase/plugin-";
|
|
@@ -80,5 +81,6 @@ const OFFICIAL_PLUGIN_PREFIX = "@nocobase/plugin-";
|
|
|
80
81
|
...require("./migration"),
|
|
81
82
|
...require("./plugin"),
|
|
82
83
|
...require("./plugin-manager"),
|
|
83
|
-
...require("./pub-sub-manager")
|
|
84
|
+
...require("./pub-sub-manager"),
|
|
85
|
+
...require("./event-queue")
|
|
84
86
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/server",
|
|
3
|
-
"version": "1.8.0-beta.
|
|
3
|
+
"version": "1.8.0-beta.13",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
"@koa/cors": "^5.0.0",
|
|
11
11
|
"@koa/multer": "^3.1.0",
|
|
12
12
|
"@koa/router": "^13.1.0",
|
|
13
|
-
"@nocobase/acl": "1.8.0-beta.
|
|
14
|
-
"@nocobase/actions": "1.8.0-beta.
|
|
15
|
-
"@nocobase/auth": "1.8.0-beta.
|
|
16
|
-
"@nocobase/cache": "1.8.0-beta.
|
|
17
|
-
"@nocobase/data-source-manager": "1.8.0-beta.
|
|
18
|
-
"@nocobase/database": "1.8.0-beta.
|
|
19
|
-
"@nocobase/evaluators": "1.8.0-beta.
|
|
20
|
-
"@nocobase/lock-manager": "1.8.0-beta.
|
|
21
|
-
"@nocobase/logger": "1.8.0-beta.
|
|
22
|
-
"@nocobase/resourcer": "1.8.0-beta.
|
|
23
|
-
"@nocobase/sdk": "1.8.0-beta.
|
|
24
|
-
"@nocobase/telemetry": "1.8.0-beta.
|
|
25
|
-
"@nocobase/utils": "1.8.0-beta.
|
|
13
|
+
"@nocobase/acl": "1.8.0-beta.13",
|
|
14
|
+
"@nocobase/actions": "1.8.0-beta.13",
|
|
15
|
+
"@nocobase/auth": "1.8.0-beta.13",
|
|
16
|
+
"@nocobase/cache": "1.8.0-beta.13",
|
|
17
|
+
"@nocobase/data-source-manager": "1.8.0-beta.13",
|
|
18
|
+
"@nocobase/database": "1.8.0-beta.13",
|
|
19
|
+
"@nocobase/evaluators": "1.8.0-beta.13",
|
|
20
|
+
"@nocobase/lock-manager": "1.8.0-beta.13",
|
|
21
|
+
"@nocobase/logger": "1.8.0-beta.13",
|
|
22
|
+
"@nocobase/resourcer": "1.8.0-beta.13",
|
|
23
|
+
"@nocobase/sdk": "1.8.0-beta.13",
|
|
24
|
+
"@nocobase/telemetry": "1.8.0-beta.13",
|
|
25
|
+
"@nocobase/utils": "1.8.0-beta.13",
|
|
26
26
|
"@types/decompress": "4.2.7",
|
|
27
27
|
"@types/ini": "^1.3.31",
|
|
28
28
|
"@types/koa-send": "^4.1.3",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"@types/serve-handler": "^6.1.1",
|
|
58
58
|
"@types/ws": "^8.5.5"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "9ccd306ff667a54317ba07a000fc5aa00685d5e6"
|
|
61
61
|
}
|