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