@dyrected/core 2.6.4 → 2.7.1
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-ChsrBB7p.d.cts +2100 -0
- package/dist/app-config-ChsrBB7p.d.ts +2100 -0
- package/dist/chunk-2UXLA4YN.js +3139 -0
- package/dist/chunk-5U2WRCNH.js +3140 -0
- package/dist/chunk-AKYYGMFS.js +3365 -0
- package/dist/chunk-EJG6AMYB.js +3537 -0
- package/dist/chunk-FRSJJO27.js +519 -0
- package/dist/chunk-G43REXPX.js +3147 -0
- package/dist/chunk-GFKBPBJJ.js +3637 -0
- package/dist/chunk-LFGSRQLX.js +3568 -0
- package/dist/chunk-NNZRYF3U.js +3537 -0
- package/dist/chunk-PYE2JSY4.js +3568 -0
- package/dist/chunk-QCXLDKZO.js +3272 -0
- package/dist/chunk-UKUEKTAO.js +513 -0
- package/dist/chunk-VM43G3IG.js +506 -0
- package/dist/chunk-VWJKFZQU.js +3139 -0
- package/dist/chunk-YSSHLGQ6.js +3139 -0
- package/dist/chunk-Z7CFUYVP.js +3653 -0
- package/dist/index.cjs +984 -82
- package/dist/index.d.cts +199 -3
- package/dist/index.d.ts +199 -3
- package/dist/index.js +67 -1
- package/dist/observability-CNM4QFE4.js +34 -0
- package/dist/observability-KCMTV7GO.js +34 -0
- package/dist/observability-L5BORBB2.js +34 -0
- package/dist/server.cjs +1268 -888
- package/dist/server.d.cts +28 -3
- package/dist/server.d.ts +28 -3
- package/dist/server.js +38 -33
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,13 +30,526 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/observability.ts
|
|
34
|
+
var observability_exports = {};
|
|
35
|
+
__export(observability_exports, {
|
|
36
|
+
attachRequestMetrics: () => attachRequestMetrics,
|
|
37
|
+
bindObservabilityRuntime: () => bindObservabilityRuntime,
|
|
38
|
+
buildRequestLogger: () => buildRequestLogger,
|
|
39
|
+
captureRequestBody: () => captureRequestBody,
|
|
40
|
+
createObservabilityRuntime: () => createObservabilityRuntime,
|
|
41
|
+
createRequestSpan: () => createRequestSpan,
|
|
42
|
+
endRequestSpan: () => endRequestSpan,
|
|
43
|
+
getConfigLogger: () => getConfigLogger,
|
|
44
|
+
getObservabilityRuntime: () => getObservabilityRuntime,
|
|
45
|
+
getRequestLogger: () => getRequestLogger,
|
|
46
|
+
redactBody: () => redactBody,
|
|
47
|
+
redactHeaders: () => redactHeaders,
|
|
48
|
+
renderPrometheusMetrics: () => renderPrometheusMetrics,
|
|
49
|
+
resolveObservabilityConfig: () => resolveObservabilityConfig,
|
|
50
|
+
shouldSampleRequest: () => shouldSampleRequest
|
|
51
|
+
});
|
|
52
|
+
function resolveObservabilityConfig(config) {
|
|
53
|
+
const requestLogging = config?.requestLogging;
|
|
54
|
+
const sampling = config?.sampling;
|
|
55
|
+
const tracing = config?.tracing;
|
|
56
|
+
const metrics = config?.metrics;
|
|
57
|
+
return {
|
|
58
|
+
requestLogging: {
|
|
59
|
+
enabled: requestLogging?.enabled ?? true,
|
|
60
|
+
logBodies: requestLogging?.logBodies ?? false,
|
|
61
|
+
maxBodyBytes: requestLogging?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
|
|
62
|
+
redactPaths: uniqueValues([
|
|
63
|
+
...DEFAULT_REDACT_PATHS,
|
|
64
|
+
...requestLogging?.redactPaths ?? []
|
|
65
|
+
]),
|
|
66
|
+
includeHeaders: normalizeHeaderNames(requestLogging?.includeHeaders ?? []),
|
|
67
|
+
redactHeaders: uniqueValues(
|
|
68
|
+
normalizeHeaderNames([
|
|
69
|
+
...DEFAULT_REDACT_HEADERS,
|
|
70
|
+
...requestLogging?.redactHeaders ?? []
|
|
71
|
+
])
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
sampling: {
|
|
75
|
+
successRate: clampRate(sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE),
|
|
76
|
+
traceSuccessRate: clampRate(
|
|
77
|
+
sampling?.traceSuccessRate ?? sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE
|
|
78
|
+
),
|
|
79
|
+
bodySuccessRate: clampRate(
|
|
80
|
+
sampling?.bodySuccessRate ?? Math.min(DEFAULT_BODY_SAMPLE_RATE, sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE)
|
|
81
|
+
),
|
|
82
|
+
alwaysKeep4xx: sampling?.alwaysKeep4xx ?? true,
|
|
83
|
+
alwaysKeep5xx: sampling?.alwaysKeep5xx ?? true
|
|
84
|
+
},
|
|
85
|
+
tracing: {
|
|
86
|
+
enabled: tracing?.enabled ?? false,
|
|
87
|
+
serviceName: tracing?.serviceName ?? "dyrected",
|
|
88
|
+
exporter: tracing?.exporter ?? "none",
|
|
89
|
+
headers: tracing?.headers ?? {},
|
|
90
|
+
endpoint: tracing?.endpoint
|
|
91
|
+
},
|
|
92
|
+
metrics: {
|
|
93
|
+
enabled: metrics?.enabled ?? false,
|
|
94
|
+
exporter: metrics?.exporter ?? "none",
|
|
95
|
+
endpoint: metrics?.endpoint,
|
|
96
|
+
path: metrics?.path ?? "/metrics"
|
|
97
|
+
},
|
|
98
|
+
transports: {
|
|
99
|
+
targets: config?.transports?.targets && config.transports.targets.length > 0 ? config.transports.targets : [{ type: "stdout" }]
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function createObservabilityRuntime(config) {
|
|
104
|
+
const resolved = resolveObservabilityConfig(config.observability);
|
|
105
|
+
const logger = createRootLogger(config.logger, resolved);
|
|
106
|
+
const shutdownTasks = [];
|
|
107
|
+
const meterRuntime = resolved.metrics.enabled || resolved.metrics.exporter === "prometheus" ? createMeterProvider(resolved) : void 0;
|
|
108
|
+
const tracerProvider = resolved.tracing.enabled ? createTracerProvider(resolved) : void 0;
|
|
109
|
+
const tracer = tracerProvider?.getTracer("dyrected");
|
|
110
|
+
const meter = meterRuntime?.provider.getMeter("dyrected");
|
|
111
|
+
const metrics = meter ? {
|
|
112
|
+
requestCount: meter.createCounter("dyrected_http_requests_total", {
|
|
113
|
+
description: "Total HTTP requests served by Dyrected"
|
|
114
|
+
}),
|
|
115
|
+
authFailureCount: meter.createCounter("dyrected_auth_failures_total", {
|
|
116
|
+
description: "Authentication failures"
|
|
117
|
+
}),
|
|
118
|
+
uncaughtErrorCount: meter.createCounter("dyrected_uncaught_errors_total", {
|
|
119
|
+
description: "Uncaught application errors"
|
|
120
|
+
}),
|
|
121
|
+
auditWriteFailureCount: meter.createCounter("dyrected_audit_write_failures_total", {
|
|
122
|
+
description: "Audit log persistence failures"
|
|
123
|
+
}),
|
|
124
|
+
emailSendFailureCount: meter.createCounter("dyrected_email_send_failures_total", {
|
|
125
|
+
description: "Transactional email delivery failures"
|
|
126
|
+
}),
|
|
127
|
+
workflowHookFailureCount: meter.createCounter("dyrected_workflow_hook_failures_total", {
|
|
128
|
+
description: "Workflow hook failures isolated after successful writes"
|
|
129
|
+
}),
|
|
130
|
+
requestDuration: meter.createHistogram("dyrected_http_request_duration_ms", {
|
|
131
|
+
description: "HTTP request duration in milliseconds",
|
|
132
|
+
unit: "ms"
|
|
133
|
+
})
|
|
134
|
+
} : void 0;
|
|
135
|
+
if (meterRuntime) {
|
|
136
|
+
shutdownTasks.push(() => meterRuntime.provider.shutdown());
|
|
137
|
+
}
|
|
138
|
+
if (tracerProvider) {
|
|
139
|
+
shutdownTasks.push(() => tracerProvider.shutdown());
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
logger,
|
|
143
|
+
config: resolved,
|
|
144
|
+
tracer,
|
|
145
|
+
metrics,
|
|
146
|
+
prometheusExporter: meterRuntime?.prometheusExporter,
|
|
147
|
+
shutdown: async () => {
|
|
148
|
+
for (const task of shutdownTasks) {
|
|
149
|
+
await task();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
recordAuthFailure: (attributes) => metrics?.authFailureCount.add(1, attributes),
|
|
153
|
+
recordUncaughtError: (attributes) => metrics?.uncaughtErrorCount.add(1, attributes),
|
|
154
|
+
recordAuditWriteFailure: (attributes) => metrics?.auditWriteFailureCount.add(1, attributes),
|
|
155
|
+
recordEmailSendFailure: (attributes) => metrics?.emailSendFailureCount.add(1, attributes),
|
|
156
|
+
recordWorkflowHookFailure: (attributes) => metrics?.workflowHookFailureCount.add(1, attributes)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function bindObservabilityRuntime(config, runtime) {
|
|
160
|
+
runtimeByConfig.set(config, runtime);
|
|
161
|
+
}
|
|
162
|
+
function getObservabilityRuntime(config) {
|
|
163
|
+
if (!config || typeof config !== "object") return void 0;
|
|
164
|
+
return runtimeByConfig.get(config);
|
|
165
|
+
}
|
|
166
|
+
function createRootLogger(loggerConfig, observability) {
|
|
167
|
+
if (loggerConfig && !("options" in loggerConfig)) {
|
|
168
|
+
return loggerConfig;
|
|
169
|
+
}
|
|
170
|
+
const options = loggerConfig?.options ?? {};
|
|
171
|
+
const enabled = options.enabled ?? process.env.DISABLE_LOGGING !== "true";
|
|
172
|
+
const name = options.name ?? "dyrected";
|
|
173
|
+
if (loggerConfig?.destination) {
|
|
174
|
+
return (0, import_pino.default)(
|
|
175
|
+
{
|
|
176
|
+
...options,
|
|
177
|
+
enabled,
|
|
178
|
+
name,
|
|
179
|
+
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
180
|
+
},
|
|
181
|
+
loggerConfig.destination
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const streams = buildTransportStreams(observability);
|
|
185
|
+
const destination = streams.length === 1 ? streams[0] : (0, import_pino.multistream)(streams.map((stream) => ({ stream })));
|
|
186
|
+
return (0, import_pino.default)(
|
|
187
|
+
{
|
|
188
|
+
...options,
|
|
189
|
+
enabled,
|
|
190
|
+
name,
|
|
191
|
+
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
192
|
+
},
|
|
193
|
+
destination
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function buildTransportStreams(observability) {
|
|
197
|
+
const targets = observability.transports.targets;
|
|
198
|
+
if (targets.length === 0) {
|
|
199
|
+
return [defaultLoggerDestination()];
|
|
200
|
+
}
|
|
201
|
+
return targets.map((target) => {
|
|
202
|
+
switch (target.type) {
|
|
203
|
+
case "stdout":
|
|
204
|
+
return defaultLoggerDestination();
|
|
205
|
+
case "stderr":
|
|
206
|
+
return (0, import_pino.destination)(2);
|
|
207
|
+
case "file":
|
|
208
|
+
return (0, import_pino.destination)(target.path);
|
|
209
|
+
case "otlp":
|
|
210
|
+
return new OtlpLogWritable(target.endpoint, target.headers);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function defaultLoggerDestination() {
|
|
215
|
+
if (process.env.NODE_ENV !== "production" && typeof window === "undefined") {
|
|
216
|
+
try {
|
|
217
|
+
const req = globalThis.require;
|
|
218
|
+
if (req) {
|
|
219
|
+
const pinoPretty = req("pino-pretty");
|
|
220
|
+
return pinoPretty({
|
|
221
|
+
colorize: true,
|
|
222
|
+
ignore: "pid,hostname",
|
|
223
|
+
translateTime: "SYS:HH:MM:ss",
|
|
224
|
+
destination: 1,
|
|
225
|
+
sync: true
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return (0, import_pino.destination)(1);
|
|
232
|
+
}
|
|
233
|
+
function createTracerProvider(observability) {
|
|
234
|
+
const exporter = createTraceExporter(observability);
|
|
235
|
+
const provider = new import_sdk_trace_base.BasicTracerProvider({
|
|
236
|
+
resource: (0, import_resources.resourceFromAttributes)({
|
|
237
|
+
"service.name": observability.tracing.serviceName
|
|
238
|
+
}),
|
|
239
|
+
spanProcessors: exporter ? [new import_sdk_trace_base.BatchSpanProcessor(exporter)] : []
|
|
240
|
+
});
|
|
241
|
+
import_api.trace.setGlobalTracerProvider(provider);
|
|
242
|
+
return provider;
|
|
243
|
+
}
|
|
244
|
+
function createTraceExporter(observability) {
|
|
245
|
+
switch (observability.tracing.exporter) {
|
|
246
|
+
case "console":
|
|
247
|
+
return new import_sdk_trace_base.ConsoleSpanExporter();
|
|
248
|
+
case "otlp":
|
|
249
|
+
return new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
250
|
+
url: observability.tracing.endpoint,
|
|
251
|
+
headers: observability.tracing.headers
|
|
252
|
+
});
|
|
253
|
+
default:
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function createMeterProvider(observability) {
|
|
258
|
+
const readers = [];
|
|
259
|
+
let prometheusExporter;
|
|
260
|
+
switch (observability.metrics.exporter) {
|
|
261
|
+
case "otlp":
|
|
262
|
+
readers.push(
|
|
263
|
+
new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
264
|
+
exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
265
|
+
url: observability.metrics.endpoint
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
break;
|
|
270
|
+
case "prometheus":
|
|
271
|
+
prometheusExporter = new import_exporter_prometheus.PrometheusExporter({
|
|
272
|
+
endpoint: observability.metrics.path,
|
|
273
|
+
preventServerStart: true
|
|
274
|
+
});
|
|
275
|
+
readers.push(prometheusExporter);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
const provider = new import_sdk_metrics.MeterProvider({
|
|
279
|
+
resource: (0, import_resources.resourceFromAttributes)({
|
|
280
|
+
"service.name": observability.tracing.serviceName
|
|
281
|
+
}),
|
|
282
|
+
readers
|
|
283
|
+
});
|
|
284
|
+
return { provider, prometheusExporter };
|
|
285
|
+
}
|
|
286
|
+
function getConfigLogger(config, component) {
|
|
287
|
+
const base = config?.logger && !("options" in config.logger) ? config.logger : fallbackLogger;
|
|
288
|
+
return base.child({ component });
|
|
289
|
+
}
|
|
290
|
+
function getRequestLogger(c, component) {
|
|
291
|
+
const base = c.get("logger") ?? fallbackLogger;
|
|
292
|
+
return component ? base.child({ component }) : base;
|
|
293
|
+
}
|
|
294
|
+
function buildRequestLogger(runtime, vars, requestId) {
|
|
295
|
+
const trace2 = vars.requestTrace;
|
|
296
|
+
return runtime.logger.child({
|
|
297
|
+
requestId,
|
|
298
|
+
siteId: vars.siteId,
|
|
299
|
+
workspaceId: vars.workspaceId,
|
|
300
|
+
traceId: trace2?.traceId,
|
|
301
|
+
spanId: trace2?.spanId
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function shouldSampleRequest(requestId, statusCode, sampling, kind) {
|
|
305
|
+
if (statusCode >= 500 && sampling.alwaysKeep5xx) return true;
|
|
306
|
+
if (statusCode >= 400 && sampling.alwaysKeep4xx) return true;
|
|
307
|
+
const rate = kind === "trace" ? sampling.traceSuccessRate : kind === "body" ? sampling.bodySuccessRate : sampling.successRate;
|
|
308
|
+
return deterministicSample(requestId ?? "no-request-id", rate);
|
|
309
|
+
}
|
|
310
|
+
function redactHeaders(headers, observability) {
|
|
311
|
+
const includeHeaders = new Set(observability.requestLogging.includeHeaders);
|
|
312
|
+
const redactHeaders2 = new Set(observability.requestLogging.redactHeaders);
|
|
313
|
+
const output = {};
|
|
314
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
315
|
+
const normalizedKey = key.toLowerCase();
|
|
316
|
+
if (includeHeaders.size > 0 && !includeHeaders.has(normalizedKey)) continue;
|
|
317
|
+
output[normalizedKey] = redactHeaders2.has(normalizedKey) ? REDACTED_VALUE : value;
|
|
318
|
+
}
|
|
319
|
+
return output;
|
|
320
|
+
}
|
|
321
|
+
async function captureRequestBody(request, observability) {
|
|
322
|
+
const contentType = request.headers.get("content-type") ?? void 0;
|
|
323
|
+
const contentLength = toNumber(request.headers.get("content-length"));
|
|
324
|
+
const jsonBody = contentType?.includes("application/json");
|
|
325
|
+
if (!jsonBody) {
|
|
326
|
+
return {
|
|
327
|
+
attempted: false,
|
|
328
|
+
contentType,
|
|
329
|
+
contentLength
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const text = await request.clone().text();
|
|
334
|
+
if (!text) {
|
|
335
|
+
return { attempted: true, contentType, contentLength };
|
|
336
|
+
}
|
|
337
|
+
const truncated = text.length > observability.requestLogging.maxBodyBytes;
|
|
338
|
+
const raw = truncated ? text.slice(0, observability.requestLogging.maxBodyBytes) : text;
|
|
339
|
+
try {
|
|
340
|
+
const parsed = JSON.parse(raw);
|
|
341
|
+
return {
|
|
342
|
+
attempted: true,
|
|
343
|
+
contentType,
|
|
344
|
+
contentLength: contentLength ?? text.length,
|
|
345
|
+
truncated,
|
|
346
|
+
body: redactBody(parsed, observability.requestLogging.redactPaths)
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
return {
|
|
350
|
+
attempted: true,
|
|
351
|
+
contentType,
|
|
352
|
+
contentLength: contentLength ?? text.length,
|
|
353
|
+
truncated,
|
|
354
|
+
parseFailed: true
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
} catch {
|
|
358
|
+
return {
|
|
359
|
+
attempted: true,
|
|
360
|
+
contentType,
|
|
361
|
+
contentLength,
|
|
362
|
+
parseFailed: true
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function redactBody(value, redactPaths) {
|
|
367
|
+
const cloned = deepClone(value);
|
|
368
|
+
for (const path of redactPaths) {
|
|
369
|
+
applyRedaction(cloned, path.split("."));
|
|
370
|
+
}
|
|
371
|
+
return cloned;
|
|
372
|
+
}
|
|
373
|
+
function applyRedaction(target, segments) {
|
|
374
|
+
if (!target || segments.length === 0) return;
|
|
375
|
+
const [segment, ...rest] = segments;
|
|
376
|
+
if (Array.isArray(target)) {
|
|
377
|
+
for (const item of target) {
|
|
378
|
+
if (segment === "*") {
|
|
379
|
+
applyRedaction(item, rest);
|
|
380
|
+
} else {
|
|
381
|
+
applyRedaction(item, segments);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (typeof target !== "object") return;
|
|
387
|
+
const record = target;
|
|
388
|
+
if (segment === "*") {
|
|
389
|
+
for (const value of Object.values(record)) {
|
|
390
|
+
applyRedaction(value, rest);
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!(segment in record)) return;
|
|
395
|
+
if (rest.length === 0) {
|
|
396
|
+
record[segment] = REDACTED_VALUE;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
applyRedaction(record[segment], rest);
|
|
400
|
+
}
|
|
401
|
+
function deepClone(value) {
|
|
402
|
+
if (typeof structuredClone === "function") {
|
|
403
|
+
return structuredClone(value);
|
|
404
|
+
}
|
|
405
|
+
return JSON.parse(JSON.stringify(value));
|
|
406
|
+
}
|
|
407
|
+
function createRequestSpan(runtime, name, attributes) {
|
|
408
|
+
if (!runtime.tracer) return void 0;
|
|
409
|
+
const span = runtime.tracer.startSpan(name, { attributes }, import_api.context.active());
|
|
410
|
+
const spanContext = span.spanContext();
|
|
411
|
+
return {
|
|
412
|
+
span,
|
|
413
|
+
traceId: spanContext.traceId,
|
|
414
|
+
spanId: spanContext.spanId,
|
|
415
|
+
sampled: spanContext.traceFlags === import_api.TraceFlags.SAMPLED
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function endRequestSpan(requestTrace, statusCode, error) {
|
|
419
|
+
if (!requestTrace) return;
|
|
420
|
+
requestTrace.span.setAttribute("http.status_code", statusCode);
|
|
421
|
+
if (error) {
|
|
422
|
+
requestTrace.span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
423
|
+
}
|
|
424
|
+
requestTrace.span.end();
|
|
425
|
+
}
|
|
426
|
+
function attachRequestMetrics(runtime, args) {
|
|
427
|
+
runtime.metrics?.requestCount.add(1, {
|
|
428
|
+
method: args.method,
|
|
429
|
+
route: args.route,
|
|
430
|
+
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
431
|
+
});
|
|
432
|
+
runtime.metrics?.requestDuration.record(args.durationMs, {
|
|
433
|
+
method: args.method,
|
|
434
|
+
route: args.route,
|
|
435
|
+
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async function renderPrometheusMetrics(exporter) {
|
|
439
|
+
let statusCode = 200;
|
|
440
|
+
const headers = {};
|
|
441
|
+
let body = "";
|
|
442
|
+
await new Promise((resolve) => {
|
|
443
|
+
const response = {
|
|
444
|
+
statusCode,
|
|
445
|
+
setHeader(name, value) {
|
|
446
|
+
headers[name.toLowerCase()] = value;
|
|
447
|
+
},
|
|
448
|
+
end(chunk) {
|
|
449
|
+
if (chunk) body += chunk;
|
|
450
|
+
statusCode = this.statusCode;
|
|
451
|
+
resolve();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
exporter.getMetricsRequestHandler({}, response);
|
|
455
|
+
});
|
|
456
|
+
return { body, headers, statusCode };
|
|
457
|
+
}
|
|
458
|
+
function normalizeHeaderNames(headers) {
|
|
459
|
+
return headers.map((value) => value.toLowerCase());
|
|
460
|
+
}
|
|
461
|
+
function uniqueValues(values) {
|
|
462
|
+
return [...new Set(values)];
|
|
463
|
+
}
|
|
464
|
+
function clampRate(value) {
|
|
465
|
+
if (Number.isNaN(value)) return 0;
|
|
466
|
+
return Math.max(0, Math.min(1, value));
|
|
467
|
+
}
|
|
468
|
+
function deterministicSample(seed, rate) {
|
|
469
|
+
if (rate >= 1) return true;
|
|
470
|
+
if (rate <= 0) return false;
|
|
471
|
+
let hash = 0;
|
|
472
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
473
|
+
hash = hash * 31 + seed.charCodeAt(index) >>> 0;
|
|
474
|
+
}
|
|
475
|
+
return hash / 4294967295 <= rate;
|
|
476
|
+
}
|
|
477
|
+
function toNumber(value) {
|
|
478
|
+
if (!value) return void 0;
|
|
479
|
+
const parsed = Number(value);
|
|
480
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
481
|
+
}
|
|
482
|
+
var import_api, import_exporter_metrics_otlp_http, import_exporter_prometheus, import_exporter_trace_otlp_http, import_resources, import_sdk_metrics, import_sdk_trace_base, import_pino, import_node_stream, DEFAULT_REDACT_HEADERS, DEFAULT_REDACT_PATHS, DEFAULT_MAX_BODY_BYTES, DEFAULT_SUCCESS_SAMPLE_RATE, DEFAULT_BODY_SAMPLE_RATE, REDACTED_VALUE, runtimeByConfig, fallbackLogger, OtlpLogWritable;
|
|
483
|
+
var init_observability = __esm({
|
|
484
|
+
"src/observability.ts"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
import_api = require("@opentelemetry/api");
|
|
487
|
+
import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
488
|
+
import_exporter_prometheus = require("@opentelemetry/exporter-prometheus");
|
|
489
|
+
import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
490
|
+
import_resources = require("@opentelemetry/resources");
|
|
491
|
+
import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
492
|
+
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
493
|
+
import_pino = __toESM(require("pino"), 1);
|
|
494
|
+
import_node_stream = require("stream");
|
|
495
|
+
DEFAULT_REDACT_HEADERS = ["authorization", "cookie", "set-cookie", "x-api-key"];
|
|
496
|
+
DEFAULT_REDACT_PATHS = [
|
|
497
|
+
"password",
|
|
498
|
+
"currentPassword",
|
|
499
|
+
"newPassword",
|
|
500
|
+
"confirmPassword",
|
|
501
|
+
"token",
|
|
502
|
+
"refreshToken",
|
|
503
|
+
"accessToken",
|
|
504
|
+
"secret",
|
|
505
|
+
"apiKey",
|
|
506
|
+
"inviteToken",
|
|
507
|
+
"resetToken"
|
|
508
|
+
];
|
|
509
|
+
DEFAULT_MAX_BODY_BYTES = 8192;
|
|
510
|
+
DEFAULT_SUCCESS_SAMPLE_RATE = 0.1;
|
|
511
|
+
DEFAULT_BODY_SAMPLE_RATE = 0.02;
|
|
512
|
+
REDACTED_VALUE = "[REDACTED]";
|
|
513
|
+
runtimeByConfig = /* @__PURE__ */ new WeakMap();
|
|
514
|
+
fallbackLogger = (0, import_pino.default)({
|
|
515
|
+
name: "dyrected",
|
|
516
|
+
enabled: process.env.DISABLE_LOGGING !== "true",
|
|
517
|
+
timestamp: import_pino.stdTimeFunctions.isoTime
|
|
518
|
+
});
|
|
519
|
+
OtlpLogWritable = class extends import_node_stream.Writable {
|
|
520
|
+
constructor(endpoint, headers = {}) {
|
|
521
|
+
super();
|
|
522
|
+
this.endpoint = endpoint;
|
|
523
|
+
this.headers = headers;
|
|
524
|
+
}
|
|
525
|
+
endpoint;
|
|
526
|
+
headers;
|
|
527
|
+
_write(chunk, _encoding, callback) {
|
|
528
|
+
const payload = chunk.toString();
|
|
529
|
+
void fetch(this.endpoint, {
|
|
530
|
+
method: "POST",
|
|
531
|
+
headers: {
|
|
532
|
+
"content-type": "application/json",
|
|
533
|
+
...this.headers
|
|
534
|
+
},
|
|
535
|
+
body: JSON.stringify({ logs: payload.trim().split("\n").filter(Boolean) })
|
|
536
|
+
}).then(() => callback()).catch(
|
|
537
|
+
(error) => callback(error instanceof Error ? error : new Error(String(error)))
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
30
544
|
// src/index.ts
|
|
31
545
|
var index_exports = {};
|
|
32
546
|
__export(index_exports, {
|
|
547
|
+
BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
|
|
33
548
|
ConfigValidationError: () => ConfigValidationError,
|
|
34
549
|
LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
|
|
35
550
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
36
551
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
552
|
+
addDays: () => addDays,
|
|
37
553
|
assertValidAdminConditionsInConfig: () => assertValidAdminConditionsInConfig,
|
|
38
554
|
assertValidDeclarativeAccessInConfig: () => assertValidDeclarativeAccessInConfig,
|
|
39
555
|
assertValidDeclarativeHookExpression: () => assertValidDeclarativeHookExpression,
|
|
@@ -43,13 +559,18 @@ __export(index_exports, {
|
|
|
43
559
|
availableWorkflowTransitions: () => availableWorkflowTransitions,
|
|
44
560
|
buildSqlOrderBy: () => buildSqlOrderBy,
|
|
45
561
|
canViewWorkflowDraft: () => canViewWorkflowDraft,
|
|
562
|
+
capitalize: () => capitalize,
|
|
563
|
+
clamp: () => clamp,
|
|
564
|
+
coalesce: () => coalesce,
|
|
46
565
|
collectAdminConditionValidationIssues: () => collectAdminConditionValidationIssues,
|
|
47
566
|
collectConfigDiagnostics: () => collectConfigDiagnostics,
|
|
48
567
|
collectDeclarativeAccessValidationIssues: () => collectDeclarativeAccessValidationIssues,
|
|
49
568
|
collectDeclarativeHookValidationIssues: () => collectDeclarativeHookValidationIssues,
|
|
50
569
|
collectPreviewUrlValidationIssues: () => collectPreviewUrlValidationIssues,
|
|
570
|
+
compact: () => compact,
|
|
51
571
|
createLifecycleEvent: () => createLifecycleEvent,
|
|
52
572
|
createWorkflowDocument: () => createWorkflowDocument,
|
|
573
|
+
defaultValue: () => defaultValue,
|
|
53
574
|
defineArrayField: () => defineArrayField,
|
|
54
575
|
defineBlock: () => defineBlock,
|
|
55
576
|
defineBlocksField: () => defineBlocksField,
|
|
@@ -79,33 +600,59 @@ __export(index_exports, {
|
|
|
79
600
|
defineTextareaField: () => defineTextareaField,
|
|
80
601
|
defineTimeField: () => defineTimeField,
|
|
81
602
|
defineUrlField: () => defineUrlField,
|
|
603
|
+
diffDays: () => diffDays,
|
|
82
604
|
dispatchLifecycleEvent: () => dispatchLifecycleEvent,
|
|
83
605
|
dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
|
|
606
|
+
endsWith: () => endsWith,
|
|
84
607
|
executeFieldAfterRead: () => executeFieldAfterRead,
|
|
85
608
|
executeFieldBeforeChange: () => executeFieldBeforeChange,
|
|
609
|
+
first: () => first,
|
|
86
610
|
formatConfigDiagnostics: () => formatConfigDiagnostics,
|
|
611
|
+
formatDate: () => formatDate,
|
|
87
612
|
generateOpenApi: () => generateOpenApi,
|
|
613
|
+
get: () => get,
|
|
88
614
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
89
615
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
616
|
+
includes: () => includes,
|
|
90
617
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
91
618
|
isConfigValidationError: () => isConfigValidationError,
|
|
619
|
+
isEmpty: () => isEmpty,
|
|
620
|
+
isFuture: () => isFuture,
|
|
621
|
+
isPast: () => isPast,
|
|
622
|
+
join: () => join,
|
|
623
|
+
last: () => last,
|
|
624
|
+
length: () => length,
|
|
625
|
+
lower: () => lower,
|
|
92
626
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
93
627
|
normalizeConfig: () => normalizeConfig,
|
|
628
|
+
now: () => now,
|
|
94
629
|
numericFieldNames: () => numericFieldNames,
|
|
95
630
|
parseMongoWhere: () => parseMongoWhere,
|
|
96
631
|
parseSort: () => parseSort,
|
|
97
632
|
parseSqlWhere: () => parseSqlWhere,
|
|
98
633
|
publishedStateName: () => publishedStateName,
|
|
99
634
|
publishingWorkflow: () => publishingWorkflow,
|
|
635
|
+
readingTime: () => readingTime,
|
|
636
|
+
registerJexlHelpers: () => registerJexlHelpers,
|
|
637
|
+
replace: () => replace,
|
|
100
638
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
639
|
+
round: () => round,
|
|
101
640
|
runCollectionHooks: () => runCollectionHooks,
|
|
102
641
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
103
642
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
643
|
+
slugify: () => slugify,
|
|
644
|
+
startsWith: () => startsWith,
|
|
645
|
+
today: () => today,
|
|
104
646
|
transitionWorkflow: () => transitionWorkflow,
|
|
647
|
+
trim: () => trim,
|
|
648
|
+
truncate: () => truncate,
|
|
649
|
+
unique: () => unique,
|
|
650
|
+
upper: () => upper,
|
|
105
651
|
validateAdminConditionExpression: () => validateAdminConditionExpression,
|
|
106
652
|
validateDeclarativeAccessExpression: () => validateDeclarativeAccessExpression,
|
|
107
653
|
validateDeclarativeHookExpression: () => validateDeclarativeHookExpression,
|
|
108
654
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
655
|
+
wordCount: () => wordCount,
|
|
109
656
|
workflowCapabilities: () => workflowCapabilities
|
|
110
657
|
});
|
|
111
658
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -120,37 +667,10 @@ var LIFECYCLE_EVENT_NAMES = [
|
|
|
120
667
|
|
|
121
668
|
// src/auth/token.ts
|
|
122
669
|
var import_jose = require("jose");
|
|
123
|
-
var import_node_util = require("util");
|
|
124
670
|
|
|
125
671
|
// src/auth/sessions.ts
|
|
126
672
|
var AUTH_SESSIONS_COLLECTION = "__auth_sessions";
|
|
127
673
|
|
|
128
|
-
// src/observability.ts
|
|
129
|
-
var import_api = require("@opentelemetry/api");
|
|
130
|
-
var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
131
|
-
var import_exporter_prometheus = require("@opentelemetry/exporter-prometheus");
|
|
132
|
-
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
133
|
-
var import_resources = require("@opentelemetry/resources");
|
|
134
|
-
var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
135
|
-
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
136
|
-
var import_pino = __toESM(require("pino"), 1);
|
|
137
|
-
var import_pino_pretty = __toESM(require("pino-pretty"), 1);
|
|
138
|
-
var import_node_stream = require("stream");
|
|
139
|
-
var runtimeByConfig = /* @__PURE__ */ new WeakMap();
|
|
140
|
-
var fallbackLogger = (0, import_pino.default)({
|
|
141
|
-
name: "dyrected",
|
|
142
|
-
enabled: process.env.DISABLE_LOGGING !== "true",
|
|
143
|
-
timestamp: import_pino.stdTimeFunctions.isoTime
|
|
144
|
-
});
|
|
145
|
-
function getObservabilityRuntime(config) {
|
|
146
|
-
if (!config || typeof config !== "object") return void 0;
|
|
147
|
-
return runtimeByConfig.get(config);
|
|
148
|
-
}
|
|
149
|
-
function getConfigLogger(config, component) {
|
|
150
|
-
const base = config?.logger && !("options" in config.logger) ? config.logger : fallbackLogger;
|
|
151
|
-
return base.child({ component });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
674
|
// src/workflows.ts
|
|
155
675
|
var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
|
|
156
676
|
var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
|
|
@@ -168,10 +688,36 @@ function definePublishingWorkflow(options = {}) {
|
|
|
168
688
|
{ name: "published", label: "Published", color: "success", published: true }
|
|
169
689
|
],
|
|
170
690
|
transitions: [
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
691
|
+
{
|
|
692
|
+
name: "submit",
|
|
693
|
+
label: "Submit for review",
|
|
694
|
+
from: "draft",
|
|
695
|
+
to: "in_review",
|
|
696
|
+
requiredCapabilities: ["entry.submit"]
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
name: "publish",
|
|
700
|
+
label: "Publish",
|
|
701
|
+
from: "in_review",
|
|
702
|
+
to: "published",
|
|
703
|
+
requiredCapabilities: ["entry.publish"]
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: "reject",
|
|
707
|
+
label: "Request changes",
|
|
708
|
+
from: "in_review",
|
|
709
|
+
to: "draft",
|
|
710
|
+
requiredCapabilities: ["entry.publish"],
|
|
711
|
+
requireComment: true
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "unpublish",
|
|
715
|
+
label: "Unpublish",
|
|
716
|
+
from: "published",
|
|
717
|
+
to: "draft",
|
|
718
|
+
requiredCapabilities: ["entry.unpublish"],
|
|
719
|
+
unpublish: true
|
|
720
|
+
}
|
|
175
721
|
],
|
|
176
722
|
roles: [
|
|
177
723
|
...editors.map((role) => ({ role, capabilities: [...EDITOR_CAPABILITIES] })),
|
|
@@ -191,7 +737,7 @@ function simplePublishingWorkflow() {
|
|
|
191
737
|
{ name: "published", label: "Published", color: "success", published: true }
|
|
192
738
|
],
|
|
193
739
|
transitions: [
|
|
194
|
-
{ name: "publish", label: "Publish", from: "draft", to: "published" },
|
|
740
|
+
{ name: "publish", label: "Publish", from: ["draft", "published"], to: "published" },
|
|
195
741
|
{ name: "unpublish", label: "Unpublish", from: "published", to: "draft", unpublish: true }
|
|
196
742
|
]
|
|
197
743
|
// Intentionally no `roles`: this lightweight workflow (synthesized from
|
|
@@ -298,7 +844,11 @@ async function dispatchLifecycleEvent(config, event) {
|
|
|
298
844
|
const retryDelayMs = config.events.retryDelayMs ?? 1e3;
|
|
299
845
|
const attempts = event.attempts + 1;
|
|
300
846
|
try {
|
|
301
|
-
await db.update({
|
|
847
|
+
await db.update({
|
|
848
|
+
collection: LIFECYCLE_EVENTS_COLLECTION,
|
|
849
|
+
id: event.id,
|
|
850
|
+
data: { status: "processing", attempts }
|
|
851
|
+
});
|
|
302
852
|
for (const handler of config.events.handlers) await handler({ ...event, status: "processing", attempts });
|
|
303
853
|
await db.update({
|
|
304
854
|
collection: LIFECYCLE_EVENTS_COLLECTION,
|
|
@@ -328,9 +878,9 @@ async function dispatchPendingLifecycleEvents(config, limit = 50) {
|
|
|
328
878
|
sort: "occurredAt",
|
|
329
879
|
limit
|
|
330
880
|
});
|
|
331
|
-
const
|
|
881
|
+
const now2 = Date.now();
|
|
332
882
|
const due = result.docs.filter(
|
|
333
|
-
(doc) => Number(doc.attempts ?? 0) < maxAttempts && (!doc.nextAttemptAt || new Date(doc.nextAttemptAt).getTime() <=
|
|
883
|
+
(doc) => Number(doc.attempts ?? 0) < maxAttempts && (!doc.nextAttemptAt || new Date(doc.nextAttemptAt).getTime() <= now2)
|
|
334
884
|
);
|
|
335
885
|
for (const event of due) await dispatchLifecycleEvent(config, event);
|
|
336
886
|
return due.length;
|
|
@@ -405,12 +955,16 @@ async function transitionWorkflow(args) {
|
|
|
405
955
|
const meta = original.__workflow;
|
|
406
956
|
if (!meta) throw Object.assign(new Error("Entry has no workflow metadata"), { statusCode: 409 });
|
|
407
957
|
if (expectedRevision !== void 0 && expectedRevision !== meta.revision) {
|
|
408
|
-
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
958
|
+
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
959
|
+
statusCode: 409
|
|
960
|
+
});
|
|
409
961
|
}
|
|
410
962
|
const transition = workflow.transitions.find((item) => item.name === transitionName);
|
|
411
963
|
const fromStates = transition ? Array.isArray(transition.from) ? transition.from : [transition.from] : [];
|
|
412
964
|
if (!transition || !fromStates.includes(meta.state)) {
|
|
413
|
-
throw Object.assign(new Error(`Transition "${transitionName}" is not valid from "${meta.state}".`), {
|
|
965
|
+
throw Object.assign(new Error(`Transition "${transitionName}" is not valid from "${meta.state}".`), {
|
|
966
|
+
statusCode: 409
|
|
967
|
+
});
|
|
414
968
|
}
|
|
415
969
|
if (!availableWorkflowTransitions(workflow, meta.state, user).some((item) => item.name === transition.name)) {
|
|
416
970
|
throw Object.assign(new Error(`You do not have permission to perform "${transition.label}".`), { statusCode: 403 });
|
|
@@ -420,32 +974,52 @@ async function transitionWorkflow(args) {
|
|
|
420
974
|
}
|
|
421
975
|
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
|
|
422
976
|
for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
|
|
423
|
-
const events = [
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
977
|
+
const events = [
|
|
978
|
+
createLifecycleEvent({
|
|
979
|
+
name: "workflow.transitioned",
|
|
980
|
+
collection: collection.slug,
|
|
981
|
+
documentId: id,
|
|
982
|
+
actorId: user?.sub,
|
|
983
|
+
payload: { transition: transition.name, from: meta.state, to: transition.to, revision: meta.revision, comment }
|
|
984
|
+
})
|
|
985
|
+
];
|
|
430
986
|
const targetState = workflow.states.find((state) => state.name === transition.to);
|
|
431
987
|
if (targetState.published) {
|
|
432
|
-
events.push(
|
|
988
|
+
events.push(
|
|
989
|
+
createLifecycleEvent({
|
|
990
|
+
name: "entry.published",
|
|
991
|
+
collection: collection.slug,
|
|
992
|
+
documentId: id,
|
|
993
|
+
actorId: user?.sub,
|
|
994
|
+
payload: { revision: meta.revision }
|
|
995
|
+
})
|
|
996
|
+
);
|
|
433
997
|
} else if (transition.unpublish) {
|
|
434
|
-
events.push(
|
|
998
|
+
events.push(
|
|
999
|
+
createLifecycleEvent({
|
|
1000
|
+
name: "entry.unpublished",
|
|
1001
|
+
collection: collection.slug,
|
|
1002
|
+
documentId: id,
|
|
1003
|
+
actorId: user?.sub,
|
|
1004
|
+
payload: { revision: meta.publishedRevision }
|
|
1005
|
+
})
|
|
1006
|
+
);
|
|
435
1007
|
}
|
|
436
1008
|
const updated = await db.transaction(async (tx) => {
|
|
437
1009
|
const locked = await tx.findOne({ collection: collection.slug, id });
|
|
438
1010
|
if (!locked) throw Object.assign(new Error("Not Found"), { statusCode: 404 });
|
|
439
1011
|
const lockedMeta = locked.__workflow;
|
|
440
1012
|
if (lockedMeta.revision !== meta.revision || lockedMeta.state !== meta.state || expectedRevision !== void 0 && lockedMeta.revision !== expectedRevision) {
|
|
441
|
-
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
1013
|
+
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
1014
|
+
statusCode: 409
|
|
1015
|
+
});
|
|
442
1016
|
}
|
|
443
|
-
const
|
|
1017
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
444
1018
|
const { __published: _published, __workflow: _workflow, id: _id, ...working } = locked;
|
|
445
1019
|
const nextMeta = {
|
|
446
1020
|
...lockedMeta,
|
|
447
1021
|
state: transition.to,
|
|
448
|
-
...targetState.published ? { publishedRevision: lockedMeta.revision, publishedAt:
|
|
1022
|
+
...targetState.published ? { publishedRevision: lockedMeta.revision, publishedAt: now2, publishedBy: user?.sub } : {},
|
|
449
1023
|
...transition.unpublish ? { publishedRevision: void 0, publishedAt: void 0, publishedBy: void 0 } : {}
|
|
450
1024
|
};
|
|
451
1025
|
const data = { __workflow: nextMeta };
|
|
@@ -454,11 +1028,36 @@ async function transitionWorkflow(args) {
|
|
|
454
1028
|
const next = await tx.update({ collection: collection.slug, id, data });
|
|
455
1029
|
await tx.create({
|
|
456
1030
|
collection: WORKFLOW_HISTORY_COLLECTION,
|
|
457
|
-
data: {
|
|
1031
|
+
data: {
|
|
1032
|
+
collection: collection.slug,
|
|
1033
|
+
documentId: id,
|
|
1034
|
+
transition: transition.name,
|
|
1035
|
+
from: lockedMeta.state,
|
|
1036
|
+
to: transition.to,
|
|
1037
|
+
revision: lockedMeta.revision,
|
|
1038
|
+
comment: comment ?? null,
|
|
1039
|
+
actorId: user?.sub ?? null,
|
|
1040
|
+
createdAt: now2
|
|
1041
|
+
}
|
|
458
1042
|
});
|
|
459
1043
|
for (const event of events) await persistEvent(tx, event);
|
|
460
1044
|
if (collection.audit) {
|
|
461
|
-
await tx.create({
|
|
1045
|
+
await tx.create({
|
|
1046
|
+
collection: "__audit",
|
|
1047
|
+
data: {
|
|
1048
|
+
collection: collection.slug,
|
|
1049
|
+
documentId: id,
|
|
1050
|
+
operation: "workflow.transition",
|
|
1051
|
+
user: user?.sub ?? null,
|
|
1052
|
+
timestamp: now2,
|
|
1053
|
+
changes: JSON.stringify({
|
|
1054
|
+
transition: transition.name,
|
|
1055
|
+
from: lockedMeta.state,
|
|
1056
|
+
to: transition.to,
|
|
1057
|
+
revision: lockedMeta.revision
|
|
1058
|
+
})
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
462
1061
|
}
|
|
463
1062
|
return next;
|
|
464
1063
|
});
|
|
@@ -467,17 +1066,22 @@ async function transitionWorkflow(args) {
|
|
|
467
1066
|
try {
|
|
468
1067
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
469
1068
|
} catch (error) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1069
|
+
try {
|
|
1070
|
+
const { getConfigLogger: getConfigLogger2, getObservabilityRuntime: getObservabilityRuntime2 } = await Promise.resolve().then(() => (init_observability(), observability_exports));
|
|
1071
|
+
getObservabilityRuntime2(config)?.recordWorkflowHookFailure({
|
|
1072
|
+
collection: collection.slug,
|
|
1073
|
+
transition: transition.name
|
|
1074
|
+
});
|
|
1075
|
+
getConfigLogger2(config, "workflow").error({
|
|
1076
|
+
err: error,
|
|
1077
|
+
msg: "afterTransition hook failed",
|
|
1078
|
+
collection: collection.slug,
|
|
1079
|
+
transition: transition.name,
|
|
1080
|
+
documentId: id
|
|
1081
|
+
});
|
|
1082
|
+
} catch {
|
|
1083
|
+
console.error("afterTransition hook failed:", error);
|
|
1084
|
+
}
|
|
481
1085
|
}
|
|
482
1086
|
}
|
|
483
1087
|
return updated;
|
|
@@ -611,25 +1215,290 @@ function normalizeSchemaFragment(fragment) {
|
|
|
611
1215
|
}
|
|
612
1216
|
|
|
613
1217
|
// src/utils/declarative-hooks.ts
|
|
1218
|
+
var import_jexl2 = __toESM(require("jexl"), 1);
|
|
1219
|
+
|
|
1220
|
+
// src/utils/jexl-helpers.ts
|
|
614
1221
|
var import_jexl = __toESM(require("jexl"), 1);
|
|
1222
|
+
function slugify(val) {
|
|
1223
|
+
if (val === void 0 || val === null) return "";
|
|
1224
|
+
return String(val).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1225
|
+
}
|
|
1226
|
+
function lower(val) {
|
|
1227
|
+
if (val === void 0 || val === null) return "";
|
|
1228
|
+
return String(val).toLowerCase();
|
|
1229
|
+
}
|
|
1230
|
+
function upper(val) {
|
|
1231
|
+
if (val === void 0 || val === null) return "";
|
|
1232
|
+
return String(val).toUpperCase();
|
|
1233
|
+
}
|
|
1234
|
+
function trim(val) {
|
|
1235
|
+
if (val === void 0 || val === null) return "";
|
|
1236
|
+
return String(val).trim();
|
|
1237
|
+
}
|
|
1238
|
+
function capitalize(val) {
|
|
1239
|
+
if (val === void 0 || val === null) return "";
|
|
1240
|
+
const str = String(val).trim();
|
|
1241
|
+
if (!str) return "";
|
|
1242
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
1243
|
+
}
|
|
1244
|
+
function truncate(val, length2, ellipsis = "...") {
|
|
1245
|
+
if (val === void 0 || val === null) return "";
|
|
1246
|
+
const str = String(val);
|
|
1247
|
+
const maxLen = typeof length2 === "number" && length2 > 0 ? length2 : 100;
|
|
1248
|
+
if (str.length <= maxLen) return str;
|
|
1249
|
+
return str.slice(0, maxLen).trim() + String(ellipsis ?? "...");
|
|
1250
|
+
}
|
|
1251
|
+
function readingTime(val, wpm = 200) {
|
|
1252
|
+
if (val === void 0 || val === null) return 1;
|
|
1253
|
+
const str = String(val).trim();
|
|
1254
|
+
if (!str) return 1;
|
|
1255
|
+
const words = str.split(/\s+/).filter(Boolean).length;
|
|
1256
|
+
return Math.max(1, Math.ceil(words / (typeof wpm === "number" && wpm > 0 ? wpm : 200)));
|
|
1257
|
+
}
|
|
1258
|
+
function wordCount(val) {
|
|
1259
|
+
if (val === void 0 || val === null) return 0;
|
|
1260
|
+
const str = String(val).trim();
|
|
1261
|
+
if (!str) return 0;
|
|
1262
|
+
return str.split(/\s+/).filter(Boolean).length;
|
|
1263
|
+
}
|
|
1264
|
+
function replace(val, search, replacement) {
|
|
1265
|
+
if (val === void 0 || val === null) return "";
|
|
1266
|
+
return String(val).replace(new RegExp(String(search || ""), "g"), String(replacement ?? ""));
|
|
1267
|
+
}
|
|
1268
|
+
function startsWith(str, prefix) {
|
|
1269
|
+
if (str === void 0 || str === null) return false;
|
|
1270
|
+
return String(str).startsWith(String(prefix ?? ""));
|
|
1271
|
+
}
|
|
1272
|
+
function endsWith(str, suffix) {
|
|
1273
|
+
if (str === void 0 || str === null) return false;
|
|
1274
|
+
return String(str).endsWith(String(suffix ?? ""));
|
|
1275
|
+
}
|
|
1276
|
+
function now() {
|
|
1277
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1278
|
+
}
|
|
1279
|
+
function today() {
|
|
1280
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1281
|
+
}
|
|
1282
|
+
function formatDate(val, style = "short") {
|
|
1283
|
+
if (!val) return "";
|
|
1284
|
+
const d = new Date(val);
|
|
1285
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
1286
|
+
const styleStr = String(style).toLowerCase();
|
|
1287
|
+
if (styleStr === "iso" || styleStr === "date") return d.toISOString().slice(0, 10);
|
|
1288
|
+
if (styleStr === "datetime" || styleStr === "full") return d.toISOString();
|
|
1289
|
+
return d.toLocaleDateString("en-US", {
|
|
1290
|
+
year: "numeric",
|
|
1291
|
+
month: "short",
|
|
1292
|
+
day: "numeric"
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
function addDays(val, days) {
|
|
1296
|
+
const d = val ? new Date(val) : /* @__PURE__ */ new Date();
|
|
1297
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
1298
|
+
const daysNum = typeof days === "number" ? days : Number(days) || 0;
|
|
1299
|
+
d.setDate(d.getDate() + daysNum);
|
|
1300
|
+
return d.toISOString();
|
|
1301
|
+
}
|
|
1302
|
+
function diffDays(dateA, dateB) {
|
|
1303
|
+
const dA = dateA ? new Date(dateA) : /* @__PURE__ */ new Date();
|
|
1304
|
+
const dB = dateB ? new Date(dateB) : /* @__PURE__ */ new Date();
|
|
1305
|
+
if (Number.isNaN(dA.getTime()) || Number.isNaN(dB.getTime())) return 0;
|
|
1306
|
+
const diffMs = Math.abs(dA.getTime() - dB.getTime());
|
|
1307
|
+
return Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
1308
|
+
}
|
|
1309
|
+
function isPast(val) {
|
|
1310
|
+
if (!val) return false;
|
|
1311
|
+
const d = new Date(val);
|
|
1312
|
+
if (Number.isNaN(d.getTime())) return false;
|
|
1313
|
+
return d.getTime() < Date.now();
|
|
1314
|
+
}
|
|
1315
|
+
function isFuture(val) {
|
|
1316
|
+
if (!val) return false;
|
|
1317
|
+
const d = new Date(val);
|
|
1318
|
+
if (Number.isNaN(d.getTime())) return false;
|
|
1319
|
+
return d.getTime() > Date.now();
|
|
1320
|
+
}
|
|
1321
|
+
function includes(arrOrStr, item) {
|
|
1322
|
+
if (Array.isArray(arrOrStr)) {
|
|
1323
|
+
return arrOrStr.includes(item);
|
|
1324
|
+
}
|
|
1325
|
+
if (typeof arrOrStr === "string") {
|
|
1326
|
+
return arrOrStr.includes(String(item ?? ""));
|
|
1327
|
+
}
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
function join(arr, separator = ", ") {
|
|
1331
|
+
if (!Array.isArray(arr)) return "";
|
|
1332
|
+
return arr.filter((item) => item !== void 0 && item !== null).join(String(separator));
|
|
1333
|
+
}
|
|
1334
|
+
function first(arr) {
|
|
1335
|
+
if (Array.isArray(arr)) return arr[0] ?? null;
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
function last(arr) {
|
|
1339
|
+
if (Array.isArray(arr)) return arr[arr.length - 1] ?? null;
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
function compact(arr) {
|
|
1343
|
+
if (!Array.isArray(arr)) return [];
|
|
1344
|
+
return arr.filter((item) => item !== void 0 && item !== null && item !== "");
|
|
1345
|
+
}
|
|
1346
|
+
function unique(arr) {
|
|
1347
|
+
if (!Array.isArray(arr)) return [];
|
|
1348
|
+
return Array.from(new Set(arr));
|
|
1349
|
+
}
|
|
1350
|
+
function length(val) {
|
|
1351
|
+
if (Array.isArray(val) || typeof val === "string") return val.length;
|
|
1352
|
+
if (val && typeof val === "object") return Object.keys(val).length;
|
|
1353
|
+
return 0;
|
|
1354
|
+
}
|
|
1355
|
+
function round(val, decimals = 0) {
|
|
1356
|
+
const num = Number(val);
|
|
1357
|
+
if (Number.isNaN(num)) return 0;
|
|
1358
|
+
const dec = typeof decimals === "number" ? decimals : Number(decimals) || 0;
|
|
1359
|
+
const factor = Math.pow(10, dec);
|
|
1360
|
+
return Math.round(num * factor) / factor;
|
|
1361
|
+
}
|
|
1362
|
+
function clamp(val, minVal, maxVal) {
|
|
1363
|
+
const num = Number(val) || 0;
|
|
1364
|
+
const min = Number(minVal) || 0;
|
|
1365
|
+
const max = Number(maxVal) || 0;
|
|
1366
|
+
return Math.min(Math.max(num, min), max);
|
|
1367
|
+
}
|
|
1368
|
+
function defaultValue(val, fallback) {
|
|
1369
|
+
if (val === void 0 || val === null || val === "") return fallback;
|
|
1370
|
+
return val;
|
|
1371
|
+
}
|
|
1372
|
+
function coalesce(...args) {
|
|
1373
|
+
for (const arg of args) {
|
|
1374
|
+
if (arg !== void 0 && arg !== null && arg !== "") return arg;
|
|
1375
|
+
}
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
function isEmpty(val) {
|
|
1379
|
+
if (val === void 0 || val === null || val === "") return true;
|
|
1380
|
+
if (Array.isArray(val)) return val.length === 0;
|
|
1381
|
+
if (typeof val === "object") return Object.keys(val).length === 0;
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
function get(obj, path, fallback = null) {
|
|
1385
|
+
if (!obj || typeof obj !== "object") return fallback;
|
|
1386
|
+
const pathStr = String(path || "").trim();
|
|
1387
|
+
if (!pathStr) return fallback;
|
|
1388
|
+
const parts = pathStr.split(".");
|
|
1389
|
+
let curr = obj;
|
|
1390
|
+
for (const part of parts) {
|
|
1391
|
+
if (curr === void 0 || curr === null) return fallback;
|
|
1392
|
+
curr = curr[part];
|
|
1393
|
+
}
|
|
1394
|
+
return curr === void 0 || curr === null ? fallback : curr;
|
|
1395
|
+
}
|
|
1396
|
+
function registerJexlHelpers(jexlInstance = import_jexl.default) {
|
|
1397
|
+
const helpers = {
|
|
1398
|
+
// Strings
|
|
1399
|
+
slugify,
|
|
1400
|
+
lower,
|
|
1401
|
+
upper,
|
|
1402
|
+
trim,
|
|
1403
|
+
capitalize,
|
|
1404
|
+
truncate,
|
|
1405
|
+
readingTime,
|
|
1406
|
+
wordCount,
|
|
1407
|
+
replace,
|
|
1408
|
+
startsWith,
|
|
1409
|
+
endsWith,
|
|
1410
|
+
// Dates
|
|
1411
|
+
now,
|
|
1412
|
+
today,
|
|
1413
|
+
formatDate,
|
|
1414
|
+
addDays,
|
|
1415
|
+
diffDays,
|
|
1416
|
+
isPast,
|
|
1417
|
+
isFuture,
|
|
1418
|
+
// Arrays
|
|
1419
|
+
includes,
|
|
1420
|
+
join,
|
|
1421
|
+
first,
|
|
1422
|
+
last,
|
|
1423
|
+
compact,
|
|
1424
|
+
unique,
|
|
1425
|
+
length,
|
|
1426
|
+
// Math
|
|
1427
|
+
round,
|
|
1428
|
+
clamp,
|
|
1429
|
+
// Objects & Logical
|
|
1430
|
+
default: defaultValue,
|
|
1431
|
+
coalesce,
|
|
1432
|
+
isEmpty,
|
|
1433
|
+
get
|
|
1434
|
+
};
|
|
1435
|
+
for (const [name, fn] of Object.entries(helpers)) {
|
|
1436
|
+
try {
|
|
1437
|
+
jexlInstance.addFunction(name, fn);
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
registerJexlHelpers(import_jexl.default);
|
|
1443
|
+
var BUILTIN_JEXL_HELPERS = [
|
|
1444
|
+
// Strings
|
|
1445
|
+
"slugify",
|
|
1446
|
+
"lower",
|
|
1447
|
+
"upper",
|
|
1448
|
+
"trim",
|
|
1449
|
+
"capitalize",
|
|
1450
|
+
"truncate",
|
|
1451
|
+
"readingTime",
|
|
1452
|
+
"wordCount",
|
|
1453
|
+
"replace",
|
|
1454
|
+
"startsWith",
|
|
1455
|
+
"endsWith",
|
|
1456
|
+
// Dates
|
|
1457
|
+
"now",
|
|
1458
|
+
"today",
|
|
1459
|
+
"formatDate",
|
|
1460
|
+
"addDays",
|
|
1461
|
+
"diffDays",
|
|
1462
|
+
"isPast",
|
|
1463
|
+
"isFuture",
|
|
1464
|
+
// Arrays
|
|
1465
|
+
"includes",
|
|
1466
|
+
"join",
|
|
1467
|
+
"first",
|
|
1468
|
+
"last",
|
|
1469
|
+
"compact",
|
|
1470
|
+
"unique",
|
|
1471
|
+
"length",
|
|
1472
|
+
// Math
|
|
1473
|
+
"round",
|
|
1474
|
+
"clamp",
|
|
1475
|
+
// Objects & Logical
|
|
1476
|
+
"default",
|
|
1477
|
+
"coalesce",
|
|
1478
|
+
"isEmpty",
|
|
1479
|
+
"get"
|
|
1480
|
+
];
|
|
1481
|
+
|
|
1482
|
+
// src/utils/declarative-hooks.ts
|
|
1483
|
+
registerJexlHelpers(import_jexl2.default);
|
|
615
1484
|
var SURFACE_ROOTS = {
|
|
616
|
-
"collection.beforeRead": ["req", "user", "query"],
|
|
617
|
-
"collection.afterRead": ["req", "user", "doc"],
|
|
618
|
-
"collection.beforeChange": ["req", "user", "data", "doc", "operation"],
|
|
619
|
-
"global.beforeRead": ["req", "user", "query"],
|
|
620
|
-
"global.afterRead": ["req", "user", "doc"],
|
|
621
|
-
"global.beforeChange": ["req", "user", "data", "doc", "operation"],
|
|
622
|
-
"field.beforeChange": ["value", "data", "originalDoc", "user"],
|
|
623
|
-
"field.admin.onChange": ["value", "siblingData", "data"]
|
|
1485
|
+
"collection.beforeRead": ["req", "user", "query", ...BUILTIN_JEXL_HELPERS],
|
|
1486
|
+
"collection.afterRead": ["req", "user", "doc", ...BUILTIN_JEXL_HELPERS],
|
|
1487
|
+
"collection.beforeChange": ["req", "user", "data", "doc", "operation", ...BUILTIN_JEXL_HELPERS],
|
|
1488
|
+
"global.beforeRead": ["req", "user", "query", ...BUILTIN_JEXL_HELPERS],
|
|
1489
|
+
"global.afterRead": ["req", "user", "doc", ...BUILTIN_JEXL_HELPERS],
|
|
1490
|
+
"global.beforeChange": ["req", "user", "data", "doc", "operation", ...BUILTIN_JEXL_HELPERS],
|
|
1491
|
+
"field.beforeChange": ["value", "data", "originalDoc", "user", ...BUILTIN_JEXL_HELPERS],
|
|
1492
|
+
"field.admin.onChange": ["value", "siblingData", "data", ...BUILTIN_JEXL_HELPERS]
|
|
624
1493
|
};
|
|
625
|
-
var ACCESS_ROOTS = ["user", "req", "doc", "data", "id", "config"];
|
|
626
|
-
var ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id"];
|
|
627
|
-
var PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id"];
|
|
1494
|
+
var ACCESS_ROOTS = ["user", "req", "doc", "data", "id", "config", ...BUILTIN_JEXL_HELPERS];
|
|
1495
|
+
var ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id", ...BUILTIN_JEXL_HELPERS];
|
|
1496
|
+
var PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id", ...BUILTIN_JEXL_HELPERS];
|
|
628
1497
|
var COMPILE_CACHE = /* @__PURE__ */ new Map();
|
|
629
1498
|
function compileExpression(expression) {
|
|
630
1499
|
const cached = COMPILE_CACHE.get(expression);
|
|
631
1500
|
if (cached) return cached;
|
|
632
|
-
const compiled =
|
|
1501
|
+
const compiled = import_jexl2.default.compile(expression);
|
|
633
1502
|
const next = { ast: compiled._ast };
|
|
634
1503
|
COMPILE_CACHE.set(expression, next);
|
|
635
1504
|
return next;
|
|
@@ -1152,13 +2021,16 @@ function collectConfigDiagnostics(config) {
|
|
|
1152
2021
|
function formatConfigDiagnostics(source, diagnostics, options = {}) {
|
|
1153
2022
|
const color = options.color ?? false;
|
|
1154
2023
|
const header = `${colorize("Config validation failed", 31, color)} in ${colorize(source, 36, color)}`;
|
|
2024
|
+
const docLink = `${colorize(" Learn how to write declarative rules:", 90, color)} ${colorize("https://docs.dyrected.com/docs/features/admin/form-and-field-hooks", 34, color)}`;
|
|
1155
2025
|
const lines = diagnostics.flatMap((issue, index) => [
|
|
1156
2026
|
`${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
|
|
1157
2027
|
` ${issue.message}`,
|
|
1158
2028
|
` ${colorize("surface:", 90, color)} ${issue.surface}`
|
|
1159
2029
|
]);
|
|
1160
2030
|
return `${header}
|
|
1161
|
-
${lines.join("\n")}
|
|
2031
|
+
${lines.join("\n")}
|
|
2032
|
+
|
|
2033
|
+
${docLink}`;
|
|
1162
2034
|
}
|
|
1163
2035
|
var ConfigValidationError = class extends Error {
|
|
1164
2036
|
diagnostics;
|
|
@@ -1728,7 +2600,7 @@ function buildSqlOrderBy(sort, existingColumns, numericFields, dialect) {
|
|
|
1728
2600
|
}
|
|
1729
2601
|
|
|
1730
2602
|
// src/utils/hooks.ts
|
|
1731
|
-
var
|
|
2603
|
+
var import_jexl3 = __toESM(require("jexl"), 1);
|
|
1732
2604
|
async function runCollectionHooks(hooks, args, options = {}) {
|
|
1733
2605
|
if (!hooks || hooks.length === 0) {
|
|
1734
2606
|
return args.data ?? args.doc ?? args.query ?? void 0;
|
|
@@ -1749,7 +2621,7 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
1749
2621
|
options.path ?? options.surface
|
|
1750
2622
|
);
|
|
1751
2623
|
}
|
|
1752
|
-
return
|
|
2624
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1753
2625
|
})() : await hook(hookArgs);
|
|
1754
2626
|
if (result !== void 0) {
|
|
1755
2627
|
if (typeof hook === "string" && options.surface) {
|
|
@@ -1766,10 +2638,7 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
1766
2638
|
}
|
|
1767
2639
|
} catch (err) {
|
|
1768
2640
|
if (options.isolated) {
|
|
1769
|
-
|
|
1770
|
-
err,
|
|
1771
|
-
msg: "Side-effect hook failed after a successful write"
|
|
1772
|
-
});
|
|
2641
|
+
console.error("Side-effect hook failed after a successful write", err);
|
|
1773
2642
|
} else {
|
|
1774
2643
|
throw err;
|
|
1775
2644
|
}
|
|
@@ -1801,7 +2670,7 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db, pat
|
|
|
1801
2670
|
"field.beforeChange",
|
|
1802
2671
|
fieldPath
|
|
1803
2672
|
);
|
|
1804
|
-
return
|
|
2673
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1805
2674
|
})() : await hook(hookArgs);
|
|
1806
2675
|
}
|
|
1807
2676
|
result[field.name] = updatedValue;
|
|
@@ -2943,10 +3812,12 @@ function defineTab(args) {
|
|
|
2943
3812
|
}
|
|
2944
3813
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2945
3814
|
0 && (module.exports = {
|
|
3815
|
+
BUILTIN_JEXL_HELPERS,
|
|
2946
3816
|
ConfigValidationError,
|
|
2947
3817
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
2948
3818
|
LIFECYCLE_EVENT_NAMES,
|
|
2949
3819
|
WORKFLOW_HISTORY_COLLECTION,
|
|
3820
|
+
addDays,
|
|
2950
3821
|
assertValidAdminConditionsInConfig,
|
|
2951
3822
|
assertValidDeclarativeAccessInConfig,
|
|
2952
3823
|
assertValidDeclarativeHookExpression,
|
|
@@ -2956,13 +3827,18 @@ function defineTab(args) {
|
|
|
2956
3827
|
availableWorkflowTransitions,
|
|
2957
3828
|
buildSqlOrderBy,
|
|
2958
3829
|
canViewWorkflowDraft,
|
|
3830
|
+
capitalize,
|
|
3831
|
+
clamp,
|
|
3832
|
+
coalesce,
|
|
2959
3833
|
collectAdminConditionValidationIssues,
|
|
2960
3834
|
collectConfigDiagnostics,
|
|
2961
3835
|
collectDeclarativeAccessValidationIssues,
|
|
2962
3836
|
collectDeclarativeHookValidationIssues,
|
|
2963
3837
|
collectPreviewUrlValidationIssues,
|
|
3838
|
+
compact,
|
|
2964
3839
|
createLifecycleEvent,
|
|
2965
3840
|
createWorkflowDocument,
|
|
3841
|
+
defaultValue,
|
|
2966
3842
|
defineArrayField,
|
|
2967
3843
|
defineBlock,
|
|
2968
3844
|
defineBlocksField,
|
|
@@ -2992,32 +3868,58 @@ function defineTab(args) {
|
|
|
2992
3868
|
defineTextareaField,
|
|
2993
3869
|
defineTimeField,
|
|
2994
3870
|
defineUrlField,
|
|
3871
|
+
diffDays,
|
|
2995
3872
|
dispatchLifecycleEvent,
|
|
2996
3873
|
dispatchPendingLifecycleEvents,
|
|
3874
|
+
endsWith,
|
|
2997
3875
|
executeFieldAfterRead,
|
|
2998
3876
|
executeFieldBeforeChange,
|
|
3877
|
+
first,
|
|
2999
3878
|
formatConfigDiagnostics,
|
|
3879
|
+
formatDate,
|
|
3000
3880
|
generateOpenApi,
|
|
3881
|
+
get,
|
|
3001
3882
|
getAdminAuthCollection,
|
|
3002
3883
|
getPublicAdminAuthConfig,
|
|
3884
|
+
includes,
|
|
3003
3885
|
initializeWorkflowDocument,
|
|
3004
3886
|
isConfigValidationError,
|
|
3887
|
+
isEmpty,
|
|
3888
|
+
isFuture,
|
|
3889
|
+
isPast,
|
|
3890
|
+
join,
|
|
3891
|
+
last,
|
|
3892
|
+
length,
|
|
3893
|
+
lower,
|
|
3005
3894
|
materializeWorkflowDocument,
|
|
3006
3895
|
normalizeConfig,
|
|
3896
|
+
now,
|
|
3007
3897
|
numericFieldNames,
|
|
3008
3898
|
parseMongoWhere,
|
|
3009
3899
|
parseSort,
|
|
3010
3900
|
parseSqlWhere,
|
|
3011
3901
|
publishedStateName,
|
|
3012
3902
|
publishingWorkflow,
|
|
3903
|
+
readingTime,
|
|
3904
|
+
registerJexlHelpers,
|
|
3905
|
+
replace,
|
|
3013
3906
|
resolveAdminAuthCollection,
|
|
3907
|
+
round,
|
|
3014
3908
|
runCollectionHooks,
|
|
3015
3909
|
saveWorkflowDraft,
|
|
3016
3910
|
simplePublishingWorkflow,
|
|
3911
|
+
slugify,
|
|
3912
|
+
startsWith,
|
|
3913
|
+
today,
|
|
3017
3914
|
transitionWorkflow,
|
|
3915
|
+
trim,
|
|
3916
|
+
truncate,
|
|
3917
|
+
unique,
|
|
3918
|
+
upper,
|
|
3018
3919
|
validateAdminConditionExpression,
|
|
3019
3920
|
validateDeclarativeAccessExpression,
|
|
3020
3921
|
validateDeclarativeHookExpression,
|
|
3021
3922
|
validatePreviewUrlExpression,
|
|
3923
|
+
wordCount,
|
|
3022
3924
|
workflowCapabilities
|
|
3023
3925
|
});
|