@bernierllc/content-management-suite 0.4.2 → 0.7.0

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/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -31,73 +30,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
30
  // src/index.ts
32
31
  var src_exports = {};
33
32
  __export(src_exports, {
34
- AudioContentTypeManager: () => import_content_type_audio2.AudioContentTypeManager,
35
- AutosaveManager: () => import_content_autosave_manager.AutosaveManager,
33
+ ConflictError: () => ConflictError,
36
34
  ContentManagementConfigSchema: () => ContentManagementConfigSchema,
37
- ContentManagementSuiteConflictError: () => ContentManagementSuiteConflictError,
38
- ContentManagementSuiteError: () => ContentManagementSuiteError,
39
- ContentManagementSuiteForbiddenError: () => ContentManagementSuiteForbiddenError,
40
- ContentManagementSuiteInternalError: () => ContentManagementSuiteInternalError,
41
- ContentManagementSuiteNotFoundError: () => ContentManagementSuiteNotFoundError,
42
- ContentManagementSuiteUnauthorizedError: () => ContentManagementSuiteUnauthorizedError,
43
- ContentManagementSuiteValidationError: () => ContentManagementSuiteValidationError,
44
- ContentSoftDelete: () => import_content_soft_delete.ContentSoftDelete,
45
- ContentTypeRegistry: () => import_content_type_registry2.ContentTypeRegistry,
46
- EditorialWorkflowEngine: () => import_content_editorial_workflow.EditorialWorkflowEngine,
47
- ImageContentType: () => import_content_type_image2.ImageContentType,
48
- StageActionButtons: () => import_content_workflow_ui2.StageActionButtons,
49
- TextContentType: () => import_content_type_text2.TextContentType,
50
- VideoContentType: () => import_content_type_video2.VideoContentType,
51
- WorkflowAdminConfig: () => import_content_workflow_ui2.WorkflowAdminConfig,
52
- WorkflowBuilder: () => import_content_editorial_workflow.WorkflowBuilder,
53
- WorkflowFactory: () => import_content_editorial_workflow.WorkflowFactory,
54
- WorkflowStepper: () => import_content_workflow_ui2.WorkflowStepper,
55
- WorkflowTemplates: () => import_content_editorial_workflow.WorkflowTemplates,
56
- WorkflowTimeline: () => import_content_workflow_ui2.WorkflowTimeline,
57
- createContentManagementSuite: () => createContentManagementSuite
35
+ ContentManagementError: () => ContentManagementError,
36
+ ForbiddenError: () => ForbiddenError,
37
+ InternalError: () => InternalError,
38
+ NotFoundError: () => NotFoundError,
39
+ UnauthorizedError: () => UnauthorizedError,
40
+ ValidationError: () => ValidationError,
41
+ createContentManagementSuite: () => createContentManagementSuite,
42
+ createInMemoryAdapter: () => createInMemoryAdapter
58
43
  });
59
44
  module.exports = __toCommonJS(src_exports);
60
45
 
61
- // src/types.ts
46
+ // src/content-management-suite.ts
47
+ var import_events = require("events");
48
+
49
+ // src/config.ts
62
50
  var import_zod = require("zod");
63
51
  var ContentManagementConfigSchema = import_zod.z.object({
64
- // Server Configuration
65
- server: import_zod.z.object({
66
- port: import_zod.z.number().default(3e3),
67
- host: import_zod.z.string().default("localhost"),
68
- cors: import_zod.z.object({
69
- origin: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.string())]).default("*"),
70
- credentials: import_zod.z.boolean().default(true)
71
- }).default({}),
72
- security: import_zod.z.object({
73
- helmet: import_zod.z.boolean().default(true),
74
- rateLimit: import_zod.z.object({
75
- enabled: import_zod.z.boolean().default(true),
76
- windowMs: import_zod.z.number().default(15 * 60 * 1e3),
77
- // 15 minutes
78
- max: import_zod.z.number().default(100)
79
- // limit each IP to 100 requests per windowMs
80
- }).default({})
81
- }).default({})
82
- }).default({}),
83
- // Database Configuration
84
- database: import_zod.z.object({
85
- type: import_zod.z.enum(["sqlite", "postgresql", "mysql", "mongodb"]).default("sqlite"),
86
- url: import_zod.z.string().optional(),
87
- host: import_zod.z.string().default("localhost"),
88
- port: import_zod.z.number().optional(),
89
- name: import_zod.z.string().default("content_management"),
90
- username: import_zod.z.string().optional(),
91
- password: import_zod.z.string().optional(),
92
- ssl: import_zod.z.boolean().default(false),
93
- pool: import_zod.z.object({
94
- min: import_zod.z.number().default(2),
95
- max: import_zod.z.number().default(10)
96
- }).default({})
97
- }).default({}),
98
- // Content Configuration
99
52
  content: import_zod.z.object({
100
- // Default editorial workflow
101
53
  defaultWorkflow: import_zod.z.object({
102
54
  id: import_zod.z.string().default("standard"),
103
55
  name: import_zod.z.string().default("Standard Workflow"),
@@ -111,24 +63,8 @@ var ContentManagementConfigSchema = import_zod.z.object({
111
63
  description: import_zod.z.string().optional(),
112
64
  permissions: import_zod.z.array(import_zod.z.string()).default([])
113
65
  })).default([
114
- {
115
- id: "write",
116
- name: "Write",
117
- order: 1,
118
- isPublishStage: false,
119
- allowsScheduling: false,
120
- description: "Write content",
121
- permissions: ["content.edit"]
122
- },
123
- {
124
- id: "publish",
125
- name: "Publish",
126
- order: 2,
127
- isPublishStage: true,
128
- allowsScheduling: true,
129
- description: "Publish content",
130
- permissions: ["content.publish"]
131
- }
66
+ { id: "write", name: "Write", order: 1, isPublishStage: false, allowsScheduling: false, description: "Write content", permissions: ["content.edit"] },
67
+ { id: "publish", name: "Publish", order: 2, isPublishStage: true, allowsScheduling: true, description: "Publish content", permissions: ["content.publish"] }
132
68
  ]),
133
69
  transitions: import_zod.z.array(import_zod.z.object({
134
70
  id: import_zod.z.string(),
@@ -137,34 +73,23 @@ var ContentManagementConfigSchema = import_zod.z.object({
137
73
  description: import_zod.z.string().optional(),
138
74
  permissions: import_zod.z.array(import_zod.z.string()).default([])
139
75
  })).default([
140
- {
141
- id: "write-to-publish",
142
- from: "write",
143
- to: "publish",
144
- description: "Move from write to publish",
145
- permissions: ["content.publish"]
146
- }
76
+ { id: "write-to-publish", from: "write", to: "publish", description: "Move from write to publish", permissions: ["content.publish"] }
147
77
  ])
148
78
  }).default({}),
149
- // Content types
150
79
  contentTypes: import_zod.z.array(import_zod.z.string()).default(["text", "image", "audio", "video"]),
151
- // Auto-save configuration
152
80
  autoSave: import_zod.z.object({
153
81
  enabled: import_zod.z.boolean().default(true),
154
82
  debounceMs: import_zod.z.number().default(1e3),
155
83
  maxRetries: import_zod.z.number().default(3),
156
84
  backoffMs: import_zod.z.number().default(1e3)
157
85
  }).default({}),
158
- // Soft delete configuration
159
86
  softDelete: import_zod.z.object({
160
87
  enabled: import_zod.z.boolean().default(true),
161
88
  showDeletedToUsers: import_zod.z.boolean().default(false),
162
89
  retentionDays: import_zod.z.number().default(30)
163
90
  }).default({}),
164
- // File upload configuration
165
91
  upload: import_zod.z.object({
166
92
  maxFileSize: import_zod.z.number().default(104857600),
167
- // 100MB
168
93
  allowedTypes: import_zod.z.array(import_zod.z.string()).default([
169
94
  "image/jpeg",
170
95
  "image/png",
@@ -178,893 +103,1281 @@ var ContentManagementConfigSchema = import_zod.z.object({
178
103
  "video/ogg"
179
104
  ]),
180
105
  storage: import_zod.z.object({
181
- type: import_zod.z.enum(["local", "s3", "gcs", "azure"]).default("local"),
106
+ type: import_zod.z.enum(["local", "s3"]).default("local"),
182
107
  path: import_zod.z.string().default("./uploads"),
183
108
  bucket: import_zod.z.string().optional(),
184
- region: import_zod.z.string().optional(),
185
- accessKey: import_zod.z.string().optional(),
186
- secretKey: import_zod.z.string().optional()
187
- }).default({})
109
+ region: import_zod.z.string().optional()
110
+ }).default({}).optional()
188
111
  }).default({})
189
112
  }).default({}),
190
- // UI Configuration
191
- ui: import_zod.z.object({
192
- theme: import_zod.z.object({
193
- mode: import_zod.z.enum(["light", "dark", "auto"]).default("auto"),
194
- primaryColor: import_zod.z.string().default("#007bff"),
195
- secondaryColor: import_zod.z.string().default("#6c757d"),
196
- accentColor: import_zod.z.string().default("#17a2b8")
197
- }).default({}),
198
- editor: import_zod.z.object({
199
- showToolbar: import_zod.z.boolean().default(true),
200
- showStatusBar: import_zod.z.boolean().default(true),
201
- showWordCount: import_zod.z.boolean().default(true),
202
- showCharacterCount: import_zod.z.boolean().default(true),
203
- autoSave: import_zod.z.boolean().default(true),
204
- placeholder: import_zod.z.string().default("Start writing your content...")
205
- }).default({}),
206
- list: import_zod.z.object({
207
- defaultView: import_zod.z.enum(["table", "list", "grid", "kanban"]).default("table"),
208
- pageSize: import_zod.z.number().default(20),
209
- showSearch: import_zod.z.boolean().default(true),
210
- showFilters: import_zod.z.boolean().default(true),
211
- showSorting: import_zod.z.boolean().default(true),
212
- showPagination: import_zod.z.boolean().default(true)
113
+ database: import_zod.z.object({
114
+ type: import_zod.z.enum(["sqlite", "postgresql", "mysql", "mongodb"]).default("sqlite"),
115
+ url: import_zod.z.string().optional(),
116
+ host: import_zod.z.string().default("localhost"),
117
+ port: import_zod.z.number().optional(),
118
+ name: import_zod.z.string().default("content_management"),
119
+ username: import_zod.z.string().optional(),
120
+ password: import_zod.z.string().optional(),
121
+ ssl: import_zod.z.boolean().default(false),
122
+ pool: import_zod.z.object({
123
+ min: import_zod.z.number().default(2),
124
+ max: import_zod.z.number().default(10),
125
+ idleTimeoutMs: import_zod.z.number().optional()
213
126
  }).default({})
214
127
  }).default({}),
215
- // Integration Configuration
216
128
  integrations: import_zod.z.object({
217
129
  neverAdmin: import_zod.z.object({
218
130
  enabled: import_zod.z.boolean().default(false),
219
- url: import_zod.z.string().optional(),
220
- apiKey: import_zod.z.string().optional(),
221
- syncInterval: import_zod.z.number().default(3e5)
222
- // 5 minutes
131
+ endpoint: import_zod.z.string().optional()
223
132
  }).default({}),
224
133
  neverHub: import_zod.z.object({
225
134
  enabled: import_zod.z.boolean().default(false),
226
- url: import_zod.z.string().optional(),
227
- apiKey: import_zod.z.string().optional(),
228
- packageDiscovery: import_zod.z.boolean().default(true)
135
+ endpoint: import_zod.z.string().optional()
229
136
  }).default({}),
230
137
  analytics: import_zod.z.object({
231
138
  enabled: import_zod.z.boolean().default(false),
232
- provider: import_zod.z.enum(["google", "mixpanel", "amplitude", "custom"]).optional(),
233
- trackingId: import_zod.z.string().optional(),
234
- config: import_zod.z.record(import_zod.z.any()).default({})
139
+ provider: import_zod.z.string().optional()
235
140
  }).default({})
236
141
  }).default({}),
237
- // Security Configuration
238
142
  security: import_zod.z.object({
239
- jwt: import_zod.z.object({
240
- secret: import_zod.z.string().default("your-secret-key"),
241
- expiresIn: import_zod.z.string().default("24h"),
242
- issuer: import_zod.z.string().default("content-management-suite")
243
- }).default({}),
244
143
  permissions: import_zod.z.object({
245
144
  enabled: import_zod.z.boolean().default(true),
246
- defaultRole: import_zod.z.string().default("user"),
247
- roles: import_zod.z.array(import_zod.z.object({
248
- name: import_zod.z.string(),
249
- permissions: import_zod.z.array(import_zod.z.string()),
250
- description: import_zod.z.string().optional()
251
- })).default([
252
- {
253
- name: "admin",
254
- permissions: ["*"],
255
- description: "Full administrative access"
256
- },
257
- {
258
- name: "editor",
259
- permissions: ["content.edit", "content.publish", "content.schedule"],
260
- description: "Content editing and publishing"
261
- },
262
- {
263
- name: "author",
264
- permissions: ["content.edit"],
265
- description: "Content creation and editing"
266
- },
267
- {
268
- name: "user",
269
- permissions: ["content.view"],
270
- description: "Content viewing only"
271
- }
272
- ])
273
- }).default({})
145
+ defaultRole: import_zod.z.string().default("user")
146
+ }).default({}),
147
+ roles: import_zod.z.array(import_zod.z.object({
148
+ name: import_zod.z.string(),
149
+ permissions: import_zod.z.array(import_zod.z.string()),
150
+ description: import_zod.z.string().optional()
151
+ })).default([
152
+ { name: "admin", permissions: ["*"], description: "Full administrative access" },
153
+ { name: "editor", permissions: ["content.edit", "content.publish", "content.schedule"], description: "Content editing and publishing" },
154
+ { name: "author", permissions: ["content.edit"], description: "Content creation and editing" },
155
+ { name: "user", permissions: ["content.view"], description: "Content viewing only" }
156
+ ])
157
+ }).default({}),
158
+ workflows: import_zod.z.object({
159
+ defaults: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({})
274
160
  }).default({}),
275
- // Logging Configuration
276
161
  logging: import_zod.z.object({
277
- level: import_zod.z.enum(["error", "warn", "info", "debug"]).default("info"),
278
- format: import_zod.z.enum(["json", "text"]).default("json"),
279
- file: import_zod.z.object({
280
- enabled: import_zod.z.boolean().default(true),
281
- path: import_zod.z.string().default("./logs"),
282
- maxSize: import_zod.z.string().default("10MB"),
283
- maxFiles: import_zod.z.number().default(5)
284
- }).default({}),
285
- console: import_zod.z.object({
286
- enabled: import_zod.z.boolean().default(true),
287
- colorize: import_zod.z.boolean().default(true)
288
- }).default({})
162
+ level: import_zod.z.enum(["error", "warn", "info", "debug"]).default("info")
289
163
  }).default({}),
290
- // Performance Configuration
291
164
  performance: import_zod.z.object({
292
165
  cache: import_zod.z.object({
293
166
  enabled: import_zod.z.boolean().default(true),
294
167
  ttl: import_zod.z.number().default(300),
295
- // 5 minutes
296
168
  maxSize: import_zod.z.number().default(1e3)
297
- }).default({}),
298
- compression: import_zod.z.object({
299
- enabled: import_zod.z.boolean().default(true),
300
- level: import_zod.z.number().min(1).max(9).default(6)
301
- }).default({}),
302
- rateLimit: import_zod.z.object({
303
- enabled: import_zod.z.boolean().default(true),
304
- windowMs: import_zod.z.number().default(15 * 60 * 1e3),
305
- // 15 minutes
306
- max: import_zod.z.number().default(100)
307
169
  }).default({})
308
170
  }).default({})
309
171
  });
310
- var ContentManagementSuiteError = class extends Error {
311
- constructor(message, code, statusCode = 500, details) {
172
+
173
+ // src/errors.ts
174
+ var ContentManagementError = class extends Error {
175
+ constructor(message, options) {
312
176
  super(message);
313
- this.name = "ContentManagementSuiteError";
314
- this.code = code;
315
- this.statusCode = statusCode;
316
- this.details = details;
177
+ if (options?.cause !== void 0) {
178
+ this.cause = options.cause;
179
+ }
180
+ this.name = this.constructor.name;
181
+ this.code = options?.code ?? "CONTENT_MANAGEMENT_ERROR";
182
+ if (options?.details !== void 0) {
183
+ this.details = options.details;
184
+ }
317
185
  }
318
186
  };
319
- var ContentManagementSuiteValidationError = class extends ContentManagementSuiteError {
320
- constructor(message, details) {
321
- super(message, "VALIDATION_ERROR", 400, details);
322
- this.name = "ContentManagementSuiteValidationError";
187
+ var ValidationError = class extends ContentManagementError {
188
+ constructor(message, options) {
189
+ super(message, { ...options, code: "VALIDATION_ERROR" });
323
190
  }
324
191
  };
325
- var ContentManagementSuiteNotFoundError = class extends ContentManagementSuiteError {
326
- constructor(message, details) {
327
- super(message, "NOT_FOUND", 404, details);
328
- this.name = "ContentManagementSuiteNotFoundError";
192
+ var NotFoundError = class extends ContentManagementError {
193
+ constructor(message, options) {
194
+ super(message, { ...options, code: "NOT_FOUND" });
329
195
  }
330
196
  };
331
- var ContentManagementSuiteUnauthorizedError = class extends ContentManagementSuiteError {
332
- constructor(message, details) {
333
- super(message, "UNAUTHORIZED", 401, details);
334
- this.name = "ContentManagementSuiteUnauthorizedError";
197
+ var UnauthorizedError = class extends ContentManagementError {
198
+ constructor(message, options) {
199
+ super(message, { ...options, code: "UNAUTHORIZED" });
335
200
  }
336
201
  };
337
- var ContentManagementSuiteForbiddenError = class extends ContentManagementSuiteError {
338
- constructor(message, details) {
339
- super(message, "FORBIDDEN", 403, details);
340
- this.name = "ContentManagementSuiteForbiddenError";
202
+ var ForbiddenError = class extends ContentManagementError {
203
+ constructor(message, options) {
204
+ super(message, { ...options, code: "FORBIDDEN" });
341
205
  }
342
206
  };
343
- var ContentManagementSuiteConflictError = class extends ContentManagementSuiteError {
344
- constructor(message, details) {
345
- super(message, "CONFLICT", 409, details);
346
- this.name = "ContentManagementSuiteConflictError";
207
+ var ConflictError = class extends ContentManagementError {
208
+ constructor(message, options) {
209
+ super(message, { ...options, code: "CONFLICT" });
347
210
  }
348
211
  };
349
- var ContentManagementSuiteInternalError = class extends ContentManagementSuiteError {
350
- constructor(message, details) {
351
- super(message, "INTERNAL_ERROR", 500, details);
352
- this.name = "ContentManagementSuiteInternalError";
212
+ var InternalError = class extends ContentManagementError {
213
+ constructor(message, options) {
214
+ super(message, { ...options, code: "INTERNAL_ERROR" });
353
215
  }
354
216
  };
355
217
 
356
- // src/index.ts
357
- var import_content_workflow_ui2 = require("@bernierllc/content-workflow-ui");
358
- var import_content_type_text2 = require("@bernierllc/content-type-text");
359
- var import_content_type_image2 = require("@bernierllc/content-type-image");
360
- var import_content_type_audio2 = require("@bernierllc/content-type-audio");
361
- var import_content_type_video2 = require("@bernierllc/content-type-video");
362
- var import_content_type_registry2 = require("@bernierllc/content-type-registry");
363
- var import_content_editorial_workflow = require("@bernierllc/content-editorial-workflow");
364
- var import_content_autosave_manager = require("@bernierllc/content-autosave-manager");
365
- var import_content_soft_delete = require("@bernierllc/content-soft-delete");
366
-
367
- // src/content-management-suite.ts
368
- var import_events = require("events");
369
- var import_express = __toESM(require("express"));
370
- var import_cors = __toESM(require("cors"));
371
- var import_helmet = __toESM(require("helmet"));
372
- var import_morgan = __toESM(require("morgan"));
373
- var import_compression = __toESM(require("compression"));
374
- var import_uuid = require("uuid");
375
- var import_content_type_registry = require("@bernierllc/content-type-registry");
376
- var import_content_type_text = require("@bernierllc/content-type-text");
377
- var import_content_type_image = require("@bernierllc/content-type-image");
378
- var import_content_type_audio = require("@bernierllc/content-type-audio");
379
- var import_content_type_video = require("@bernierllc/content-type-video");
380
- var import_content_workflow_ui = require("@bernierllc/content-workflow-ui");
381
- var ContentManagementSuiteImpl = class extends import_events.EventEmitter {
382
- constructor(options = {}) {
383
- super();
384
- // Stub until @bernierllc/content-editor-service is published
385
- this.autosaveManager = null;
386
- this.softDelete = null;
387
- this.workflowEngine = null;
388
- // Content Types
389
- this.contentTypes = /* @__PURE__ */ new Map();
390
- // Plugins and Middleware
391
- this.plugins = /* @__PURE__ */ new Map();
392
- this.middleware = [];
393
- this.hooks = /* @__PURE__ */ new Map();
394
- // State
395
- this.isStarted = false;
396
- this.isStopped = false;
397
- this.httpServer = null;
398
- this.options = options;
399
- this.config = this.loadConfiguration();
400
- this.contentTypeRegistry = new import_content_type_registry.ContentTypeRegistry();
401
- this.configManager = this.createConfigManagerStub();
402
- this.workflowService = this.createWorkflowServiceStub();
403
- this.editorService = this.createEditorServiceStub();
404
- this.workflow = {
405
- stepper: import_content_workflow_ui.WorkflowStepper,
406
- actions: import_content_workflow_ui.StageActionButtons,
407
- timeline: import_content_workflow_ui.WorkflowTimeline,
408
- admin: import_content_workflow_ui.WorkflowAdminConfig
409
- };
410
- this.server = (0, import_express.default)();
411
- this.setupMiddleware();
412
- this.setupRoutes();
413
- this.editor = this.createEditorStub();
414
- this.list = this.createListStub();
415
- this.registerDefaultContentTypes();
416
- if (options.plugins) {
417
- options.plugins.forEach((plugin) => this.registerPlugin(plugin));
418
- }
419
- }
420
- // Stub creators for services not yet published
421
- createConfigManagerStub() {
422
- return {
423
- getConfig: () => this.config,
424
- updateConfig: async (config) => {
425
- this.config = { ...this.config, ...config };
426
- },
427
- start: async () => {
428
- },
429
- stop: async () => {
430
- }
431
- };
218
+ // src/in-memory-adapter.ts
219
+ var InMemoryContentStorage = class {
220
+ constructor() {
221
+ this.store = /* @__PURE__ */ new Map();
432
222
  }
433
- createWorkflowServiceStub() {
434
- return {
435
- listWorkflows: async () => [],
436
- getWorkflow: async (id) => ({ id, name: "Default Workflow" }),
437
- createWorkflow: async (data) => ({ id: (0, import_uuid.v4)(), ...data }),
438
- updateWorkflow: async (id, data) => ({ id, ...data }),
439
- deleteWorkflow: async (_id) => {
440
- },
441
- publishContent: async (id) => ({ id, status: "published" }),
442
- scheduleContent: async (id, date) => ({ id, scheduledFor: date }),
443
- unpublishContent: async (id) => ({ id, status: "draft" }),
444
- start: async () => {
445
- },
446
- stop: async () => {
223
+ async list(filters) {
224
+ let items = Array.from(this.store.values());
225
+ if (filters?.type) {
226
+ items = items.filter((item) => item.type === filters.type);
227
+ }
228
+ if (filters?.status) {
229
+ items = items.filter((item) => item.status === filters.status);
230
+ }
231
+ const total = items.length;
232
+ const page = filters?.page ?? 1;
233
+ const limit = filters?.limit ?? items.length;
234
+ const start = (page - 1) * limit;
235
+ const paged = items.slice(start, start + limit);
236
+ return { items: paged, total, page, limit };
237
+ }
238
+ async get(id) {
239
+ return this.store.get(id) ?? null;
240
+ }
241
+ async create(item) {
242
+ this.store.set(item.id, item);
243
+ return item;
244
+ }
245
+ async update(id, partial) {
246
+ const existing = this.store.get(id);
247
+ if (!existing) {
248
+ throw new Error(`Content item not found: ${id}`);
249
+ }
250
+ const updated = { ...existing, ...partial };
251
+ this.store.set(id, updated);
252
+ return updated;
253
+ }
254
+ async delete(id) {
255
+ this.store.delete(id);
256
+ }
257
+ async findBySource(sourceType, sourceId) {
258
+ for (const item of this.store.values()) {
259
+ if (item.sourceType === sourceType && item.sourceId === sourceId) {
260
+ return item;
447
261
  }
448
- };
262
+ }
263
+ return null;
449
264
  }
450
- createEditorServiceStub() {
451
- const contentStore = /* @__PURE__ */ new Map();
452
- return {
453
- listContent: async (_filters) => ({ items: Array.from(contentStore.values()), total: contentStore.size }),
454
- getContent: async (id) => contentStore.get(id) || null,
455
- createContent: async (type, data) => {
456
- const content = { id: (0, import_uuid.v4)(), type, ...data, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
457
- contentStore.set(content.id, content);
458
- return content;
459
- },
460
- updateContent: async (id, data) => {
461
- const existing = contentStore.get(id);
462
- if (!existing)
463
- throw new Error("Content not found");
464
- const updated = { ...existing, ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
465
- contentStore.set(id, updated);
466
- return updated;
467
- },
468
- deleteContent: async (id, _soft) => {
469
- contentStore.delete(id);
470
- },
471
- start: async () => {
472
- },
473
- stop: async () => {
265
+ async search(query, options) {
266
+ const lowerQuery = query.toLowerCase();
267
+ let items = Array.from(this.store.values()).filter((item) => {
268
+ const titleMatch = item.title && item.title.toLowerCase().includes(lowerQuery);
269
+ const bodyMatch = item.body && item.body.toLowerCase().includes(lowerQuery);
270
+ const dataMatch = item.data && Object.values(item.data).some(
271
+ (value) => typeof value === "string" && value.toLowerCase().includes(lowerQuery)
272
+ );
273
+ return titleMatch || bodyMatch || dataMatch;
274
+ });
275
+ if (options?.type) {
276
+ items = items.filter((item) => item.type === options.type);
277
+ }
278
+ if (options?.status) {
279
+ items = items.filter((item) => item.status === options.status);
280
+ }
281
+ const total = items.length;
282
+ const page = options?.page ?? 1;
283
+ const limit = options?.limit ?? items.length;
284
+ const start = (page - 1) * limit;
285
+ const paged = items.slice(start, start + limit);
286
+ return { items: paged, total, page, limit };
287
+ }
288
+ };
289
+ var InMemoryWorkflowStorage = class {
290
+ constructor() {
291
+ this.store = /* @__PURE__ */ new Map();
292
+ this.contentTypeMap = /* @__PURE__ */ new Map();
293
+ }
294
+ // contentType -> workflowId
295
+ async list() {
296
+ return Array.from(this.store.values());
297
+ }
298
+ async get(id) {
299
+ return this.store.get(id) ?? null;
300
+ }
301
+ async create(workflow) {
302
+ this.store.set(workflow.id, workflow);
303
+ return workflow;
304
+ }
305
+ async update(id, partial) {
306
+ const existing = this.store.get(id);
307
+ if (!existing) {
308
+ throw new Error(`Workflow not found: ${id}`);
309
+ }
310
+ const updated = { ...existing, ...partial };
311
+ this.store.set(id, updated);
312
+ return updated;
313
+ }
314
+ async delete(id) {
315
+ this.store.delete(id);
316
+ for (const [ct, wId] of this.contentTypeMap.entries()) {
317
+ if (wId === id) {
318
+ this.contentTypeMap.delete(ct);
474
319
  }
475
- };
320
+ }
476
321
  }
477
- // Stub for editor interface (until @bernierllc/content-editor-ui is published)
478
- createEditorStub() {
479
- return {
480
- render: () => null,
481
- getValue: () => "",
482
- setValue: (_value) => {
483
- },
484
- focus: () => {
485
- },
486
- blur: () => {
487
- },
488
- on: (_event, _callback) => {
489
- },
490
- off: (_event, _callback) => {
322
+ async findByContentType(contentType) {
323
+ const workflowId = this.contentTypeMap.get(contentType);
324
+ if (!workflowId) {
325
+ return null;
326
+ }
327
+ return this.store.get(workflowId) ?? null;
328
+ }
329
+ setContentTypeMapping(contentType, workflowId) {
330
+ this.contentTypeMap.set(contentType, workflowId);
331
+ }
332
+ };
333
+ var InMemoryContentTypeStorage = class {
334
+ constructor() {
335
+ this.store = /* @__PURE__ */ new Map();
336
+ }
337
+ async list() {
338
+ return Array.from(this.store.values());
339
+ }
340
+ async get(id) {
341
+ return this.store.get(id) ?? null;
342
+ }
343
+ async create(def) {
344
+ this.store.set(def.id, def);
345
+ return def;
346
+ }
347
+ async update(id, partial) {
348
+ const existing = this.store.get(id);
349
+ if (!existing) {
350
+ throw new Error(`Content type not found: ${id}`);
351
+ }
352
+ const updated = { ...existing, ...partial };
353
+ this.store.set(id, updated);
354
+ return updated;
355
+ }
356
+ async delete(id) {
357
+ this.store.delete(id);
358
+ }
359
+ };
360
+ var InMemorySocialPostStorage = class {
361
+ constructor() {
362
+ this.store = /* @__PURE__ */ new Map();
363
+ }
364
+ async list(filters) {
365
+ let items = Array.from(this.store.values());
366
+ if (filters?.contentId) {
367
+ items = items.filter((p) => p.contentId === filters.contentId);
368
+ }
369
+ if (filters?.platform) {
370
+ items = items.filter((p) => p.platform === filters.platform);
371
+ }
372
+ if (filters?.status) {
373
+ items = items.filter((p) => p.status === filters.status);
374
+ }
375
+ return items;
376
+ }
377
+ async get(id) {
378
+ return this.store.get(id) ?? null;
379
+ }
380
+ async create(post) {
381
+ this.store.set(post.id, post);
382
+ return post;
383
+ }
384
+ async update(id, partial) {
385
+ const existing = this.store.get(id);
386
+ if (!existing) {
387
+ throw new Error(`Social post not found: ${id}`);
388
+ }
389
+ const updated = { ...existing, ...partial };
390
+ this.store.set(id, updated);
391
+ return updated;
392
+ }
393
+ async delete(id) {
394
+ this.store.delete(id);
395
+ }
396
+ async findByContent(contentId) {
397
+ return Array.from(this.store.values()).filter((p) => p.contentId === contentId);
398
+ }
399
+ };
400
+ var InMemoryAIReviewStorage = class {
401
+ constructor() {
402
+ this.store = /* @__PURE__ */ new Map();
403
+ }
404
+ async list(filters) {
405
+ let items = Array.from(this.store.values());
406
+ if (filters?.contentId) {
407
+ items = items.filter((r) => r.contentId === filters.contentId);
408
+ }
409
+ return items;
410
+ }
411
+ async get(id) {
412
+ return this.store.get(id) ?? null;
413
+ }
414
+ async create(review) {
415
+ this.store.set(review.id, review);
416
+ return review;
417
+ }
418
+ async findByContent(contentId) {
419
+ return Array.from(this.store.values()).filter((r) => r.contentId === contentId);
420
+ }
421
+ async getLatestForContent(contentId) {
422
+ const reviews = await this.findByContent(contentId);
423
+ if (reviews.length === 0) {
424
+ return null;
425
+ }
426
+ reviews.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
427
+ return reviews[0] ?? null;
428
+ }
429
+ };
430
+ function createInMemoryAdapter() {
431
+ return {
432
+ content: new InMemoryContentStorage(),
433
+ workflows: new InMemoryWorkflowStorage(),
434
+ contentTypes: new InMemoryContentTypeStorage(),
435
+ socialPosts: new InMemorySocialPostStorage(),
436
+ aiReviews: new InMemoryAIReviewStorage()
437
+ };
438
+ }
439
+
440
+ // src/namespaces/content.ts
441
+ var crypto = __toESM(require("crypto"));
442
+ var ContentNamespaceImpl = class {
443
+ constructor(emitter, storage, getContentTypes, getPublishers) {
444
+ this.emitter = emitter;
445
+ this.storage = storage;
446
+ this.getContentTypes = getContentTypes;
447
+ this.getPublishers = getPublishers;
448
+ }
449
+ async create(input) {
450
+ const contentTypes = this.getContentTypes();
451
+ const typeDef = contentTypes.get(input.type);
452
+ if (typeDef && typeDef.schema) {
453
+ const dataToValidate = {
454
+ ...input.title !== void 0 ? { title: input.title } : {},
455
+ ...input.body !== void 0 ? { body: input.body } : {},
456
+ ...input.data ?? {}
457
+ };
458
+ const result = contentTypes.validate(input.type, dataToValidate);
459
+ if (!result.valid) {
460
+ throw new ValidationError("Content type schema validation failed", {
461
+ details: { errors: result.errors }
462
+ });
491
463
  }
464
+ }
465
+ const item = {
466
+ id: crypto.randomUUID(),
467
+ type: input.type,
468
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
469
+ status: "draft",
470
+ ...input.title !== void 0 ? { title: input.title } : {},
471
+ ...input.body !== void 0 ? { body: input.body } : {},
472
+ ...input.data !== void 0 ? { data: { ...input.data } } : {},
473
+ ...input.channels !== void 0 ? { channels: input.channels } : {},
474
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
492
475
  };
476
+ const created = await this.storage.content.create(item);
477
+ this.emitter.emit("content:created", created);
478
+ return created;
493
479
  }
494
- // Stub for list interface (until @bernierllc/content-list-ui is published)
495
- createListStub() {
496
- return {
497
- render: () => null,
498
- getItems: () => [],
499
- setItems: (_items) => {
500
- },
501
- getSelectedItems: () => [],
502
- setSelectedItems: (_items) => {
503
- },
504
- on: (_event, _callback) => {
505
- },
506
- off: (_event, _callback) => {
507
- }
480
+ async get(id) {
481
+ const item = await this.storage.content.get(id);
482
+ if (!item) {
483
+ throw new NotFoundError(`Content item not found: ${id}`);
484
+ }
485
+ return item;
486
+ }
487
+ async list(filters) {
488
+ return this.storage.content.list(filters);
489
+ }
490
+ async update(id, input) {
491
+ const existing = await this.storage.content.get(id);
492
+ if (!existing) {
493
+ throw new NotFoundError(`Content item not found: ${id}`);
494
+ }
495
+ const partial = {
496
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
497
+ ...input.title !== void 0 ? { title: input.title } : {},
498
+ ...input.body !== void 0 ? { body: input.body } : {},
499
+ ...input.status !== void 0 ? { status: input.status } : {},
500
+ ...input.channels !== void 0 ? { channels: input.channels } : {},
501
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
508
502
  };
503
+ if (input.data !== void 0) {
504
+ partial.data = { ...existing.data ?? {}, ...input.data };
505
+ }
506
+ const updated = await this.storage.content.update(id, partial);
507
+ this.emitter.emit("content:updated", updated);
508
+ return updated;
509
509
  }
510
- loadConfiguration() {
511
- try {
512
- if (this.options.config) {
513
- return ContentManagementConfigSchema.parse(this.options.config);
514
- }
515
- const config = this.configManager?.getConfig() || {};
516
- return ContentManagementConfigSchema.parse(config);
517
- } catch (error) {
518
- console.error("Failed to load configuration:", error);
519
- return ContentManagementConfigSchema.parse({});
510
+ async delete(id) {
511
+ const existing = await this.storage.content.get(id);
512
+ if (!existing) {
513
+ throw new NotFoundError(`Content item not found: ${id}`);
520
514
  }
515
+ await this.storage.content.delete(id);
516
+ this.emitter.emit("content:deleted", { id });
521
517
  }
522
- setupMiddleware() {
523
- if (this.config.server.security.helmet) {
524
- this.server.use((0, import_helmet.default)());
518
+ async publish(id, options) {
519
+ const existing = await this.storage.content.get(id);
520
+ if (!existing) {
521
+ throw new NotFoundError(`Content item not found: ${id}`);
525
522
  }
526
- this.server.use((0, import_cors.default)(this.config.server.cors));
527
- if (this.config.performance.compression.enabled) {
528
- this.server.use((0, import_compression.default)({
529
- level: this.config.performance.compression.level
530
- }));
523
+ if (existing.workflowId) {
524
+ const workflow = await this.storage.workflows.get(existing.workflowId);
525
+ if (workflow && workflow.stages && workflow.stages.length > 0) {
526
+ const currentStage = workflow.stages.find((s) => s.id === existing.workflowStage);
527
+ const publishStage = workflow.stages.find((s) => s.isPublishStage);
528
+ if (publishStage && (!currentStage || !currentStage.isPublishStage)) {
529
+ throw new ValidationError(
530
+ `Content is not at a publish-eligible stage. Current stage: "${existing.workflowStage ?? "none"}", required publish stage: "${publishStage.id}"`,
531
+ {
532
+ details: {
533
+ currentStage: existing.workflowStage ?? null,
534
+ requiredStage: publishStage.id
535
+ }
536
+ }
537
+ );
538
+ }
539
+ }
531
540
  }
532
- if (this.options.logger) {
533
- this.server.use((0, import_morgan.default)("combined", {
534
- stream: {
535
- write: (message) => {
536
- this.options.logger?.info(message.trim());
537
- }
541
+ const publishers = this.getPublishers();
542
+ const allPublishers = publishers.list();
543
+ const channelIds = options?.channels ?? existing.channels;
544
+ let targetPublishers;
545
+ if (channelIds && channelIds.length > 0) {
546
+ targetPublishers = allPublishers.filter((p) => channelIds.includes(p.id));
547
+ } else {
548
+ targetPublishers = allPublishers.filter((p) => p.acceptedTypes.includes(existing.type));
549
+ }
550
+ const results = [];
551
+ for (const publisher of targetPublishers) {
552
+ if (!publisher.acceptedTypes.includes(existing.type)) {
553
+ results.push({
554
+ channelId: publisher.id,
555
+ success: false,
556
+ error: `Content type "${existing.type}" not accepted by publisher "${publisher.id}". Accepted: ${publisher.acceptedTypes.join(", ")}`
557
+ });
558
+ continue;
559
+ }
560
+ if (publisher.validate) {
561
+ const validationErrors = await publisher.validate(existing);
562
+ if (validationErrors.length > 0) {
563
+ results.push({
564
+ channelId: publisher.id,
565
+ success: false,
566
+ error: `Validation failed: ${validationErrors.map((e) => e.message).join(", ")}`
567
+ });
568
+ continue;
538
569
  }
539
- }));
570
+ }
571
+ try {
572
+ const result = await publisher.publish(existing);
573
+ results.push(result);
574
+ } catch (error) {
575
+ results.push({
576
+ channelId: publisher.id,
577
+ success: false,
578
+ error: error instanceof Error ? error.message : String(error)
579
+ });
580
+ }
540
581
  }
541
- this.server.use(import_express.default.json({ limit: "10mb" }));
542
- this.server.use(import_express.default.urlencoded({ extended: true, limit: "10mb" }));
543
- this.middleware.sort((a, b) => (a.order || 0) - (b.order || 0)).forEach((middleware) => {
544
- this.server.use(middleware.handler);
582
+ const now = (/* @__PURE__ */ new Date()).toISOString();
583
+ const updated = await this.storage.content.update(id, {
584
+ status: "published",
585
+ publishedAt: now,
586
+ updatedAt: now
545
587
  });
588
+ const publishResult = { item: updated, results };
589
+ this.emitter.emit("content:published", publishResult);
590
+ return publishResult;
546
591
  }
547
- setupRoutes() {
548
- this.server.get("/health", (req, res) => {
549
- res.json({
550
- status: "healthy",
551
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
552
- version: "0.1.0",
553
- uptime: process.uptime()
554
- });
592
+ async unpublish(id) {
593
+ const existing = await this.storage.content.get(id);
594
+ if (!existing) {
595
+ throw new NotFoundError(`Content item not found: ${id}`);
596
+ }
597
+ const updated = await this.storage.content.update(id, {
598
+ status: "draft",
599
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
555
600
  });
556
- this.server.use("/api", this.createAPIRoutes());
557
- this.server.use("/admin", this.createAdminRoutes());
558
- this.server.use("/static", import_express.default.static("public"));
559
- this.server.use(this.errorHandler.bind(this));
560
- }
561
- createAPIRoutes() {
562
- const router = import_express.default.Router();
563
- router.get("/content", this.getContentList.bind(this));
564
- router.get("/content/:id", this.getContent.bind(this));
565
- router.post("/content", this.createContent.bind(this));
566
- router.put("/content/:id", this.updateContent.bind(this));
567
- router.delete("/content/:id", this.deleteContent.bind(this));
568
- router.post("/content/:id/publish", this.publishContent.bind(this));
569
- router.post("/content/:id/schedule", this.scheduleContent.bind(this));
570
- router.post("/content/:id/unpublish", this.unpublishContent.bind(this));
571
- router.get("/workflows", this.getWorkflows.bind(this));
572
- router.get("/workflows/:id", this.getWorkflow.bind(this));
573
- router.post("/workflows", this.createWorkflow.bind(this));
574
- router.put("/workflows/:id", this.updateWorkflow.bind(this));
575
- router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
576
- router.get("/content-types", this.getContentTypes.bind(this));
577
- router.get("/content-types/:id", this.handleGetContentType.bind(this));
578
- router.post("/content-types", this.createContentType.bind(this));
579
- router.put("/content-types/:id", this.updateContentType.bind(this));
580
- router.delete("/content-types/:id", this.deleteContentType.bind(this));
581
- router.get("/config", this.handleGetConfig.bind(this));
582
- router.put("/config", this.handleUpdateConfig.bind(this));
583
- router.get("/users", this.getUsers.bind(this));
584
- router.get("/users/:id", this.getUser.bind(this));
585
- router.post("/users", this.createUser.bind(this));
586
- router.put("/users/:id", this.updateUser.bind(this));
587
- router.delete("/users/:id", this.deleteUser.bind(this));
588
- return router;
589
- }
590
- createAdminRoutes() {
591
- const router = import_express.default.Router();
592
- router.get("/", (req, res) => {
593
- res.json({
594
- message: "Content Management Suite Admin",
595
- version: "0.1.0",
596
- endpoints: [
597
- "/workflows",
598
- "/content-types",
599
- "/config",
600
- "/users"
601
- ]
602
- });
601
+ this.emitter.emit("content:unpublished", updated);
602
+ return updated;
603
+ }
604
+ async schedule(id, date) {
605
+ const existing = await this.storage.content.get(id);
606
+ if (!existing) {
607
+ throw new NotFoundError(`Content item not found: ${id}`);
608
+ }
609
+ const updated = await this.storage.content.update(id, {
610
+ scheduledFor: date,
611
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
603
612
  });
604
- router.get("/workflows", this.getWorkflows.bind(this));
605
- router.post("/workflows", this.createWorkflow.bind(this));
606
- router.put("/workflows/:id", this.updateWorkflow.bind(this));
607
- router.delete("/workflows/:id", this.deleteWorkflow.bind(this));
608
- router.get("/content-types", this.getContentTypes.bind(this));
609
- router.post("/content-types", this.createContentType.bind(this));
610
- router.put("/content-types/:id", this.updateContentType.bind(this));
611
- router.delete("/content-types/:id", this.deleteContentType.bind(this));
612
- router.get("/config", this.handleGetConfig.bind(this));
613
- router.put("/config", this.handleUpdateConfig.bind(this));
614
- return router;
615
- }
616
- errorHandler(error, _req, res, _next) {
617
- this.emit("error", error);
618
- if (error instanceof ContentManagementSuiteError) {
619
- res.status(error.statusCode).json({
620
- error: {
621
- code: error.code,
622
- message: error.message,
623
- details: error.details
624
- }
625
- });
613
+ this.emitter.emit("content:scheduled", { item: updated, date });
614
+ return updated;
615
+ }
616
+ async search(query, options) {
617
+ return this.storage.content.search(query, options);
618
+ }
619
+ };
620
+
621
+ // src/namespaces/content-types.ts
622
+ var import_zod2 = require("zod");
623
+ var ContentTypesNamespaceImpl = class {
624
+ constructor() {
625
+ this.store = /* @__PURE__ */ new Map();
626
+ }
627
+ register(definition) {
628
+ if ("schema" in definition && definition.schema) {
629
+ const def = definition;
630
+ this.store.set(def.id, def);
626
631
  } else {
627
- res.status(500).json({
628
- error: {
629
- code: "INTERNAL_ERROR",
630
- message: "An unexpected error occurred",
631
- details: process.env.NODE_ENV === "development" ? error.message : void 0
632
- }
632
+ const id = definition.id ?? definition.name;
633
+ if (!id) {
634
+ throw new Error("Content type must have an id or name");
635
+ }
636
+ const name = definition.name ?? definition.id ?? id;
637
+ this.store.set(id, {
638
+ id,
639
+ name,
640
+ schema: null
633
641
  });
634
642
  }
635
643
  }
636
- registerDefaultContentTypes() {
637
- this.contentTypes.set("text", import_content_type_text.TextContentType);
638
- this.contentTypes.set("image", import_content_type_image.ImageContentType);
639
- this.contentTypes.set("audio", import_content_type_audio.AudioContentTypeManager);
640
- this.contentTypes.set("video", import_content_type_video.VideoContentType);
644
+ unregister(id) {
645
+ if (!this.store.has(id)) {
646
+ throw new NotFoundError(`Content type not found: ${id}`);
647
+ }
648
+ this.store.delete(id);
649
+ }
650
+ get(id) {
651
+ return this.store.get(id);
652
+ }
653
+ list() {
654
+ return Array.from(this.store.values());
641
655
  }
642
- // Public API Methods
643
- async start() {
644
- if (this.isStarted) {
645
- throw new ContentManagementSuiteError("Suite is already started", "ALREADY_STARTED");
656
+ validate(type, data) {
657
+ const def = this.store.get(type);
658
+ if (!def || !def.schema) {
659
+ return { valid: true };
646
660
  }
647
661
  try {
648
- await this.configManager.start();
649
- await this.workflowService.start();
650
- await this.editorService.start();
651
- const port = this.config.server.port;
652
- const host = this.config.server.host;
653
- return new Promise((resolve, reject) => {
654
- this.httpServer = this.server.listen(port, host, () => {
655
- console.log(`Content Management Suite started on http://${host}:${port}`);
656
- this.isStarted = true;
657
- this.emit("started");
658
- resolve();
659
- });
660
- this.httpServer.on("error", (err) => {
661
- this.emit("error", err);
662
- reject(new ContentManagementSuiteInternalError("Failed to start server", err));
663
- });
664
- });
662
+ const schema = def.schema;
663
+ schema.parse(data);
664
+ return { valid: true };
665
665
  } catch (error) {
666
- this.emit("error", error);
667
- throw new ContentManagementSuiteInternalError("Failed to start suite", error);
666
+ if (error instanceof import_zod2.z.ZodError) {
667
+ return {
668
+ valid: false,
669
+ errors: error.issues.map((issue) => ({
670
+ message: issue.message,
671
+ path: issue.path.map(String)
672
+ }))
673
+ };
674
+ }
675
+ return {
676
+ valid: false,
677
+ errors: [{ message: error instanceof Error ? error.message : String(error) }]
678
+ };
668
679
  }
669
680
  }
670
- async stop() {
671
- if (this.isStopped) {
672
- throw new ContentManagementSuiteError("Suite is already stopped", "ALREADY_STOPPED");
681
+ };
682
+
683
+ // src/namespaces/workflows.ts
684
+ var crypto2 = __toESM(require("crypto"));
685
+ var WorkflowsNamespaceImpl = class {
686
+ constructor(storage) {
687
+ this.cache = /* @__PURE__ */ new Map();
688
+ this.storage = storage;
689
+ }
690
+ /** Load workflows from storage into cache. Called during suite initialization. */
691
+ async loadFromStorage() {
692
+ if (!this.storage) {
693
+ return;
673
694
  }
674
- try {
675
- await this.configManager.stop();
676
- await this.workflowService.stop();
677
- await this.editorService.stop();
678
- if (this.httpServer) {
679
- return new Promise((resolve) => {
680
- this.httpServer.close(() => {
681
- console.log("Content Management Suite stopped");
682
- this.isStopped = true;
683
- this.emit("stopped");
684
- resolve();
685
- });
686
- });
687
- }
688
- this.isStopped = true;
689
- } catch (error) {
690
- this.emit("error", error);
691
- throw new ContentManagementSuiteInternalError("Failed to stop suite", error);
695
+ const workflows = await this.storage.workflows.list();
696
+ for (const w of workflows) {
697
+ this.cache.set(w.id, w);
692
698
  }
693
699
  }
694
- getConfig() {
695
- return this.config;
700
+ async create(input) {
701
+ const workflow = {
702
+ id: crypto2.randomUUID(),
703
+ name: input.name,
704
+ ...input.description !== void 0 ? { description: input.description } : {},
705
+ ...input.stages !== void 0 ? { stages: input.stages } : {},
706
+ ...input.transitions !== void 0 ? { transitions: input.transitions } : {}
707
+ };
708
+ if (this.storage) {
709
+ await this.storage.workflows.create(workflow);
710
+ }
711
+ this.cache.set(workflow.id, workflow);
712
+ return workflow;
696
713
  }
697
- async updateConfig(config) {
698
- try {
699
- const newConfig = ContentManagementConfigSchema.parse({
700
- ...this.config,
701
- ...config
702
- });
703
- this.config = newConfig;
704
- await this.configManager.updateConfig(newConfig);
705
- this.emit("config:updated", newConfig);
706
- } catch (error) {
707
- throw new ContentManagementSuiteValidationError("Invalid configuration", error);
714
+ async get(id) {
715
+ const workflow = this.cache.get(id);
716
+ if (!workflow) {
717
+ throw new NotFoundError(`Workflow not found: ${id}`);
708
718
  }
719
+ return workflow;
709
720
  }
710
- registerContentType(contentType) {
711
- const id = contentType?.id || contentType?.name || "unknown";
712
- this.contentTypes.set(id, contentType);
713
- this.emit("contentType:registered", contentType);
721
+ async list() {
722
+ return Array.from(this.cache.values());
714
723
  }
715
- unregisterContentType(contentTypeId) {
716
- if (!this.contentTypes.has(contentTypeId)) {
717
- throw new ContentManagementSuiteNotFoundError("Content type not found");
724
+ async update(id, input) {
725
+ const existing = this.cache.get(id);
726
+ if (!existing) {
727
+ throw new NotFoundError(`Workflow not found: ${id}`);
728
+ }
729
+ const updated = {
730
+ ...existing,
731
+ ...input.name !== void 0 ? { name: input.name } : {},
732
+ ...input.description !== void 0 ? { description: input.description } : {},
733
+ ...input.stages !== void 0 ? { stages: input.stages } : {},
734
+ ...input.transitions !== void 0 ? { transitions: input.transitions } : {}
735
+ };
736
+ if (this.storage) {
737
+ await this.storage.workflows.update(id, updated);
718
738
  }
719
- this.contentTypes.delete(contentTypeId);
720
- this.emit("contentType:unregistered", contentTypeId);
739
+ this.cache.set(id, updated);
740
+ return updated;
721
741
  }
722
- getContentType(contentTypeId) {
723
- const contentType = this.contentTypes.get(contentTypeId);
724
- if (!contentType) {
725
- throw new ContentManagementSuiteNotFoundError("Content type not found");
742
+ async delete(id) {
743
+ if (!this.cache.has(id)) {
744
+ throw new NotFoundError(`Workflow not found: ${id}`);
726
745
  }
727
- return contentType;
746
+ if (this.storage) {
747
+ const contentResult = await this.storage.content.list();
748
+ const assignedContent = contentResult.items.filter((item) => item.workflowId === id);
749
+ if (assignedContent.length > 0) {
750
+ throw new ConflictError(
751
+ `Cannot delete workflow "${id}": ${assignedContent.length} content item(s) are assigned to it. Reassign or archive them first.`,
752
+ { details: { assignedCount: assignedContent.length } }
753
+ );
754
+ }
755
+ await this.storage.workflows.delete(id);
756
+ }
757
+ this.cache.delete(id);
728
758
  }
729
- listContentTypes() {
730
- return Array.from(this.contentTypes.entries()).map(([id, contentType]) => ({
731
- id,
732
- contentType,
733
- ...contentType
734
- }));
759
+ };
760
+
761
+ // src/namespaces/users.ts
762
+ var crypto3 = __toESM(require("crypto"));
763
+ var UsersNamespaceImpl = class {
764
+ constructor(emitter) {
765
+ this.store = /* @__PURE__ */ new Map();
766
+ this.emitter = emitter;
735
767
  }
736
- // Plugin Management
737
- registerPlugin(plugin) {
738
- if (this.plugins.has(plugin.name)) {
739
- throw new ContentManagementSuiteConflictError("Plugin already registered");
740
- }
741
- this.plugins.set(plugin.name, plugin);
742
- plugin.install(this);
743
- this.emit("plugin:registered", plugin);
768
+ async create(input) {
769
+ const user = {
770
+ id: crypto3.randomUUID(),
771
+ name: input.name,
772
+ ...input.email !== void 0 ? { email: input.email } : {},
773
+ ...input.role !== void 0 ? { role: input.role } : {},
774
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
775
+ };
776
+ this.store.set(user.id, user);
777
+ this.emitter.emit("user:created", user);
778
+ return user;
744
779
  }
745
- unregisterPlugin(pluginName) {
746
- const plugin = this.plugins.get(pluginName);
747
- if (!plugin) {
748
- throw new ContentManagementSuiteNotFoundError("Plugin not found");
749
- }
750
- plugin.uninstall(this);
751
- this.plugins.delete(pluginName);
752
- this.emit("plugin:unregistered", pluginName);
753
- }
754
- // Middleware Management
755
- addMiddleware(middleware) {
756
- this.middleware.push(middleware);
757
- this.emit("middleware:added", middleware);
758
- }
759
- removeMiddleware(middlewareName) {
760
- const index = this.middleware.findIndex((m) => m.name === middlewareName);
761
- if (index === -1) {
762
- throw new ContentManagementSuiteNotFoundError("Middleware not found");
763
- }
764
- this.middleware.splice(index, 1);
765
- this.emit("middleware:removed", middlewareName);
766
- }
767
- // Hook Management
768
- addHook(hook) {
769
- if (!this.hooks.has(hook.name)) {
770
- this.hooks.set(hook.name, []);
771
- }
772
- this.hooks.get(hook.name).push(hook);
773
- this.hooks.get(hook.name).sort((a, b) => (a.priority || 0) - (b.priority || 0));
774
- this.emit("hook:added", hook);
775
- }
776
- removeHook(hookName, handler) {
777
- const hooks = this.hooks.get(hookName);
778
- if (!hooks) {
779
- throw new ContentManagementSuiteNotFoundError("Hook not found");
780
- }
781
- if (handler) {
782
- const index = hooks.findIndex((h) => h.handler === handler);
783
- if (index === -1) {
784
- throw new ContentManagementSuiteNotFoundError("Hook handler not found");
785
- }
786
- hooks.splice(index, 1);
787
- } else {
788
- this.hooks.delete(hookName);
780
+ async get(id) {
781
+ const user = this.store.get(id);
782
+ if (!user) {
783
+ throw new NotFoundError(`User not found: ${id}`);
789
784
  }
790
- this.emit("hook:removed", hookName);
785
+ return user;
791
786
  }
792
- // API Route Handlers
793
- async getContentList(req, res) {
794
- try {
795
- const { type, status, page = 1, limit = 20 } = req.query;
796
- const filters = { type, status, page: parseInt(page), limit: parseInt(limit) };
797
- const content = await this.editorService.listContent(filters);
798
- res.json(content);
799
- } catch (error) {
800
- throw new ContentManagementSuiteInternalError("Failed to get content list", error);
787
+ async list() {
788
+ const items = Array.from(this.store.values());
789
+ return { items, total: items.length };
790
+ }
791
+ async update(id, input) {
792
+ const existing = this.store.get(id);
793
+ if (!existing) {
794
+ throw new NotFoundError(`User not found: ${id}`);
801
795
  }
796
+ const updated = {
797
+ ...existing,
798
+ ...input,
799
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
800
+ };
801
+ this.store.set(id, updated);
802
+ this.emitter.emit("user:updated", updated);
803
+ return updated;
802
804
  }
803
- async getContent(req, res) {
804
- try {
805
- const { id } = req.params;
806
- const content = await this.editorService.getContent(id);
807
- res.json(content);
808
- } catch (error) {
809
- throw new ContentManagementSuiteNotFoundError("Content not found", error);
805
+ async delete(id) {
806
+ if (!this.store.has(id)) {
807
+ throw new NotFoundError(`User not found: ${id}`);
810
808
  }
809
+ this.store.delete(id);
810
+ this.emitter.emit("user:deleted", { id });
811
811
  }
812
- async createContent(req, res) {
813
- try {
814
- const { type, data } = req.body;
815
- const content = await this.editorService.createContent(type, data);
816
- this.emit("content:created", content);
817
- res.status(201).json(content);
818
- } catch (error) {
819
- throw new ContentManagementSuiteInternalError("Failed to create content", error);
812
+ };
813
+ var PermissionsNamespaceImpl = class {
814
+ constructor() {
815
+ this.store = /* @__PURE__ */ new Map();
816
+ }
817
+ async check(userId, permission) {
818
+ const perms = this.store.get(userId);
819
+ return perms?.has(permission) ?? false;
820
+ }
821
+ async grant(userId, permission) {
822
+ let perms = this.store.get(userId);
823
+ if (!perms) {
824
+ perms = /* @__PURE__ */ new Set();
825
+ this.store.set(userId, perms);
820
826
  }
827
+ perms.add(permission);
821
828
  }
822
- async updateContent(req, res) {
823
- try {
824
- const { id } = req.params;
825
- const { data } = req.body;
826
- const content = await this.editorService.updateContent(id, data);
827
- this.emit("content:updated", content);
828
- res.json(content);
829
- } catch (error) {
830
- throw new ContentManagementSuiteInternalError("Failed to update content", error);
829
+ async revoke(userId, permission) {
830
+ const perms = this.store.get(userId);
831
+ if (perms) {
832
+ perms.delete(permission);
831
833
  }
832
834
  }
833
- async deleteContent(req, res) {
834
- try {
835
- const { id } = req.params;
836
- const { soft = true } = req.query;
837
- await this.editorService.deleteContent(id, soft === "true");
838
- this.emit("content:deleted", { id });
839
- res.status(204).send();
840
- } catch (error) {
841
- throw new ContentManagementSuiteInternalError("Failed to delete content", error);
835
+ };
836
+
837
+ // src/namespaces/config.ts
838
+ function deepMerge(target, source) {
839
+ const result = { ...target };
840
+ for (const key of Object.keys(source)) {
841
+ const sourceVal = source[key];
842
+ const targetVal = result[key];
843
+ if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && targetVal !== void 0 && typeof targetVal === "object" && !Array.isArray(targetVal)) {
844
+ result[key] = deepMerge(
845
+ targetVal,
846
+ sourceVal
847
+ );
848
+ } else {
849
+ result[key] = sourceVal;
842
850
  }
843
851
  }
844
- async publishContent(req, res) {
852
+ return result;
853
+ }
854
+ var ConfigNamespaceImpl = class {
855
+ constructor(initialConfig, emitter) {
856
+ this.config = initialConfig;
857
+ this.emitter = emitter;
858
+ }
859
+ get() {
860
+ return this.config;
861
+ }
862
+ update(partial) {
863
+ const merged = deepMerge(this.config, partial);
845
864
  try {
846
- const { id } = req.params;
847
- const content = await this.workflowService.publishContent(id);
848
- this.emit("content:published", content);
849
- res.json(content);
850
- } catch (error) {
851
- throw new ContentManagementSuiteInternalError("Failed to publish content", error);
865
+ this.config = ContentManagementConfigSchema.parse(merged);
866
+ } catch (err) {
867
+ throw new ValidationError(
868
+ "Invalid configuration",
869
+ err instanceof Error ? { cause: err } : void 0
870
+ );
852
871
  }
872
+ this.emitter.emit("config:updated", this.config);
873
+ return this.config;
853
874
  }
854
- async scheduleContent(req, res) {
855
- try {
856
- const { id } = req.params;
857
- const { date } = req.body;
858
- const content = await this.workflowService.scheduleContent(id, new Date(date));
859
- this.emit("content:scheduled", content, new Date(date));
860
- res.json(content);
861
- } catch (error) {
862
- throw new ContentManagementSuiteInternalError("Failed to schedule content", error);
875
+ };
876
+
877
+ // src/namespaces/plugins.ts
878
+ var PluginsNamespaceImpl = class {
879
+ constructor(suite) {
880
+ this.store = /* @__PURE__ */ new Map();
881
+ this.suite = suite;
882
+ }
883
+ async register(plugin) {
884
+ if (this.store.has(plugin.name)) {
885
+ throw new ConflictError(`Plugin already registered: ${plugin.name}`);
863
886
  }
887
+ await plugin.setup(this.suite);
888
+ this.store.set(plugin.name, plugin);
864
889
  }
865
- async unpublishContent(req, res) {
866
- try {
867
- const { id } = req.params;
868
- const content = await this.workflowService.unpublishContent(id);
869
- res.json(content);
870
- } catch (error) {
871
- throw new ContentManagementSuiteInternalError("Failed to unpublish content", error);
890
+ async unregister(name) {
891
+ const plugin = this.store.get(name);
892
+ if (!plugin) {
893
+ throw new NotFoundError(`Plugin not found: ${name}`);
872
894
  }
895
+ if (plugin.teardown) {
896
+ await plugin.teardown(this.suite);
897
+ }
898
+ this.store.delete(name);
873
899
  }
874
- async getWorkflows(req, res) {
875
- try {
876
- const workflows = await this.workflowService.listWorkflows();
877
- res.json(workflows);
878
- } catch (error) {
879
- throw new ContentManagementSuiteInternalError("Failed to get workflows", error);
900
+ };
901
+
902
+ // src/namespaces/publishers.ts
903
+ var WebsitePublisher = class {
904
+ constructor() {
905
+ this.id = "website";
906
+ this.name = "Website";
907
+ this.acceptedTypes = ["blog-post", "article", "page", "generic", "text"];
908
+ }
909
+ async publish(content) {
910
+ const slug = content.title ? content.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : content.id;
911
+ return {
912
+ channelId: this.id,
913
+ success: true,
914
+ url: `/content/${slug}`
915
+ };
916
+ }
917
+ };
918
+ var RSSPublisher = class {
919
+ constructor() {
920
+ this.id = "rss";
921
+ this.name = "RSS Feed";
922
+ this.acceptedTypes = ["blog-post", "article", "rss-article", "generic", "text"];
923
+ }
924
+ async publish(content) {
925
+ return {
926
+ channelId: this.id,
927
+ success: true,
928
+ url: `/feed/rss/${content.id}`
929
+ };
930
+ }
931
+ };
932
+ var PublishersNamespaceImpl = class {
933
+ constructor(emitter) {
934
+ this.store = /* @__PURE__ */ new Map();
935
+ this.emitter = emitter;
936
+ this.store.set("website", new WebsitePublisher());
937
+ this.store.set("rss", new RSSPublisher());
938
+ }
939
+ register(publisher) {
940
+ this.store.set(publisher.id, publisher);
941
+ this.emitter.emit("publisher:registered", { publisher });
942
+ }
943
+ unregister(id) {
944
+ if (!this.store.has(id)) {
945
+ throw new NotFoundError(`Publisher not found: ${id}`);
880
946
  }
947
+ this.store.delete(id);
881
948
  }
882
- async getWorkflow(req, res) {
883
- try {
884
- const { id } = req.params;
885
- const workflow = await this.workflowService.getWorkflow(id);
886
- res.json(workflow);
887
- } catch (error) {
888
- throw new ContentManagementSuiteNotFoundError("Workflow not found", error);
949
+ list() {
950
+ return Array.from(this.store.values());
951
+ }
952
+ get(id) {
953
+ return this.store.get(id);
954
+ }
955
+ };
956
+
957
+ // src/namespaces/sources.ts
958
+ var crypto4 = __toESM(require("crypto"));
959
+ var SourcesNamespaceImpl = class {
960
+ constructor(emitter, storage, workflowDefaults) {
961
+ this.store = /* @__PURE__ */ new Map();
962
+ this.emitter = emitter;
963
+ this.storage = storage;
964
+ this.workflowDefaults = workflowDefaults;
965
+ }
966
+ register(source) {
967
+ this.store.set(source.id, source);
968
+ this.emitter.emit("source:registered", { source });
969
+ }
970
+ unregister(id) {
971
+ if (!this.store.has(id)) {
972
+ throw new NotFoundError(`Source not found: ${id}`);
889
973
  }
974
+ this.store.delete(id);
890
975
  }
891
- async createWorkflow(req, res) {
892
- try {
893
- const { data } = req.body;
894
- const workflow = await this.workflowService.createWorkflow(data);
895
- res.status(201).json(workflow);
896
- } catch (error) {
897
- throw new ContentManagementSuiteInternalError("Failed to create workflow", error);
976
+ list() {
977
+ return Array.from(this.store.values());
978
+ }
979
+ get(id) {
980
+ return this.store.get(id);
981
+ }
982
+ async ingest(sourceId, rawPayload) {
983
+ const source = this.store.get(sourceId);
984
+ if (!source) {
985
+ throw new NotFoundError(`Source not found: ${sourceId}`);
898
986
  }
987
+ if (source.validate) {
988
+ const validationErrors = await source.validate(rawPayload);
989
+ if (validationErrors.length > 0) {
990
+ throw new ValidationError("Source validation failed", {
991
+ details: { errors: validationErrors }
992
+ });
993
+ }
994
+ }
995
+ const rawItem = await source.ingest(rawPayload);
996
+ if (rawItem.sourceId) {
997
+ const existing = await this.storage.content.findBySource(source.id, rawItem.sourceId);
998
+ if (existing) {
999
+ return { created: false, content: existing };
1000
+ }
1001
+ }
1002
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1003
+ const workflowId = this.workflowDefaults[rawItem.type] ?? this.workflowDefaults["generic"];
1004
+ const item = {
1005
+ id: crypto4.randomUUID(),
1006
+ type: rawItem.type,
1007
+ createdAt: now,
1008
+ status: "review",
1009
+ sourceType: source.id,
1010
+ sourceId: rawItem.sourceId ?? null,
1011
+ ...rawItem.title !== void 0 ? { title: rawItem.title } : {},
1012
+ ...rawItem.body !== void 0 ? { body: rawItem.body } : {},
1013
+ ...rawItem.data !== void 0 ? { data: rawItem.data } : {},
1014
+ ...rawItem.metadata !== void 0 ? { metadata: rawItem.metadata } : {},
1015
+ ...workflowId !== void 0 ? { workflowId } : {}
1016
+ };
1017
+ const created = await this.storage.content.create(item);
1018
+ this.emitter.emit("content:ingested", { item: created, sourceType: source.id });
1019
+ this.emitter.emit("content:created", created);
1020
+ return { created: true, content: created };
899
1021
  }
900
- async updateWorkflow(req, res) {
901
- try {
902
- const { id } = req.params;
903
- const { data } = req.body;
904
- const workflow = await this.workflowService.updateWorkflow(id, data);
905
- res.json(workflow);
906
- } catch (error) {
907
- throw new ContentManagementSuiteInternalError("Failed to update workflow", error);
1022
+ };
1023
+
1024
+ // src/namespaces/ai.ts
1025
+ var crypto5 = __toESM(require("crypto"));
1026
+ var AINamespaceImpl = class {
1027
+ constructor(emitter, storage, options) {
1028
+ this.emitter = emitter;
1029
+ this.storage = storage;
1030
+ this.reviewService = options.reviewService;
1031
+ this.socialGeneratorService = options.socialGeneratorService;
1032
+ this.enhanceService = options.enhanceService;
1033
+ this.workflowDefaults = options.workflowDefaults ?? {};
1034
+ }
1035
+ async review(contentId) {
1036
+ if (!this.reviewService) {
1037
+ throw new InternalError("AI review service not configured");
1038
+ }
1039
+ const content = await this.storage.content.get(contentId);
1040
+ if (!content) {
1041
+ throw new NotFoundError(`Content item not found: ${contentId}`);
908
1042
  }
1043
+ const textToReview = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
1044
+ const result = await this.reviewService.review(textToReview);
1045
+ const suggestions = result.issues.map((issue) => ({
1046
+ category: issue.category,
1047
+ severity: issue.severity === "info" || issue.severity === "warning" || issue.severity === "error" ? issue.severity : "info",
1048
+ message: issue.message,
1049
+ ...issue.originalText !== void 0 ? { originalText: issue.originalText } : {},
1050
+ ...issue.suggestedFix !== void 0 ? { suggestedFix: issue.suggestedFix } : {}
1051
+ }));
1052
+ const review = {
1053
+ id: crypto5.randomUUID(),
1054
+ contentId,
1055
+ score: result.overallScore,
1056
+ suggestions,
1057
+ passesThreshold: result.passesThreshold,
1058
+ rawResult: result.raw,
1059
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1060
+ };
1061
+ await this.storage.aiReviews.create(review);
1062
+ this.emitter.emit("ai:review:completed", { contentId, review });
1063
+ return review;
909
1064
  }
910
- async deleteWorkflow(req, res) {
911
- try {
912
- const { id } = req.params;
913
- await this.workflowService.deleteWorkflow(id);
914
- res.status(204).send();
915
- } catch (error) {
916
- throw new ContentManagementSuiteInternalError("Failed to delete workflow", error);
1065
+ async generateSocialPosts(contentId, platforms) {
1066
+ if (!this.socialGeneratorService) {
1067
+ throw new InternalError("AI social generator service not configured");
1068
+ }
1069
+ const content = await this.storage.content.get(contentId);
1070
+ if (!content) {
1071
+ throw new NotFoundError(`Content item not found: ${contentId}`);
917
1072
  }
1073
+ const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
1074
+ const generated = await this.socialGeneratorService.generate(textContent, platforms);
1075
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1076
+ const workflowId = this.workflowDefaults["social-post"] ?? this.workflowDefaults["generic"];
1077
+ const posts = generated.map((g) => ({
1078
+ id: crypto5.randomUUID(),
1079
+ contentId,
1080
+ platform: g.platform,
1081
+ body: g.body,
1082
+ status: "draft",
1083
+ platformPostId: null,
1084
+ publishedAt: null,
1085
+ createdAt: now,
1086
+ ...workflowId !== void 0 ? { workflowId } : {}
1087
+ }));
1088
+ for (const post of posts) {
1089
+ await this.storage.socialPosts.create(post);
1090
+ }
1091
+ this.emitter.emit("ai:social:generated", { contentId, posts });
1092
+ return posts;
918
1093
  }
919
- async getContentTypes(req, res) {
920
- try {
921
- const contentTypes = this.listContentTypes();
922
- res.json(contentTypes);
923
- } catch (error) {
924
- throw new ContentManagementSuiteInternalError("Failed to get content types", error);
1094
+ async enhance(contentId, opts) {
1095
+ if (!this.enhanceService) {
1096
+ throw new InternalError("AI enhance service not configured");
1097
+ }
1098
+ const content = await this.storage.content.get(contentId);
1099
+ if (!content) {
1100
+ throw new NotFoundError(`Content item not found: ${contentId}`);
925
1101
  }
1102
+ const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
1103
+ return this.enhanceService.enhance(textContent, opts);
926
1104
  }
927
- async handleGetContentType(req, res) {
928
- try {
929
- const id = req.params.id;
930
- if (!id) {
931
- throw new ContentManagementSuiteNotFoundError("Content type ID required");
932
- }
933
- const contentType = this.contentTypes.get(id);
934
- if (!contentType) {
935
- throw new ContentManagementSuiteNotFoundError("Content type not found");
936
- }
937
- res.json(contentType);
938
- } catch (error) {
939
- throw new ContentManagementSuiteNotFoundError("Content type not found", error);
1105
+ async suggest(contentId) {
1106
+ if (!this.enhanceService) {
1107
+ throw new InternalError("AI enhance service not configured");
1108
+ }
1109
+ const content = await this.storage.content.get(contentId);
1110
+ if (!content) {
1111
+ throw new NotFoundError(`Content item not found: ${contentId}`);
940
1112
  }
1113
+ const textContent = [content.title, content.body].filter(Boolean).join("\n\n") || JSON.stringify(content.data ?? {});
1114
+ return this.enhanceService.suggest(textContent);
941
1115
  }
942
- async createContentType(req, res) {
943
- try {
944
- const { data } = req.body;
945
- this.registerContentType(data);
946
- res.status(201).json(data);
947
- } catch (error) {
948
- throw new ContentManagementSuiteInternalError("Failed to create content type", error);
1116
+ };
1117
+
1118
+ // src/namespaces/social.ts
1119
+ var SocialNamespaceImpl = class {
1120
+ constructor(emitter, storage) {
1121
+ this.store = /* @__PURE__ */ new Map();
1122
+ this.emitter = emitter;
1123
+ this.storage = storage;
1124
+ }
1125
+ registerPlatform(adapter) {
1126
+ this.store.set(adapter.id, adapter);
1127
+ }
1128
+ unregisterPlatform(id) {
1129
+ if (!this.store.has(id)) {
1130
+ throw new NotFoundError(`Social platform not found: ${id}`);
949
1131
  }
1132
+ this.store.delete(id);
950
1133
  }
951
- async updateContentType(req, res) {
952
- try {
953
- const id = req.params.id;
954
- if (!id) {
955
- throw new ContentManagementSuiteNotFoundError("Content type ID required");
1134
+ listPlatforms() {
1135
+ return Array.from(this.store.values()).map((adapter) => ({
1136
+ id: adapter.id,
1137
+ name: adapter.name,
1138
+ acceptedTypes: adapter.acceptedTypes,
1139
+ hasPreview: typeof adapter.preview === "function",
1140
+ hasMetrics: typeof adapter.getMetrics === "function"
1141
+ }));
1142
+ }
1143
+ async publish(contentId, platforms) {
1144
+ const missing = platforms.filter((p) => !this.store.has(p));
1145
+ if (missing.length > 0) {
1146
+ throw new ValidationError(
1147
+ `Unregistered social platform(s): ${missing.join(", ")}`,
1148
+ { details: { missingPlatforms: missing } }
1149
+ );
1150
+ }
1151
+ const content = await this.storage.content.get(contentId);
1152
+ if (!content) {
1153
+ throw new NotFoundError(`Content item not found: ${contentId}`);
1154
+ }
1155
+ if (content.workflowId) {
1156
+ const workflow = await this.storage.workflows.get(content.workflowId);
1157
+ if (workflow && workflow.stages && workflow.stages.length > 0) {
1158
+ const currentStage = workflow.stages.find((s) => s.id === content.workflowStage);
1159
+ const publishStage = workflow.stages.find((s) => s.isPublishStage);
1160
+ if (publishStage && (!currentStage || !currentStage.isPublishStage)) {
1161
+ throw new ValidationError(
1162
+ `Content is not at a publish-eligible stage. Current stage: "${content.workflowStage ?? "none"}", required publish stage: "${publishStage.id}"`,
1163
+ {
1164
+ details: {
1165
+ currentStage: content.workflowStage ?? null,
1166
+ requiredStage: publishStage.id
1167
+ }
1168
+ }
1169
+ );
1170
+ }
956
1171
  }
957
- const { data } = req.body;
958
- this.unregisterContentType(id);
959
- this.registerContentType(data);
960
- res.json(data);
961
- } catch (error) {
962
- throw new ContentManagementSuiteInternalError("Failed to update content type", error);
963
1172
  }
964
- }
965
- async deleteContentType(req, res) {
966
- try {
967
- const id = req.params.id;
968
- if (!id) {
969
- throw new ContentManagementSuiteNotFoundError("Content type ID required");
1173
+ const socialPosts = await this.storage.socialPosts.findByContent(contentId);
1174
+ const results = [];
1175
+ for (const platformId of platforms) {
1176
+ const adapter = this.store.get(platformId);
1177
+ if (!adapter) {
1178
+ continue;
1179
+ }
1180
+ if (!adapter.acceptedTypes.includes(content.type)) {
1181
+ results.push({
1182
+ platform: platformId,
1183
+ success: false,
1184
+ error: `Content type "${content.type}" not accepted by platform "${platformId}". Accepted: ${adapter.acceptedTypes.join(", ")}`
1185
+ });
1186
+ continue;
1187
+ }
1188
+ const post = socialPosts.find((p) => p.platform === platformId && p.status === "draft");
1189
+ if (!post) {
1190
+ results.push({
1191
+ platform: platformId,
1192
+ success: false,
1193
+ error: `No draft social post found for platform "${platformId}"`
1194
+ });
1195
+ continue;
1196
+ }
1197
+ try {
1198
+ const result = await adapter.publish(post);
1199
+ results.push(result);
1200
+ if (result.success) {
1201
+ await this.storage.socialPosts.update(post.id, {
1202
+ status: "published",
1203
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1204
+ ...result.platformPostId !== void 0 ? { platformPostId: result.platformPostId } : {}
1205
+ });
1206
+ this.emitter.emit("social:published", { contentId, platform: platformId, result });
1207
+ } else {
1208
+ await this.storage.socialPosts.update(post.id, { status: "failed" });
1209
+ }
1210
+ } catch (error) {
1211
+ await this.storage.socialPosts.update(post.id, { status: "failed" });
1212
+ results.push({
1213
+ platform: platformId,
1214
+ success: false,
1215
+ error: error instanceof Error ? error.message : String(error)
1216
+ });
970
1217
  }
971
- this.unregisterContentType(id);
972
- res.status(204).send();
973
- } catch (error) {
974
- throw new ContentManagementSuiteInternalError("Failed to delete content type", error);
975
1218
  }
1219
+ return results;
976
1220
  }
977
- async handleGetConfig(req, res) {
978
- try {
979
- res.json(this.config);
980
- } catch (error) {
981
- throw new ContentManagementSuiteInternalError("Failed to get config", error);
1221
+ async preview(contentId, platform) {
1222
+ const adapter = this.store.get(platform);
1223
+ if (!adapter) {
1224
+ throw new NotFoundError(`Social platform not found: ${platform}`);
982
1225
  }
983
- }
984
- async handleUpdateConfig(req, res) {
985
- try {
986
- const { config } = req.body;
987
- const newConfig = ContentManagementConfigSchema.parse({
988
- ...this.config,
989
- ...config
990
- });
991
- this.config = newConfig;
992
- await this.configManager.updateConfig(newConfig);
993
- this.emit("config:updated", newConfig);
994
- res.json(this.config);
995
- } catch (error) {
996
- throw new ContentManagementSuiteValidationError("Invalid configuration", error);
1226
+ if (!adapter.preview) {
1227
+ throw new InternalError(`Platform "${platform}" does not support preview`);
997
1228
  }
998
- }
999
- async getUsers(req, res) {
1000
- try {
1001
- res.json([]);
1002
- } catch (error) {
1003
- throw new ContentManagementSuiteInternalError("Failed to get users", error);
1229
+ const posts = await this.storage.socialPosts.findByContent(contentId);
1230
+ const post = posts.find((p) => p.platform === platform);
1231
+ if (!post) {
1232
+ throw new NotFoundError(`No social post found for content "${contentId}" on platform "${platform}"`);
1004
1233
  }
1234
+ return adapter.preview(post);
1005
1235
  }
1006
- async getUser(req, res) {
1007
- try {
1008
- const { id } = req.params;
1009
- res.json({ id, name: "User" });
1010
- } catch (error) {
1011
- throw new ContentManagementSuiteNotFoundError("User not found", error);
1236
+ async getMetrics(contentId, platform) {
1237
+ const adapter = this.store.get(platform);
1238
+ if (!adapter) {
1239
+ throw new NotFoundError(`Social platform not found: ${platform}`);
1240
+ }
1241
+ if (!adapter.getMetrics) {
1242
+ throw new InternalError(`Platform "${platform}" does not support metrics`);
1243
+ }
1244
+ const posts = await this.storage.socialPosts.findByContent(contentId);
1245
+ const post = posts.find((p) => p.platform === platform && p.platformPostId);
1246
+ if (!post || !post.platformPostId) {
1247
+ throw new NotFoundError(`No published social post found for content "${contentId}" on platform "${platform}"`);
1012
1248
  }
1249
+ return adapter.getMetrics(post.platformPostId);
1013
1250
  }
1014
- async createUser(req, res) {
1015
- try {
1016
- const { data } = req.body;
1017
- res.status(201).json({ id: (0, import_uuid.v4)(), ...data });
1018
- } catch (error) {
1019
- throw new ContentManagementSuiteInternalError("Failed to create user", error);
1251
+ };
1252
+
1253
+ // src/content-management-suite.ts
1254
+ var ContentManagementSuiteImpl = class extends import_events.EventEmitter {
1255
+ constructor(options) {
1256
+ super();
1257
+ this._initialized = false;
1258
+ this._options = options ?? {};
1259
+ this._logger = this._options.logger;
1260
+ this._storage = this._options.storage ?? createInMemoryAdapter();
1261
+ const parsedConfig = ContentManagementConfigSchema.parse(
1262
+ this._options.config ?? {}
1263
+ );
1264
+ const workflowDefaults = this._options.workflows?.defaults ?? {};
1265
+ this.contentTypes = new ContentTypesNamespaceImpl();
1266
+ this.publishers = new PublishersNamespaceImpl(this);
1267
+ this.content = new ContentNamespaceImpl(
1268
+ this,
1269
+ this._storage,
1270
+ () => this.contentTypes,
1271
+ () => this.publishers
1272
+ );
1273
+ this.workflows = new WorkflowsNamespaceImpl(this._storage);
1274
+ this.users = new UsersNamespaceImpl(this);
1275
+ this.permissions = new PermissionsNamespaceImpl();
1276
+ this.config = new ConfigNamespaceImpl(parsedConfig, this);
1277
+ this.plugins = new PluginsNamespaceImpl(this);
1278
+ this.sources = new SourcesNamespaceImpl(this, this._storage, workflowDefaults);
1279
+ const aiOpts = {};
1280
+ if (this._options.ai?.reviewService) {
1281
+ aiOpts.reviewService = this._options.ai.reviewService;
1282
+ }
1283
+ if (this._options.ai?.socialGeneratorService) {
1284
+ aiOpts.socialGeneratorService = this._options.ai.socialGeneratorService;
1020
1285
  }
1286
+ if (this._options.ai?.enhanceService) {
1287
+ aiOpts.enhanceService = this._options.ai.enhanceService;
1288
+ }
1289
+ if (Object.keys(workflowDefaults).length > 0) {
1290
+ aiOpts.workflowDefaults = workflowDefaults;
1291
+ }
1292
+ this.ai = new AINamespaceImpl(this, this._storage, aiOpts);
1293
+ this.social = new SocialNamespaceImpl(this, this._storage);
1021
1294
  }
1022
- async updateUser(req, res) {
1023
- try {
1024
- const { id } = req.params;
1025
- const { data } = req.body;
1026
- res.json({ id, ...data });
1027
- } catch (error) {
1028
- throw new ContentManagementSuiteInternalError("Failed to update user", error);
1295
+ async initialize() {
1296
+ if (this._initialized) {
1297
+ throw new InternalError("Suite is already initialized");
1298
+ }
1299
+ this.contentTypes.register({ id: "text", name: "text" });
1300
+ this.contentTypes.register({ id: "image", name: "image" });
1301
+ this.contentTypes.register({ id: "audio", name: "audio" });
1302
+ this.contentTypes.register({ id: "video", name: "video" });
1303
+ const workflowsImpl = this.workflows;
1304
+ if (typeof workflowsImpl.loadFromStorage === "function") {
1305
+ await workflowsImpl.loadFromStorage();
1029
1306
  }
1307
+ if (this._options.plugins) {
1308
+ for (const plugin of this._options.plugins) {
1309
+ await this.plugins.register(plugin);
1310
+ }
1311
+ }
1312
+ this.on("ai:review:completed", async ({ contentId, review }) => {
1313
+ try {
1314
+ const content = await this._storage.content.get(contentId);
1315
+ if (!content || !content.workflowId || !content.workflowStage) {
1316
+ return;
1317
+ }
1318
+ const workflow = await this._storage.workflows.get(content.workflowId);
1319
+ if (!workflow || !workflow.stages) {
1320
+ return;
1321
+ }
1322
+ const currentStage = workflow.stages.find((s) => s.id === content.workflowStage);
1323
+ if (!currentStage || !currentStage.conditions) {
1324
+ return;
1325
+ }
1326
+ const { autoAdvance, minScore, requiresHumanReview } = currentStage.conditions;
1327
+ if (requiresHumanReview) {
1328
+ return;
1329
+ }
1330
+ if (autoAdvance && review.passesThreshold && (minScore === void 0 || review.score >= minScore)) {
1331
+ const sortedStages = [...workflow.stages].sort((a, b) => a.order - b.order);
1332
+ const currentIndex = sortedStages.findIndex((s) => s.id === currentStage.id);
1333
+ const nextStage = sortedStages[currentIndex + 1];
1334
+ if (nextStage) {
1335
+ const fromStage = content.workflowStage;
1336
+ await this._storage.content.update(contentId, {
1337
+ workflowStage: nextStage.id
1338
+ });
1339
+ this.emit("workflow:stage:changed", {
1340
+ contentId,
1341
+ workflowId: content.workflowId,
1342
+ from: fromStage,
1343
+ to: nextStage.id
1344
+ });
1345
+ }
1346
+ }
1347
+ } catch {
1348
+ }
1349
+ });
1350
+ this._initialized = true;
1351
+ this.emit("initialized", void 0);
1030
1352
  }
1031
- async deleteUser(req, res) {
1032
- try {
1033
- const { id: _id } = req.params;
1034
- res.status(204).send();
1035
- } catch (error) {
1036
- throw new ContentManagementSuiteInternalError("Failed to delete user", error);
1353
+ async dispose() {
1354
+ if (!this._initialized) {
1355
+ throw new InternalError("Suite is not initialized");
1037
1356
  }
1357
+ this._initialized = false;
1358
+ this.emit("disposed", void 0);
1359
+ }
1360
+ on(event, listener) {
1361
+ return super.on(event, listener);
1362
+ }
1363
+ emit(event, ...args) {
1364
+ return super.emit(event, ...args);
1038
1365
  }
1039
1366
  };
1040
- function createContentManagementSuite(options = {}) {
1367
+ function createContentManagementSuite(options) {
1041
1368
  return new ContentManagementSuiteImpl(options);
1042
1369
  }
1043
1370
  // Annotate the CommonJS export names for ESM import in node:
1044
1371
  0 && (module.exports = {
1045
- AudioContentTypeManager,
1046
- AutosaveManager,
1372
+ ConflictError,
1047
1373
  ContentManagementConfigSchema,
1048
- ContentManagementSuiteConflictError,
1049
- ContentManagementSuiteError,
1050
- ContentManagementSuiteForbiddenError,
1051
- ContentManagementSuiteInternalError,
1052
- ContentManagementSuiteNotFoundError,
1053
- ContentManagementSuiteUnauthorizedError,
1054
- ContentManagementSuiteValidationError,
1055
- ContentSoftDelete,
1056
- ContentTypeRegistry,
1057
- EditorialWorkflowEngine,
1058
- ImageContentType,
1059
- StageActionButtons,
1060
- TextContentType,
1061
- VideoContentType,
1062
- WorkflowAdminConfig,
1063
- WorkflowBuilder,
1064
- WorkflowFactory,
1065
- WorkflowStepper,
1066
- WorkflowTemplates,
1067
- WorkflowTimeline,
1068
- createContentManagementSuite
1374
+ ContentManagementError,
1375
+ ForbiddenError,
1376
+ InternalError,
1377
+ NotFoundError,
1378
+ UnauthorizedError,
1379
+ ValidationError,
1380
+ createContentManagementSuite,
1381
+ createInMemoryAdapter
1069
1382
  });
1070
1383
  //# sourceMappingURL=index.js.map