@dyrected/core 2.5.61 → 2.5.62

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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -46,6 +56,7 @@ __export(index_exports, {
46
56
  defineMultiSelectField: () => defineMultiSelectField,
47
57
  defineNumberField: () => defineNumberField,
48
58
  defineObjectField: () => defineObjectField,
59
+ definePublishingWorkflow: () => definePublishingWorkflow,
49
60
  defineRadioField: () => defineRadioField,
50
61
  defineRelationshipField: () => defineRelationshipField,
51
62
  defineRichTextField: () => defineRichTextField,
@@ -88,10 +99,47 @@ var LIFECYCLE_EVENT_NAMES = [
88
99
  "entry.unpublished"
89
100
  ];
90
101
 
102
+ // src/auth/token.ts
103
+ var import_jose = require("jose");
104
+ var import_node_util = require("util");
105
+
106
+ // src/auth/sessions.ts
107
+ var AUTH_SESSIONS_COLLECTION = "__auth_sessions";
108
+
109
+ // src/observability.ts
110
+ var import_api = require("@opentelemetry/api");
111
+ var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
112
+ var import_exporter_prometheus = require("@opentelemetry/exporter-prometheus");
113
+ var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
114
+ var import_resources = require("@opentelemetry/resources");
115
+ var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
116
+ var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
117
+ var import_pino = __toESM(require("pino"), 1);
118
+ var import_pino_pretty = __toESM(require("pino-pretty"), 1);
119
+ var import_node_stream = require("stream");
120
+ var runtimeByConfig = /* @__PURE__ */ new WeakMap();
121
+ var fallbackLogger = (0, import_pino.default)({
122
+ name: "dyrected",
123
+ enabled: process.env.DISABLE_LOGGING !== "true",
124
+ timestamp: import_pino.stdTimeFunctions.isoTime
125
+ });
126
+ function getObservabilityRuntime(config) {
127
+ if (!config || typeof config !== "object") return void 0;
128
+ return runtimeByConfig.get(config);
129
+ }
130
+ function getConfigLogger(config, component) {
131
+ const base = config?.logger && !("options" in config.logger) ? config.logger : fallbackLogger;
132
+ return base.child({ component });
133
+ }
134
+
91
135
  // src/workflows.ts
92
136
  var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
93
137
  var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
94
- function publishingWorkflow() {
138
+ var EDITOR_CAPABILITIES = ["entry.edit", "entry.submit"];
139
+ var PUBLISHER_CAPABILITIES = ["entry.edit", "entry.submit", "entry.publish", "entry.unpublish"];
140
+ function definePublishingWorkflow(options = {}) {
141
+ const editors = options.editors ?? ["editor"];
142
+ const publishers = options.publishers ?? ["publisher", "admin"];
95
143
  return {
96
144
  initialState: "draft",
97
145
  draftState: "draft",
@@ -107,12 +155,14 @@ function publishingWorkflow() {
107
155
  { name: "unpublish", label: "Unpublish", from: "published", to: "draft", requiredCapabilities: ["entry.unpublish"], unpublish: true }
108
156
  ],
109
157
  roles: [
110
- { role: "editor", capabilities: ["entry.edit", "entry.submit"] },
111
- { role: "publisher", capabilities: ["entry.edit", "entry.submit", "entry.publish", "entry.unpublish"] },
112
- { role: "admin", capabilities: ["entry.edit", "entry.submit", "entry.publish", "entry.unpublish"] }
158
+ ...editors.map((role) => ({ role, capabilities: [...EDITOR_CAPABILITIES] })),
159
+ ...publishers.map((role) => ({ role, capabilities: [...PUBLISHER_CAPABILITIES] }))
113
160
  ]
114
161
  };
115
162
  }
163
+ function publishingWorkflow() {
164
+ return definePublishingWorkflow();
165
+ }
116
166
  function simplePublishingWorkflow() {
117
167
  return {
118
168
  initialState: "draft",
@@ -125,6 +175,9 @@ function simplePublishingWorkflow() {
125
175
  { name: "publish", label: "Publish", from: "draft", to: "published" },
126
176
  { name: "unpublish", label: "Unpublish", from: "published", to: "draft", unpublish: true }
127
177
  ]
178
+ // Intentionally no `roles`: this lightweight workflow (synthesized from
179
+ // `drafts: true`) does no capability-based gating. Draft visibility for a
180
+ // no-roles workflow is handled in `canViewWorkflowDraft`.
128
181
  };
129
182
  }
130
183
  function publicMetadata(meta) {
@@ -143,9 +196,13 @@ function canViewWorkflowDraft(workflow, user) {
143
196
  if (!user) return false;
144
197
  const capabilities = workflowCapabilities(workflow, user);
145
198
  if (capabilities.has("entry.edit")) return true;
146
- return workflow.transitions.some(
199
+ if (workflow.transitions.some(
147
200
  (transition) => (transition.requiredCapabilities ?? []).some((capability) => capabilities.has(capability))
148
- );
201
+ )) {
202
+ return true;
203
+ }
204
+ if (!workflow.roles || workflow.roles.length === 0) return true;
205
+ return false;
149
206
  }
150
207
  function availableWorkflowTransitions(workflow, state, user) {
151
208
  const capabilities = workflowCapabilities(workflow, user);
@@ -371,7 +428,17 @@ async function transitionWorkflow(args) {
371
428
  try {
372
429
  await hook({ ...hookContext, doc: updated, event: events[0] });
373
430
  } catch (error) {
374
- console.error("[dyrected/workflow] afterTransition hook failed:", error);
431
+ getObservabilityRuntime(config)?.recordWorkflowHookFailure({
432
+ collection: collection.slug,
433
+ transition: transition.name
434
+ });
435
+ getConfigLogger(config, "workflow").error({
436
+ err: error,
437
+ msg: "afterTransition hook failed",
438
+ collection: collection.slug,
439
+ transition: transition.name,
440
+ documentId: id
441
+ });
375
442
  }
376
443
  }
377
444
  return updated;
@@ -412,6 +479,98 @@ function humanizeProviderName(id, type) {
412
479
  return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
413
480
  }
414
481
 
482
+ // src/utils/block-references.ts
483
+ function mergeUniqueBlocks(blocks) {
484
+ const seen = /* @__PURE__ */ new Map();
485
+ for (const block of blocks) {
486
+ const existing = seen.get(block.slug);
487
+ if (existing && existing !== block) {
488
+ throw new Error(
489
+ `Duplicate block slug "${block.slug}" found in the reusable block registry. Block slugs must be unique.`
490
+ );
491
+ }
492
+ if (!existing) seen.set(block.slug, block);
493
+ }
494
+ return Array.from(seen.values());
495
+ }
496
+ function buildRegistry(blocks) {
497
+ return new Map(blocks.map((block) => [block.slug, block]));
498
+ }
499
+ function resolveField(field, registry, blockCache) {
500
+ const next = { ...field };
501
+ if (next.fields) {
502
+ next.fields = next.fields.map(
503
+ (child) => resolveField(child, registry, blockCache)
504
+ );
505
+ }
506
+ if (next.type !== "blocks") return next;
507
+ const hasInlineBlocks = Array.isArray(next.blocks) && next.blocks.length > 0;
508
+ const hasReferences = Array.isArray(next.blockReferences) && next.blockReferences.length > 0;
509
+ if (hasInlineBlocks && hasReferences) {
510
+ throw new Error(
511
+ `Blocks field "${next.name ?? "(unnamed)"}" cannot define both "blocks" and "blockReferences". Use one or the other.`
512
+ );
513
+ }
514
+ if (hasReferences) {
515
+ next.blocks = next.blockReferences.map((slug) => {
516
+ const block = registry.get(slug);
517
+ if (!block) {
518
+ throw new Error(
519
+ `Unknown block reference "${slug}" on blocks field "${next.name ?? "(unnamed)"}". Add it to defineConfig({ blocks: [...] }).`
520
+ );
521
+ }
522
+ return resolveBlock(block, registry, blockCache);
523
+ });
524
+ return next;
525
+ }
526
+ if (hasInlineBlocks) {
527
+ next.blocks = next.blocks.map(
528
+ (block) => resolveBlock(block, registry, blockCache)
529
+ );
530
+ }
531
+ return next;
532
+ }
533
+ function resolveBlock(block, registry, blockCache) {
534
+ const cached = blockCache.get(block);
535
+ if (cached) return cached;
536
+ const resolved = {
537
+ ...block,
538
+ fields: []
539
+ };
540
+ blockCache.set(block, resolved);
541
+ resolved.fields = block.fields.map(
542
+ (field) => resolveField(field, registry, blockCache)
543
+ );
544
+ return resolved;
545
+ }
546
+ function resolveFields(fields, registry, blockCache) {
547
+ return fields.map((field) => resolveField(field, registry, blockCache));
548
+ }
549
+ function normalizeSchemaFragment(fragment) {
550
+ const reusableBlocks = mergeUniqueBlocks(fragment.blocks ?? []);
551
+ const registry = buildRegistry(reusableBlocks);
552
+ const blockCache = /* @__PURE__ */ new WeakMap();
553
+ const resolvedBlocks = reusableBlocks.map(
554
+ (block) => resolveBlock(block, registry, blockCache)
555
+ );
556
+ const resolvedCollections = (fragment.collections ?? []).map(
557
+ (collection) => ({
558
+ ...collection,
559
+ fields: resolveFields(collection.fields ?? [], registry, blockCache)
560
+ })
561
+ );
562
+ const resolvedGlobals = (fragment.globals ?? []).map((global) => ({
563
+ ...global,
564
+ fields: resolveFields(global.fields ?? [], registry, blockCache)
565
+ }));
566
+ return {
567
+ ...fragment,
568
+ ...fragment.blocks ? { blocks: resolvedBlocks } : {},
569
+ ...fragment.collections ? { collections: resolvedCollections } : {},
570
+ ...fragment.globals ? { globals: resolvedGlobals } : {}
571
+ };
572
+ }
573
+
415
574
  // src/utils/config.ts
416
575
  var AUDIT_COLLECTION_SLUG = "__audit";
417
576
  var SYSTEM_FIELDS = [
@@ -446,12 +605,23 @@ var AUDIT_COLLECTION = {
446
605
  fields: [
447
606
  { name: "collection", type: "text", label: "Collection", required: true },
448
607
  { name: "documentId", type: "text", label: "Document ID" },
449
- { name: "operation", type: "select", label: "Operation", options: ["create", "update", "delete"], required: true },
608
+ {
609
+ name: "operation",
610
+ type: "select",
611
+ label: "Operation",
612
+ options: ["create", "update", "delete"],
613
+ required: true
614
+ },
450
615
  { name: "user", type: "text", label: "User ID" },
451
616
  { name: "timestamp", type: "date", label: "Timestamp", required: true },
452
617
  { name: "changes", type: "json", label: "Changes" }
453
618
  ],
454
- access: { read: () => false, create: () => false, update: () => false, delete: () => false },
619
+ access: {
620
+ read: () => false,
621
+ create: () => false,
622
+ update: () => false,
623
+ delete: () => false
624
+ },
455
625
  admin: { hidden: true }
456
626
  };
457
627
  var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
@@ -468,7 +638,12 @@ var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
468
638
  { name: "actorId", type: "text" },
469
639
  { name: "createdAt", type: "date", required: true }
470
640
  ],
471
- access: { read: ({ user }) => !!user, create: () => false, update: () => false, delete: () => false },
641
+ access: {
642
+ read: ({ user }) => !!user,
643
+ create: () => false,
644
+ update: () => false,
645
+ delete: () => false
646
+ },
472
647
  admin: { hidden: true }
473
648
  };
474
649
  var LIFECYCLE_EVENTS_COLLECTION_CONFIG = {
@@ -482,22 +657,56 @@ var LIFECYCLE_EVENTS_COLLECTION_CONFIG = {
482
657
  { name: "actorId", type: "text" },
483
658
  { name: "payload", type: "json", required: true },
484
659
  { name: "attempts", type: "number", required: true },
485
- { name: "status", type: "select", options: ["pending", "processing", "delivered", "failed"], required: true },
660
+ {
661
+ name: "status",
662
+ type: "select",
663
+ options: ["pending", "processing", "delivered", "failed"],
664
+ required: true
665
+ },
486
666
  { name: "nextAttemptAt", type: "date" },
487
667
  { name: "deliveredAt", type: "date" },
488
668
  { name: "lastError", type: "textarea" }
489
669
  ],
490
- access: { read: ({ user }) => !!user?.roles?.includes("admin"), create: () => false, update: () => false, delete: () => false },
670
+ access: {
671
+ read: ({ user }) => !!user?.roles?.includes("admin"),
672
+ create: () => false,
673
+ update: () => false,
674
+ delete: () => false
675
+ },
676
+ admin: { hidden: true }
677
+ };
678
+ var AUTH_SESSIONS_COLLECTION_CONFIG = {
679
+ slug: AUTH_SESSIONS_COLLECTION,
680
+ labels: { singular: "Auth session", plural: "Auth sessions" },
681
+ fields: [
682
+ { name: "userId", type: "text", required: true },
683
+ { name: "email", type: "email", required: true },
684
+ { name: "collection", type: "text", required: true },
685
+ { name: "authSource", type: "text" },
686
+ { name: "providerId", type: "text" },
687
+ { name: "lastIp", type: "text" },
688
+ { name: "lastSeenAt", type: "date" },
689
+ { name: "expiresAt", type: "date" },
690
+ { name: "revokedAt", type: "date" }
691
+ ],
692
+ access: {
693
+ read: () => false,
694
+ create: () => false,
695
+ update: () => false,
696
+ delete: () => false
697
+ },
491
698
  admin: { hidden: true }
492
699
  };
493
700
  function normalizeConfig(config) {
494
- const collections = config?.collections || [];
495
- const globals = config?.globals || [];
701
+ const schemaAwareConfig = normalizeSchemaFragment(config);
702
+ const collections = schemaAwareConfig?.collections || [];
703
+ const globals = schemaAwareConfig?.globals || [];
496
704
  const needsAudit = collections.some((col) => col.audit);
497
705
  const needsWorkflow = collections.some((col) => col.workflow || col.drafts);
706
+ const needsAuthSessions = collections.some((col) => !!col.auth);
498
707
  const adminAuthCollectionSlug = getAdminAuthCollection({
499
708
  collections,
500
- adminAuth: config.adminAuth
709
+ adminAuth: schemaAwareConfig.adminAuth
501
710
  })?.slug;
502
711
  const normalizedCollections = collections.map((col) => {
503
712
  let fields = col.fields || [];
@@ -594,7 +803,7 @@ function normalizeConfig(config) {
594
803
  return field;
595
804
  });
596
805
  }
597
- if (adminAuthCollectionSlug && col.slug === adminAuthCollectionSlug && config.adminAuth?.mode === "external") {
806
+ if (adminAuthCollectionSlug && col.slug === adminAuthCollectionSlug && schemaAwareConfig.adminAuth?.mode === "external") {
598
807
  const externalAdminFields = [
599
808
  {
600
809
  name: "authProvider",
@@ -628,7 +837,9 @@ function normalizeConfig(config) {
628
837
  }
629
838
  }
630
839
  const updatedFieldNames = new Set(fields.map((f) => f.name));
631
- const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
840
+ const fieldsToInject = SYSTEM_FIELDS.filter(
841
+ (f) => !updatedFieldNames.has(f.name)
842
+ );
632
843
  const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
633
844
  return {
634
845
  ...col,
@@ -636,17 +847,27 @@ function normalizeConfig(config) {
636
847
  fields: [...fields, ...fieldsToInject]
637
848
  };
638
849
  });
639
- const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
850
+ const hasAuditCollection = normalizedCollections.some(
851
+ (col) => col.slug === AUDIT_COLLECTION_SLUG
852
+ );
640
853
  const systemCollections = [];
641
- if (needsAudit && !hasAuditCollection) systemCollections.push(AUDIT_COLLECTION);
642
- if (needsWorkflow && !normalizedCollections.some((col) => col.slug === WORKFLOW_HISTORY_COLLECTION)) {
854
+ if (needsAudit && !hasAuditCollection)
855
+ systemCollections.push(AUDIT_COLLECTION);
856
+ if (needsWorkflow && !normalizedCollections.some(
857
+ (col) => col.slug === WORKFLOW_HISTORY_COLLECTION
858
+ )) {
643
859
  systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
644
860
  }
645
- if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
861
+ if (needsWorkflow && !normalizedCollections.some(
862
+ (col) => col.slug === LIFECYCLE_EVENTS_COLLECTION
863
+ )) {
646
864
  systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
647
865
  }
866
+ if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {
867
+ systemCollections.push(AUTH_SESSIONS_COLLECTION_CONFIG);
868
+ }
648
869
  return {
649
- ...config,
870
+ ...schemaAwareConfig,
650
871
  collections: [...normalizedCollections, ...systemCollections],
651
872
  globals
652
873
  };
@@ -872,7 +1093,10 @@ async function runCollectionHooks(hooks, args, options = {}) {
872
1093
  }
873
1094
  } catch (err) {
874
1095
  if (options.isolated) {
875
- console.error("[dyrected/core] Side-effect hook failed (error isolated \u2014 DB write was successful):", err);
1096
+ getConfigLogger(void 0, "hooks").error({
1097
+ err,
1098
+ msg: "Side-effect hook failed after a successful write"
1099
+ });
876
1100
  } else {
877
1101
  throw err;
878
1102
  }
@@ -917,7 +1141,13 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
917
1141
  const item = updatedValue[i];
918
1142
  const origItem = Array.isArray(origValue) ? origValue[i] : null;
919
1143
  arrayResult.push(
920
- await executeFieldBeforeChange(field.fields, item, origItem, user, db)
1144
+ await executeFieldBeforeChange(
1145
+ field.fields,
1146
+ item,
1147
+ origItem,
1148
+ user,
1149
+ db
1150
+ )
921
1151
  );
922
1152
  }
923
1153
  result[field.name] = arrayResult;
@@ -1058,7 +1288,15 @@ function generateOpenApi(config) {
1058
1288
  },
1059
1289
  WorkflowHistoryEntry: {
1060
1290
  type: "object",
1061
- required: ["collection", "documentId", "transition", "from", "to", "revision", "createdAt"],
1291
+ required: [
1292
+ "collection",
1293
+ "documentId",
1294
+ "transition",
1295
+ "from",
1296
+ "to",
1297
+ "revision",
1298
+ "createdAt"
1299
+ ],
1062
1300
  properties: {
1063
1301
  id: { type: "string" },
1064
1302
  collection: { type: "string" },
@@ -1083,7 +1321,11 @@ function generateOpenApi(config) {
1083
1321
  user: { type: "string", nullable: true },
1084
1322
  timestamp: { type: "string", format: "date-time" },
1085
1323
  changes: {
1086
- oneOf: [{ type: "string" }, { type: "object", additionalProperties: true }, { type: "null" }]
1324
+ oneOf: [
1325
+ { type: "string" },
1326
+ { type: "object", additionalProperties: true },
1327
+ { type: "null" }
1328
+ ]
1087
1329
  }
1088
1330
  }
1089
1331
  },
@@ -1175,13 +1417,17 @@ function generateOpenApi(config) {
1175
1417
  get: {
1176
1418
  tags: ["Preferences"],
1177
1419
  summary: "Get an authenticated user preference",
1178
- parameters: [{ name: "key", in: "path", required: true, schema: { type: "string" } }],
1420
+ parameters: [
1421
+ { name: "key", in: "path", required: true, schema: { type: "string" } }
1422
+ ],
1179
1423
  responses: { 200: { description: "Preference value" } }
1180
1424
  },
1181
1425
  put: {
1182
1426
  tags: ["Preferences"],
1183
1427
  summary: "Set an authenticated user preference",
1184
- parameters: [{ name: "key", in: "path", required: true, schema: { type: "string" } }],
1428
+ parameters: [
1429
+ { name: "key", in: "path", required: true, schema: { type: "string" } }
1430
+ ],
1185
1431
  requestBody: {
1186
1432
  required: true,
1187
1433
  content: { "application/json": { schema: { type: "object" } } }
@@ -1217,10 +1463,23 @@ function generateOpenApi(config) {
1217
1463
  tags: ["Audit"],
1218
1464
  summary: "Get audit entries across all readable audited collections",
1219
1465
  parameters: [
1220
- { name: "limit", in: "query", schema: { type: "integer", default: 50, maximum: 100 } },
1466
+ {
1467
+ name: "limit",
1468
+ in: "query",
1469
+ schema: { type: "integer", default: 50, maximum: 100 }
1470
+ },
1221
1471
  { name: "page", in: "query", schema: { type: "integer", default: 1 } },
1222
- { name: "where", in: "query", schema: { type: "string" }, description: "JSON filter" },
1223
- { name: "sort", in: "query", schema: { type: "string", default: "-timestamp" } }
1472
+ {
1473
+ name: "where",
1474
+ in: "query",
1475
+ schema: { type: "string" },
1476
+ description: "JSON filter"
1477
+ },
1478
+ {
1479
+ name: "sort",
1480
+ in: "query",
1481
+ schema: { type: "string", default: "-timestamp" }
1482
+ }
1224
1483
  ],
1225
1484
  responses: {
1226
1485
  200: {
@@ -1425,10 +1684,27 @@ function generateOpenApi(config) {
1425
1684
  tags: [collectionTag],
1426
1685
  summary: `Get ${labels.singular} audit entries`,
1427
1686
  parameters: [
1428
- { name: "limit", in: "query", schema: { type: "integer", default: 50, maximum: 100 } },
1429
- { name: "page", in: "query", schema: { type: "integer", default: 1 } },
1430
- { name: "where", in: "query", schema: { type: "string" }, description: "JSON filter" },
1431
- { name: "sort", in: "query", schema: { type: "string", default: "-timestamp" } }
1687
+ {
1688
+ name: "limit",
1689
+ in: "query",
1690
+ schema: { type: "integer", default: 50, maximum: 100 }
1691
+ },
1692
+ {
1693
+ name: "page",
1694
+ in: "query",
1695
+ schema: { type: "integer", default: 1 }
1696
+ },
1697
+ {
1698
+ name: "where",
1699
+ in: "query",
1700
+ schema: { type: "string" },
1701
+ description: "JSON filter"
1702
+ },
1703
+ {
1704
+ name: "sort",
1705
+ in: "query",
1706
+ schema: { type: "string", default: "-timestamp" }
1707
+ }
1432
1708
  ],
1433
1709
  responses: {
1434
1710
  200: {
@@ -1554,6 +1830,15 @@ function generateOpenApi(config) {
1554
1830
  post: {
1555
1831
  tags: [collectionTag],
1556
1832
  summary: `Log out of ${labels.plural}`,
1833
+ parameters: [
1834
+ {
1835
+ name: "allSessions",
1836
+ in: "query",
1837
+ required: false,
1838
+ schema: { type: "boolean" },
1839
+ description: "Revoke every active session for this account instead of only the current one."
1840
+ }
1841
+ ],
1557
1842
  responses: { 200: { description: "Logged out" } }
1558
1843
  }
1559
1844
  };
@@ -1579,7 +1864,11 @@ function generateOpenApi(config) {
1579
1864
  post: {
1580
1865
  tags: [collectionTag],
1581
1866
  summary: "Refresh an authentication token",
1582
- responses: { 200: { description: "Refreshed token" } }
1867
+ responses: {
1868
+ 200: {
1869
+ description: "Refreshed token for the current active session"
1870
+ }
1871
+ }
1583
1872
  }
1584
1873
  };
1585
1874
  spec.paths[`${path}/forgot-password`] = {
@@ -1610,7 +1899,11 @@ function generateOpenApi(config) {
1610
1899
  schema: { type: "string" }
1611
1900
  }
1612
1901
  ],
1613
- responses: { 200: { description: "Password changed" } }
1902
+ responses: {
1903
+ 200: {
1904
+ description: "Password changed and active sessions revoked"
1905
+ }
1906
+ }
1614
1907
  }
1615
1908
  };
1616
1909
  }
@@ -1819,7 +2112,7 @@ function fieldsToProperties(fields) {
1819
2112
  return { properties: props, required };
1820
2113
  }
1821
2114
  function fieldToSchema(field) {
1822
- let schema = {};
2115
+ let schema;
1823
2116
  switch (field.type) {
1824
2117
  case "text":
1825
2118
  case "textarea":
@@ -1828,7 +2121,10 @@ function fieldToSchema(field) {
1828
2121
  break;
1829
2122
  case "url":
1830
2123
  schema = {
1831
- oneOf: [{ type: "string" }, { type: "object", additionalProperties: true }]
2124
+ oneOf: [
2125
+ { type: "string" },
2126
+ { type: "object", additionalProperties: true }
2127
+ ]
1832
2128
  };
1833
2129
  break;
1834
2130
  case "icon":
@@ -1998,6 +2294,7 @@ function defineTab(args) {
1998
2294
  defineMultiSelectField,
1999
2295
  defineNumberField,
2000
2296
  defineObjectField,
2297
+ definePublishingWorkflow,
2001
2298
  defineRadioField,
2002
2299
  defineRelationshipField,
2003
2300
  defineRichTextField,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as DateField, i as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, j as ImageField, J as JoinField, k as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, l as RelationshipField, m as RichTextField, n as RowField, S as SelectField, T as TextField, o as TextareaField, p as TimeField, U as UrlField } from './app-config-C0uxEU7Q.cjs';
2
- export { q as AccessFunction, r as AccessFunctionArgs, s as AccessPolicyResolver, t as AccessResult, u as AccessRule, v as AdminAuthAccessResolution, w as AdminAuthClaimMapping, x as AdminAuthMember, y as AdminAuthProvider, z as AdminAuthProviderMembersConfig, K as AdminAuthProvisioningMode, Q as AdminAuthResolveAccessArgs, V as AdminAuthResolvedIdentity, X as AdminConfig, Y as AdminDashboardComponentSlots, Z as AdminIconName, _ as BaseAdminAuthProvider, $ as BaseFieldAdmin, a0 as BlockVariant, a1 as BooleanFieldAdmin, a2 as BooleanFormat, a3 as CharacterLimitFieldAdmin, a4 as CharacterLimitFieldConfig, a5 as CollectionAfterChangeHook, a6 as CollectionAfterDeleteHook, a7 as CollectionAfterReadHook, a8 as CollectionAfterTransitionHook, a9 as CollectionBeforeChangeHook, aa as CollectionBeforeDeleteHook, ab as CollectionBeforeReadHook, ac as CollectionBeforeTransitionHook, ad as CollectionListComponentSlots, ae as CustomAdminAuthProvider, af as DatabaseAdapter, ag as DateFieldAdmin, ah as DateFormat, ai as DisplayTone, aj as DynamicOptionItem, ak as DynamicOptionsConfig, al as DynamicOptionsResolver, am as DynamicOptionsResolverArgs, an as EmailFieldAdmin, ao as FieldAdminHooks, ap as FieldAdminOnChangeHook, aq as FieldAdminOnChangeHookArgs, ar as FieldAdminOptionsHook, as as FieldAdminOptionsHookArgs, at as FieldAdminOptionsHookResult, au as FieldAfterReadHook, av as FieldAfterReadHookArgs, aw as FieldBase, ax as FieldBeforeChangeHook, ay as FieldBeforeChangeHookArgs, az as FieldHook, aA as FieldHooks, aB as FieldType, aC as FileData, aD as GlobalAfterChangeHook, aE as GlobalAfterReadHook, aF as GlobalBeforeChangeHook, aG as GlobalBeforeReadHook, aH as HeadingLevel, aI as HookFunction, aJ as IconFieldAdmin, aK as ImageService, aL as JsonFieldAdmin, aM as JsonFormat, aN as LIFECYCLE_EVENT_NAMES, aO as LifecycleEventHandler, aP as LinkFormat, aQ as MultiSelectFieldAdmin, aR as NamedAccessPolicy, aS as NumberFieldAdmin, aT as NumberFormat, aU as NumberLimitFieldAdmin, aV as NumberLimitFieldConfig, aW as OIDCAdminAuthProvider, aX as OptionFormat, aY as PaginatedResult, aZ as PublicAdminAuthProvider, a_ as RadioFieldAdmin, a$ as ReadonlyDatabaseAdapter, b0 as RichTextFeature, b1 as RichTextFieldConfig, b2 as SelectFieldAdmin, b3 as StorageAdapter, b4 as TextFieldAdmin, b5 as TextFormat, b6 as TextareaFieldAdmin, b7 as TypedField, b8 as UploadConfig, b9 as UrlFieldAdmin, ba as UrlLinkValue, bb as WordLimitFieldAdmin, bc as WordLimitFieldConfig, bd as WorkflowMetadata, be as WorkflowRole, bf as WorkflowState, bg as WorkflowTransitionContext } from './app-config-C0uxEU7Q.cjs';
1
+ import { F as Field, B as Block, D as DyrectedConfig, C as CollectionConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, W as WorkflowConfig, a as AuthenticatedUser, b as WorkflowTransition, L as LifecycleEventName, c as LifecycleEvent, d as BaseDocument, H as HookRequestContext, e as ArrayField, f as BlocksField, g as BooleanField, h as AuthConfig, i as DateField, j as DateTimeField, E as EmailField, G as GlobalConfig, I as IconField, k as ImageField, J as JoinField, l as JsonField, M as MultiSelectField, N as NumberField, O as ObjectField, R as RadioField, m as RelationshipField, n as RichTextField, o as RowField, S as SelectField, T as TextField, p as TextareaField, q as TimeField, U as UrlField } from './app-config-Dv5XACR4.cjs';
2
+ export { r as AccessFunction, s as AccessFunctionArgs, t as AccessPolicyResolver, u as AccessResult, v as AccessRule, w as AdminAuthAccessResolution, x as AdminAuthClaimMapping, y as AdminAuthMember, z as AdminAuthProvider, K as AdminAuthProviderMembersConfig, Q as AdminAuthProvisioningMode, V as AdminAuthResolveAccessArgs, X as AdminAuthResolvedIdentity, Y as AdminConfig, Z as AdminDashboardComponentSlots, _ as AdminIconName, $ as BaseAdminAuthProvider, a0 as BaseFieldAdmin, a1 as BlockVariant, a2 as BooleanFieldAdmin, a3 as BooleanFormat, a4 as CharacterLimitFieldAdmin, a5 as CharacterLimitFieldConfig, a6 as CollectionAfterChangeHook, a7 as CollectionAfterDeleteHook, a8 as CollectionAfterReadHook, a9 as CollectionAfterTransitionHook, aa as CollectionBeforeChangeHook, ab as CollectionBeforeDeleteHook, ac as CollectionBeforeReadHook, ad as CollectionBeforeTransitionHook, ae as CollectionListComponentSlots, af as CustomAdminAuthProvider, ag as DatabaseAdapter, ah as DateFieldAdmin, ai as DateFormat, aj as DisplayTone, ak as DynamicOptionItem, al as DynamicOptionsConfig, am as DynamicOptionsResolver, an as DynamicOptionsResolverArgs, ao as DyrectedLoggerConfig, ap as DyrectedObservabilityConfig, aq as EmailFieldAdmin, ar as FieldAdminHooks, as as FieldAdminOnChangeHook, at as FieldAdminOnChangeHookArgs, au as FieldAdminOptionsHook, av as FieldAdminOptionsHookArgs, aw as FieldAdminOptionsHookResult, ax as FieldAfterReadHook, ay as FieldAfterReadHookArgs, az as FieldBase, aA as FieldBeforeChangeHook, aB as FieldBeforeChangeHookArgs, aC as FieldHook, aD as FieldHooks, aE as FieldType, aF as FileData, aG as GlobalAfterChangeHook, aH as GlobalAfterReadHook, aI as GlobalBeforeChangeHook, aJ as GlobalBeforeReadHook, aK as HeadingLevel, aL as HookFunction, aM as IconFieldAdmin, aN as ImageService, aO as JsonFieldAdmin, aP as JsonFormat, aQ as LIFECYCLE_EVENT_NAMES, aR as LifecycleEventHandler, aS as LinkFormat, aT as MultiSelectFieldAdmin, aU as NamedAccessPolicy, aV as NumberFieldAdmin, aW as NumberFormat, aX as NumberLimitFieldAdmin, aY as NumberLimitFieldConfig, aZ as OIDCAdminAuthProvider, a_ as OptionFormat, a$ as PaginatedResult, b0 as PublicAdminAuthProvider, b1 as RadioFieldAdmin, b2 as RateLimitConfig, b3 as ReadonlyDatabaseAdapter, b4 as RichTextFeature, b5 as RichTextFieldConfig, b6 as SelectFieldAdmin, b7 as StorageAdapter, b8 as TextFieldAdmin, b9 as TextFormat, ba as TextareaFieldAdmin, bb as TrustProxyConfig, bc as TypedField, bd as UploadConfig, be as UrlFieldAdmin, bf as UrlLinkValue, bg as WordLimitFieldAdmin, bh as WordLimitFieldConfig, bi as WorkflowMetadata, bj as WorkflowRole, bk as WorkflowState, bl as WorkflowTransitionContext } from './app-config-Dv5XACR4.cjs';
3
3
  import 'lucide-react';
4
+ import 'pino';
4
5
 
5
6
  type Prettify<T> = {
6
7
  [K in keyof T]: T[K];
@@ -241,6 +242,35 @@ declare function generateOpenApi(config: DyrectedConfig): any;
241
242
 
242
243
  declare const WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
243
244
  declare const LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
245
+ /**
246
+ * Role mapping for {@link definePublishingWorkflow}. Map your app's own role
247
+ * values onto the publishing workflow's two capability tiers.
248
+ */
249
+ interface PublishingWorkflowOptions {
250
+ /** Role values allowed to edit and submit for review. Defaults to `["editor"]`. */
251
+ editors?: string[];
252
+ /** Role values allowed to also publish and unpublish. Defaults to `["publisher", "admin"]`. */
253
+ publishers?: string[];
254
+ }
255
+ /**
256
+ * Build a `draft → in review → published` editorial workflow, mapping your own
257
+ * role values onto its capability tiers. Reach for this instead of
258
+ * {@link publishingWorkflow} when your roles aren't named
259
+ * `editor`/`publisher`/`admin`.
260
+ *
261
+ * @example
262
+ * workflow: definePublishingWorkflow({
263
+ * editors: ["writer"],
264
+ * publishers: ["managing-editor", "admin"],
265
+ * })
266
+ */
267
+ declare function definePublishingWorkflow(options?: PublishingWorkflowOptions): WorkflowConfig;
268
+ /**
269
+ * The default `draft → in review → published` editorial workflow, using the
270
+ * conventional role names `editor`, `publisher`, and `admin`. Shorthand for
271
+ * `definePublishingWorkflow()` — use {@link definePublishingWorkflow} to map
272
+ * your own role names onto its capability tiers.
273
+ */
244
274
  declare function publishingWorkflow(): WorkflowConfig;
245
275
  declare function simplePublishingWorkflow(): WorkflowConfig;
246
276
  declare function workflowCapabilities(workflow: WorkflowConfig, user?: AuthenticatedUser): Set<string>;
@@ -326,7 +356,7 @@ declare function defineCollection<const TFields extends Field[]>(config: Omit<Co
326
356
  id: string;
327
357
  } & InferDocShape<TFields> & SystemDocFields & AuthDocFields>>, "fields" | "auth"> & {
328
358
  fields: TFields;
329
- auth: true;
359
+ auth: true | AuthConfig;
330
360
  }): CollectionConfig<Prettify<{
331
361
  id: string;
332
362
  } & InferDocShape<TFields> & SystemDocFields & AuthDocFields>>;
@@ -539,4 +569,4 @@ declare function defineTab<const T extends readonly Field[]>(args: {
539
569
  fields: T;
540
570
  }): T;
541
571
 
542
- export { AdminAuthConfig, ArrayField, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlSortDialect, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, buildSqlOrderBy, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTab, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, numericFieldNames, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, simplePublishingWorkflow, transitionWorkflow, workflowCapabilities };
572
+ export { AdminAuthConfig, ArrayField, AuthConfig, type AuthDocFields, AuthenticatedUser, BaseDocument, Block, BlocksField, BooleanField, CollectionConfig, DateField, DateTimeField, DyrectedConfig, EmailField, Field, GlobalConfig, HookRequestContext, IconField, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, LifecycleEvent, LifecycleEventName, MultiSelectField, NumberField, ObjectField, type Prettify, PublicAdminAuthConfig, type PublishingWorkflowOptions, RadioField, RelationshipField, RichTextField, RowField, SelectField, type SortClause, type SortDirection, type SqlSortDialect, type SqlWhereResult, type SystemDocFields, TextField, TextareaField, TimeField, type UploadDocFields, UrlField, WORKFLOW_HISTORY_COLLECTION, type WhereClause, type WhereOperator, type WhereOperatorName, WorkflowConfig, WorkflowTransition, availableWorkflowTransitions, buildSqlOrderBy, canViewWorkflowDraft, createLifecycleEvent, createWorkflowDocument, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMultiSelectField, defineNumberField, defineObjectField, definePublishingWorkflow, defineRadioField, defineRelationshipField, defineRichTextField, defineRowField, defineSelectField, defineTab, defineTextField, defineTextareaField, defineTimeField, defineUrlField, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, executeFieldAfterRead, executeFieldBeforeChange, generateOpenApi, getAdminAuthCollection, getPublicAdminAuthConfig, initializeWorkflowDocument, materializeWorkflowDocument, normalizeConfig, numericFieldNames, parseMongoWhere, parseSort, parseSqlWhere, publishingWorkflow, resolveAdminAuthCollection, runCollectionHooks, saveWorkflowDraft, simplePublishingWorkflow, transitionWorkflow, workflowCapabilities };