@dyrected/core 2.5.29 → 2.5.30

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.cjs CHANGED
@@ -20,21 +20,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
24
+ LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
25
+ WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
26
+ availableWorkflowTransitions: () => availableWorkflowTransitions,
27
+ createLifecycleEvent: () => createLifecycleEvent,
28
+ createWorkflowDocument: () => createWorkflowDocument,
23
29
  defineCollection: () => defineCollection,
24
30
  defineConfig: () => defineConfig,
25
31
  defineGlobal: () => defineGlobal,
32
+ dispatchLifecycleEvent: () => dispatchLifecycleEvent,
33
+ dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
26
34
  executeFieldAfterRead: () => executeFieldAfterRead,
27
35
  executeFieldBeforeChange: () => executeFieldBeforeChange,
28
36
  generateAIPrompt: () => generateAIPrompt,
29
37
  generateFreshSetupPrompt: () => generateFreshSetupPrompt,
38
+ initializeWorkflowDocument: () => initializeWorkflowDocument,
39
+ materializeWorkflowDocument: () => materializeWorkflowDocument,
30
40
  normalizeConfig: () => normalizeConfig,
31
41
  parseMongoWhere: () => parseMongoWhere,
32
42
  parseSort: () => parseSort,
33
43
  parseSqlWhere: () => parseSqlWhere,
34
- runCollectionHooks: () => runCollectionHooks
44
+ publishingWorkflow: () => publishingWorkflow,
45
+ runCollectionHooks: () => runCollectionHooks,
46
+ saveWorkflowDraft: () => saveWorkflowDraft,
47
+ transitionWorkflow: () => transitionWorkflow,
48
+ workflowCapabilities: () => workflowCapabilities
35
49
  });
36
50
  module.exports = __toCommonJS(index_exports);
37
51
 
52
+ // src/types/index.ts
53
+ var LIFECYCLE_EVENT_NAMES = [
54
+ "revision.created",
55
+ "workflow.transitioned",
56
+ "entry.published",
57
+ "entry.unpublished"
58
+ ];
59
+
38
60
  // src/utils/setup-prompt.ts
39
61
  function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
40
62
  const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
@@ -183,6 +205,7 @@ COLLECTION OPTIONS:
183
205
  - upload: true \u2014 media library with file upload support
184
206
  - auth: true \u2014 adds login/me endpoints; password field is auto-managed
185
207
  - audit: true \u2014 enables activity logging
208
+ - admin.icon \u2014 valid Lucide component name for sidebar navigation (e.g. 'Newspaper')
186
209
  - admin.useAsTitle \u2014 field used as display title in admin list view
187
210
  - admin.group \u2014 groups collection under a sidebar heading
188
211
  - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
@@ -227,7 +250,7 @@ const media = defineCollection({
227
250
 
228
251
  const pages = defineCollection({
229
252
  slug: 'pages',
230
- admin: { useAsTitle: 'title', group: 'Content' },
253
+ admin: { icon: 'FileText', useAsTitle: 'title', group: 'Content' },
231
254
  fields: [
232
255
  { name: 'title', label: 'Title', type: 'text', required: true },
233
256
  { name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
@@ -855,6 +878,250 @@ function generateAIPrompt(activeTab, config) {
855
878
  return sections.join("\n");
856
879
  }
857
880
 
881
+ // src/workflows.ts
882
+ var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
883
+ var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
884
+ function publishingWorkflow() {
885
+ return {
886
+ initialState: "draft",
887
+ draftState: "draft",
888
+ states: [
889
+ { name: "draft", label: "Draft", color: "neutral" },
890
+ { name: "in_review", label: "In review", color: "warning" },
891
+ { name: "published", label: "Published", color: "success", published: true }
892
+ ],
893
+ transitions: [
894
+ { name: "submit", label: "Submit for review", from: "draft", to: "in_review", requiredCapabilities: ["entry.submit"] },
895
+ { name: "publish", label: "Publish", from: "in_review", to: "published", requiredCapabilities: ["entry.publish"] },
896
+ { name: "reject", label: "Request changes", from: "in_review", to: "draft", requiredCapabilities: ["entry.publish"], requireComment: true },
897
+ { name: "unpublish", label: "Unpublish", from: "published", to: "draft", requiredCapabilities: ["entry.unpublish"], unpublish: true }
898
+ ],
899
+ roles: [
900
+ { role: "editor", capabilities: ["entry.edit", "entry.submit"] },
901
+ { role: "publisher", capabilities: ["entry.edit", "entry.submit", "entry.publish", "entry.unpublish"] },
902
+ { role: "admin", capabilities: ["entry.edit", "entry.submit", "entry.publish", "entry.unpublish"] }
903
+ ]
904
+ };
905
+ }
906
+ function publicMetadata(meta) {
907
+ const { availableTransitions: _availableTransitions, ...safe } = meta;
908
+ return safe;
909
+ }
910
+ function workflowCapabilities(workflow, user) {
911
+ const capabilities = new Set(Array.isArray(user?.capabilities) ? user.capabilities : []);
912
+ const roles = Array.isArray(user?.roles) ? user.roles : [];
913
+ for (const mapping of workflow.roles ?? []) {
914
+ if (roles.includes(mapping.role)) mapping.capabilities.forEach((capability) => capabilities.add(capability));
915
+ }
916
+ return capabilities;
917
+ }
918
+ function availableWorkflowTransitions(workflow, state, user) {
919
+ const capabilities = workflowCapabilities(workflow, user);
920
+ return workflow.transitions.filter((transition) => {
921
+ const from = Array.isArray(transition.from) ? transition.from : [transition.from];
922
+ return from.includes(state) && (transition.requiredCapabilities ?? []).every((item) => capabilities.has(item));
923
+ });
924
+ }
925
+ function initializeWorkflowDocument(data, workflow) {
926
+ return {
927
+ ...data,
928
+ __workflow: { state: workflow.initialState, revision: 1 }
929
+ };
930
+ }
931
+ function materializeWorkflowDocument(doc, workflow, user) {
932
+ const meta = doc.__workflow;
933
+ if (!meta) return doc;
934
+ const { __published, __workflow, ...working } = doc;
935
+ if (!user) {
936
+ if (!__published || typeof __published !== "object") return null;
937
+ return { id: doc.id, ...__published, _workflow: publicMetadata(meta) };
938
+ }
939
+ return {
940
+ ...working,
941
+ _workflow: {
942
+ ...meta,
943
+ availableTransitions: availableWorkflowTransitions(workflow, meta.state, user).map((item) => item.name)
944
+ }
945
+ };
946
+ }
947
+ function eventId() {
948
+ return globalThis.crypto?.randomUUID?.() ?? `evt_${Date.now()}_${Math.random().toString(36).slice(2)}`;
949
+ }
950
+ function createLifecycleEvent(args) {
951
+ return {
952
+ id: eventId(),
953
+ name: args.name,
954
+ collection: args.collection,
955
+ documentId: args.documentId,
956
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
957
+ actorId: args.actorId,
958
+ payload: args.payload,
959
+ attempts: 0,
960
+ status: "pending"
961
+ };
962
+ }
963
+ async function persistEvent(db, event) {
964
+ await db.create({ collection: LIFECYCLE_EVENTS_COLLECTION, data: event });
965
+ }
966
+ async function dispatchLifecycleEvent(config, event) {
967
+ const db = config.db;
968
+ if (!db || !config.events?.handlers.length) return;
969
+ const maxAttempts = config.events.maxAttempts ?? 8;
970
+ const retryDelayMs = config.events.retryDelayMs ?? 1e3;
971
+ const attempts = event.attempts + 1;
972
+ try {
973
+ await db.update({ collection: LIFECYCLE_EVENTS_COLLECTION, id: event.id, data: { status: "processing", attempts } });
974
+ for (const handler of config.events.handlers) await handler({ ...event, status: "processing", attempts });
975
+ await db.update({
976
+ collection: LIFECYCLE_EVENTS_COLLECTION,
977
+ id: event.id,
978
+ data: { status: "delivered", attempts, deliveredAt: (/* @__PURE__ */ new Date()).toISOString(), lastError: null }
979
+ });
980
+ } catch (error) {
981
+ const exhausted = attempts >= maxAttempts;
982
+ await db.update({
983
+ collection: LIFECYCLE_EVENTS_COLLECTION,
984
+ id: event.id,
985
+ data: {
986
+ status: "failed",
987
+ attempts,
988
+ lastError: error instanceof Error ? error.message : String(error),
989
+ nextAttemptAt: exhausted ? null : new Date(Date.now() + retryDelayMs * 2 ** (attempts - 1)).toISOString()
990
+ }
991
+ });
992
+ }
993
+ }
994
+ async function dispatchPendingLifecycleEvents(config, limit = 50) {
995
+ if (!config.db || !config.events?.handlers.length) return 0;
996
+ const result = await config.db.find({
997
+ collection: LIFECYCLE_EVENTS_COLLECTION,
998
+ where: { status: { in: ["pending", "failed"] } },
999
+ sort: "occurredAt",
1000
+ limit
1001
+ });
1002
+ const now = Date.now();
1003
+ const due = result.docs.filter((doc) => !doc.nextAttemptAt || new Date(doc.nextAttemptAt).getTime() <= now);
1004
+ for (const event of due) await dispatchLifecycleEvent(config, event);
1005
+ return due.length;
1006
+ }
1007
+ async function saveWorkflowDraft(args) {
1008
+ const { config, collection, id, originalDoc, data, user } = args;
1009
+ const db = config.db;
1010
+ const workflow = collection.workflow;
1011
+ if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
1012
+ const previous = originalDoc.__workflow;
1013
+ const event = createLifecycleEvent({
1014
+ name: "revision.created",
1015
+ collection: collection.slug,
1016
+ documentId: id,
1017
+ actorId: user?.sub,
1018
+ payload: { revision: previous.revision + 1, previousRevision: previous.revision }
1019
+ });
1020
+ const doc = await db.transaction(async (tx) => {
1021
+ const nextMeta = {
1022
+ ...previous,
1023
+ state: originalDoc.__published ? workflow.draftState ?? workflow.initialState : previous.state,
1024
+ revision: previous.revision + 1
1025
+ };
1026
+ const updated = await tx.update({ collection: collection.slug, id, data: { ...data, __workflow: nextMeta } });
1027
+ await persistEvent(tx, event);
1028
+ return updated;
1029
+ });
1030
+ void dispatchLifecycleEvent(config, event);
1031
+ return { doc, event };
1032
+ }
1033
+ async function createWorkflowDocument(args) {
1034
+ const { config, collection, data, user } = args;
1035
+ const db = config.db;
1036
+ if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
1037
+ let event;
1038
+ const doc = await db.transaction(async (tx) => {
1039
+ const created = await tx.create({ collection: collection.slug, data });
1040
+ event = createLifecycleEvent({
1041
+ name: "revision.created",
1042
+ collection: collection.slug,
1043
+ documentId: created.id,
1044
+ actorId: user?.sub,
1045
+ payload: { revision: 1, previousRevision: null }
1046
+ });
1047
+ await persistEvent(tx, event);
1048
+ return created;
1049
+ });
1050
+ void dispatchLifecycleEvent(config, event);
1051
+ return { doc, event };
1052
+ }
1053
+ async function transitionWorkflow(args) {
1054
+ const { config, collection, id, transitionName, expectedRevision, comment, user, req } = args;
1055
+ const db = config.db;
1056
+ const workflow = collection.workflow;
1057
+ if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
1058
+ const original = await db.findOne({ collection: collection.slug, id });
1059
+ if (!original) throw Object.assign(new Error("Not Found"), { statusCode: 404 });
1060
+ const meta = original.__workflow;
1061
+ if (!meta) throw Object.assign(new Error("Entry has no workflow metadata"), { statusCode: 409 });
1062
+ if (expectedRevision !== void 0 && expectedRevision !== meta.revision) {
1063
+ throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), { statusCode: 409 });
1064
+ }
1065
+ const transition = workflow.transitions.find((item) => item.name === transitionName);
1066
+ const fromStates = transition ? Array.isArray(transition.from) ? transition.from : [transition.from] : [];
1067
+ if (!transition || !fromStates.includes(meta.state)) {
1068
+ throw Object.assign(new Error(`Transition "${transitionName}" is not valid from "${meta.state}".`), { statusCode: 409 });
1069
+ }
1070
+ if (!availableWorkflowTransitions(workflow, meta.state, user).some((item) => item.name === transition.name)) {
1071
+ throw Object.assign(new Error(`You do not have permission to perform "${transition.label}".`), { statusCode: 403 });
1072
+ }
1073
+ if (transition.requireComment && !comment?.trim()) {
1074
+ throw Object.assign(new Error(`A comment is required for "${transition.label}".`), { statusCode: 400 });
1075
+ }
1076
+ const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
1077
+ for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
1078
+ const events = [createLifecycleEvent({
1079
+ name: "workflow.transitioned",
1080
+ collection: collection.slug,
1081
+ documentId: id,
1082
+ actorId: user?.sub,
1083
+ payload: { transition: transition.name, from: meta.state, to: transition.to, revision: meta.revision, comment }
1084
+ })];
1085
+ const targetState = workflow.states.find((state) => state.name === transition.to);
1086
+ if (targetState.published) {
1087
+ events.push(createLifecycleEvent({ name: "entry.published", collection: collection.slug, documentId: id, actorId: user?.sub, payload: { revision: meta.revision } }));
1088
+ } else if (transition.unpublish) {
1089
+ events.push(createLifecycleEvent({ name: "entry.unpublished", collection: collection.slug, documentId: id, actorId: user?.sub, payload: { revision: meta.publishedRevision } }));
1090
+ }
1091
+ const updated = await db.transaction(async (tx) => {
1092
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1093
+ const { __published: _published, __workflow: _workflow, id: _id, ...working } = original;
1094
+ const nextMeta = {
1095
+ ...meta,
1096
+ state: transition.to,
1097
+ ...targetState.published ? { publishedRevision: meta.revision, publishedAt: now, publishedBy: user?.sub } : {},
1098
+ ...transition.unpublish ? { publishedRevision: void 0, publishedAt: void 0, publishedBy: void 0 } : {}
1099
+ };
1100
+ const data = { __workflow: nextMeta };
1101
+ if (targetState.published) data.__published = working;
1102
+ if (transition.unpublish) data.__published = null;
1103
+ const next = await tx.update({ collection: collection.slug, id, data });
1104
+ await tx.create({
1105
+ collection: WORKFLOW_HISTORY_COLLECTION,
1106
+ data: { collection: collection.slug, documentId: id, transition: transition.name, from: meta.state, to: transition.to, revision: meta.revision, comment: comment ?? null, actorId: user?.sub ?? null, createdAt: now }
1107
+ });
1108
+ for (const event of events) await persistEvent(tx, event);
1109
+ if (collection.audit) {
1110
+ await tx.create({ collection: "__audit", data: { collection: collection.slug, documentId: id, operation: "workflow.transition", user: user?.sub ?? null, timestamp: now, changes: JSON.stringify({ transition: transition.name, from: meta.state, to: transition.to, revision: meta.revision }) } });
1111
+ }
1112
+ return next;
1113
+ });
1114
+ for (const event of events) void dispatchLifecycleEvent(config, event);
1115
+ for (const hook of workflow.hooks?.afterTransition ?? []) {
1116
+ try {
1117
+ await hook({ ...hookContext, doc: updated, event: events[0] });
1118
+ } catch (error) {
1119
+ console.error("[dyrected/workflow] afterTransition hook failed:", error);
1120
+ }
1121
+ }
1122
+ return updated;
1123
+ }
1124
+
858
1125
  // src/utils/config.ts
859
1126
  var AUDIT_COLLECTION_SLUG = "__audit";
860
1127
  var SYSTEM_FIELDS = [
@@ -896,10 +1163,47 @@ var AUDIT_COLLECTION = {
896
1163
  ],
897
1164
  admin: { hidden: true }
898
1165
  };
1166
+ var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
1167
+ slug: WORKFLOW_HISTORY_COLLECTION,
1168
+ labels: { singular: "Workflow transition", plural: "Workflow transitions" },
1169
+ fields: [
1170
+ { name: "collection", type: "text", required: true },
1171
+ { name: "documentId", type: "text", required: true },
1172
+ { name: "transition", type: "text", required: true },
1173
+ { name: "from", type: "text", required: true },
1174
+ { name: "to", type: "text", required: true },
1175
+ { name: "revision", type: "number", required: true },
1176
+ { name: "comment", type: "textarea" },
1177
+ { name: "actorId", type: "text" },
1178
+ { name: "createdAt", type: "date", required: true }
1179
+ ],
1180
+ access: { read: ({ user }) => !!user, create: () => false, update: () => false, delete: () => false },
1181
+ admin: { hidden: true }
1182
+ };
1183
+ var LIFECYCLE_EVENTS_COLLECTION_CONFIG = {
1184
+ slug: LIFECYCLE_EVENTS_COLLECTION,
1185
+ labels: { singular: "Lifecycle event", plural: "Lifecycle events" },
1186
+ fields: [
1187
+ { name: "name", type: "text", required: true },
1188
+ { name: "collection", type: "text", required: true },
1189
+ { name: "documentId", type: "text", required: true },
1190
+ { name: "occurredAt", type: "date", required: true },
1191
+ { name: "actorId", type: "text" },
1192
+ { name: "payload", type: "json", required: true },
1193
+ { name: "attempts", type: "number", required: true },
1194
+ { name: "status", type: "select", options: ["pending", "processing", "delivered", "failed"], required: true },
1195
+ { name: "nextAttemptAt", type: "date" },
1196
+ { name: "deliveredAt", type: "date" },
1197
+ { name: "lastError", type: "textarea" }
1198
+ ],
1199
+ access: { read: ({ user }) => !!user?.roles?.includes("admin"), create: () => false, update: () => false, delete: () => false },
1200
+ admin: { hidden: true }
1201
+ };
899
1202
  function normalizeConfig(config) {
900
1203
  const collections = config?.collections || [];
901
1204
  const globals = config?.globals || [];
902
1205
  const needsAudit = collections.some((col) => col.audit);
1206
+ const needsWorkflow = collections.some((col) => col.workflow);
903
1207
  const normalizedCollections = collections.map((col) => {
904
1208
  let fields = col.fields || [];
905
1209
  const existingFieldNames = new Set(fields.map((f) => f.name));
@@ -994,9 +1298,17 @@ function normalizeConfig(config) {
994
1298
  };
995
1299
  });
996
1300
  const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
1301
+ const systemCollections = [];
1302
+ if (needsAudit && !hasAuditCollection) systemCollections.push(AUDIT_COLLECTION);
1303
+ if (needsWorkflow && !normalizedCollections.some((col) => col.slug === WORKFLOW_HISTORY_COLLECTION)) {
1304
+ systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
1305
+ }
1306
+ if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
1307
+ systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
1308
+ }
997
1309
  return {
998
1310
  ...config,
999
- collections: [...normalizedCollections, ...needsAudit && !hasAuditCollection ? [AUDIT_COLLECTION] : []],
1311
+ collections: [...normalizedCollections, ...systemCollections],
1000
1312
  globals
1001
1313
  };
1002
1314
  }
@@ -1347,16 +1659,30 @@ function defineConfig(config) {
1347
1659
  }
1348
1660
  // Annotate the CommonJS export names for ESM import in node:
1349
1661
  0 && (module.exports = {
1662
+ LIFECYCLE_EVENTS_COLLECTION,
1663
+ LIFECYCLE_EVENT_NAMES,
1664
+ WORKFLOW_HISTORY_COLLECTION,
1665
+ availableWorkflowTransitions,
1666
+ createLifecycleEvent,
1667
+ createWorkflowDocument,
1350
1668
  defineCollection,
1351
1669
  defineConfig,
1352
1670
  defineGlobal,
1671
+ dispatchLifecycleEvent,
1672
+ dispatchPendingLifecycleEvents,
1353
1673
  executeFieldAfterRead,
1354
1674
  executeFieldBeforeChange,
1355
1675
  generateAIPrompt,
1356
1676
  generateFreshSetupPrompt,
1677
+ initializeWorkflowDocument,
1678
+ materializeWorkflowDocument,
1357
1679
  normalizeConfig,
1358
1680
  parseMongoWhere,
1359
1681
  parseSort,
1360
1682
  parseSqlWhere,
1361
- runCollectionHooks
1683
+ publishingWorkflow,
1684
+ runCollectionHooks,
1685
+ saveWorkflowDraft,
1686
+ transitionWorkflow,
1687
+ workflowCapabilities
1362
1688
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { D as DyrectedConfig, F as Field, C as CollectionConfig, P as Prettify, I as InferDocShape, S as SystemDocFields, A as AuthDocFields, U as UploadDocFields, G as GlobalConfig } from './index-Bp7PDOYG.cjs';
2
- export { a as AccessFunction, b as AdminConfig, c as AuthenticatedUser, B as BaseDocument, d as Block, e as CollectionAfterChangeHook, f as CollectionAfterDeleteHook, g as CollectionAfterReadHook, h as CollectionBeforeChangeHook, i as CollectionBeforeDeleteHook, j as CollectionBeforeReadHook, k as DatabaseAdapter, l as DynamicOptionItem, m as DynamicOptionsConfig, n as DynamicOptionsResolver, o as DynamicOptionsResolverArgs, p as FieldAfterReadHook, q as FieldBeforeChangeHook, r as FieldHook, s as FieldType, t as FileData, u as GlobalAfterChangeHook, v as GlobalAfterReadHook, w as GlobalBeforeChangeHook, x as GlobalBeforeReadHook, H as HookFunction, y as HookRequestContext, z as ImageService, E as PaginatedResult, R as ReadonlyDatabaseAdapter, J as StorageAdapter, K as UploadConfig } from './index-Bp7PDOYG.cjs';
1
+ import { D as DyrectedConfig, F as Field, W as WorkflowConfig, A as AuthenticatedUser, a as WorkflowTransition, L as LifecycleEventName, b as LifecycleEvent, C as CollectionConfig, B as BaseDocument, H as HookRequestContext, P as Prettify, I as InferDocShape, S as SystemDocFields, c as AuthDocFields, U as UploadDocFields, G as GlobalConfig } from './index-DTrpTru7.cjs';
2
+ export { d as AccessFunction, e as AdminConfig, f as AdminDashboardComponentSlots, g as AdminIconName, h as Block, i as CollectionAfterChangeHook, j as CollectionAfterDeleteHook, k as CollectionAfterReadHook, l as CollectionAfterTransitionHook, m as CollectionBeforeChangeHook, n as CollectionBeforeDeleteHook, o as CollectionBeforeReadHook, p as CollectionBeforeTransitionHook, q as CollectionListComponentSlots, r as DatabaseAdapter, s as DynamicOptionItem, t as DynamicOptionsConfig, u as DynamicOptionsResolver, v as DynamicOptionsResolverArgs, w as FieldAfterReadHook, x as FieldBeforeChangeHook, y as FieldHook, z as FieldType, E as FileData, J as GlobalAfterChangeHook, K as GlobalAfterReadHook, M as GlobalBeforeChangeHook, N as GlobalBeforeReadHook, O as HookFunction, Q as ImageService, R as LIFECYCLE_EVENT_NAMES, T as LifecycleEventHandler, V as PaginatedResult, X as ReadonlyDatabaseAdapter, Y as StorageAdapter, Z as UploadConfig, _ as WorkflowMetadata, $ as WorkflowRole, a0 as WorkflowState, a1 as WorkflowTransitionContext } from './index-DTrpTru7.cjs';
3
+ import 'lucide-react';
3
4
 
4
5
  interface SetupPromptConfig {
5
6
  siteName?: string;
@@ -128,6 +129,58 @@ declare function executeFieldBeforeChange(fields: Field[], data: Record<string,
128
129
  */
129
130
  declare function executeFieldAfterRead(fields: Field[], doc: Record<string, unknown>, user: unknown, db?: unknown): Promise<Record<string, unknown>>;
130
131
 
132
+ declare const WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
133
+ declare const LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
134
+ declare function publishingWorkflow(): WorkflowConfig;
135
+ declare function workflowCapabilities(workflow: WorkflowConfig, user?: AuthenticatedUser): Set<string>;
136
+ declare function availableWorkflowTransitions(workflow: WorkflowConfig, state: string, user?: AuthenticatedUser): WorkflowTransition[];
137
+ declare function initializeWorkflowDocument(data: Record<string, unknown>, workflow: WorkflowConfig): {
138
+ __workflow: {
139
+ state: string;
140
+ revision: number;
141
+ };
142
+ };
143
+ declare function materializeWorkflowDocument(doc: BaseDocument, workflow: WorkflowConfig, user?: AuthenticatedUser): BaseDocument | null;
144
+ declare function createLifecycleEvent(args: {
145
+ name: LifecycleEventName;
146
+ collection: string;
147
+ documentId: string;
148
+ actorId?: string;
149
+ payload: Record<string, unknown>;
150
+ }): LifecycleEvent;
151
+ declare function dispatchLifecycleEvent(config: DyrectedConfig, event: LifecycleEvent): Promise<void>;
152
+ declare function dispatchPendingLifecycleEvents(config: DyrectedConfig, limit?: number): Promise<number>;
153
+ declare function saveWorkflowDraft(args: {
154
+ config: DyrectedConfig;
155
+ collection: CollectionConfig;
156
+ id: string;
157
+ originalDoc: BaseDocument;
158
+ data: Record<string, unknown>;
159
+ user?: AuthenticatedUser;
160
+ }): Promise<{
161
+ doc: BaseDocument;
162
+ event: LifecycleEvent;
163
+ }>;
164
+ declare function createWorkflowDocument(args: {
165
+ config: DyrectedConfig;
166
+ collection: CollectionConfig;
167
+ data: Record<string, unknown>;
168
+ user?: AuthenticatedUser;
169
+ }): Promise<{
170
+ doc: BaseDocument;
171
+ event: LifecycleEvent;
172
+ }>;
173
+ declare function transitionWorkflow(args: {
174
+ config: DyrectedConfig;
175
+ collection: CollectionConfig;
176
+ id: string;
177
+ transitionName: string;
178
+ expectedRevision?: number;
179
+ comment?: string;
180
+ user?: AuthenticatedUser;
181
+ req: HookRequestContext;
182
+ }): Promise<BaseDocument>;
183
+
131
184
  /**
132
185
  * Define a collection. When called without an explicit type argument, TypeScript
133
186
  * automatically infers the document shape from the `fields` array you provide —
@@ -219,4 +272,4 @@ declare function defineGlobal<TDoc extends object>(config: GlobalConfig<TDoc>):
219
272
  */
220
273
  declare function defineConfig(config: DyrectedConfig): DyrectedConfig;
221
274
 
222
- export { AuthDocFields, CollectionConfig, DyrectedConfig, Field, GlobalConfig, InferDocShape, Prettify, type SetupPromptConfig, type SortClause, type SortDirection, type SqlWhereResult, SystemDocFields, UploadDocFields, type WhereClause, type WhereOperator, type WhereOperatorName, defineCollection, defineConfig, defineGlobal, executeFieldAfterRead, executeFieldBeforeChange, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, runCollectionHooks };
275
+ export { AuthDocFields, AuthenticatedUser, BaseDocument, CollectionConfig, DyrectedConfig, Field, GlobalConfig, HookRequestContext, InferDocShape, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, Prettify, type SetupPromptConfig, type SortClause, type SortDirection, type SqlWhereResult, SystemDocFields, UploadDocFields, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, createLifecycleEvent, createWorkflowDocument, defineCollection, defineConfig, defineGlobal, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateAIPrompt, generateFreshSetupPrompt, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { D as DyrectedConfig, F as Field, C as CollectionConfig, P as Prettify, I as InferDocShape, S as SystemDocFields, A as AuthDocFields, U as UploadDocFields, G as GlobalConfig } from './index-Bp7PDOYG.js';
2
- export { a as AccessFunction, b as AdminConfig, c as AuthenticatedUser, B as BaseDocument, d as Block, e as CollectionAfterChangeHook, f as CollectionAfterDeleteHook, g as CollectionAfterReadHook, h as CollectionBeforeChangeHook, i as CollectionBeforeDeleteHook, j as CollectionBeforeReadHook, k as DatabaseAdapter, l as DynamicOptionItem, m as DynamicOptionsConfig, n as DynamicOptionsResolver, o as DynamicOptionsResolverArgs, p as FieldAfterReadHook, q as FieldBeforeChangeHook, r as FieldHook, s as FieldType, t as FileData, u as GlobalAfterChangeHook, v as GlobalAfterReadHook, w as GlobalBeforeChangeHook, x as GlobalBeforeReadHook, H as HookFunction, y as HookRequestContext, z as ImageService, E as PaginatedResult, R as ReadonlyDatabaseAdapter, J as StorageAdapter, K as UploadConfig } from './index-Bp7PDOYG.js';
1
+ import { D as DyrectedConfig, F as Field, W as WorkflowConfig, A as AuthenticatedUser, a as WorkflowTransition, L as LifecycleEventName, b as LifecycleEvent, C as CollectionConfig, B as BaseDocument, H as HookRequestContext, P as Prettify, I as InferDocShape, S as SystemDocFields, c as AuthDocFields, U as UploadDocFields, G as GlobalConfig } from './index-DTrpTru7.js';
2
+ export { d as AccessFunction, e as AdminConfig, f as AdminDashboardComponentSlots, g as AdminIconName, h as Block, i as CollectionAfterChangeHook, j as CollectionAfterDeleteHook, k as CollectionAfterReadHook, l as CollectionAfterTransitionHook, m as CollectionBeforeChangeHook, n as CollectionBeforeDeleteHook, o as CollectionBeforeReadHook, p as CollectionBeforeTransitionHook, q as CollectionListComponentSlots, r as DatabaseAdapter, s as DynamicOptionItem, t as DynamicOptionsConfig, u as DynamicOptionsResolver, v as DynamicOptionsResolverArgs, w as FieldAfterReadHook, x as FieldBeforeChangeHook, y as FieldHook, z as FieldType, E as FileData, J as GlobalAfterChangeHook, K as GlobalAfterReadHook, M as GlobalBeforeChangeHook, N as GlobalBeforeReadHook, O as HookFunction, Q as ImageService, R as LIFECYCLE_EVENT_NAMES, T as LifecycleEventHandler, V as PaginatedResult, X as ReadonlyDatabaseAdapter, Y as StorageAdapter, Z as UploadConfig, _ as WorkflowMetadata, $ as WorkflowRole, a0 as WorkflowState, a1 as WorkflowTransitionContext } from './index-DTrpTru7.js';
3
+ import 'lucide-react';
3
4
 
4
5
  interface SetupPromptConfig {
5
6
  siteName?: string;
@@ -128,6 +129,58 @@ declare function executeFieldBeforeChange(fields: Field[], data: Record<string,
128
129
  */
129
130
  declare function executeFieldAfterRead(fields: Field[], doc: Record<string, unknown>, user: unknown, db?: unknown): Promise<Record<string, unknown>>;
130
131
 
132
+ declare const WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
133
+ declare const LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
134
+ declare function publishingWorkflow(): WorkflowConfig;
135
+ declare function workflowCapabilities(workflow: WorkflowConfig, user?: AuthenticatedUser): Set<string>;
136
+ declare function availableWorkflowTransitions(workflow: WorkflowConfig, state: string, user?: AuthenticatedUser): WorkflowTransition[];
137
+ declare function initializeWorkflowDocument(data: Record<string, unknown>, workflow: WorkflowConfig): {
138
+ __workflow: {
139
+ state: string;
140
+ revision: number;
141
+ };
142
+ };
143
+ declare function materializeWorkflowDocument(doc: BaseDocument, workflow: WorkflowConfig, user?: AuthenticatedUser): BaseDocument | null;
144
+ declare function createLifecycleEvent(args: {
145
+ name: LifecycleEventName;
146
+ collection: string;
147
+ documentId: string;
148
+ actorId?: string;
149
+ payload: Record<string, unknown>;
150
+ }): LifecycleEvent;
151
+ declare function dispatchLifecycleEvent(config: DyrectedConfig, event: LifecycleEvent): Promise<void>;
152
+ declare function dispatchPendingLifecycleEvents(config: DyrectedConfig, limit?: number): Promise<number>;
153
+ declare function saveWorkflowDraft(args: {
154
+ config: DyrectedConfig;
155
+ collection: CollectionConfig;
156
+ id: string;
157
+ originalDoc: BaseDocument;
158
+ data: Record<string, unknown>;
159
+ user?: AuthenticatedUser;
160
+ }): Promise<{
161
+ doc: BaseDocument;
162
+ event: LifecycleEvent;
163
+ }>;
164
+ declare function createWorkflowDocument(args: {
165
+ config: DyrectedConfig;
166
+ collection: CollectionConfig;
167
+ data: Record<string, unknown>;
168
+ user?: AuthenticatedUser;
169
+ }): Promise<{
170
+ doc: BaseDocument;
171
+ event: LifecycleEvent;
172
+ }>;
173
+ declare function transitionWorkflow(args: {
174
+ config: DyrectedConfig;
175
+ collection: CollectionConfig;
176
+ id: string;
177
+ transitionName: string;
178
+ expectedRevision?: number;
179
+ comment?: string;
180
+ user?: AuthenticatedUser;
181
+ req: HookRequestContext;
182
+ }): Promise<BaseDocument>;
183
+
131
184
  /**
132
185
  * Define a collection. When called without an explicit type argument, TypeScript
133
186
  * automatically infers the document shape from the `fields` array you provide —
@@ -219,4 +272,4 @@ declare function defineGlobal<TDoc extends object>(config: GlobalConfig<TDoc>):
219
272
  */
220
273
  declare function defineConfig(config: DyrectedConfig): DyrectedConfig;
221
274
 
222
- export { AuthDocFields, CollectionConfig, DyrectedConfig, Field, GlobalConfig, InferDocShape, Prettify, type SetupPromptConfig, type SortClause, type SortDirection, type SqlWhereResult, SystemDocFields, UploadDocFields, type WhereClause, type WhereOperator, type WhereOperatorName, defineCollection, defineConfig, defineGlobal, executeFieldAfterRead, executeFieldBeforeChange, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, runCollectionHooks };
275
+ export { AuthDocFields, AuthenticatedUser, BaseDocument, CollectionConfig, DyrectedConfig, Field, GlobalConfig, HookRequestContext, InferDocShape, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, Prettify, type SetupPromptConfig, type SortClause, type SortDirection, type SqlWhereResult, SystemDocFields, UploadDocFields, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, createLifecycleEvent, createWorkflowDocument, defineCollection, defineConfig, defineGlobal, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateAIPrompt, generateFreshSetupPrompt, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, runCollectionHooks, saveWorkflowDraft, transitionWorkflow, workflowCapabilities };
package/dist/index.js CHANGED
@@ -1,9 +1,30 @@
1
1
  import {
2
+ LIFECYCLE_EVENTS_COLLECTION,
3
+ WORKFLOW_HISTORY_COLLECTION,
4
+ availableWorkflowTransitions,
5
+ createLifecycleEvent,
6
+ createWorkflowDocument,
7
+ dispatchLifecycleEvent,
8
+ dispatchPendingLifecycleEvents,
2
9
  executeFieldAfterRead,
3
10
  executeFieldBeforeChange,
11
+ initializeWorkflowDocument,
12
+ materializeWorkflowDocument,
4
13
  normalizeConfig,
5
- runCollectionHooks
6
- } from "./chunk-SUGK7UYL.js";
14
+ publishingWorkflow,
15
+ runCollectionHooks,
16
+ saveWorkflowDraft,
17
+ transitionWorkflow,
18
+ workflowCapabilities
19
+ } from "./chunk-CQDVPMEU.js";
20
+
21
+ // src/types/index.ts
22
+ var LIFECYCLE_EVENT_NAMES = [
23
+ "revision.created",
24
+ "workflow.transitioned",
25
+ "entry.published",
26
+ "entry.unpublished"
27
+ ];
7
28
 
8
29
  // src/utils/setup-prompt.ts
9
30
  function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
@@ -153,6 +174,7 @@ COLLECTION OPTIONS:
153
174
  - upload: true \u2014 media library with file upload support
154
175
  - auth: true \u2014 adds login/me endpoints; password field is auto-managed
155
176
  - audit: true \u2014 enables activity logging
177
+ - admin.icon \u2014 valid Lucide component name for sidebar navigation (e.g. 'Newspaper')
156
178
  - admin.useAsTitle \u2014 field used as display title in admin list view
157
179
  - admin.group \u2014 groups collection under a sidebar heading
158
180
  - admin.hidden \u2014 hides collection from the sidebar (internal/system use)
@@ -197,7 +219,7 @@ const media = defineCollection({
197
219
 
198
220
  const pages = defineCollection({
199
221
  slug: 'pages',
200
- admin: { useAsTitle: 'title', group: 'Content' },
222
+ admin: { icon: 'FileText', useAsTitle: 'title', group: 'Content' },
201
223
  fields: [
202
224
  { name: 'title', label: 'Title', type: 'text', required: true },
203
225
  { name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
@@ -1011,16 +1033,30 @@ function defineConfig(config) {
1011
1033
  return config;
1012
1034
  }
1013
1035
  export {
1036
+ LIFECYCLE_EVENTS_COLLECTION,
1037
+ LIFECYCLE_EVENT_NAMES,
1038
+ WORKFLOW_HISTORY_COLLECTION,
1039
+ availableWorkflowTransitions,
1040
+ createLifecycleEvent,
1041
+ createWorkflowDocument,
1014
1042
  defineCollection,
1015
1043
  defineConfig,
1016
1044
  defineGlobal,
1045
+ dispatchLifecycleEvent,
1046
+ dispatchPendingLifecycleEvents,
1017
1047
  executeFieldAfterRead,
1018
1048
  executeFieldBeforeChange,
1019
1049
  generateAIPrompt,
1020
1050
  generateFreshSetupPrompt,
1051
+ initializeWorkflowDocument,
1052
+ materializeWorkflowDocument,
1021
1053
  normalizeConfig,
1022
1054
  parseMongoWhere,
1023
1055
  parseSort,
1024
1056
  parseSqlWhere,
1025
- runCollectionHooks
1057
+ publishingWorkflow,
1058
+ runCollectionHooks,
1059
+ saveWorkflowDraft,
1060
+ transitionWorkflow,
1061
+ workflowCapabilities
1026
1062
  };