@dyrected/core 2.5.60 → 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/app-config-9hs8DRED.d.cts +1718 -0
- package/dist/app-config-9hs8DRED.d.ts +1718 -0
- package/dist/app-config-BcuZIpvL.d.cts +1951 -0
- package/dist/app-config-BcuZIpvL.d.ts +1951 -0
- package/dist/app-config-Bm9-2Am6.d.cts +1875 -0
- package/dist/app-config-Bm9-2Am6.d.ts +1875 -0
- package/dist/app-config-C0uxEU7Q.d.cts +1874 -0
- package/dist/app-config-C0uxEU7Q.d.ts +1874 -0
- package/dist/app-config-CBOn8IyZ.d.cts +1838 -0
- package/dist/app-config-CBOn8IyZ.d.ts +1838 -0
- package/dist/app-config-CJAGGPrk.d.cts +1900 -0
- package/dist/app-config-CJAGGPrk.d.ts +1900 -0
- package/dist/app-config-CiEDJm0e.d.cts +1912 -0
- package/dist/app-config-CiEDJm0e.d.ts +1912 -0
- package/dist/app-config-CwaU1de2.d.cts +1868 -0
- package/dist/app-config-CwaU1de2.d.ts +1868 -0
- package/dist/app-config-DCDh8Gbx.d.cts +1926 -0
- package/dist/app-config-DCDh8Gbx.d.ts +1926 -0
- package/dist/app-config-DVdSospO.d.cts +1842 -0
- package/dist/app-config-DVdSospO.d.ts +1842 -0
- package/dist/app-config-Dv5XACR4.d.cts +2065 -0
- package/dist/app-config-Dv5XACR4.d.ts +2065 -0
- package/dist/app-config-_kkj71CB.d.cts +2010 -0
- package/dist/app-config-_kkj71CB.d.ts +2010 -0
- package/dist/app-config-ouBRb6Bf.d.cts +1716 -0
- package/dist/app-config-ouBRb6Bf.d.ts +1716 -0
- package/dist/app-config-tITj_0sn.d.cts +1926 -0
- package/dist/app-config-tITj_0sn.d.ts +1926 -0
- package/dist/chunk-35NM2WPO.js +1658 -0
- package/dist/chunk-57FNM42D.js +2392 -0
- package/dist/chunk-BAMX7YUC.js +1815 -0
- package/dist/chunk-BQV3QW3Y.js +2588 -0
- package/dist/chunk-CUOPCOU2.js +1644 -0
- package/dist/chunk-EH3MJGB5.js +2571 -0
- package/dist/chunk-M3HKRN7E.js +1665 -0
- package/dist/chunk-MQZH7RQC.js +1667 -0
- package/dist/chunk-VCYYBN5J.js +1873 -0
- package/dist/chunk-WVD7PORQ.js +1672 -0
- package/dist/chunk-XZLIBQSO.js +2397 -0
- package/dist/index.cjs +404 -44
- package/dist/index.d.cts +77 -5
- package/dist/index.d.ts +77 -5
- package/dist/index.js +37 -4
- package/dist/server.cjs +2973 -708
- package/dist/server.d.cts +124 -12
- package/dist/server.d.ts +124 -12
- package/dist/server.js +2066 -695
- package/package.json +10 -1
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
|
|
@@ -24,6 +34,7 @@ __export(index_exports, {
|
|
|
24
34
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
25
35
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
26
36
|
availableWorkflowTransitions: () => availableWorkflowTransitions,
|
|
37
|
+
buildSqlOrderBy: () => buildSqlOrderBy,
|
|
27
38
|
canViewWorkflowDraft: () => canViewWorkflowDraft,
|
|
28
39
|
createLifecycleEvent: () => createLifecycleEvent,
|
|
29
40
|
createWorkflowDocument: () => createWorkflowDocument,
|
|
@@ -45,6 +56,7 @@ __export(index_exports, {
|
|
|
45
56
|
defineMultiSelectField: () => defineMultiSelectField,
|
|
46
57
|
defineNumberField: () => defineNumberField,
|
|
47
58
|
defineObjectField: () => defineObjectField,
|
|
59
|
+
definePublishingWorkflow: () => definePublishingWorkflow,
|
|
48
60
|
defineRadioField: () => defineRadioField,
|
|
49
61
|
defineRelationshipField: () => defineRelationshipField,
|
|
50
62
|
defineRichTextField: () => defineRichTextField,
|
|
@@ -65,6 +77,7 @@ __export(index_exports, {
|
|
|
65
77
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
66
78
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
67
79
|
normalizeConfig: () => normalizeConfig,
|
|
80
|
+
numericFieldNames: () => numericFieldNames,
|
|
68
81
|
parseMongoWhere: () => parseMongoWhere,
|
|
69
82
|
parseSort: () => parseSort,
|
|
70
83
|
parseSqlWhere: () => parseSqlWhere,
|
|
@@ -72,6 +85,7 @@ __export(index_exports, {
|
|
|
72
85
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
73
86
|
runCollectionHooks: () => runCollectionHooks,
|
|
74
87
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
88
|
+
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
75
89
|
transitionWorkflow: () => transitionWorkflow,
|
|
76
90
|
workflowCapabilities: () => workflowCapabilities
|
|
77
91
|
});
|
|
@@ -85,10 +99,47 @@ var LIFECYCLE_EVENT_NAMES = [
|
|
|
85
99
|
"entry.unpublished"
|
|
86
100
|
];
|
|
87
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
|
+
|
|
88
135
|
// src/workflows.ts
|
|
89
136
|
var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
|
|
90
137
|
var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
|
|
91
|
-
|
|
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"];
|
|
92
143
|
return {
|
|
93
144
|
initialState: "draft",
|
|
94
145
|
draftState: "draft",
|
|
@@ -104,10 +155,29 @@ function publishingWorkflow() {
|
|
|
104
155
|
{ name: "unpublish", label: "Unpublish", from: "published", to: "draft", requiredCapabilities: ["entry.unpublish"], unpublish: true }
|
|
105
156
|
],
|
|
106
157
|
roles: [
|
|
107
|
-
{ role
|
|
108
|
-
{ role
|
|
109
|
-
|
|
158
|
+
...editors.map((role) => ({ role, capabilities: [...EDITOR_CAPABILITIES] })),
|
|
159
|
+
...publishers.map((role) => ({ role, capabilities: [...PUBLISHER_CAPABILITIES] }))
|
|
160
|
+
]
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function publishingWorkflow() {
|
|
164
|
+
return definePublishingWorkflow();
|
|
165
|
+
}
|
|
166
|
+
function simplePublishingWorkflow() {
|
|
167
|
+
return {
|
|
168
|
+
initialState: "draft",
|
|
169
|
+
draftState: "draft",
|
|
170
|
+
states: [
|
|
171
|
+
{ name: "draft", label: "Draft", color: "neutral" },
|
|
172
|
+
{ name: "published", label: "Published", color: "success", published: true }
|
|
173
|
+
],
|
|
174
|
+
transitions: [
|
|
175
|
+
{ name: "publish", label: "Publish", from: "draft", to: "published" },
|
|
176
|
+
{ name: "unpublish", label: "Unpublish", from: "published", to: "draft", unpublish: true }
|
|
110
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`.
|
|
111
181
|
};
|
|
112
182
|
}
|
|
113
183
|
function publicMetadata(meta) {
|
|
@@ -126,9 +196,13 @@ function canViewWorkflowDraft(workflow, user) {
|
|
|
126
196
|
if (!user) return false;
|
|
127
197
|
const capabilities = workflowCapabilities(workflow, user);
|
|
128
198
|
if (capabilities.has("entry.edit")) return true;
|
|
129
|
-
|
|
199
|
+
if (workflow.transitions.some(
|
|
130
200
|
(transition) => (transition.requiredCapabilities ?? []).some((capability) => capabilities.has(capability))
|
|
131
|
-
)
|
|
201
|
+
)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (!workflow.roles || workflow.roles.length === 0) return true;
|
|
205
|
+
return false;
|
|
132
206
|
}
|
|
133
207
|
function availableWorkflowTransitions(workflow, state, user) {
|
|
134
208
|
const capabilities = workflowCapabilities(workflow, user);
|
|
@@ -143,9 +217,23 @@ function initializeWorkflowDocument(data, workflow) {
|
|
|
143
217
|
__workflow: { state: workflow.initialState, revision: 1 }
|
|
144
218
|
};
|
|
145
219
|
}
|
|
220
|
+
function publishedStateName(workflow) {
|
|
221
|
+
return workflow.states.find((state) => state.published)?.name ?? workflow.states[workflow.states.length - 1]?.name ?? workflow.initialState;
|
|
222
|
+
}
|
|
146
223
|
function materializeWorkflowDocument(doc, workflow, user) {
|
|
147
224
|
const meta = doc.__workflow;
|
|
148
|
-
if (!meta)
|
|
225
|
+
if (!meta) {
|
|
226
|
+
const { __published: _legacyPublished, __workflow: _legacyWorkflow, ...working2 } = doc;
|
|
227
|
+
const state = publishedStateName(workflow);
|
|
228
|
+
return {
|
|
229
|
+
...working2,
|
|
230
|
+
_workflow: {
|
|
231
|
+
state,
|
|
232
|
+
revision: 1,
|
|
233
|
+
availableTransitions: availableWorkflowTransitions(workflow, state, user).map((item) => item.name)
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
149
237
|
const { __published, __workflow, ...working } = doc;
|
|
150
238
|
if (!canViewWorkflowDraft(workflow, user)) {
|
|
151
239
|
if (!__published || typeof __published !== "object") return null;
|
|
@@ -340,7 +428,17 @@ async function transitionWorkflow(args) {
|
|
|
340
428
|
try {
|
|
341
429
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
342
430
|
} catch (error) {
|
|
343
|
-
|
|
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
|
+
});
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
444
|
return updated;
|
|
@@ -381,6 +479,98 @@ function humanizeProviderName(id, type) {
|
|
|
381
479
|
return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
382
480
|
}
|
|
383
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
|
+
|
|
384
574
|
// src/utils/config.ts
|
|
385
575
|
var AUDIT_COLLECTION_SLUG = "__audit";
|
|
386
576
|
var SYSTEM_FIELDS = [
|
|
@@ -415,12 +605,23 @@ var AUDIT_COLLECTION = {
|
|
|
415
605
|
fields: [
|
|
416
606
|
{ name: "collection", type: "text", label: "Collection", required: true },
|
|
417
607
|
{ name: "documentId", type: "text", label: "Document ID" },
|
|
418
|
-
{
|
|
608
|
+
{
|
|
609
|
+
name: "operation",
|
|
610
|
+
type: "select",
|
|
611
|
+
label: "Operation",
|
|
612
|
+
options: ["create", "update", "delete"],
|
|
613
|
+
required: true
|
|
614
|
+
},
|
|
419
615
|
{ name: "user", type: "text", label: "User ID" },
|
|
420
616
|
{ name: "timestamp", type: "date", label: "Timestamp", required: true },
|
|
421
617
|
{ name: "changes", type: "json", label: "Changes" }
|
|
422
618
|
],
|
|
423
|
-
access: {
|
|
619
|
+
access: {
|
|
620
|
+
read: () => false,
|
|
621
|
+
create: () => false,
|
|
622
|
+
update: () => false,
|
|
623
|
+
delete: () => false
|
|
624
|
+
},
|
|
424
625
|
admin: { hidden: true }
|
|
425
626
|
};
|
|
426
627
|
var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
|
|
@@ -437,7 +638,12 @@ var WORKFLOW_HISTORY_COLLECTION_CONFIG = {
|
|
|
437
638
|
{ name: "actorId", type: "text" },
|
|
438
639
|
{ name: "createdAt", type: "date", required: true }
|
|
439
640
|
],
|
|
440
|
-
access: {
|
|
641
|
+
access: {
|
|
642
|
+
read: ({ user }) => !!user,
|
|
643
|
+
create: () => false,
|
|
644
|
+
update: () => false,
|
|
645
|
+
delete: () => false
|
|
646
|
+
},
|
|
441
647
|
admin: { hidden: true }
|
|
442
648
|
};
|
|
443
649
|
var LIFECYCLE_EVENTS_COLLECTION_CONFIG = {
|
|
@@ -451,22 +657,56 @@ var LIFECYCLE_EVENTS_COLLECTION_CONFIG = {
|
|
|
451
657
|
{ name: "actorId", type: "text" },
|
|
452
658
|
{ name: "payload", type: "json", required: true },
|
|
453
659
|
{ name: "attempts", type: "number", required: true },
|
|
454
|
-
{
|
|
660
|
+
{
|
|
661
|
+
name: "status",
|
|
662
|
+
type: "select",
|
|
663
|
+
options: ["pending", "processing", "delivered", "failed"],
|
|
664
|
+
required: true
|
|
665
|
+
},
|
|
455
666
|
{ name: "nextAttemptAt", type: "date" },
|
|
456
667
|
{ name: "deliveredAt", type: "date" },
|
|
457
668
|
{ name: "lastError", type: "textarea" }
|
|
458
669
|
],
|
|
459
|
-
access: {
|
|
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
|
+
},
|
|
460
698
|
admin: { hidden: true }
|
|
461
699
|
};
|
|
462
700
|
function normalizeConfig(config) {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
701
|
+
const schemaAwareConfig = normalizeSchemaFragment(config);
|
|
702
|
+
const collections = schemaAwareConfig?.collections || [];
|
|
703
|
+
const globals = schemaAwareConfig?.globals || [];
|
|
465
704
|
const needsAudit = collections.some((col) => col.audit);
|
|
466
|
-
const needsWorkflow = collections.some((col) => col.workflow);
|
|
705
|
+
const needsWorkflow = collections.some((col) => col.workflow || col.drafts);
|
|
706
|
+
const needsAuthSessions = collections.some((col) => !!col.auth);
|
|
467
707
|
const adminAuthCollectionSlug = getAdminAuthCollection({
|
|
468
708
|
collections,
|
|
469
|
-
adminAuth:
|
|
709
|
+
adminAuth: schemaAwareConfig.adminAuth
|
|
470
710
|
})?.slug;
|
|
471
711
|
const normalizedCollections = collections.map((col) => {
|
|
472
712
|
let fields = col.fields || [];
|
|
@@ -563,7 +803,7 @@ function normalizeConfig(config) {
|
|
|
563
803
|
return field;
|
|
564
804
|
});
|
|
565
805
|
}
|
|
566
|
-
if (adminAuthCollectionSlug && col.slug === adminAuthCollectionSlug &&
|
|
806
|
+
if (adminAuthCollectionSlug && col.slug === adminAuthCollectionSlug && schemaAwareConfig.adminAuth?.mode === "external") {
|
|
567
807
|
const externalAdminFields = [
|
|
568
808
|
{
|
|
569
809
|
name: "authProvider",
|
|
@@ -597,23 +837,37 @@ function normalizeConfig(config) {
|
|
|
597
837
|
}
|
|
598
838
|
}
|
|
599
839
|
const updatedFieldNames = new Set(fields.map((f) => f.name));
|
|
600
|
-
const fieldsToInject = SYSTEM_FIELDS.filter(
|
|
840
|
+
const fieldsToInject = SYSTEM_FIELDS.filter(
|
|
841
|
+
(f) => !updatedFieldNames.has(f.name)
|
|
842
|
+
);
|
|
843
|
+
const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
|
|
601
844
|
return {
|
|
602
845
|
...col,
|
|
846
|
+
workflow,
|
|
603
847
|
fields: [...fields, ...fieldsToInject]
|
|
604
848
|
};
|
|
605
849
|
});
|
|
606
|
-
const hasAuditCollection = normalizedCollections.some(
|
|
850
|
+
const hasAuditCollection = normalizedCollections.some(
|
|
851
|
+
(col) => col.slug === AUDIT_COLLECTION_SLUG
|
|
852
|
+
);
|
|
607
853
|
const systemCollections = [];
|
|
608
|
-
if (needsAudit && !hasAuditCollection)
|
|
609
|
-
|
|
854
|
+
if (needsAudit && !hasAuditCollection)
|
|
855
|
+
systemCollections.push(AUDIT_COLLECTION);
|
|
856
|
+
if (needsWorkflow && !normalizedCollections.some(
|
|
857
|
+
(col) => col.slug === WORKFLOW_HISTORY_COLLECTION
|
|
858
|
+
)) {
|
|
610
859
|
systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
|
|
611
860
|
}
|
|
612
|
-
if (needsWorkflow && !normalizedCollections.some(
|
|
861
|
+
if (needsWorkflow && !normalizedCollections.some(
|
|
862
|
+
(col) => col.slug === LIFECYCLE_EVENTS_COLLECTION
|
|
863
|
+
)) {
|
|
613
864
|
systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
|
|
614
865
|
}
|
|
866
|
+
if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {
|
|
867
|
+
systemCollections.push(AUTH_SESSIONS_COLLECTION_CONFIG);
|
|
868
|
+
}
|
|
615
869
|
return {
|
|
616
|
-
...
|
|
870
|
+
...schemaAwareConfig,
|
|
617
871
|
collections: [...normalizedCollections, ...systemCollections],
|
|
618
872
|
globals
|
|
619
873
|
};
|
|
@@ -623,7 +877,7 @@ function normalizeConfig(config) {
|
|
|
623
877
|
function assertNever(op, context) {
|
|
624
878
|
throw new Error(`[dyrected/core] Unhandled where operator "${op}" in ${context}`);
|
|
625
879
|
}
|
|
626
|
-
function parseSqlWhere(where, getJsonField, placeholder = "?") {
|
|
880
|
+
function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "LIKE") {
|
|
627
881
|
const params = [];
|
|
628
882
|
let pgIndex = 1;
|
|
629
883
|
function next() {
|
|
@@ -685,10 +939,10 @@ function parseSqlWhere(where, getJsonField, placeholder = "?") {
|
|
|
685
939
|
return `${c} <= ${next()}`;
|
|
686
940
|
case "contains":
|
|
687
941
|
params.push(`%${operand}%`);
|
|
688
|
-
return `${c}
|
|
942
|
+
return `${c} ${likeOperator} ${next()}`;
|
|
689
943
|
case "starts_with":
|
|
690
944
|
params.push(`${operand}%`);
|
|
691
|
-
return `${c}
|
|
945
|
+
return `${c} ${likeOperator} ${next()}`;
|
|
692
946
|
case "exists":
|
|
693
947
|
return operand ? `${c} IS NOT NULL` : `${c} IS NULL`;
|
|
694
948
|
default:
|
|
@@ -793,6 +1047,33 @@ function parseSort(sort, fallback = { field: "createdAt", direction: "DESC" }) {
|
|
|
793
1047
|
}).filter((clause) => Boolean(clause));
|
|
794
1048
|
return clauses.length > 0 ? clauses : [fallback];
|
|
795
1049
|
}
|
|
1050
|
+
function numericFieldNames(fields) {
|
|
1051
|
+
const names = /* @__PURE__ */ new Set();
|
|
1052
|
+
for (const field of fields ?? []) {
|
|
1053
|
+
if (field.type === "number" && "name" in field && field.name) {
|
|
1054
|
+
names.add(field.name);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return names;
|
|
1058
|
+
}
|
|
1059
|
+
var TIMESTAMP_COLUMNS = {
|
|
1060
|
+
createdat: "created_at",
|
|
1061
|
+
created_at: "created_at",
|
|
1062
|
+
updatedat: "updated_at",
|
|
1063
|
+
updated_at: "updated_at"
|
|
1064
|
+
};
|
|
1065
|
+
function buildSqlOrderBy(sort, existingColumns, numericFields, dialect) {
|
|
1066
|
+
return parseSort(sort).map(({ field, direction }) => {
|
|
1067
|
+
const timestamp = TIMESTAMP_COLUMNS[field.toLowerCase()];
|
|
1068
|
+
if (timestamp) return `${dialect.quoteIdent(timestamp)} ${direction}`;
|
|
1069
|
+
if (existingColumns.includes(field) && field !== "id" && field !== "data") {
|
|
1070
|
+
return `${dialect.quoteIdent(field)} ${direction}`;
|
|
1071
|
+
}
|
|
1072
|
+
const extracted = dialect.jsonExtract(field);
|
|
1073
|
+
const expr = numericFields.has(field) ? dialect.castNumeric(extracted) : extracted;
|
|
1074
|
+
return `${expr} ${direction}`;
|
|
1075
|
+
}).join(", ");
|
|
1076
|
+
}
|
|
796
1077
|
|
|
797
1078
|
// src/utils/hooks.ts
|
|
798
1079
|
async function runCollectionHooks(hooks, args, options = {}) {
|
|
@@ -812,7 +1093,10 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
812
1093
|
}
|
|
813
1094
|
} catch (err) {
|
|
814
1095
|
if (options.isolated) {
|
|
815
|
-
|
|
1096
|
+
getConfigLogger(void 0, "hooks").error({
|
|
1097
|
+
err,
|
|
1098
|
+
msg: "Side-effect hook failed after a successful write"
|
|
1099
|
+
});
|
|
816
1100
|
} else {
|
|
817
1101
|
throw err;
|
|
818
1102
|
}
|
|
@@ -857,7 +1141,13 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
|
|
|
857
1141
|
const item = updatedValue[i];
|
|
858
1142
|
const origItem = Array.isArray(origValue) ? origValue[i] : null;
|
|
859
1143
|
arrayResult.push(
|
|
860
|
-
await executeFieldBeforeChange(
|
|
1144
|
+
await executeFieldBeforeChange(
|
|
1145
|
+
field.fields,
|
|
1146
|
+
item,
|
|
1147
|
+
origItem,
|
|
1148
|
+
user,
|
|
1149
|
+
db
|
|
1150
|
+
)
|
|
861
1151
|
);
|
|
862
1152
|
}
|
|
863
1153
|
result[field.name] = arrayResult;
|
|
@@ -998,7 +1288,15 @@ function generateOpenApi(config) {
|
|
|
998
1288
|
},
|
|
999
1289
|
WorkflowHistoryEntry: {
|
|
1000
1290
|
type: "object",
|
|
1001
|
-
required: [
|
|
1291
|
+
required: [
|
|
1292
|
+
"collection",
|
|
1293
|
+
"documentId",
|
|
1294
|
+
"transition",
|
|
1295
|
+
"from",
|
|
1296
|
+
"to",
|
|
1297
|
+
"revision",
|
|
1298
|
+
"createdAt"
|
|
1299
|
+
],
|
|
1002
1300
|
properties: {
|
|
1003
1301
|
id: { type: "string" },
|
|
1004
1302
|
collection: { type: "string" },
|
|
@@ -1023,7 +1321,11 @@ function generateOpenApi(config) {
|
|
|
1023
1321
|
user: { type: "string", nullable: true },
|
|
1024
1322
|
timestamp: { type: "string", format: "date-time" },
|
|
1025
1323
|
changes: {
|
|
1026
|
-
oneOf: [
|
|
1324
|
+
oneOf: [
|
|
1325
|
+
{ type: "string" },
|
|
1326
|
+
{ type: "object", additionalProperties: true },
|
|
1327
|
+
{ type: "null" }
|
|
1328
|
+
]
|
|
1027
1329
|
}
|
|
1028
1330
|
}
|
|
1029
1331
|
},
|
|
@@ -1115,13 +1417,17 @@ function generateOpenApi(config) {
|
|
|
1115
1417
|
get: {
|
|
1116
1418
|
tags: ["Preferences"],
|
|
1117
1419
|
summary: "Get an authenticated user preference",
|
|
1118
|
-
parameters: [
|
|
1420
|
+
parameters: [
|
|
1421
|
+
{ name: "key", in: "path", required: true, schema: { type: "string" } }
|
|
1422
|
+
],
|
|
1119
1423
|
responses: { 200: { description: "Preference value" } }
|
|
1120
1424
|
},
|
|
1121
1425
|
put: {
|
|
1122
1426
|
tags: ["Preferences"],
|
|
1123
1427
|
summary: "Set an authenticated user preference",
|
|
1124
|
-
parameters: [
|
|
1428
|
+
parameters: [
|
|
1429
|
+
{ name: "key", in: "path", required: true, schema: { type: "string" } }
|
|
1430
|
+
],
|
|
1125
1431
|
requestBody: {
|
|
1126
1432
|
required: true,
|
|
1127
1433
|
content: { "application/json": { schema: { type: "object" } } }
|
|
@@ -1157,10 +1463,23 @@ function generateOpenApi(config) {
|
|
|
1157
1463
|
tags: ["Audit"],
|
|
1158
1464
|
summary: "Get audit entries across all readable audited collections",
|
|
1159
1465
|
parameters: [
|
|
1160
|
-
{
|
|
1466
|
+
{
|
|
1467
|
+
name: "limit",
|
|
1468
|
+
in: "query",
|
|
1469
|
+
schema: { type: "integer", default: 50, maximum: 100 }
|
|
1470
|
+
},
|
|
1161
1471
|
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
1162
|
-
{
|
|
1163
|
-
|
|
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
|
+
}
|
|
1164
1483
|
],
|
|
1165
1484
|
responses: {
|
|
1166
1485
|
200: {
|
|
@@ -1365,10 +1684,27 @@ function generateOpenApi(config) {
|
|
|
1365
1684
|
tags: [collectionTag],
|
|
1366
1685
|
summary: `Get ${labels.singular} audit entries`,
|
|
1367
1686
|
parameters: [
|
|
1368
|
-
{
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
+
}
|
|
1372
1708
|
],
|
|
1373
1709
|
responses: {
|
|
1374
1710
|
200: {
|
|
@@ -1494,6 +1830,15 @@ function generateOpenApi(config) {
|
|
|
1494
1830
|
post: {
|
|
1495
1831
|
tags: [collectionTag],
|
|
1496
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
|
+
],
|
|
1497
1842
|
responses: { 200: { description: "Logged out" } }
|
|
1498
1843
|
}
|
|
1499
1844
|
};
|
|
@@ -1519,7 +1864,11 @@ function generateOpenApi(config) {
|
|
|
1519
1864
|
post: {
|
|
1520
1865
|
tags: [collectionTag],
|
|
1521
1866
|
summary: "Refresh an authentication token",
|
|
1522
|
-
responses: {
|
|
1867
|
+
responses: {
|
|
1868
|
+
200: {
|
|
1869
|
+
description: "Refreshed token for the current active session"
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1523
1872
|
}
|
|
1524
1873
|
};
|
|
1525
1874
|
spec.paths[`${path}/forgot-password`] = {
|
|
@@ -1550,7 +1899,11 @@ function generateOpenApi(config) {
|
|
|
1550
1899
|
schema: { type: "string" }
|
|
1551
1900
|
}
|
|
1552
1901
|
],
|
|
1553
|
-
responses: {
|
|
1902
|
+
responses: {
|
|
1903
|
+
200: {
|
|
1904
|
+
description: "Password changed and active sessions revoked"
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1554
1907
|
}
|
|
1555
1908
|
};
|
|
1556
1909
|
}
|
|
@@ -1759,7 +2112,7 @@ function fieldsToProperties(fields) {
|
|
|
1759
2112
|
return { properties: props, required };
|
|
1760
2113
|
}
|
|
1761
2114
|
function fieldToSchema(field) {
|
|
1762
|
-
let schema
|
|
2115
|
+
let schema;
|
|
1763
2116
|
switch (field.type) {
|
|
1764
2117
|
case "text":
|
|
1765
2118
|
case "textarea":
|
|
@@ -1768,7 +2121,10 @@ function fieldToSchema(field) {
|
|
|
1768
2121
|
break;
|
|
1769
2122
|
case "url":
|
|
1770
2123
|
schema = {
|
|
1771
|
-
oneOf: [
|
|
2124
|
+
oneOf: [
|
|
2125
|
+
{ type: "string" },
|
|
2126
|
+
{ type: "object", additionalProperties: true }
|
|
2127
|
+
]
|
|
1772
2128
|
};
|
|
1773
2129
|
break;
|
|
1774
2130
|
case "icon":
|
|
@@ -1916,6 +2272,7 @@ function defineTab(args) {
|
|
|
1916
2272
|
LIFECYCLE_EVENT_NAMES,
|
|
1917
2273
|
WORKFLOW_HISTORY_COLLECTION,
|
|
1918
2274
|
availableWorkflowTransitions,
|
|
2275
|
+
buildSqlOrderBy,
|
|
1919
2276
|
canViewWorkflowDraft,
|
|
1920
2277
|
createLifecycleEvent,
|
|
1921
2278
|
createWorkflowDocument,
|
|
@@ -1937,6 +2294,7 @@ function defineTab(args) {
|
|
|
1937
2294
|
defineMultiSelectField,
|
|
1938
2295
|
defineNumberField,
|
|
1939
2296
|
defineObjectField,
|
|
2297
|
+
definePublishingWorkflow,
|
|
1940
2298
|
defineRadioField,
|
|
1941
2299
|
defineRelationshipField,
|
|
1942
2300
|
defineRichTextField,
|
|
@@ -1957,6 +2315,7 @@ function defineTab(args) {
|
|
|
1957
2315
|
initializeWorkflowDocument,
|
|
1958
2316
|
materializeWorkflowDocument,
|
|
1959
2317
|
normalizeConfig,
|
|
2318
|
+
numericFieldNames,
|
|
1960
2319
|
parseMongoWhere,
|
|
1961
2320
|
parseSort,
|
|
1962
2321
|
parseSqlWhere,
|
|
@@ -1964,6 +2323,7 @@ function defineTab(args) {
|
|
|
1964
2323
|
resolveAdminAuthCollection,
|
|
1965
2324
|
runCollectionHooks,
|
|
1966
2325
|
saveWorkflowDraft,
|
|
2326
|
+
simplePublishingWorkflow,
|
|
1967
2327
|
transitionWorkflow,
|
|
1968
2328
|
workflowCapabilities
|
|
1969
2329
|
});
|