@yunsoft/yuncms-api 0.1.23 → 0.1.25

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/src/server.js CHANGED
@@ -23,13 +23,16 @@ import { createApp } from './app.js';
23
23
  import { AiAssistantService } from './ai/service.js';
24
24
  import { loadOrCreateAiSettingsKey } from './ai/secret-key.js';
25
25
  import { AiSettingsStore } from './ai/settings-store.js';
26
- import { INTERNAL_AUDIT_EVENTS } from './audit-events.js';
26
+ import { registerInternalAudit } from './audit-hooks.js';
27
27
  import { loadExternalAuthConfig } from './external-auth/config.js';
28
28
  import { ExternalAuthProviderRegistry } from './external-auth/providers.js';
29
29
  import { loadExtensionRuntime } from './extensions/runtime.js';
30
30
  import { createMcpRouter } from './mcp.js';
31
31
  import { McpSettingsStore } from './mcp/settings-store.js';
32
32
  import { createAiRouter } from './routes/ai.js';
33
+ import { createAutomationsRouter } from './routes/automations.js';
34
+ import { AiAutomationsService } from './automations/service.js';
35
+ import { AiAutomationWorker } from './automations/worker.js';
33
36
 
34
37
  loadEnvFileIfPresent();
35
38
  await assertMaintenanceStartupAllowed({ cwd: process.cwd(), env: process.env });
@@ -82,30 +85,9 @@ const mailer = config.mail.host ? new SmtpMailer({
82
85
 
83
86
  let server = null;
84
87
  let extensionRuntime = null;
88
+ let automationWorker = null;
85
89
  let shuttingDown = false;
86
90
 
87
- function registerInternalAudit({ emitter, services }) {
88
- const AuditService = services.AuditService;
89
- const systemAccountability = createSystemAccountability();
90
- for (const event of INTERNAL_AUDIT_EVENTS) {
91
- emitter.registerAction(event, async (payload, context) => {
92
- try {
93
- const audit = new AuditService({ accountability: systemAccountability, database: pool, logger, requestId: context.requestId ?? null });
94
- await audit.record({
95
- user: context.accountability?.user ?? null,
96
- action: event,
97
- collection: context.collection ?? null,
98
- itemKey: payload?.key ?? null,
99
- requestId: context.requestId ?? null,
100
- payload,
101
- });
102
- } catch (error) {
103
- logger.error('YunCMS audit write failed after committed mutation', { event, requestId: context.requestId ?? null, code: error?.code, error });
104
- }
105
- }, { extensionId: 'core.audit', priority: 1000 });
106
- }
107
- }
108
-
109
91
  async function start() {
110
92
  await assertDatabaseCompatible(pool);
111
93
  const aiKey = await loadOrCreateAiSettingsKey();
@@ -136,9 +118,17 @@ async function start() {
136
118
  const aiAssistant = new AiAssistantService({ settingsStore: aiSettingsStore, logger });
137
119
  const aiRouter = createAiRouter({ assistant: aiAssistant, settingsStore: aiSettingsStore });
138
120
  const mcpSettingsStore = new McpSettingsStore({ database: pool });
139
- const mcpRouter = createMcpRouter({ settingsStore: mcpSettingsStore, logger });
140
121
  mailer?.setEmitter(emitter);
141
- registerInternalAudit({ emitter, services });
122
+ registerInternalAudit({ emitter, services, database: pool, logger });
123
+ const automationOptions = { assistant: aiAssistant, settingsStore: aiSettingsStore, schemaCache };
124
+ const automations = new AiAutomationsService({ ...automationOptions, database: pool,
125
+ accountability: createSystemAccountability(), services, emitter, storage, logger });
126
+ for (const event of ['items.create', 'items.update']) {
127
+ emitter.registerAction(event, (payload, context) => automations.enqueue(event, payload, context), { extensionId: 'core.ai-automations' });
128
+ }
129
+ automationWorker = new AiAutomationWorker(automations);
130
+ const automationsRouter = createAutomationsRouter(automationOptions);
131
+ const mcpRouter = createMcpRouter({ settingsStore: mcpSettingsStore, logger, automationOptions });
142
132
 
143
133
  extensionRuntime = await loadExtensionRuntime({
144
134
  services,
@@ -165,12 +155,17 @@ async function start() {
165
155
  externalAuthRegistry,
166
156
  endpointExtensions: extensionRuntime.endpointExtensions,
167
157
  aiRouter,
158
+ automationsRouter,
168
159
  mcpRouter,
169
160
  });
170
161
 
171
162
  await extensionRuntime.init('app.beforeStart');
172
163
  server = await new Promise((resolve, reject) => {
173
- const listeningServer = app.listen(config.server.port, config.server.host, () => {
164
+ const listeningServer = app.listen(config.server.port, config.server.host, (error) => {
165
+ if (error) {
166
+ reject(error);
167
+ return;
168
+ }
174
169
  logger.info('YunCMS API listening', { host: config.server.host, port: config.server.port });
175
170
  resolve(listeningServer);
176
171
  });
@@ -178,6 +173,7 @@ async function start() {
178
173
  });
179
174
  await extensionRuntime.init('app.afterStart');
180
175
  extensionRuntime.startSchedules();
176
+ automationWorker.start();
181
177
  }
182
178
 
183
179
  async function shutdown(signal) {
@@ -189,6 +185,7 @@ async function shutdown(signal) {
189
185
  process.exit(1);
190
186
  }, 10_000);
191
187
  forceExit.unref();
188
+ await automationWorker?.stop();
192
189
 
193
190
  const schedulesStopped = await extensionRuntime?.stopSchedules({ timeoutMs: 5_000 }).catch(() => false);
194
191
  if (schedulesStopped === false) {
@@ -218,7 +215,8 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
218
215
 
219
216
  start().catch(async (error) => {
220
217
  logger.error('YunCMS API failed to start', { code: error?.code, error });
221
- await redisClient?.close().catch(() => {});
222
- await closeDatabasePool(pool).catch(() => {});
218
+ await shutdown('startup-failure').catch((cleanupError) => {
219
+ logger.error('YunCMS API startup cleanup failed', { code: cleanupError?.code, error: cleanupError });
220
+ });
223
221
  process.exit(1);
224
222
  });