@nocobase/server 1.9.0-beta.4 → 1.9.0-beta.6

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.
@@ -279,6 +279,9 @@ const _Application = class _Application extends import_koa.default {
279
279
  if (!WORKER_MODE) {
280
280
  return true;
281
281
  }
282
+ if (WORKER_MODE === "-") {
283
+ return false;
284
+ }
282
285
  const topics = WORKER_MODE.trim().split(",");
283
286
  if (key) {
284
287
  if (WORKER_MODE === "*") {
@@ -252,6 +252,9 @@ const _AuditManager = class _AuditManager {
252
252
  async output(ctx, reqId, metadata) {
253
253
  var _a;
254
254
  try {
255
+ if (!ctx.action) {
256
+ return;
257
+ }
255
258
  const { resourceName, actionName } = ctx.action;
256
259
  const action = this.getAction(actionName, resourceName);
257
260
  if (!action) {
@@ -15,7 +15,7 @@ export type QueueCallbackOptions = {
15
15
  retried?: number;
16
16
  signal?: AbortSignal;
17
17
  };
18
- export type QueueCallback = (message: any, options: QueueCallbackOptions) => Promise<void> | void;
18
+ export type QueueCallback = (message: any, options: QueueCallbackOptions) => Promise<void>;
19
19
  export type QueueEventOptions = {
20
20
  /**
21
21
  * @experimental
@@ -53,9 +53,9 @@ export declare class MemoryEventQueueAdapter implements IEventQueueAdapter {
53
53
  content: any;
54
54
  options?: QueueMessageOptions;
55
55
  }[]>;
56
- get processing(): Promise<void[]>;
56
+ get processing(): Promise<Promise<void>[][]>;
57
57
  private get storagePath();
58
- listen: (channel: string) => Promise<void>;
58
+ listen: (channel: string) => void;
59
59
  constructor(options: {
60
60
  appName: string;
61
61
  });
@@ -69,7 +69,7 @@ export declare class MemoryEventQueueAdapter implements IEventQueueAdapter {
69
69
  unsubscribe(channel: string): void;
70
70
  publish(channel: string, content: any, options?: QueueMessageOptions): void;
71
71
  consume(channel: string, once?: boolean): Promise<void>;
72
- read(channel: string): Promise<void>;
72
+ read(channel: string, n: number): Promise<void>[];
73
73
  process(channel: string, { id, message }: {
74
74
  id: any;
75
75
  message: any;
@@ -73,26 +73,37 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
73
73
  get storagePath() {
74
74
  return import_path.default.resolve(process.cwd(), "storage", "apps", this.options.appName, "event-queue.json");
75
75
  }
76
- listen = /* @__PURE__ */ __name(async (channel) => {
76
+ listen = /* @__PURE__ */ __name((channel) => {
77
77
  if (!this.connected) {
78
78
  return;
79
79
  }
80
- if (this.reading.has(channel)) {
81
- console.debug(`memory queue (${channel}) is already reading, waiting last reading to end...`);
82
- await this.reading.get(channel);
83
- }
84
80
  const event = this.events.get(channel);
85
81
  if (!event) {
86
82
  console.warn(`memory queue (${channel}) not found, skipping...`);
87
83
  return;
88
84
  }
89
85
  if (!event.idle()) {
90
- console.debug(`memory queue (${channel}) is not idle, skipping...`);
91
86
  return;
92
87
  }
93
- const reading = this.read(channel);
88
+ const reading = this.reading.get(channel) || [];
89
+ const count = (event.concurrency || QUEUE_DEFAULT_CONCURRENCY) - reading.length;
90
+ if (count <= 0) {
91
+ console.debug(
92
+ `memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`
93
+ );
94
+ return;
95
+ }
96
+ console.debug(`reading more from queue (${channel}), count: ${count}`);
97
+ this.read(channel, count).forEach((promise) => {
98
+ reading.push(promise);
99
+ promise.finally(() => {
100
+ const index = reading.indexOf(promise);
101
+ if (index > -1) {
102
+ reading.splice(index, 1);
103
+ }
104
+ });
105
+ });
94
106
  this.reading.set(channel, reading);
95
- await reading;
96
107
  }, "listen");
97
108
  isConnected() {
98
109
  return this.connected;
@@ -149,6 +160,9 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
149
160
  });
150
161
  }
151
162
  async close() {
163
+ if (!this.connected) {
164
+ return;
165
+ }
152
166
  this.connected = false;
153
167
  if (this.processing) {
154
168
  console.info("memory queue waiting for processing job...");
@@ -181,6 +195,7 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
181
195
  publish(channel, content, options = { timestamp: Date.now() }) {
182
196
  const event = this.events.get(channel);
183
197
  if (!event) {
198
+ console.debug(`memory queue (${channel}) not subscribed, skip`);
184
199
  return;
185
200
  }
186
201
  if (!this.queues.get(channel)) {
@@ -200,7 +215,7 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
200
215
  const interval = event.interval || QUEUE_DEFAULT_INTERVAL;
201
216
  const queue = this.queues.get(channel);
202
217
  if (event.idle() && (queue == null ? void 0 : queue.length)) {
203
- await this.listen(channel);
218
+ this.listen(channel);
204
219
  }
205
220
  if (once) {
206
221
  break;
@@ -208,47 +223,41 @@ const _MemoryEventQueueAdapter = class _MemoryEventQueueAdapter {
208
223
  await (0, import_utils.sleep)(interval);
209
224
  }
210
225
  }
211
- async read(channel) {
212
- const event = this.events.get(channel);
213
- if (!event) {
214
- this.reading.delete(channel);
215
- return;
216
- }
226
+ read(channel, n) {
217
227
  const queue = this.queues.get(channel);
218
- if (queue == null ? void 0 : queue.length) {
219
- const messages = queue.slice(0, event.concurrency || QUEUE_DEFAULT_CONCURRENCY);
220
- console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
221
- queue.splice(0, messages.length);
222
- const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message }));
223
- await Promise.all(batch);
224
- }
225
- this.reading.delete(channel);
228
+ if (!(queue == null ? void 0 : queue.length)) {
229
+ return [];
230
+ }
231
+ const messages = queue.slice(0, n);
232
+ console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
233
+ queue.splice(0, messages.length);
234
+ const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message }));
235
+ return batch;
226
236
  }
227
237
  async process(channel, { id, message }) {
228
238
  const event = this.events.get(channel);
229
239
  const { content, options: { timeout = QUEUE_DEFAULT_ACK_TIMEOUT, maxRetries = 0, retried = 0 } = {} } = message;
230
- try {
231
- console.debug(`memory queue (${channel}) processing message (${id})...`, content);
232
- await event.process(content, {
233
- id,
234
- retried,
235
- signal: AbortSignal.timeout(timeout)
236
- });
240
+ console.debug(`memory queue (${channel}) processing message (${id})...`, content);
241
+ return (async () => event.process(content, {
242
+ id,
243
+ retried,
244
+ signal: AbortSignal.timeout(timeout)
245
+ }))().then(() => {
237
246
  console.debug(`memory queue (${channel}) consumed message (${id})`);
238
- } catch (ex) {
247
+ }).catch((ex) => {
239
248
  if (maxRetries > 0 && retried < maxRetries) {
240
249
  const currentRetry = retried + 1;
241
250
  console.warn(
242
251
  `memory queue (${channel}) consum message (${id}) failed, retrying (${currentRetry} / ${maxRetries})...`,
243
252
  ex
244
253
  );
245
- setImmediate(() => {
254
+ setTimeout(() => {
246
255
  this.publish(channel, content, { timeout, maxRetries, retried: currentRetry, timestamp: Date.now() });
247
- });
256
+ }, 500);
248
257
  } else {
249
258
  console.error(ex);
250
259
  }
251
- }
260
+ });
252
261
  }
253
262
  };
254
263
  __name(_MemoryEventQueueAdapter, "MemoryEventQueueAdapter");
@@ -257,14 +266,16 @@ const _EventQueue = class _EventQueue {
257
266
  constructor(app, options = {}) {
258
267
  this.app = app;
259
268
  this.options = options;
260
- this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name }));
261
- app.on("afterStart", async () => {
262
- await this.connect();
263
- });
264
- app.on("beforeStop", async () => {
265
- app.logger.info("[queue] gracefully shutting down...");
266
- await this.close();
267
- });
269
+ if (app.serving()) {
270
+ this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name }));
271
+ app.on("afterStart", async () => {
272
+ await this.connect();
273
+ });
274
+ app.on("beforeStop", async () => {
275
+ app.logger.info("[queue] gracefully shutting down...");
276
+ await this.close();
277
+ });
278
+ }
268
279
  }
269
280
  adapter;
270
281
  events = /* @__PURE__ */ new Map();
@@ -288,7 +299,12 @@ const _EventQueue = class _EventQueue {
288
299
  if (!this.adapter) {
289
300
  throw new Error("no adapter set, cannot connect");
290
301
  }
302
+ if (!this.app.serving()) {
303
+ this.app.logger.warn("app is not serving, will not connect to event queue");
304
+ return;
305
+ }
291
306
  await this.adapter.connect();
307
+ this.app.logger.debug(`connected to adapter, using memory? ${this.adapter instanceof MemoryEventQueueAdapter}`);
292
308
  for (const [channel, event] of this.events.entries()) {
293
309
  this.adapter.subscribe(this.getFullChannel(channel), event);
294
310
  }
@@ -329,7 +345,7 @@ const _EventQueue = class _EventQueue {
329
345
  throw new Error("event queue not connected, cannot publish");
330
346
  }
331
347
  const c = this.getFullChannel(channel);
332
- this.app.logger.debug("event queue publishing:", { channel: c, message });
348
+ this.app.logger.debug(`event queue publishing to channel(${c})`, { message });
333
349
  await this.adapter.publish(c, message, {
334
350
  timeout: QUEUE_DEFAULT_ACK_TIMEOUT,
335
351
  ...options,
@@ -172,6 +172,20 @@ const _WSServer = class _WSServer extends import_events.default {
172
172
  message
173
173
  );
174
174
  });
175
+ app.on("ws:sendToUser", ({ userId, message }) => {
176
+ this.sendToAppUser(app.name, userId, message);
177
+ app.logger.trace(`[broadcasting message] ws:sendToUser for user ${userId}`, { message });
178
+ app.pubSubManager.publish(
179
+ "ws:sendToUser",
180
+ {
181
+ userId,
182
+ message
183
+ },
184
+ {
185
+ skipSelf: true
186
+ }
187
+ );
188
+ });
175
189
  app.on("ws:sendToClient", ({ clientId, message }) => {
176
190
  this.sendToClient(clientId, message);
177
191
  });
@@ -184,6 +198,12 @@ const _WSServer = class _WSServer extends import_events.default {
184
198
  app.on("ws:authorized", ({ clientId, userId }) => {
185
199
  this.sendToClient(clientId, { type: "authorized" });
186
200
  });
201
+ app.on("afterLoad", () => {
202
+ app.pubSubManager.subscribe("ws:sendToUser", ({ userId, message }) => {
203
+ app.logger.debug(`[receive broadcasting message] ws:sendToUser for user ${userId}`, { message });
204
+ this.sendToAppUser(app.name, userId, message);
205
+ });
206
+ });
187
207
  }
188
208
  addNewConnection(ws, request) {
189
209
  const id = (0, import_nanoid.nanoid)();
package/lib/helper.d.ts CHANGED
@@ -16,3 +16,4 @@ export declare const createAppProxy: (app: Application) => Application<import(".
16
16
  export declare const getCommandFullName: (command: Command) => string;
17
17
  export declare const tsxRerunning: () => Promise<void>;
18
18
  export declare const enablePerfHooks: (app: Application) => void;
19
+ export declare function getBodyLimit(): string;
package/lib/helper.js CHANGED
@@ -41,6 +41,7 @@ __export(helper_exports, {
41
41
  createI18n: () => createI18n,
42
42
  createResourcer: () => createResourcer,
43
43
  enablePerfHooks: () => enablePerfHooks,
44
+ getBodyLimit: () => getBodyLimit,
44
45
  getCommandFullName: () => getCommandFullName,
45
46
  registerMiddlewares: () => registerMiddlewares,
46
47
  tsxRerunning: () => tsxRerunning
@@ -99,7 +100,7 @@ function registerMiddlewares(app, options) {
99
100
  }
100
101
  );
101
102
  if (options.bodyParser !== false) {
102
- const bodyLimit = "10mb";
103
+ const bodyLimit = getBodyLimit();
103
104
  app.use(
104
105
  (0, import_koa_bodyparser.default)({
105
106
  jsonLimit: bodyLimit,
@@ -188,12 +189,17 @@ const enablePerfHooks = /* @__PURE__ */ __name((app) => {
188
189
  });
189
190
  app.acl.allow("perf", "*", "public");
190
191
  }, "enablePerfHooks");
192
+ function getBodyLimit() {
193
+ return process.env.REQUEST_BODY_LIMIT || "10mb";
194
+ }
195
+ __name(getBodyLimit, "getBodyLimit");
191
196
  // Annotate the CommonJS export names for ESM import in node:
192
197
  0 && (module.exports = {
193
198
  createAppProxy,
194
199
  createI18n,
195
200
  createResourcer,
196
201
  enablePerfHooks,
202
+ getBodyLimit,
197
203
  getCommandFullName,
198
204
  registerMiddlewares,
199
205
  tsxRerunning
@@ -46,7 +46,7 @@ const traverseHasMany = /* @__PURE__ */ __name((arr, { collection, exclude = [],
46
46
  if (!arr) {
47
47
  return arr;
48
48
  }
49
- return arr.map((item) => traverseJSON(item, { collection, exclude, include }));
49
+ return arr.map((item) => traverseJSON(item, { collection, exclude, include, isHasManyField: true }));
50
50
  }, "traverseHasMany");
51
51
  const traverseBelongsToMany = /* @__PURE__ */ __name((arr, { collection, exclude = [], through }) => {
52
52
  if (!arr) {
@@ -107,7 +107,10 @@ const traverseJSON = /* @__PURE__ */ __name((data, options) => {
107
107
  if (field.options.isForeignKey) {
108
108
  continue;
109
109
  }
110
- if (["sort", "password", "sequence"].includes(field.type)) {
110
+ if (!options.isHasManyField && ["sort"].includes(field.type)) {
111
+ continue;
112
+ }
113
+ if (["password", "sequence"].includes(field.type)) {
111
114
  continue;
112
115
  }
113
116
  if (field.type === "hasOne") {
@@ -11,11 +11,12 @@ import { HandlerManager } from './handler-manager';
11
11
  import { PubSubCallback, type IPubSubAdapter, type PubSubManagerOptions, type PubSubManagerPublishOptions, type PubSubManagerSubscribeOptions } from './types';
12
12
  export declare const createPubSubManager: (app: Application, options: PubSubManagerOptions) => PubSubManager;
13
13
  export declare class PubSubManager {
14
+ protected app: Application;
14
15
  protected options: PubSubManagerOptions;
15
16
  protected publisherId: string;
16
17
  protected adapter: IPubSubAdapter;
17
18
  protected handlerManager: HandlerManager;
18
- constructor(options?: PubSubManagerOptions);
19
+ constructor(app: Application, options?: PubSubManagerOptions);
19
20
  get channelPrefix(): string;
20
21
  setAdapter(adapter: IPubSubAdapter): void;
21
22
  isConnected(): Promise<boolean>;
@@ -23,5 +24,5 @@ export declare class PubSubManager {
23
24
  close(): Promise<any>;
24
25
  subscribe(channel: string, callback: PubSubCallback, options?: PubSubManagerSubscribeOptions): Promise<void>;
25
26
  unsubscribe(channel: string, callback: PubSubCallback): Promise<any>;
26
- publish(channel: string, message: any, options?: PubSubManagerPublishOptions): Promise<any>;
27
+ publish(channel: string, message: any, options?: PubSubManagerPublishOptions): Promise<void>;
27
28
  }
@@ -34,17 +34,20 @@ module.exports = __toCommonJS(pub_sub_manager_exports);
34
34
  var import_utils = require("@nocobase/utils");
35
35
  var import_handler_manager = require("./handler-manager");
36
36
  const createPubSubManager = /* @__PURE__ */ __name((app, options) => {
37
- const pubSubManager = new PubSubManager(options);
38
- app.on("afterStart", async () => {
39
- await pubSubManager.connect();
40
- });
41
- app.on("afterStop", async () => {
42
- await pubSubManager.close();
43
- });
37
+ const pubSubManager = new PubSubManager(app, options);
38
+ if (app.serving()) {
39
+ app.on("afterStart", async () => {
40
+ await pubSubManager.connect();
41
+ });
42
+ app.on("afterStop", async () => {
43
+ await pubSubManager.close();
44
+ });
45
+ }
44
46
  return pubSubManager;
45
47
  }, "createPubSubManager");
46
48
  const _PubSubManager = class _PubSubManager {
47
- constructor(options = {}) {
49
+ constructor(app, options = {}) {
50
+ this.app = app;
48
51
  this.options = options;
49
52
  this.publisherId = (0, import_utils.uid)();
50
53
  this.handlerManager = new import_handler_manager.HandlerManager(this.publisherId);
@@ -69,8 +72,13 @@ const _PubSubManager = class _PubSubManager {
69
72
  if (!this.adapter) {
70
73
  return;
71
74
  }
75
+ if (!this.app.serving()) {
76
+ this.app.logger.warn("app is not serving, will not connect to event queue");
77
+ return;
78
+ }
72
79
  await this.adapter.connect();
73
80
  await this.handlerManager.each(async (channel, headler) => {
81
+ this.app.logger.debug(`[PubSubManager] subscribe ${channel} added before connected`);
74
82
  await this.adapter.subscribe(`${this.channelPrefix}${channel}`, headler);
75
83
  });
76
84
  }
@@ -84,6 +92,7 @@ const _PubSubManager = class _PubSubManager {
84
92
  await this.unsubscribe(channel, callback);
85
93
  const handler = this.handlerManager.set(channel, callback, options);
86
94
  if (await this.isConnected()) {
95
+ this.app.logger.debug(`[PubSubManager] subscribe ${channel} added after connected`);
87
96
  await this.adapter.subscribe(`${this.channelPrefix}${channel}`, handler);
88
97
  }
89
98
  }
@@ -97,6 +106,9 @@ const _PubSubManager = class _PubSubManager {
97
106
  async publish(channel, message, options) {
98
107
  var _a;
99
108
  if (!((_a = this.adapter) == null ? void 0 : _a.isConnected())) {
109
+ this.app.logger.warn(
110
+ `[PubSubManager] adapter is not exist or not connected, cannot publish message to channel ${channel}`
111
+ );
100
112
  return;
101
113
  }
102
114
  const wrappedMessage = JSON.stringify({
@@ -104,7 +116,8 @@ const _PubSubManager = class _PubSubManager {
104
116
  ...options,
105
117
  message
106
118
  });
107
- return this.adapter.publish(`${this.channelPrefix}${channel}`, wrappedMessage);
119
+ await this.adapter.publish(`${this.channelPrefix}${channel}`, wrappedMessage);
120
+ this.app.logger.trace(`[PubSubManager] published message to channel ${channel}`);
108
121
  }
109
122
  };
110
123
  __name(_PubSubManager, "PubSubManager");
@@ -16,7 +16,7 @@ export declare class SyncMessageManager {
16
16
  protected pubSubManager: PubSubManager;
17
17
  constructor(app: Application, options?: any);
18
18
  get debounce(): any;
19
- publish(channel: string, message: any, options?: PubSubManagerPublishOptions & Transactionable): Promise<any>;
19
+ publish(channel: string, message: any, options?: PubSubManagerPublishOptions & Transactionable): Promise<unknown>;
20
20
  subscribe(channel: string, callback: PubSubCallback): Promise<void>;
21
21
  unsubscribe(channel: string, callback: PubSubCallback): Promise<any>;
22
22
  sync(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/server",
3
- "version": "1.9.0-beta.4",
3
+ "version": "1.9.0-beta.6",
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.9.0-beta.4",
14
- "@nocobase/actions": "1.9.0-beta.4",
15
- "@nocobase/auth": "1.9.0-beta.4",
16
- "@nocobase/cache": "1.9.0-beta.4",
17
- "@nocobase/data-source-manager": "1.9.0-beta.4",
18
- "@nocobase/database": "1.9.0-beta.4",
19
- "@nocobase/evaluators": "1.9.0-beta.4",
20
- "@nocobase/lock-manager": "1.9.0-beta.4",
21
- "@nocobase/logger": "1.9.0-beta.4",
22
- "@nocobase/resourcer": "1.9.0-beta.4",
23
- "@nocobase/sdk": "1.9.0-beta.4",
24
- "@nocobase/telemetry": "1.9.0-beta.4",
25
- "@nocobase/utils": "1.9.0-beta.4",
13
+ "@nocobase/acl": "1.9.0-beta.6",
14
+ "@nocobase/actions": "1.9.0-beta.6",
15
+ "@nocobase/auth": "1.9.0-beta.6",
16
+ "@nocobase/cache": "1.9.0-beta.6",
17
+ "@nocobase/data-source-manager": "1.9.0-beta.6",
18
+ "@nocobase/database": "1.9.0-beta.6",
19
+ "@nocobase/evaluators": "1.9.0-beta.6",
20
+ "@nocobase/lock-manager": "1.9.0-beta.6",
21
+ "@nocobase/logger": "1.9.0-beta.6",
22
+ "@nocobase/resourcer": "1.9.0-beta.6",
23
+ "@nocobase/sdk": "1.9.0-beta.6",
24
+ "@nocobase/telemetry": "1.9.0-beta.6",
25
+ "@nocobase/utils": "1.9.0-beta.6",
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": "0276aa12d87f82c73d62a9712134fe363e2c772a"
60
+ "gitHead": "ada5e359a91135cb9baf605c04c5c380f067a046"
61
61
  }