@akanjs/server 0.9.42 → 0.9.44

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/cjs/src/boot.js CHANGED
@@ -56,10 +56,12 @@ var import_dgram = __toESM(require("dgram"));
56
56
  var import_events = __toESM(require("events"));
57
57
  var import_graphql_upload = require("graphql-upload");
58
58
  var import_meilisearch = require("meilisearch");
59
+ var import_mongoose2 = __toESM(require("mongoose"));
59
60
  var import_redis = require("redis");
60
61
  var import_base2 = require("./base.module");
61
62
  var import_gql = require("./gql");
62
63
  var import_module = require("./module");
64
+ var import_schedule2 = require("./schedule");
63
65
  var import_searchDaemon = require("./searchDaemon");
64
66
  const createNestApp = async ({ registerModules, serverMode = "federation", env, log = true }) => {
65
67
  const backendLogger = new import_common.Logger("Backend");
@@ -109,9 +111,10 @@ const createNestApp = async ({ registerModules, serverMode = "federation", env,
109
111
  provide: "MEILI_CLIENT",
110
112
  useFactory: () => new import_meilisearch.MeiliSearch({ host: meiliUri, apiKey: (0, import_nest.generateMeiliKey)(env) })
111
113
  },
112
- { provide: "GLOBAL_ENV", useValue: env }
114
+ { provide: "GLOBAL_ENV", useValue: env },
115
+ { provide: "MONGO_CLIENT", useValue: import_mongoose2.default.connection }
113
116
  ],
114
- exports: ["REDIS_CLIENT", "MEILI_CLIENT", "GLOBAL_ENV"]
117
+ exports: ["REDIS_CLIENT", "MEILI_CLIENT", "GLOBAL_ENV", "MONGO_CLIENT"]
115
118
  })
116
119
  ], GlobalProvideModule);
117
120
  let AppModule = class {
@@ -145,7 +148,8 @@ const createNestApp = async ({ registerModules, serverMode = "federation", env,
145
148
  injects: { SearchClient: import_nest.SearchClient, DatabaseClient: import_nest.DatabaseClient, CacheClient: import_nest.CacheClient }
146
149
  }),
147
150
  ...["batch", "all"].includes(serverMode) && import_base.baseEnv.operationMode !== "edge" ? [import_searchDaemon.SearchDaemonModule] : [],
148
- ...[(0, import_base2.registerBaseModule)(env), ...registerModules(env)].filter((m) => !!m)
151
+ ...[(0, import_base2.registerBaseModule)(env), ...registerModules(env)].filter((m) => !!m),
152
+ (0, import_schedule2.makeScheduleModule)(serverMode, env)
149
153
  ],
150
154
  providers: [import_gql.DateScalar]
151
155
  })
@@ -51,7 +51,7 @@ const processorOf = (sigRef, allSrvs) => {
51
51
  const sigMeta = (0, import_signal.getSigMeta)(sigRef);
52
52
  const serverMode = process.env.SERVER_MODE ?? "federation";
53
53
  const gqlMetas = (0, import_signal.getGqlMetas)(sigRef).filter((gqlMeta) => gqlMeta.type === "Process").filter(
54
- (gqlMeta) => gqlMeta.signalOption.serverType === "all" || serverMode === "all" || gqlMeta.signalOption.serverType === serverMode
54
+ (gqlMeta) => gqlMeta.signalOption.serverMode === "all" || serverMode === "all" || gqlMeta.signalOption.serverMode === serverMode
55
55
  );
56
56
  class QueueProcessor {
57
57
  }
@@ -0,0 +1,231 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result)
25
+ __defProp(target, key, result);
26
+ return result;
27
+ };
28
+ var __publicField = (obj, key, value) => {
29
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
30
+ return value;
31
+ };
32
+ var __accessCheck = (obj, member, msg) => {
33
+ if (!member.has(obj))
34
+ throw TypeError("Cannot " + msg);
35
+ };
36
+ var __privateGet = (obj, member, getter) => {
37
+ __accessCheck(obj, member, "read from private field");
38
+ return getter ? getter.call(obj) : member.get(obj);
39
+ };
40
+ var __privateAdd = (obj, member, value) => {
41
+ if (member.has(obj))
42
+ throw TypeError("Cannot add the same private member more than once");
43
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
44
+ };
45
+ var schedule_exports = {};
46
+ __export(schedule_exports, {
47
+ makeScheduleModule: () => makeScheduleModule
48
+ });
49
+ module.exports = __toCommonJS(schedule_exports);
50
+ var import_common = require("@akanjs/common");
51
+ var import_service = require("@akanjs/service");
52
+ var import_signal = require("@akanjs/signal");
53
+ var import_common2 = require("@nestjs/common");
54
+ var import_cron = require("cron");
55
+ const makeScheduleModule = (serverMode, backendEnv) => {
56
+ var _cronMap, _timeoutMap, _intervalMap, _lockMap;
57
+ const srvRefs = (0, import_service.getAllServiceRefs)();
58
+ const sigRefs = (0, import_signal.getAllSignalRefs)();
59
+ const initMetas = [];
60
+ const cronMetas = [];
61
+ const intervalMetas = [];
62
+ const timeoutMetas = [];
63
+ const destroyMetas = [];
64
+ sigRefs.forEach((sigRef) => {
65
+ const gqlMetas = (0, import_signal.getGqlMetas)(sigRef);
66
+ gqlMetas.forEach((gqlMeta) => {
67
+ const { enabled, operationMode, serverMode: targetServerMode, scheduleType } = gqlMeta.signalOption;
68
+ if (gqlMeta.type !== "Schedule")
69
+ return;
70
+ else if (!enabled)
71
+ return;
72
+ else if (operationMode && !operationMode.includes(backendEnv.operationMode))
73
+ return;
74
+ else if (targetServerMode && targetServerMode !== "all" && serverMode !== "all" && targetServerMode !== serverMode)
75
+ return;
76
+ switch (scheduleType) {
77
+ case "init":
78
+ initMetas.push(gqlMeta);
79
+ break;
80
+ case "cron":
81
+ cronMetas.push(gqlMeta);
82
+ break;
83
+ case "interval":
84
+ intervalMetas.push(gqlMeta);
85
+ break;
86
+ case "timeout":
87
+ timeoutMetas.push(gqlMeta);
88
+ break;
89
+ case "destroy":
90
+ destroyMetas.push(gqlMeta);
91
+ break;
92
+ default:
93
+ break;
94
+ }
95
+ });
96
+ });
97
+ let Schedule = class {
98
+ constructor() {
99
+ __publicField(this, "logger", new import_common2.Logger("Schedule"));
100
+ __privateAdd(this, _cronMap, /* @__PURE__ */ new Map());
101
+ __privateAdd(this, _timeoutMap, /* @__PURE__ */ new Map());
102
+ __privateAdd(this, _intervalMap, /* @__PURE__ */ new Map());
103
+ __privateAdd(this, _lockMap, /* @__PURE__ */ new Map());
104
+ }
105
+ async onModuleInit() {
106
+ await Promise.all(
107
+ initMetas.map(async (gqlMeta) => {
108
+ const fn = gqlMeta.descriptor.value;
109
+ const before = Date.now();
110
+ this.logger.debug(`Init Before ${gqlMeta.key} / ${before}`);
111
+ await fn.apply(this);
112
+ const after = Date.now();
113
+ this.logger.debug(`Init After ${gqlMeta.key} / ${after} (${after - before}ms)`);
114
+ })
115
+ );
116
+ timeoutMetas.forEach((gqlMeta) => {
117
+ const fn = gqlMeta.descriptor.value;
118
+ const timeout = gqlMeta.signalOption.scheduleTime;
119
+ const timer = setTimeout(async () => {
120
+ const before = Date.now();
121
+ this.logger.debug(`Timemout Before ${gqlMeta.key} / ${before}`);
122
+ await fn.apply(this);
123
+ const after = Date.now();
124
+ this.logger.debug(`Timemout After ${gqlMeta.key} / ${after} (${after - before}ms)`);
125
+ __privateGet(this, _timeoutMap).delete(fn);
126
+ }, timeout);
127
+ __privateGet(this, _timeoutMap).set(fn, timer);
128
+ });
129
+ intervalMetas.forEach((gqlMeta) => {
130
+ const lock = gqlMeta.signalOption.lock;
131
+ const fn = gqlMeta.descriptor.value;
132
+ const interval = gqlMeta.signalOption.scheduleTime;
133
+ const timer = setInterval(async () => {
134
+ if (lock) {
135
+ if (__privateGet(this, _lockMap).get(fn)) {
136
+ this.logger.warn(`${gqlMeta.key} is locked, skipping...`);
137
+ return;
138
+ }
139
+ __privateGet(this, _lockMap).set(fn, true);
140
+ }
141
+ const before = Date.now();
142
+ this.logger.debug(`Interval Before ${gqlMeta.key} / ${before}`);
143
+ await fn.apply(this);
144
+ const after = Date.now();
145
+ this.logger.debug(`Interval After ${gqlMeta.key} / ${after} (${after - before}ms)`);
146
+ if (lock)
147
+ __privateGet(this, _lockMap).set(fn, false);
148
+ }, interval);
149
+ __privateGet(this, _intervalMap).set(fn, timer);
150
+ });
151
+ cronMetas.forEach((gqlMeta) => {
152
+ const lock = gqlMeta.signalOption.lock;
153
+ const fn = gqlMeta.descriptor.value;
154
+ const cronTime = gqlMeta.signalOption.scheduleCron;
155
+ if (!cronTime)
156
+ throw new Error(`Cron time is not found for ${gqlMeta.key}`);
157
+ const cronJob = import_cron.CronJob.from({
158
+ cronTime,
159
+ onTick: async () => {
160
+ if (lock) {
161
+ if (__privateGet(this, _lockMap).get(fn)) {
162
+ this.logger.warn(`${gqlMeta.key} is locked, skipping...`);
163
+ return;
164
+ }
165
+ __privateGet(this, _lockMap).set(fn, true);
166
+ }
167
+ const before = Date.now();
168
+ this.logger.debug(`Cron Before ${gqlMeta.key} / ${before}`);
169
+ await fn.apply(this);
170
+ const after = Date.now();
171
+ this.logger.debug(`Cron After ${gqlMeta.key} / ${after} (${after - before}ms)`);
172
+ if (lock)
173
+ __privateGet(this, _lockMap).set(fn, false);
174
+ },
175
+ start: true
176
+ });
177
+ __privateGet(this, _cronMap).set(fn, cronJob);
178
+ });
179
+ }
180
+ async onModuleDestroy() {
181
+ __privateGet(this, _timeoutMap).forEach((timer, fn) => {
182
+ clearTimeout(timer);
183
+ __privateGet(this, _timeoutMap).delete(fn);
184
+ });
185
+ __privateGet(this, _intervalMap).forEach((timer, fn) => {
186
+ clearInterval(timer);
187
+ __privateGet(this, _intervalMap).delete(fn);
188
+ });
189
+ await Promise.all(
190
+ [...__privateGet(this, _cronMap).entries()].map(async ([fn, cronJob]) => {
191
+ await cronJob.stop();
192
+ __privateGet(this, _cronMap).delete(fn);
193
+ })
194
+ );
195
+ await Promise.all(
196
+ destroyMetas.map(async (gqlMeta) => {
197
+ const fn = gqlMeta.descriptor.value;
198
+ const before = Date.now();
199
+ this.logger.debug(`Destroy Before ${gqlMeta.key} / ${before}`);
200
+ await fn.apply(this);
201
+ const after = Date.now();
202
+ this.logger.debug(`Destroy After ${gqlMeta.key} / ${after} (${after - before}ms)`);
203
+ })
204
+ );
205
+ }
206
+ };
207
+ _cronMap = new WeakMap();
208
+ _timeoutMap = new WeakMap();
209
+ _intervalMap = new WeakMap();
210
+ _lockMap = new WeakMap();
211
+ Schedule = __decorateClass([
212
+ (0, import_common2.Injectable)()
213
+ ], Schedule);
214
+ srvRefs.forEach((srvRef) => {
215
+ const serviceMeta = (0, import_service.getServiceMeta)(srvRef);
216
+ if (!serviceMeta)
217
+ throw new Error(`Service ${srvRef.name} is not found`);
218
+ (0, import_common2.Inject)(srvRef)(Schedule.prototype, (0, import_common.lowerlize)(serviceMeta.name));
219
+ });
220
+ let ScheduleModule = class {
221
+ };
222
+ ScheduleModule = __decorateClass([
223
+ (0, import_common2.Global)(),
224
+ (0, import_common2.Module)({ providers: [Schedule] })
225
+ ], ScheduleModule);
226
+ return ScheduleModule;
227
+ };
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ makeScheduleModule
231
+ });
package/cjs/src/schema.js CHANGED
@@ -206,13 +206,42 @@ const schemaOf = (modelRef, docRef, middleware) => {
206
206
  schema.methods[name] = Object.getOwnPropertyDescriptor(docRef.prototype, name)?.value;
207
207
  });
208
208
  schema.pre("save", async function(next) {
209
- const model = this.constructor;
210
- if (this.isNew)
211
- model.addSummary(["total", this.status]);
212
- else if (!!this.removedAt && this.isModified("removedAt"))
213
- model.subSummary(["total", this.status]);
209
+ const saveType = this.isNew ? "create" : this.isModified("removedAt") ? this.removedAt ? "remove" : "create" : "update";
210
+ const saveListeners = [
211
+ ...this.constructor.preSaveListenerSet,
212
+ ...saveType === "create" ? [...this.constructor.preCreateListenerSet] : saveType === "update" ? [...this.constructor.preUpdateListenerSet] : [...this.constructor.preRemoveListenerSet]
213
+ ];
214
+ await Promise.all(
215
+ saveListeners.map(async (listener) => {
216
+ try {
217
+ await listener(this, saveType);
218
+ } catch (e) {
219
+ import_common.Logger.error(
220
+ `Pre Save Listener Error ${this.constructor.modelName}: ${e instanceof Error ? e.message : typeof e === "string" ? e : "unknown error"}`
221
+ );
222
+ }
223
+ })
224
+ );
214
225
  next();
215
226
  });
227
+ schema.post("save", async function() {
228
+ const saveType = this.isNew ? "create" : this.isModified("removedAt") ? this.removedAt ? "remove" : "create" : "update";
229
+ const saveListeners = [
230
+ ...this.constructor.postSaveListenerSet,
231
+ ...saveType === "create" ? [...this.constructor.postCreateListenerSet] : saveType === "update" ? [...this.constructor.postUpdateListenerSet] : [...this.constructor.postRemoveListenerSet]
232
+ ];
233
+ await Promise.all(
234
+ saveListeners.map(async (listener) => {
235
+ try {
236
+ await listener(this, saveType);
237
+ } catch (e) {
238
+ import_common.Logger.error(
239
+ `Post Save Listener Error ${this.constructor.modelName}: ${e instanceof Error ? e.message : typeof e === "string" ? e : "unknown error"}`
240
+ );
241
+ }
242
+ })
243
+ );
244
+ });
216
245
  const onSchema = Object.getOwnPropertyDescriptor(middleware.prototype, "onSchema")?.value;
217
246
  onSchema?.(schema);
218
247
  schema.index({ removedAt: -1 });
@@ -217,9 +217,7 @@ let SearchDaemonModule = class {
217
217
  };
218
218
  SearchDaemonModule = __decorateClass([
219
219
  (0, import_common2.Global)(),
220
- (0, import_common2.Module)({
221
- providers: [SearchDaemon]
222
- })
220
+ (0, import_common2.Module)({ providers: [SearchDaemon] })
223
221
  ], SearchDaemonModule);
224
222
  // Annotate the CommonJS export names for ESM import in node:
225
223
  0 && (module.exports = {
package/esm/src/boot.js CHANGED
@@ -47,10 +47,12 @@ import dgram from "dgram";
47
47
  import events from "events";
48
48
  import { graphqlUploadExpress } from "graphql-upload";
49
49
  import { MeiliSearch } from "meilisearch";
50
+ import mongoose from "mongoose";
50
51
  import { createClient } from "redis";
51
52
  import { registerBaseModule } from "./base.module";
52
53
  import { DateScalar } from "./gql";
53
54
  import { useGlobals } from "./module";
55
+ import { makeScheduleModule } from "./schedule";
54
56
  import { SearchDaemonModule } from "./searchDaemon";
55
57
  const createNestApp = async ({ registerModules, serverMode = "federation", env, log = true }) => {
56
58
  const backendLogger = new Logger("Backend");
@@ -100,9 +102,10 @@ const createNestApp = async ({ registerModules, serverMode = "federation", env,
100
102
  provide: "MEILI_CLIENT",
101
103
  useFactory: () => new MeiliSearch({ host: meiliUri, apiKey: generateMeiliKey(env) })
102
104
  },
103
- { provide: "GLOBAL_ENV", useValue: env }
105
+ { provide: "GLOBAL_ENV", useValue: env },
106
+ { provide: "MONGO_CLIENT", useValue: mongoose.connection }
104
107
  ],
105
- exports: ["REDIS_CLIENT", "MEILI_CLIENT", "GLOBAL_ENV"]
108
+ exports: ["REDIS_CLIENT", "MEILI_CLIENT", "GLOBAL_ENV", "MONGO_CLIENT"]
106
109
  })
107
110
  ], GlobalProvideModule);
108
111
  let AppModule = class {
@@ -136,7 +139,8 @@ const createNestApp = async ({ registerModules, serverMode = "federation", env,
136
139
  injects: { SearchClient, DatabaseClient, CacheClient }
137
140
  }),
138
141
  ...["batch", "all"].includes(serverMode) && baseEnv.operationMode !== "edge" ? [SearchDaemonModule] : [],
139
- ...[registerBaseModule(env), ...registerModules(env)].filter((m) => !!m)
142
+ ...[registerBaseModule(env), ...registerModules(env)].filter((m) => !!m),
143
+ makeScheduleModule(serverMode, env)
140
144
  ],
141
145
  providers: [DateScalar]
142
146
  })
@@ -33,7 +33,7 @@ const processorOf = (sigRef, allSrvs) => {
33
33
  const sigMeta = getSigMeta(sigRef);
34
34
  const serverMode = process.env.SERVER_MODE ?? "federation";
35
35
  const gqlMetas = getGqlMetas(sigRef).filter((gqlMeta) => gqlMeta.type === "Process").filter(
36
- (gqlMeta) => gqlMeta.signalOption.serverType === "all" || serverMode === "all" || gqlMeta.signalOption.serverType === serverMode
36
+ (gqlMeta) => gqlMeta.signalOption.serverMode === "all" || serverMode === "all" || gqlMeta.signalOption.serverMode === serverMode
37
37
  );
38
38
  class QueueProcessor {
39
39
  }
@@ -0,0 +1,210 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __decorateClass = (decorators, target, key, kind) => {
5
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
6
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
7
+ if (decorator = decorators[i])
8
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
9
+ if (kind && result)
10
+ __defProp(target, key, result);
11
+ return result;
12
+ };
13
+ var __publicField = (obj, key, value) => {
14
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15
+ return value;
16
+ };
17
+ var __accessCheck = (obj, member, msg) => {
18
+ if (!member.has(obj))
19
+ throw TypeError("Cannot " + msg);
20
+ };
21
+ var __privateGet = (obj, member, getter) => {
22
+ __accessCheck(obj, member, "read from private field");
23
+ return getter ? getter.call(obj) : member.get(obj);
24
+ };
25
+ var __privateAdd = (obj, member, value) => {
26
+ if (member.has(obj))
27
+ throw TypeError("Cannot add the same private member more than once");
28
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
29
+ };
30
+ import { lowerlize } from "@akanjs/common";
31
+ import { getAllServiceRefs, getServiceMeta } from "@akanjs/service";
32
+ import { getAllSignalRefs, getGqlMetas } from "@akanjs/signal";
33
+ import { Global, Inject, Injectable, Logger, Module } from "@nestjs/common";
34
+ import { CronJob } from "cron";
35
+ const makeScheduleModule = (serverMode, backendEnv) => {
36
+ var _cronMap, _timeoutMap, _intervalMap, _lockMap;
37
+ const srvRefs = getAllServiceRefs();
38
+ const sigRefs = getAllSignalRefs();
39
+ const initMetas = [];
40
+ const cronMetas = [];
41
+ const intervalMetas = [];
42
+ const timeoutMetas = [];
43
+ const destroyMetas = [];
44
+ sigRefs.forEach((sigRef) => {
45
+ const gqlMetas = getGqlMetas(sigRef);
46
+ gqlMetas.forEach((gqlMeta) => {
47
+ const { enabled, operationMode, serverMode: targetServerMode, scheduleType } = gqlMeta.signalOption;
48
+ if (gqlMeta.type !== "Schedule")
49
+ return;
50
+ else if (!enabled)
51
+ return;
52
+ else if (operationMode && !operationMode.includes(backendEnv.operationMode))
53
+ return;
54
+ else if (targetServerMode && targetServerMode !== "all" && serverMode !== "all" && targetServerMode !== serverMode)
55
+ return;
56
+ switch (scheduleType) {
57
+ case "init":
58
+ initMetas.push(gqlMeta);
59
+ break;
60
+ case "cron":
61
+ cronMetas.push(gqlMeta);
62
+ break;
63
+ case "interval":
64
+ intervalMetas.push(gqlMeta);
65
+ break;
66
+ case "timeout":
67
+ timeoutMetas.push(gqlMeta);
68
+ break;
69
+ case "destroy":
70
+ destroyMetas.push(gqlMeta);
71
+ break;
72
+ default:
73
+ break;
74
+ }
75
+ });
76
+ });
77
+ let Schedule = class {
78
+ constructor() {
79
+ __publicField(this, "logger", new Logger("Schedule"));
80
+ __privateAdd(this, _cronMap, /* @__PURE__ */ new Map());
81
+ __privateAdd(this, _timeoutMap, /* @__PURE__ */ new Map());
82
+ __privateAdd(this, _intervalMap, /* @__PURE__ */ new Map());
83
+ __privateAdd(this, _lockMap, /* @__PURE__ */ new Map());
84
+ }
85
+ async onModuleInit() {
86
+ await Promise.all(
87
+ initMetas.map(async (gqlMeta) => {
88
+ const fn = gqlMeta.descriptor.value;
89
+ const before = Date.now();
90
+ this.logger.debug(`Init Before ${gqlMeta.key} / ${before}`);
91
+ await fn.apply(this);
92
+ const after = Date.now();
93
+ this.logger.debug(`Init After ${gqlMeta.key} / ${after} (${after - before}ms)`);
94
+ })
95
+ );
96
+ timeoutMetas.forEach((gqlMeta) => {
97
+ const fn = gqlMeta.descriptor.value;
98
+ const timeout = gqlMeta.signalOption.scheduleTime;
99
+ const timer = setTimeout(async () => {
100
+ const before = Date.now();
101
+ this.logger.debug(`Timemout Before ${gqlMeta.key} / ${before}`);
102
+ await fn.apply(this);
103
+ const after = Date.now();
104
+ this.logger.debug(`Timemout After ${gqlMeta.key} / ${after} (${after - before}ms)`);
105
+ __privateGet(this, _timeoutMap).delete(fn);
106
+ }, timeout);
107
+ __privateGet(this, _timeoutMap).set(fn, timer);
108
+ });
109
+ intervalMetas.forEach((gqlMeta) => {
110
+ const lock = gqlMeta.signalOption.lock;
111
+ const fn = gqlMeta.descriptor.value;
112
+ const interval = gqlMeta.signalOption.scheduleTime;
113
+ const timer = setInterval(async () => {
114
+ if (lock) {
115
+ if (__privateGet(this, _lockMap).get(fn)) {
116
+ this.logger.warn(`${gqlMeta.key} is locked, skipping...`);
117
+ return;
118
+ }
119
+ __privateGet(this, _lockMap).set(fn, true);
120
+ }
121
+ const before = Date.now();
122
+ this.logger.debug(`Interval Before ${gqlMeta.key} / ${before}`);
123
+ await fn.apply(this);
124
+ const after = Date.now();
125
+ this.logger.debug(`Interval After ${gqlMeta.key} / ${after} (${after - before}ms)`);
126
+ if (lock)
127
+ __privateGet(this, _lockMap).set(fn, false);
128
+ }, interval);
129
+ __privateGet(this, _intervalMap).set(fn, timer);
130
+ });
131
+ cronMetas.forEach((gqlMeta) => {
132
+ const lock = gqlMeta.signalOption.lock;
133
+ const fn = gqlMeta.descriptor.value;
134
+ const cronTime = gqlMeta.signalOption.scheduleCron;
135
+ if (!cronTime)
136
+ throw new Error(`Cron time is not found for ${gqlMeta.key}`);
137
+ const cronJob = CronJob.from({
138
+ cronTime,
139
+ onTick: async () => {
140
+ if (lock) {
141
+ if (__privateGet(this, _lockMap).get(fn)) {
142
+ this.logger.warn(`${gqlMeta.key} is locked, skipping...`);
143
+ return;
144
+ }
145
+ __privateGet(this, _lockMap).set(fn, true);
146
+ }
147
+ const before = Date.now();
148
+ this.logger.debug(`Cron Before ${gqlMeta.key} / ${before}`);
149
+ await fn.apply(this);
150
+ const after = Date.now();
151
+ this.logger.debug(`Cron After ${gqlMeta.key} / ${after} (${after - before}ms)`);
152
+ if (lock)
153
+ __privateGet(this, _lockMap).set(fn, false);
154
+ },
155
+ start: true
156
+ });
157
+ __privateGet(this, _cronMap).set(fn, cronJob);
158
+ });
159
+ }
160
+ async onModuleDestroy() {
161
+ __privateGet(this, _timeoutMap).forEach((timer, fn) => {
162
+ clearTimeout(timer);
163
+ __privateGet(this, _timeoutMap).delete(fn);
164
+ });
165
+ __privateGet(this, _intervalMap).forEach((timer, fn) => {
166
+ clearInterval(timer);
167
+ __privateGet(this, _intervalMap).delete(fn);
168
+ });
169
+ await Promise.all(
170
+ [...__privateGet(this, _cronMap).entries()].map(async ([fn, cronJob]) => {
171
+ await cronJob.stop();
172
+ __privateGet(this, _cronMap).delete(fn);
173
+ })
174
+ );
175
+ await Promise.all(
176
+ destroyMetas.map(async (gqlMeta) => {
177
+ const fn = gqlMeta.descriptor.value;
178
+ const before = Date.now();
179
+ this.logger.debug(`Destroy Before ${gqlMeta.key} / ${before}`);
180
+ await fn.apply(this);
181
+ const after = Date.now();
182
+ this.logger.debug(`Destroy After ${gqlMeta.key} / ${after} (${after - before}ms)`);
183
+ })
184
+ );
185
+ }
186
+ };
187
+ _cronMap = new WeakMap();
188
+ _timeoutMap = new WeakMap();
189
+ _intervalMap = new WeakMap();
190
+ _lockMap = new WeakMap();
191
+ Schedule = __decorateClass([
192
+ Injectable()
193
+ ], Schedule);
194
+ srvRefs.forEach((srvRef) => {
195
+ const serviceMeta = getServiceMeta(srvRef);
196
+ if (!serviceMeta)
197
+ throw new Error(`Service ${srvRef.name} is not found`);
198
+ Inject(srvRef)(Schedule.prototype, lowerlize(serviceMeta.name));
199
+ });
200
+ let ScheduleModule = class {
201
+ };
202
+ ScheduleModule = __decorateClass([
203
+ Global(),
204
+ Module({ providers: [Schedule] })
205
+ ], ScheduleModule);
206
+ return ScheduleModule;
207
+ };
208
+ export {
209
+ makeScheduleModule
210
+ };
package/esm/src/schema.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  Int,
9
9
  JSON
10
10
  } from "@akanjs/base";
11
- import { isDayjs } from "@akanjs/common";
11
+ import { isDayjs, Logger } from "@akanjs/common";
12
12
  import { getClassMeta, getFieldMetas, getFullModelRef, getInputModelRef } from "@akanjs/constant";
13
13
  import { getDefaultSchemaOptions, ObjectId } from "@akanjs/document";
14
14
  import { makeDefault } from "@akanjs/signal";
@@ -191,13 +191,42 @@ const schemaOf = (modelRef, docRef, middleware) => {
191
191
  schema.methods[name] = Object.getOwnPropertyDescriptor(docRef.prototype, name)?.value;
192
192
  });
193
193
  schema.pre("save", async function(next) {
194
- const model = this.constructor;
195
- if (this.isNew)
196
- model.addSummary(["total", this.status]);
197
- else if (!!this.removedAt && this.isModified("removedAt"))
198
- model.subSummary(["total", this.status]);
194
+ const saveType = this.isNew ? "create" : this.isModified("removedAt") ? this.removedAt ? "remove" : "create" : "update";
195
+ const saveListeners = [
196
+ ...this.constructor.preSaveListenerSet,
197
+ ...saveType === "create" ? [...this.constructor.preCreateListenerSet] : saveType === "update" ? [...this.constructor.preUpdateListenerSet] : [...this.constructor.preRemoveListenerSet]
198
+ ];
199
+ await Promise.all(
200
+ saveListeners.map(async (listener) => {
201
+ try {
202
+ await listener(this, saveType);
203
+ } catch (e) {
204
+ Logger.error(
205
+ `Pre Save Listener Error ${this.constructor.modelName}: ${e instanceof Error ? e.message : typeof e === "string" ? e : "unknown error"}`
206
+ );
207
+ }
208
+ })
209
+ );
199
210
  next();
200
211
  });
212
+ schema.post("save", async function() {
213
+ const saveType = this.isNew ? "create" : this.isModified("removedAt") ? this.removedAt ? "remove" : "create" : "update";
214
+ const saveListeners = [
215
+ ...this.constructor.postSaveListenerSet,
216
+ ...saveType === "create" ? [...this.constructor.postCreateListenerSet] : saveType === "update" ? [...this.constructor.postUpdateListenerSet] : [...this.constructor.postRemoveListenerSet]
217
+ ];
218
+ await Promise.all(
219
+ saveListeners.map(async (listener) => {
220
+ try {
221
+ await listener(this, saveType);
222
+ } catch (e) {
223
+ Logger.error(
224
+ `Post Save Listener Error ${this.constructor.modelName}: ${e instanceof Error ? e.message : typeof e === "string" ? e : "unknown error"}`
225
+ );
226
+ }
227
+ })
228
+ );
229
+ });
201
230
  const onSchema = Object.getOwnPropertyDescriptor(middleware.prototype, "onSchema")?.value;
202
231
  onSchema?.(schema);
203
232
  schema.index({ removedAt: -1 });
@@ -202,9 +202,7 @@ let SearchDaemonModule = class {
202
202
  };
203
203
  SearchDaemonModule = __decorateClass([
204
204
  Global(),
205
- Module({
206
- providers: [SearchDaemon]
207
- })
205
+ Module({ providers: [SearchDaemon] })
208
206
  ], SearchDaemonModule);
209
207
  export {
210
208
  SearchDaemonModule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/server",
3
- "version": "0.9.42",
3
+ "version": "0.9.44",
4
4
  "sourceType": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -30,6 +30,7 @@
30
30
  "@nestjs/websockets": "^10.4.15",
31
31
  "body-parser": "^1.20.3",
32
32
  "cookie-parser": "^1.4.7",
33
+ "cron": "^4.3.3",
33
34
  "dayjs": "^1.11.13",
34
35
  "graphql": "^16.10.0",
35
36
  "graphql-type-json": "^0.3.2",
package/src/module.d.ts CHANGED
@@ -4,7 +4,7 @@ import { Database } from "@akanjs/document";
4
4
  import { DynamicModule } from "@nestjs/common";
5
5
  interface DatabaseModuleCreateOptions {
6
6
  constant: ConstantModel<string, any, any, any, any, any>;
7
- database: Database<string, any, any, any, any, any, any, any, any>;
7
+ database: Database<string, any, any, any, any, any, any, any>;
8
8
  signal: Type;
9
9
  service: Type;
10
10
  }
@@ -0,0 +1,4 @@
1
+ import type { BackendEnv } from "@akanjs/base";
2
+ export declare const makeScheduleModule: (serverMode: "federation" | "batch" | "all" | "none", backendEnv: BackendEnv) => {
3
+ new (): {};
4
+ };
@@ -1,5 +1,30 @@
1
1
  import type { Type } from "@akanjs/base";
2
2
  import { type TextDoc } from "@akanjs/constant";
3
+ import type { Types } from "mongoose";
4
+ export interface ChangedData {
5
+ _id: {
6
+ _data: string;
7
+ };
8
+ operationType: "update" | "insert" | "delete";
9
+ clusterTime: {
10
+ t: number;
11
+ i: number;
12
+ };
13
+ wallTime: Date;
14
+ ns: {
15
+ db: string;
16
+ coll: string;
17
+ };
18
+ documentKey: {
19
+ _id: Types.ObjectId;
20
+ };
21
+ updateDescription?: {
22
+ updatedFields: Record<string, any>;
23
+ removedFields: string[];
24
+ truncatedArrays: any[];
25
+ };
26
+ fullDocument?: Record<string, any>;
27
+ }
3
28
  export declare const makeTextFilter: (modelRef: Type) => (data: Record<string, any>, assignObj?: {
4
29
  [key: string]: string;
5
30
  }) => TextDoc;