@dyrected/core 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-config-7G-3tF_I.d.cts +2259 -0
- package/dist/app-config-7G-3tF_I.d.ts +2259 -0
- package/dist/app-config-B3ByBjCN.d.cts +2222 -0
- package/dist/app-config-B3ByBjCN.d.ts +2222 -0
- package/dist/app-config-BIkjQWzU.d.cts +2218 -0
- package/dist/app-config-BIkjQWzU.d.ts +2218 -0
- package/dist/app-config-BfpeE3bF.d.cts +2100 -0
- package/dist/app-config-BfpeE3bF.d.ts +2100 -0
- package/dist/app-config-CCe-yq9q.d.cts +2222 -0
- package/dist/app-config-CCe-yq9q.d.ts +2222 -0
- package/dist/app-config-CPOs7THF.d.cts +2217 -0
- package/dist/app-config-CPOs7THF.d.ts +2217 -0
- package/dist/app-config-Ctp454zB.d.cts +2214 -0
- package/dist/app-config-Ctp454zB.d.ts +2214 -0
- package/dist/app-config-DJmNYgYh.d.cts +2305 -0
- package/dist/app-config-DJmNYgYh.d.ts +2305 -0
- package/dist/app-config-DO3laV58.d.cts +2397 -0
- package/dist/app-config-DO3laV58.d.ts +2397 -0
- package/dist/app-config-DZf_mmi9.d.cts +2211 -0
- package/dist/app-config-DZf_mmi9.d.ts +2211 -0
- package/dist/app-config-SkUoN5JU.d.cts +2192 -0
- package/dist/app-config-SkUoN5JU.d.ts +2192 -0
- package/dist/app-config-oz8H_MOX.d.cts +2224 -0
- package/dist/app-config-oz8H_MOX.d.ts +2224 -0
- package/dist/chunk-2UXLA4YN.js +3139 -0
- package/dist/chunk-3TRRTN6U.js +3441 -0
- package/dist/chunk-5U2WRCNH.js +3140 -0
- package/dist/chunk-5ZIA5COH.js +3414 -0
- package/dist/chunk-6TWQ6GTD.js +3480 -0
- package/dist/chunk-AKYYGMFS.js +3365 -0
- package/dist/chunk-AQO4HI2Y.js +3204 -0
- package/dist/chunk-DFBLNYUH.js +3402 -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-KEKOEAX6.js +3139 -0
- package/dist/chunk-LFGSRQLX.js +3568 -0
- package/dist/chunk-NNZRYF3U.js +3537 -0
- package/dist/chunk-PRQ2RF5C.js +3485 -0
- package/dist/chunk-PYE2JSY4.js +3568 -0
- package/dist/chunk-PZMDK3MH.js +3203 -0
- package/dist/chunk-QCXLDKZO.js +3272 -0
- package/dist/chunk-SM32ND5M.js +3472 -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-XX7N64NL.js +3550 -0
- package/dist/chunk-YSSHLGQ6.js +3139 -0
- package/dist/chunk-Z7CFUYVP.js +3653 -0
- package/dist/index.cjs +1421 -82
- package/dist/index.d.cts +362 -3
- package/dist/index.d.ts +362 -3
- package/dist/index.js +119 -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 +1879 -1247
- package/dist/server.d.cts +52 -8
- package/dist/server.d.ts +52 -8
- package/dist/server.js +449 -387
- 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,527 @@ 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
|
+
abs: () => abs,
|
|
553
|
+
addDays: () => addDays,
|
|
37
554
|
assertValidAdminConditionsInConfig: () => assertValidAdminConditionsInConfig,
|
|
38
555
|
assertValidDeclarativeAccessInConfig: () => assertValidDeclarativeAccessInConfig,
|
|
39
556
|
assertValidDeclarativeHookExpression: () => assertValidDeclarativeHookExpression,
|
|
@@ -43,13 +560,20 @@ __export(index_exports, {
|
|
|
43
560
|
availableWorkflowTransitions: () => availableWorkflowTransitions,
|
|
44
561
|
buildSqlOrderBy: () => buildSqlOrderBy,
|
|
45
562
|
canViewWorkflowDraft: () => canViewWorkflowDraft,
|
|
563
|
+
capitalize: () => capitalize,
|
|
564
|
+
ceil: () => ceil,
|
|
565
|
+
clamp: () => clamp,
|
|
566
|
+
coalesce: () => coalesce,
|
|
46
567
|
collectAdminConditionValidationIssues: () => collectAdminConditionValidationIssues,
|
|
47
568
|
collectConfigDiagnostics: () => collectConfigDiagnostics,
|
|
48
569
|
collectDeclarativeAccessValidationIssues: () => collectDeclarativeAccessValidationIssues,
|
|
49
570
|
collectDeclarativeHookValidationIssues: () => collectDeclarativeHookValidationIssues,
|
|
50
571
|
collectPreviewUrlValidationIssues: () => collectPreviewUrlValidationIssues,
|
|
572
|
+
compact: () => compact,
|
|
573
|
+
count: () => count,
|
|
51
574
|
createLifecycleEvent: () => createLifecycleEvent,
|
|
52
575
|
createWorkflowDocument: () => createWorkflowDocument,
|
|
576
|
+
defaultValue: () => defaultValue,
|
|
53
577
|
defineArrayField: () => defineArrayField,
|
|
54
578
|
defineBlock: () => defineBlock,
|
|
55
579
|
defineBlocksField: () => defineBlocksField,
|
|
@@ -79,33 +603,82 @@ __export(index_exports, {
|
|
|
79
603
|
defineTextareaField: () => defineTextareaField,
|
|
80
604
|
defineTimeField: () => defineTimeField,
|
|
81
605
|
defineUrlField: () => defineUrlField,
|
|
606
|
+
diffDays: () => diffDays,
|
|
82
607
|
dispatchLifecycleEvent: () => dispatchLifecycleEvent,
|
|
83
608
|
dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
|
|
609
|
+
displayComputed: () => displayComputed,
|
|
610
|
+
displayCustom: () => displayCustom,
|
|
611
|
+
displayCustomComponent: () => displayCustomComponent,
|
|
612
|
+
displayDivider: () => displayDivider,
|
|
613
|
+
displayField: () => displayField,
|
|
614
|
+
displayGrid: () => displayGrid,
|
|
615
|
+
displayRepeat: () => displayRepeat,
|
|
616
|
+
displaySection: () => displaySection,
|
|
617
|
+
displayTab: () => displayTab,
|
|
618
|
+
displayTabs: () => displayTabs,
|
|
619
|
+
displayText: () => displayText,
|
|
620
|
+
endsWith: () => endsWith,
|
|
621
|
+
evaluateDetailComputed: () => evaluateDetailComputed,
|
|
622
|
+
evaluateJexl: () => evaluateJexl,
|
|
623
|
+
evaluateJexlSync: () => evaluateJexlSync,
|
|
84
624
|
executeFieldAfterRead: () => executeFieldAfterRead,
|
|
85
625
|
executeFieldBeforeChange: () => executeFieldBeforeChange,
|
|
626
|
+
first: () => first,
|
|
627
|
+
floor: () => floor,
|
|
86
628
|
formatConfigDiagnostics: () => formatConfigDiagnostics,
|
|
629
|
+
formatDate: () => formatDate,
|
|
630
|
+
generateDefaultDetailSchema: () => generateDefaultDetailSchema,
|
|
87
631
|
generateOpenApi: () => generateOpenApi,
|
|
632
|
+
get: () => get,
|
|
88
633
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
89
634
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
635
|
+
includes: () => includes,
|
|
90
636
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
91
637
|
isConfigValidationError: () => isConfigValidationError,
|
|
638
|
+
isDetailItemVisible: () => isDetailItemVisible,
|
|
639
|
+
isEmpty: () => isEmpty,
|
|
640
|
+
isFuture: () => isFuture,
|
|
641
|
+
isPast: () => isPast,
|
|
642
|
+
join: () => join,
|
|
643
|
+
last: () => last,
|
|
644
|
+
length: () => length,
|
|
645
|
+
lower: () => lower,
|
|
92
646
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
647
|
+
max: () => max,
|
|
648
|
+
min: () => min,
|
|
93
649
|
normalizeConfig: () => normalizeConfig,
|
|
650
|
+
normalizeDetailItem: () => normalizeDetailItem,
|
|
651
|
+
now: () => now,
|
|
94
652
|
numericFieldNames: () => numericFieldNames,
|
|
95
653
|
parseMongoWhere: () => parseMongoWhere,
|
|
96
654
|
parseSort: () => parseSort,
|
|
97
655
|
parseSqlWhere: () => parseSqlWhere,
|
|
656
|
+
pow: () => pow,
|
|
98
657
|
publishedStateName: () => publishedStateName,
|
|
99
658
|
publishingWorkflow: () => publishingWorkflow,
|
|
659
|
+
readingTime: () => readingTime,
|
|
660
|
+
registerJexlHelpers: () => registerJexlHelpers,
|
|
661
|
+
replace: () => replace,
|
|
100
662
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
663
|
+
round: () => round,
|
|
101
664
|
runCollectionHooks: () => runCollectionHooks,
|
|
102
665
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
103
666
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
667
|
+
slugify: () => slugify,
|
|
668
|
+
sqrt: () => sqrt,
|
|
669
|
+
startsWith: () => startsWith,
|
|
670
|
+
sum: () => sum,
|
|
671
|
+
today: () => today,
|
|
104
672
|
transitionWorkflow: () => transitionWorkflow,
|
|
673
|
+
trim: () => trim,
|
|
674
|
+
truncate: () => truncate,
|
|
675
|
+
unique: () => unique,
|
|
676
|
+
upper: () => upper,
|
|
105
677
|
validateAdminConditionExpression: () => validateAdminConditionExpression,
|
|
106
678
|
validateDeclarativeAccessExpression: () => validateDeclarativeAccessExpression,
|
|
107
679
|
validateDeclarativeHookExpression: () => validateDeclarativeHookExpression,
|
|
108
680
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
681
|
+
wordCount: () => wordCount,
|
|
109
682
|
workflowCapabilities: () => workflowCapabilities
|
|
110
683
|
});
|
|
111
684
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -120,37 +693,10 @@ var LIFECYCLE_EVENT_NAMES = [
|
|
|
120
693
|
|
|
121
694
|
// src/auth/token.ts
|
|
122
695
|
var import_jose = require("jose");
|
|
123
|
-
var import_node_util = require("util");
|
|
124
696
|
|
|
125
697
|
// src/auth/sessions.ts
|
|
126
698
|
var AUTH_SESSIONS_COLLECTION = "__auth_sessions";
|
|
127
699
|
|
|
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
700
|
// src/workflows.ts
|
|
155
701
|
var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
|
|
156
702
|
var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
|
|
@@ -168,10 +714,36 @@ function definePublishingWorkflow(options = {}) {
|
|
|
168
714
|
{ name: "published", label: "Published", color: "success", published: true }
|
|
169
715
|
],
|
|
170
716
|
transitions: [
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
717
|
+
{
|
|
718
|
+
name: "submit",
|
|
719
|
+
label: "Submit for review",
|
|
720
|
+
from: "draft",
|
|
721
|
+
to: "in_review",
|
|
722
|
+
requiredCapabilities: ["entry.submit"]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "publish",
|
|
726
|
+
label: "Publish",
|
|
727
|
+
from: "in_review",
|
|
728
|
+
to: "published",
|
|
729
|
+
requiredCapabilities: ["entry.publish"]
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
name: "reject",
|
|
733
|
+
label: "Request changes",
|
|
734
|
+
from: "in_review",
|
|
735
|
+
to: "draft",
|
|
736
|
+
requiredCapabilities: ["entry.publish"],
|
|
737
|
+
requireComment: true
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: "unpublish",
|
|
741
|
+
label: "Unpublish",
|
|
742
|
+
from: "published",
|
|
743
|
+
to: "draft",
|
|
744
|
+
requiredCapabilities: ["entry.unpublish"],
|
|
745
|
+
unpublish: true
|
|
746
|
+
}
|
|
175
747
|
],
|
|
176
748
|
roles: [
|
|
177
749
|
...editors.map((role) => ({ role, capabilities: [...EDITOR_CAPABILITIES] })),
|
|
@@ -191,7 +763,7 @@ function simplePublishingWorkflow() {
|
|
|
191
763
|
{ name: "published", label: "Published", color: "success", published: true }
|
|
192
764
|
],
|
|
193
765
|
transitions: [
|
|
194
|
-
{ name: "publish", label: "Publish", from: "draft", to: "published" },
|
|
766
|
+
{ name: "publish", label: "Publish", from: ["draft", "published"], to: "published" },
|
|
195
767
|
{ name: "unpublish", label: "Unpublish", from: "published", to: "draft", unpublish: true }
|
|
196
768
|
]
|
|
197
769
|
// Intentionally no `roles`: this lightweight workflow (synthesized from
|
|
@@ -298,7 +870,11 @@ async function dispatchLifecycleEvent(config, event) {
|
|
|
298
870
|
const retryDelayMs = config.events.retryDelayMs ?? 1e3;
|
|
299
871
|
const attempts = event.attempts + 1;
|
|
300
872
|
try {
|
|
301
|
-
await db.update({
|
|
873
|
+
await db.update({
|
|
874
|
+
collection: LIFECYCLE_EVENTS_COLLECTION,
|
|
875
|
+
id: event.id,
|
|
876
|
+
data: { status: "processing", attempts }
|
|
877
|
+
});
|
|
302
878
|
for (const handler of config.events.handlers) await handler({ ...event, status: "processing", attempts });
|
|
303
879
|
await db.update({
|
|
304
880
|
collection: LIFECYCLE_EVENTS_COLLECTION,
|
|
@@ -328,9 +904,9 @@ async function dispatchPendingLifecycleEvents(config, limit = 50) {
|
|
|
328
904
|
sort: "occurredAt",
|
|
329
905
|
limit
|
|
330
906
|
});
|
|
331
|
-
const
|
|
907
|
+
const now2 = Date.now();
|
|
332
908
|
const due = result.docs.filter(
|
|
333
|
-
(doc) => Number(doc.attempts ?? 0) < maxAttempts && (!doc.nextAttemptAt || new Date(doc.nextAttemptAt).getTime() <=
|
|
909
|
+
(doc) => Number(doc.attempts ?? 0) < maxAttempts && (!doc.nextAttemptAt || new Date(doc.nextAttemptAt).getTime() <= now2)
|
|
334
910
|
);
|
|
335
911
|
for (const event of due) await dispatchLifecycleEvent(config, event);
|
|
336
912
|
return due.length;
|
|
@@ -405,12 +981,16 @@ async function transitionWorkflow(args) {
|
|
|
405
981
|
const meta = original.__workflow;
|
|
406
982
|
if (!meta) throw Object.assign(new Error("Entry has no workflow metadata"), { statusCode: 409 });
|
|
407
983
|
if (expectedRevision !== void 0 && expectedRevision !== meta.revision) {
|
|
408
|
-
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
984
|
+
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
985
|
+
statusCode: 409
|
|
986
|
+
});
|
|
409
987
|
}
|
|
410
988
|
const transition = workflow.transitions.find((item) => item.name === transitionName);
|
|
411
989
|
const fromStates = transition ? Array.isArray(transition.from) ? transition.from : [transition.from] : [];
|
|
412
990
|
if (!transition || !fromStates.includes(meta.state)) {
|
|
413
|
-
throw Object.assign(new Error(`Transition "${transitionName}" is not valid from "${meta.state}".`), {
|
|
991
|
+
throw Object.assign(new Error(`Transition "${transitionName}" is not valid from "${meta.state}".`), {
|
|
992
|
+
statusCode: 409
|
|
993
|
+
});
|
|
414
994
|
}
|
|
415
995
|
if (!availableWorkflowTransitions(workflow, meta.state, user).some((item) => item.name === transition.name)) {
|
|
416
996
|
throw Object.assign(new Error(`You do not have permission to perform "${transition.label}".`), { statusCode: 403 });
|
|
@@ -420,32 +1000,52 @@ async function transitionWorkflow(args) {
|
|
|
420
1000
|
}
|
|
421
1001
|
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
|
|
422
1002
|
for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
|
|
423
|
-
const events = [
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
1003
|
+
const events = [
|
|
1004
|
+
createLifecycleEvent({
|
|
1005
|
+
name: "workflow.transitioned",
|
|
1006
|
+
collection: collection.slug,
|
|
1007
|
+
documentId: id,
|
|
1008
|
+
actorId: user?.sub,
|
|
1009
|
+
payload: { transition: transition.name, from: meta.state, to: transition.to, revision: meta.revision, comment }
|
|
1010
|
+
})
|
|
1011
|
+
];
|
|
430
1012
|
const targetState = workflow.states.find((state) => state.name === transition.to);
|
|
431
1013
|
if (targetState.published) {
|
|
432
|
-
events.push(
|
|
1014
|
+
events.push(
|
|
1015
|
+
createLifecycleEvent({
|
|
1016
|
+
name: "entry.published",
|
|
1017
|
+
collection: collection.slug,
|
|
1018
|
+
documentId: id,
|
|
1019
|
+
actorId: user?.sub,
|
|
1020
|
+
payload: { revision: meta.revision }
|
|
1021
|
+
})
|
|
1022
|
+
);
|
|
433
1023
|
} else if (transition.unpublish) {
|
|
434
|
-
events.push(
|
|
1024
|
+
events.push(
|
|
1025
|
+
createLifecycleEvent({
|
|
1026
|
+
name: "entry.unpublished",
|
|
1027
|
+
collection: collection.slug,
|
|
1028
|
+
documentId: id,
|
|
1029
|
+
actorId: user?.sub,
|
|
1030
|
+
payload: { revision: meta.publishedRevision }
|
|
1031
|
+
})
|
|
1032
|
+
);
|
|
435
1033
|
}
|
|
436
1034
|
const updated = await db.transaction(async (tx) => {
|
|
437
1035
|
const locked = await tx.findOne({ collection: collection.slug, id });
|
|
438
1036
|
if (!locked) throw Object.assign(new Error("Not Found"), { statusCode: 404 });
|
|
439
1037
|
const lockedMeta = locked.__workflow;
|
|
440
1038
|
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."), {
|
|
1039
|
+
throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), {
|
|
1040
|
+
statusCode: 409
|
|
1041
|
+
});
|
|
442
1042
|
}
|
|
443
|
-
const
|
|
1043
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
444
1044
|
const { __published: _published, __workflow: _workflow, id: _id, ...working } = locked;
|
|
445
1045
|
const nextMeta = {
|
|
446
1046
|
...lockedMeta,
|
|
447
1047
|
state: transition.to,
|
|
448
|
-
...targetState.published ? { publishedRevision: lockedMeta.revision, publishedAt:
|
|
1048
|
+
...targetState.published ? { publishedRevision: lockedMeta.revision, publishedAt: now2, publishedBy: user?.sub } : {},
|
|
449
1049
|
...transition.unpublish ? { publishedRevision: void 0, publishedAt: void 0, publishedBy: void 0 } : {}
|
|
450
1050
|
};
|
|
451
1051
|
const data = { __workflow: nextMeta };
|
|
@@ -454,11 +1054,36 @@ async function transitionWorkflow(args) {
|
|
|
454
1054
|
const next = await tx.update({ collection: collection.slug, id, data });
|
|
455
1055
|
await tx.create({
|
|
456
1056
|
collection: WORKFLOW_HISTORY_COLLECTION,
|
|
457
|
-
data: {
|
|
1057
|
+
data: {
|
|
1058
|
+
collection: collection.slug,
|
|
1059
|
+
documentId: id,
|
|
1060
|
+
transition: transition.name,
|
|
1061
|
+
from: lockedMeta.state,
|
|
1062
|
+
to: transition.to,
|
|
1063
|
+
revision: lockedMeta.revision,
|
|
1064
|
+
comment: comment ?? null,
|
|
1065
|
+
actorId: user?.sub ?? null,
|
|
1066
|
+
createdAt: now2
|
|
1067
|
+
}
|
|
458
1068
|
});
|
|
459
1069
|
for (const event of events) await persistEvent(tx, event);
|
|
460
1070
|
if (collection.audit) {
|
|
461
|
-
await tx.create({
|
|
1071
|
+
await tx.create({
|
|
1072
|
+
collection: "__audit",
|
|
1073
|
+
data: {
|
|
1074
|
+
collection: collection.slug,
|
|
1075
|
+
documentId: id,
|
|
1076
|
+
operation: "workflow.transition",
|
|
1077
|
+
user: user?.sub ?? null,
|
|
1078
|
+
timestamp: now2,
|
|
1079
|
+
changes: JSON.stringify({
|
|
1080
|
+
transition: transition.name,
|
|
1081
|
+
from: lockedMeta.state,
|
|
1082
|
+
to: transition.to,
|
|
1083
|
+
revision: lockedMeta.revision
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
462
1087
|
}
|
|
463
1088
|
return next;
|
|
464
1089
|
});
|
|
@@ -467,17 +1092,22 @@ async function transitionWorkflow(args) {
|
|
|
467
1092
|
try {
|
|
468
1093
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
469
1094
|
} catch (error) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1095
|
+
try {
|
|
1096
|
+
const { getConfigLogger: getConfigLogger2, getObservabilityRuntime: getObservabilityRuntime2 } = await Promise.resolve().then(() => (init_observability(), observability_exports));
|
|
1097
|
+
getObservabilityRuntime2(config)?.recordWorkflowHookFailure({
|
|
1098
|
+
collection: collection.slug,
|
|
1099
|
+
transition: transition.name
|
|
1100
|
+
});
|
|
1101
|
+
getConfigLogger2(config, "workflow").error({
|
|
1102
|
+
err: error,
|
|
1103
|
+
msg: "afterTransition hook failed",
|
|
1104
|
+
collection: collection.slug,
|
|
1105
|
+
transition: transition.name,
|
|
1106
|
+
documentId: id
|
|
1107
|
+
});
|
|
1108
|
+
} catch {
|
|
1109
|
+
console.error("afterTransition hook failed:", error);
|
|
1110
|
+
}
|
|
481
1111
|
}
|
|
482
1112
|
}
|
|
483
1113
|
return updated;
|
|
@@ -611,25 +1241,370 @@ function normalizeSchemaFragment(fragment) {
|
|
|
611
1241
|
}
|
|
612
1242
|
|
|
613
1243
|
// src/utils/declarative-hooks.ts
|
|
1244
|
+
var import_jexl2 = __toESM(require("jexl"), 1);
|
|
1245
|
+
|
|
1246
|
+
// src/utils/jexl-helpers.ts
|
|
614
1247
|
var import_jexl = __toESM(require("jexl"), 1);
|
|
1248
|
+
function slugify(val) {
|
|
1249
|
+
if (val === void 0 || val === null) return "";
|
|
1250
|
+
return String(val).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1251
|
+
}
|
|
1252
|
+
function lower(val) {
|
|
1253
|
+
if (val === void 0 || val === null) return "";
|
|
1254
|
+
return String(val).toLowerCase();
|
|
1255
|
+
}
|
|
1256
|
+
function upper(val) {
|
|
1257
|
+
if (val === void 0 || val === null) return "";
|
|
1258
|
+
return String(val).toUpperCase();
|
|
1259
|
+
}
|
|
1260
|
+
function trim(val) {
|
|
1261
|
+
if (val === void 0 || val === null) return "";
|
|
1262
|
+
return String(val).trim();
|
|
1263
|
+
}
|
|
1264
|
+
function capitalize(val) {
|
|
1265
|
+
if (val === void 0 || val === null) return "";
|
|
1266
|
+
const str = String(val).trim();
|
|
1267
|
+
if (!str) return "";
|
|
1268
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
1269
|
+
}
|
|
1270
|
+
function truncate(val, length2, ellipsis = "...") {
|
|
1271
|
+
if (val === void 0 || val === null) return "";
|
|
1272
|
+
const str = String(val);
|
|
1273
|
+
const maxLen = typeof length2 === "number" && length2 > 0 ? length2 : 100;
|
|
1274
|
+
if (str.length <= maxLen) return str;
|
|
1275
|
+
return str.slice(0, maxLen).trim() + String(ellipsis ?? "...");
|
|
1276
|
+
}
|
|
1277
|
+
function readingTime(val, wpm = 200) {
|
|
1278
|
+
if (val === void 0 || val === null) return 1;
|
|
1279
|
+
const str = String(val).trim();
|
|
1280
|
+
if (!str) return 1;
|
|
1281
|
+
const words = str.split(/\s+/).filter(Boolean).length;
|
|
1282
|
+
return Math.max(1, Math.ceil(words / (typeof wpm === "number" && wpm > 0 ? wpm : 200)));
|
|
1283
|
+
}
|
|
1284
|
+
function wordCount(val) {
|
|
1285
|
+
if (val === void 0 || val === null) return 0;
|
|
1286
|
+
const str = String(val).trim();
|
|
1287
|
+
if (!str) return 0;
|
|
1288
|
+
return str.split(/\s+/).filter(Boolean).length;
|
|
1289
|
+
}
|
|
1290
|
+
function replace(val, search, replacement) {
|
|
1291
|
+
if (val === void 0 || val === null) return "";
|
|
1292
|
+
return String(val).replace(new RegExp(String(search || ""), "g"), String(replacement ?? ""));
|
|
1293
|
+
}
|
|
1294
|
+
function startsWith(str, prefix) {
|
|
1295
|
+
if (str === void 0 || str === null) return false;
|
|
1296
|
+
return String(str).startsWith(String(prefix ?? ""));
|
|
1297
|
+
}
|
|
1298
|
+
function endsWith(str, suffix) {
|
|
1299
|
+
if (str === void 0 || str === null) return false;
|
|
1300
|
+
return String(str).endsWith(String(suffix ?? ""));
|
|
1301
|
+
}
|
|
1302
|
+
function now() {
|
|
1303
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1304
|
+
}
|
|
1305
|
+
function today() {
|
|
1306
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1307
|
+
}
|
|
1308
|
+
function formatDate(val, style = "short") {
|
|
1309
|
+
if (!val) return "";
|
|
1310
|
+
const d = new Date(val);
|
|
1311
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
1312
|
+
const styleStr = String(style).toLowerCase();
|
|
1313
|
+
if (styleStr === "iso" || styleStr === "date") return d.toISOString().slice(0, 10);
|
|
1314
|
+
if (styleStr === "datetime" || styleStr === "full") return d.toISOString();
|
|
1315
|
+
return d.toLocaleDateString("en-US", {
|
|
1316
|
+
year: "numeric",
|
|
1317
|
+
month: "short",
|
|
1318
|
+
day: "numeric"
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
function addDays(val, days) {
|
|
1322
|
+
const d = val ? new Date(val) : /* @__PURE__ */ new Date();
|
|
1323
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
1324
|
+
const daysNum = typeof days === "number" ? days : Number(days) || 0;
|
|
1325
|
+
d.setDate(d.getDate() + daysNum);
|
|
1326
|
+
return d.toISOString();
|
|
1327
|
+
}
|
|
1328
|
+
function diffDays(dateA, dateB) {
|
|
1329
|
+
const dA = dateA ? new Date(dateA) : /* @__PURE__ */ new Date();
|
|
1330
|
+
const dB = dateB ? new Date(dateB) : /* @__PURE__ */ new Date();
|
|
1331
|
+
if (Number.isNaN(dA.getTime()) || Number.isNaN(dB.getTime())) return 0;
|
|
1332
|
+
const diffMs = Math.abs(dA.getTime() - dB.getTime());
|
|
1333
|
+
return Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
1334
|
+
}
|
|
1335
|
+
function isPast(val) {
|
|
1336
|
+
if (!val) return false;
|
|
1337
|
+
const d = new Date(val);
|
|
1338
|
+
if (Number.isNaN(d.getTime())) return false;
|
|
1339
|
+
return d.getTime() < Date.now();
|
|
1340
|
+
}
|
|
1341
|
+
function isFuture(val) {
|
|
1342
|
+
if (!val) return false;
|
|
1343
|
+
const d = new Date(val);
|
|
1344
|
+
if (Number.isNaN(d.getTime())) return false;
|
|
1345
|
+
return d.getTime() > Date.now();
|
|
1346
|
+
}
|
|
1347
|
+
function includes(arrOrStr, item) {
|
|
1348
|
+
if (Array.isArray(arrOrStr)) {
|
|
1349
|
+
return arrOrStr.includes(item);
|
|
1350
|
+
}
|
|
1351
|
+
if (typeof arrOrStr === "string") {
|
|
1352
|
+
return arrOrStr.includes(String(item ?? ""));
|
|
1353
|
+
}
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
function join(arr, separator = ", ") {
|
|
1357
|
+
if (!Array.isArray(arr)) return "";
|
|
1358
|
+
return arr.filter((item) => item !== void 0 && item !== null).join(String(separator));
|
|
1359
|
+
}
|
|
1360
|
+
function first(arr) {
|
|
1361
|
+
if (Array.isArray(arr)) return arr[0] ?? null;
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
function last(arr) {
|
|
1365
|
+
if (Array.isArray(arr)) return arr[arr.length - 1] ?? null;
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
function compact(arr) {
|
|
1369
|
+
if (!Array.isArray(arr)) return [];
|
|
1370
|
+
return arr.filter((item) => item !== void 0 && item !== null && item !== "");
|
|
1371
|
+
}
|
|
1372
|
+
function unique(arr) {
|
|
1373
|
+
if (!Array.isArray(arr)) return [];
|
|
1374
|
+
return Array.from(new Set(arr));
|
|
1375
|
+
}
|
|
1376
|
+
function length(val) {
|
|
1377
|
+
if (Array.isArray(val) || typeof val === "string") return val.length;
|
|
1378
|
+
if (val && typeof val === "object") return Object.keys(val).length;
|
|
1379
|
+
return 0;
|
|
1380
|
+
}
|
|
1381
|
+
function round(val, decimals = 0) {
|
|
1382
|
+
const num = Number(val);
|
|
1383
|
+
if (Number.isNaN(num)) return 0;
|
|
1384
|
+
const dec = typeof decimals === "number" ? decimals : Number(decimals) || 0;
|
|
1385
|
+
const factor = Math.pow(10, dec);
|
|
1386
|
+
return Math.round(num * factor) / factor;
|
|
1387
|
+
}
|
|
1388
|
+
function clamp(val, minVal, maxVal) {
|
|
1389
|
+
const num = Number(val) || 0;
|
|
1390
|
+
const min2 = Number(minVal) || 0;
|
|
1391
|
+
const max2 = Number(maxVal) || 0;
|
|
1392
|
+
return Math.min(Math.max(num, min2), max2);
|
|
1393
|
+
}
|
|
1394
|
+
function ceil(val) {
|
|
1395
|
+
return Math.ceil(Number(val) || 0);
|
|
1396
|
+
}
|
|
1397
|
+
function floor(val) {
|
|
1398
|
+
return Math.floor(Number(val) || 0);
|
|
1399
|
+
}
|
|
1400
|
+
function abs(val) {
|
|
1401
|
+
return Math.abs(Number(val) || 0);
|
|
1402
|
+
}
|
|
1403
|
+
function min(...args) {
|
|
1404
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1405
|
+
return nums.length ? Math.min(...nums) : 0;
|
|
1406
|
+
}
|
|
1407
|
+
function max(...args) {
|
|
1408
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1409
|
+
return nums.length ? Math.max(...nums) : 0;
|
|
1410
|
+
}
|
|
1411
|
+
function pow(base, exp) {
|
|
1412
|
+
return Math.pow(Number(base) || 0, Number(exp) || 0);
|
|
1413
|
+
}
|
|
1414
|
+
function sqrt(val) {
|
|
1415
|
+
return Math.sqrt(Number(val) || 0);
|
|
1416
|
+
}
|
|
1417
|
+
function sum(arr) {
|
|
1418
|
+
if (!Array.isArray(arr)) return Number(arr) || 0;
|
|
1419
|
+
return arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
|
|
1420
|
+
}
|
|
1421
|
+
function count(val) {
|
|
1422
|
+
if (Array.isArray(val)) return val.length;
|
|
1423
|
+
if (typeof val === "string") return val.length;
|
|
1424
|
+
if (val !== void 0 && val !== null && val !== false) return 1;
|
|
1425
|
+
return 0;
|
|
1426
|
+
}
|
|
1427
|
+
function defaultValue(val, fallback) {
|
|
1428
|
+
if (val === void 0 || val === null || val === "") return fallback;
|
|
1429
|
+
return val;
|
|
1430
|
+
}
|
|
1431
|
+
function coalesce(...args) {
|
|
1432
|
+
for (const arg of args) {
|
|
1433
|
+
if (arg !== void 0 && arg !== null && arg !== "") return arg;
|
|
1434
|
+
}
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
function isEmpty(val) {
|
|
1438
|
+
if (val === void 0 || val === null || val === "") return true;
|
|
1439
|
+
if (Array.isArray(val)) return val.length === 0;
|
|
1440
|
+
if (typeof val === "object") return Object.keys(val).length === 0;
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
function get(obj, path, fallback = null) {
|
|
1444
|
+
if (obj === void 0 || obj === null || typeof obj !== "object") return fallback;
|
|
1445
|
+
if (typeof path !== "string") return fallback;
|
|
1446
|
+
const parts = path.split(".").filter(Boolean);
|
|
1447
|
+
let curr = obj;
|
|
1448
|
+
for (const part of parts) {
|
|
1449
|
+
if (curr === void 0 || curr === null || typeof curr !== "object") return fallback;
|
|
1450
|
+
curr = curr[part];
|
|
1451
|
+
}
|
|
1452
|
+
return curr === void 0 || curr === null ? fallback : curr;
|
|
1453
|
+
}
|
|
1454
|
+
function registerJexlHelpers(jexlInstance = import_jexl.default) {
|
|
1455
|
+
const helpers = {
|
|
1456
|
+
// Strings
|
|
1457
|
+
slugify,
|
|
1458
|
+
lower,
|
|
1459
|
+
upper,
|
|
1460
|
+
trim,
|
|
1461
|
+
capitalize,
|
|
1462
|
+
truncate,
|
|
1463
|
+
readingTime,
|
|
1464
|
+
wordCount,
|
|
1465
|
+
replace,
|
|
1466
|
+
startsWith,
|
|
1467
|
+
endsWith,
|
|
1468
|
+
// Dates
|
|
1469
|
+
now,
|
|
1470
|
+
today,
|
|
1471
|
+
formatDate,
|
|
1472
|
+
addDays,
|
|
1473
|
+
diffDays,
|
|
1474
|
+
isPast,
|
|
1475
|
+
isFuture,
|
|
1476
|
+
// Arrays
|
|
1477
|
+
includes,
|
|
1478
|
+
join,
|
|
1479
|
+
first,
|
|
1480
|
+
last,
|
|
1481
|
+
compact,
|
|
1482
|
+
unique,
|
|
1483
|
+
length,
|
|
1484
|
+
// Math
|
|
1485
|
+
round,
|
|
1486
|
+
clamp,
|
|
1487
|
+
ceil,
|
|
1488
|
+
floor,
|
|
1489
|
+
abs,
|
|
1490
|
+
min,
|
|
1491
|
+
max,
|
|
1492
|
+
pow,
|
|
1493
|
+
sqrt,
|
|
1494
|
+
sum,
|
|
1495
|
+
count,
|
|
1496
|
+
// Objects & Logical
|
|
1497
|
+
default: defaultValue,
|
|
1498
|
+
coalesce,
|
|
1499
|
+
isEmpty,
|
|
1500
|
+
get
|
|
1501
|
+
};
|
|
1502
|
+
for (const [name, fn] of Object.entries(helpers)) {
|
|
1503
|
+
try {
|
|
1504
|
+
jexlInstance.addFunction(name, fn);
|
|
1505
|
+
} catch {
|
|
1506
|
+
}
|
|
1507
|
+
try {
|
|
1508
|
+
jexlInstance.addTransform(name, fn);
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
|
|
1514
|
+
jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
|
|
1515
|
+
} catch {
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
registerJexlHelpers(import_jexl.default);
|
|
1519
|
+
async function evaluateJexl(expression, context = {}) {
|
|
1520
|
+
if (!expression || typeof expression !== "string") return void 0;
|
|
1521
|
+
try {
|
|
1522
|
+
const evalContext = {
|
|
1523
|
+
null: null,
|
|
1524
|
+
undefined: void 0,
|
|
1525
|
+
...context,
|
|
1526
|
+
math: Math,
|
|
1527
|
+
Math
|
|
1528
|
+
};
|
|
1529
|
+
return await import_jexl.default.eval(expression, evalContext);
|
|
1530
|
+
} catch (_err) {
|
|
1531
|
+
return void 0;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function evaluateJexlSync(expression, context = {}) {
|
|
1535
|
+
if (!expression || typeof expression !== "string") return void 0;
|
|
1536
|
+
try {
|
|
1537
|
+
const evalContext = {
|
|
1538
|
+
null: null,
|
|
1539
|
+
undefined: void 0,
|
|
1540
|
+
...context,
|
|
1541
|
+
math: Math,
|
|
1542
|
+
Math
|
|
1543
|
+
};
|
|
1544
|
+
return import_jexl.default.evalSync(expression, evalContext);
|
|
1545
|
+
} catch (_err) {
|
|
1546
|
+
return void 0;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
var BUILTIN_JEXL_HELPERS = [
|
|
1550
|
+
// Strings
|
|
1551
|
+
"slugify",
|
|
1552
|
+
"lower",
|
|
1553
|
+
"upper",
|
|
1554
|
+
"trim",
|
|
1555
|
+
"capitalize",
|
|
1556
|
+
"truncate",
|
|
1557
|
+
"readingTime",
|
|
1558
|
+
"wordCount",
|
|
1559
|
+
"replace",
|
|
1560
|
+
"startsWith",
|
|
1561
|
+
"endsWith",
|
|
1562
|
+
// Dates
|
|
1563
|
+
"now",
|
|
1564
|
+
"today",
|
|
1565
|
+
"formatDate",
|
|
1566
|
+
"addDays",
|
|
1567
|
+
"diffDays",
|
|
1568
|
+
"isPast",
|
|
1569
|
+
"isFuture",
|
|
1570
|
+
// Arrays
|
|
1571
|
+
"includes",
|
|
1572
|
+
"join",
|
|
1573
|
+
"first",
|
|
1574
|
+
"last",
|
|
1575
|
+
"compact",
|
|
1576
|
+
"unique",
|
|
1577
|
+
"length",
|
|
1578
|
+
// Math
|
|
1579
|
+
"round",
|
|
1580
|
+
"clamp",
|
|
1581
|
+
// Objects & Logical
|
|
1582
|
+
"default",
|
|
1583
|
+
"coalesce",
|
|
1584
|
+
"isEmpty",
|
|
1585
|
+
"get"
|
|
1586
|
+
];
|
|
1587
|
+
|
|
1588
|
+
// src/utils/declarative-hooks.ts
|
|
1589
|
+
registerJexlHelpers(import_jexl2.default);
|
|
615
1590
|
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"]
|
|
1591
|
+
"collection.beforeRead": ["req", "user", "query", ...BUILTIN_JEXL_HELPERS],
|
|
1592
|
+
"collection.afterRead": ["req", "user", "doc", ...BUILTIN_JEXL_HELPERS],
|
|
1593
|
+
"collection.beforeChange": ["req", "user", "data", "doc", "operation", ...BUILTIN_JEXL_HELPERS],
|
|
1594
|
+
"global.beforeRead": ["req", "user", "query", ...BUILTIN_JEXL_HELPERS],
|
|
1595
|
+
"global.afterRead": ["req", "user", "doc", ...BUILTIN_JEXL_HELPERS],
|
|
1596
|
+
"global.beforeChange": ["req", "user", "data", "doc", "operation", ...BUILTIN_JEXL_HELPERS],
|
|
1597
|
+
"field.beforeChange": ["value", "data", "originalDoc", "user", ...BUILTIN_JEXL_HELPERS],
|
|
1598
|
+
"field.admin.onChange": ["value", "siblingData", "data", ...BUILTIN_JEXL_HELPERS]
|
|
624
1599
|
};
|
|
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"];
|
|
1600
|
+
var ACCESS_ROOTS = ["user", "req", "doc", "data", "id", "config", ...BUILTIN_JEXL_HELPERS];
|
|
1601
|
+
var ADMIN_CONDITION_RESERVED_ROOTS = ["data", "siblingData", "user", "id", ...BUILTIN_JEXL_HELPERS];
|
|
1602
|
+
var PREVIEW_URL_RESERVED_ROOTS = ["siteUrl", "id", ...BUILTIN_JEXL_HELPERS];
|
|
628
1603
|
var COMPILE_CACHE = /* @__PURE__ */ new Map();
|
|
629
1604
|
function compileExpression(expression) {
|
|
630
1605
|
const cached = COMPILE_CACHE.get(expression);
|
|
631
1606
|
if (cached) return cached;
|
|
632
|
-
const compiled =
|
|
1607
|
+
const compiled = import_jexl2.default.compile(expression);
|
|
633
1608
|
const next = { ast: compiled._ast };
|
|
634
1609
|
COMPILE_CACHE.set(expression, next);
|
|
635
1610
|
return next;
|
|
@@ -1152,13 +2127,16 @@ function collectConfigDiagnostics(config) {
|
|
|
1152
2127
|
function formatConfigDiagnostics(source, diagnostics, options = {}) {
|
|
1153
2128
|
const color = options.color ?? false;
|
|
1154
2129
|
const header = `${colorize("Config validation failed", 31, color)} in ${colorize(source, 36, color)}`;
|
|
2130
|
+
const docLink = `${colorize(" Learn how to write declarative rules:", 90, color)} ${colorize("https://docs.dyrected.com/docs/editor-experience/form-and-field-hooks", 34, color)}`;
|
|
1155
2131
|
const lines = diagnostics.flatMap((issue, index) => [
|
|
1156
2132
|
`${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
|
|
1157
2133
|
` ${issue.message}`,
|
|
1158
2134
|
` ${colorize("surface:", 90, color)} ${issue.surface}`
|
|
1159
2135
|
]);
|
|
1160
2136
|
return `${header}
|
|
1161
|
-
${lines.join("\n")}
|
|
2137
|
+
${lines.join("\n")}
|
|
2138
|
+
|
|
2139
|
+
${docLink}`;
|
|
1162
2140
|
}
|
|
1163
2141
|
var ConfigValidationError = class extends Error {
|
|
1164
2142
|
diagnostics;
|
|
@@ -1728,7 +2706,7 @@ function buildSqlOrderBy(sort, existingColumns, numericFields, dialect) {
|
|
|
1728
2706
|
}
|
|
1729
2707
|
|
|
1730
2708
|
// src/utils/hooks.ts
|
|
1731
|
-
var
|
|
2709
|
+
var import_jexl3 = __toESM(require("jexl"), 1);
|
|
1732
2710
|
async function runCollectionHooks(hooks, args, options = {}) {
|
|
1733
2711
|
if (!hooks || hooks.length === 0) {
|
|
1734
2712
|
return args.data ?? args.doc ?? args.query ?? void 0;
|
|
@@ -1749,7 +2727,7 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
1749
2727
|
options.path ?? options.surface
|
|
1750
2728
|
);
|
|
1751
2729
|
}
|
|
1752
|
-
return
|
|
2730
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1753
2731
|
})() : await hook(hookArgs);
|
|
1754
2732
|
if (result !== void 0) {
|
|
1755
2733
|
if (typeof hook === "string" && options.surface) {
|
|
@@ -1766,10 +2744,7 @@ async function runCollectionHooks(hooks, args, options = {}) {
|
|
|
1766
2744
|
}
|
|
1767
2745
|
} catch (err) {
|
|
1768
2746
|
if (options.isolated) {
|
|
1769
|
-
|
|
1770
|
-
err,
|
|
1771
|
-
msg: "Side-effect hook failed after a successful write"
|
|
1772
|
-
});
|
|
2747
|
+
console.error("Side-effect hook failed after a successful write", err);
|
|
1773
2748
|
} else {
|
|
1774
2749
|
throw err;
|
|
1775
2750
|
}
|
|
@@ -1801,7 +2776,7 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db, pat
|
|
|
1801
2776
|
"field.beforeChange",
|
|
1802
2777
|
fieldPath
|
|
1803
2778
|
);
|
|
1804
|
-
return
|
|
2779
|
+
return import_jexl3.default.eval(hook, hookArgs);
|
|
1805
2780
|
})() : await hook(hookArgs);
|
|
1806
2781
|
}
|
|
1807
2782
|
result[field.name] = updatedValue;
|
|
@@ -2367,6 +3342,71 @@ function generateOpenApi(config) {
|
|
|
2367
3342
|
responses: { 200: { description: "Deleted and failed document IDs" } }
|
|
2368
3343
|
}
|
|
2369
3344
|
};
|
|
3345
|
+
spec.paths[`${path}/aggregate`] = {
|
|
3346
|
+
post: {
|
|
3347
|
+
tags: [collectionTag],
|
|
3348
|
+
summary: `Aggregate ${labels.plural}`,
|
|
3349
|
+
description: `Compute statistical aggregations (count, sum, avg, min, max) across ${labels.plural}.`,
|
|
3350
|
+
requestBody: {
|
|
3351
|
+
required: true,
|
|
3352
|
+
content: {
|
|
3353
|
+
"application/json": {
|
|
3354
|
+
schema: {
|
|
3355
|
+
type: "object",
|
|
3356
|
+
additionalProperties: {
|
|
3357
|
+
type: "object",
|
|
3358
|
+
properties: {
|
|
3359
|
+
count: { type: "string", example: "*" },
|
|
3360
|
+
sum: { type: "string", description: "Field to sum" },
|
|
3361
|
+
avg: { type: "string", description: "Field to average" },
|
|
3362
|
+
min: { type: "string", description: "Field for minimum" },
|
|
3363
|
+
max: { type: "string", description: "Field for maximum" },
|
|
3364
|
+
cast: {
|
|
3365
|
+
type: "string",
|
|
3366
|
+
enum: [
|
|
3367
|
+
"number",
|
|
3368
|
+
"integer",
|
|
3369
|
+
"float",
|
|
3370
|
+
"boolean",
|
|
3371
|
+
"date",
|
|
3372
|
+
"string"
|
|
3373
|
+
]
|
|
3374
|
+
},
|
|
3375
|
+
where: {
|
|
3376
|
+
type: "object",
|
|
3377
|
+
description: "Per-aggregate filter condition",
|
|
3378
|
+
additionalProperties: true
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
},
|
|
3386
|
+
responses: {
|
|
3387
|
+
200: {
|
|
3388
|
+
description: "Success",
|
|
3389
|
+
content: {
|
|
3390
|
+
"application/json": {
|
|
3391
|
+
schema: {
|
|
3392
|
+
type: "object",
|
|
3393
|
+
additionalProperties: {
|
|
3394
|
+
type: "number",
|
|
3395
|
+
nullable: true
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
},
|
|
3401
|
+
400: {
|
|
3402
|
+
description: "Bad Request - Invalid aggregate input"
|
|
3403
|
+
},
|
|
3404
|
+
403: {
|
|
3405
|
+
description: "Forbidden - Access denied by read rule"
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
2370
3410
|
if (collection.audit) {
|
|
2371
3411
|
spec.paths[`${path}/__audit`] = {
|
|
2372
3412
|
get: {
|
|
@@ -2894,6 +3934,246 @@ function fieldToSchema(field) {
|
|
|
2894
3934
|
return schema;
|
|
2895
3935
|
}
|
|
2896
3936
|
|
|
3937
|
+
// src/detail.ts
|
|
3938
|
+
function normalizeDetailItem(item) {
|
|
3939
|
+
if (typeof item === "string") {
|
|
3940
|
+
return {
|
|
3941
|
+
type: "field",
|
|
3942
|
+
field: item
|
|
3943
|
+
};
|
|
3944
|
+
}
|
|
3945
|
+
return item;
|
|
3946
|
+
}
|
|
3947
|
+
function displaySection(title, items, options) {
|
|
3948
|
+
return {
|
|
3949
|
+
type: "section",
|
|
3950
|
+
title,
|
|
3951
|
+
items,
|
|
3952
|
+
options
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
function displayTab(label, items, options) {
|
|
3956
|
+
return {
|
|
3957
|
+
type: "tab",
|
|
3958
|
+
label,
|
|
3959
|
+
items,
|
|
3960
|
+
options
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
function displayTabs(tabs, options) {
|
|
3964
|
+
return {
|
|
3965
|
+
type: "tabs",
|
|
3966
|
+
tabs,
|
|
3967
|
+
options
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
function displayGrid(columns, items, options) {
|
|
3971
|
+
return {
|
|
3972
|
+
type: "grid",
|
|
3973
|
+
columns,
|
|
3974
|
+
items,
|
|
3975
|
+
options
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
function displayField(fieldName, options) {
|
|
3979
|
+
return {
|
|
3980
|
+
type: "field",
|
|
3981
|
+
field: fieldName,
|
|
3982
|
+
options
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
function displayRepeat(fieldName, items, options) {
|
|
3986
|
+
return {
|
|
3987
|
+
type: "repeat",
|
|
3988
|
+
field: fieldName,
|
|
3989
|
+
items,
|
|
3990
|
+
options
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
function displayComputed(label, expressionOrOptionsOrHandler, options) {
|
|
3994
|
+
let expression;
|
|
3995
|
+
let handler;
|
|
3996
|
+
let finalOptions = options;
|
|
3997
|
+
if (typeof expressionOrOptionsOrHandler === "string") {
|
|
3998
|
+
expression = expressionOrOptionsOrHandler;
|
|
3999
|
+
} else if (typeof expressionOrOptionsOrHandler === "function") {
|
|
4000
|
+
handler = expressionOrOptionsOrHandler;
|
|
4001
|
+
} else if (expressionOrOptionsOrHandler && typeof expressionOrOptionsOrHandler === "object") {
|
|
4002
|
+
expression = expressionOrOptionsOrHandler.expression;
|
|
4003
|
+
handler = expressionOrOptionsOrHandler.handler;
|
|
4004
|
+
finalOptions = {
|
|
4005
|
+
...expressionOrOptionsOrHandler,
|
|
4006
|
+
...options
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
const id = finalOptions?.id || label.toLowerCase().trim().replace(/[^a-z0-9]+/g, "_");
|
|
4010
|
+
return {
|
|
4011
|
+
type: "computed",
|
|
4012
|
+
id,
|
|
4013
|
+
label,
|
|
4014
|
+
expression,
|
|
4015
|
+
handler,
|
|
4016
|
+
options: finalOptions
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
function displayDivider(options) {
|
|
4020
|
+
return {
|
|
4021
|
+
type: "divider",
|
|
4022
|
+
options
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
4025
|
+
function displayText(content, options) {
|
|
4026
|
+
return {
|
|
4027
|
+
type: "text",
|
|
4028
|
+
content,
|
|
4029
|
+
options
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
function displayCustom(name, options) {
|
|
4033
|
+
return {
|
|
4034
|
+
type: "custom",
|
|
4035
|
+
name,
|
|
4036
|
+
options
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
function displayCustomComponent(name, options) {
|
|
4040
|
+
return displayCustom(name, options);
|
|
4041
|
+
}
|
|
4042
|
+
async function evaluateDetailComputed(detail, doc, user, db) {
|
|
4043
|
+
if (!doc || !detail || !Array.isArray(detail)) {
|
|
4044
|
+
return doc;
|
|
4045
|
+
}
|
|
4046
|
+
const computedItems = [];
|
|
4047
|
+
const extractComputed = (items) => {
|
|
4048
|
+
for (const rawItem of items) {
|
|
4049
|
+
if (!rawItem) continue;
|
|
4050
|
+
const item = normalizeDetailItem(rawItem);
|
|
4051
|
+
if (item.type === "computed") {
|
|
4052
|
+
const id = item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
4053
|
+
if (id) {
|
|
4054
|
+
computedItems.push({
|
|
4055
|
+
id,
|
|
4056
|
+
label: item.label,
|
|
4057
|
+
expression: item.expression,
|
|
4058
|
+
handler: item.handler
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
} else if (item.type === "section" || item.type === "grid" || item.type === "repeat") {
|
|
4062
|
+
if (item.items && Array.isArray(item.items)) {
|
|
4063
|
+
extractComputed(item.items);
|
|
4064
|
+
}
|
|
4065
|
+
} else if (item.type === "tabs" && item.tabs && Array.isArray(item.tabs)) {
|
|
4066
|
+
for (const tab of item.tabs) {
|
|
4067
|
+
if (tab.items) extractComputed(tab.items);
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
extractComputed(detail);
|
|
4073
|
+
if (computedItems.length === 0) return doc;
|
|
4074
|
+
const evaluated = {};
|
|
4075
|
+
for (const item of computedItems) {
|
|
4076
|
+
try {
|
|
4077
|
+
if (typeof item.handler === "function") {
|
|
4078
|
+
evaluated[item.id] = await item.handler({ doc, user, db });
|
|
4079
|
+
} else if (item.expression) {
|
|
4080
|
+
evaluated[item.id] = await evaluateJexl(item.expression, { doc, user });
|
|
4081
|
+
}
|
|
4082
|
+
} catch (_err) {
|
|
4083
|
+
evaluated[item.id] = null;
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
return {
|
|
4087
|
+
...doc,
|
|
4088
|
+
_meta: {
|
|
4089
|
+
...doc._meta || {},
|
|
4090
|
+
computed: evaluated
|
|
4091
|
+
}
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
var SENSITIVE_DETAIL_FIELDS = /* @__PURE__ */ new Set([
|
|
4095
|
+
"password",
|
|
4096
|
+
"salt",
|
|
4097
|
+
"resetPasswordToken",
|
|
4098
|
+
"resetPasswordExpires",
|
|
4099
|
+
"loginAttempts",
|
|
4100
|
+
"lockUntil",
|
|
4101
|
+
"apiKey",
|
|
4102
|
+
"secret"
|
|
4103
|
+
]);
|
|
4104
|
+
function generateDefaultDetailSchema(schema) {
|
|
4105
|
+
const fields = schema.fields || [];
|
|
4106
|
+
const sidebarFieldTypes = /* @__PURE__ */ new Set([
|
|
4107
|
+
"select",
|
|
4108
|
+
"radio",
|
|
4109
|
+
"boolean",
|
|
4110
|
+
"date",
|
|
4111
|
+
"datetime",
|
|
4112
|
+
"relationship"
|
|
4113
|
+
]);
|
|
4114
|
+
const mainFields = [];
|
|
4115
|
+
const sidebarFields = [];
|
|
4116
|
+
for (const field of fields) {
|
|
4117
|
+
if (!field.name) continue;
|
|
4118
|
+
if (SENSITIVE_DETAIL_FIELDS.has(field.name)) continue;
|
|
4119
|
+
if (field.hidden) continue;
|
|
4120
|
+
if (field.admin?.hidden) continue;
|
|
4121
|
+
const isSidebar = sidebarFieldTypes.has(field.type) && field.type !== "relationship" && !field.hasMany;
|
|
4122
|
+
if (isSidebar && sidebarFields.length < 5) {
|
|
4123
|
+
sidebarFields.push(
|
|
4124
|
+
displayField(field.name, {
|
|
4125
|
+
span: 12,
|
|
4126
|
+
display: field.type === "select" || field.type === "radio" ? "badge" : void 0
|
|
4127
|
+
})
|
|
4128
|
+
);
|
|
4129
|
+
} else {
|
|
4130
|
+
const span = field.type === "richText" || field.type === "blocks" || field.type === "textarea" || field.type === "array" || field.type === "json" || field.type === "object" ? 12 : 6;
|
|
4131
|
+
mainFields.push(
|
|
4132
|
+
displayField(field.name, {
|
|
4133
|
+
span
|
|
4134
|
+
})
|
|
4135
|
+
);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
const sections = [];
|
|
4139
|
+
const mainTitle = schema.labels?.singular || schema.label || "Details";
|
|
4140
|
+
if (sidebarFields.length > 0) {
|
|
4141
|
+
sections.push(
|
|
4142
|
+
displaySection(mainTitle, mainFields, {
|
|
4143
|
+
span: 8
|
|
4144
|
+
})
|
|
4145
|
+
);
|
|
4146
|
+
sections.push(
|
|
4147
|
+
displaySection("Overview", sidebarFields, {
|
|
4148
|
+
span: 4
|
|
4149
|
+
})
|
|
4150
|
+
);
|
|
4151
|
+
} else {
|
|
4152
|
+
sections.push(
|
|
4153
|
+
displaySection(mainTitle, mainFields, {
|
|
4154
|
+
span: 12
|
|
4155
|
+
})
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
return sections;
|
|
4159
|
+
}
|
|
4160
|
+
function isDetailItemVisible(item, doc, user) {
|
|
4161
|
+
if (!item) return true;
|
|
4162
|
+
const it = normalizeDetailItem(item);
|
|
4163
|
+
const visible = it.options?.visible;
|
|
4164
|
+
if (visible === void 0 || visible === null) return true;
|
|
4165
|
+
if (typeof visible === "boolean") return visible;
|
|
4166
|
+
if (typeof visible === "string") {
|
|
4167
|
+
if (visible.trim().length === 0) return true;
|
|
4168
|
+
try {
|
|
4169
|
+
return Boolean(evaluateJexlSync(visible, { doc, user }));
|
|
4170
|
+
} catch {
|
|
4171
|
+
return true;
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
return true;
|
|
4175
|
+
}
|
|
4176
|
+
|
|
2897
4177
|
// src/index.ts
|
|
2898
4178
|
function defineCollection(config) {
|
|
2899
4179
|
return config;
|
|
@@ -2943,10 +4223,13 @@ function defineTab(args) {
|
|
|
2943
4223
|
}
|
|
2944
4224
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2945
4225
|
0 && (module.exports = {
|
|
4226
|
+
BUILTIN_JEXL_HELPERS,
|
|
2946
4227
|
ConfigValidationError,
|
|
2947
4228
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
2948
4229
|
LIFECYCLE_EVENT_NAMES,
|
|
2949
4230
|
WORKFLOW_HISTORY_COLLECTION,
|
|
4231
|
+
abs,
|
|
4232
|
+
addDays,
|
|
2950
4233
|
assertValidAdminConditionsInConfig,
|
|
2951
4234
|
assertValidDeclarativeAccessInConfig,
|
|
2952
4235
|
assertValidDeclarativeHookExpression,
|
|
@@ -2956,13 +4239,20 @@ function defineTab(args) {
|
|
|
2956
4239
|
availableWorkflowTransitions,
|
|
2957
4240
|
buildSqlOrderBy,
|
|
2958
4241
|
canViewWorkflowDraft,
|
|
4242
|
+
capitalize,
|
|
4243
|
+
ceil,
|
|
4244
|
+
clamp,
|
|
4245
|
+
coalesce,
|
|
2959
4246
|
collectAdminConditionValidationIssues,
|
|
2960
4247
|
collectConfigDiagnostics,
|
|
2961
4248
|
collectDeclarativeAccessValidationIssues,
|
|
2962
4249
|
collectDeclarativeHookValidationIssues,
|
|
2963
4250
|
collectPreviewUrlValidationIssues,
|
|
4251
|
+
compact,
|
|
4252
|
+
count,
|
|
2964
4253
|
createLifecycleEvent,
|
|
2965
4254
|
createWorkflowDocument,
|
|
4255
|
+
defaultValue,
|
|
2966
4256
|
defineArrayField,
|
|
2967
4257
|
defineBlock,
|
|
2968
4258
|
defineBlocksField,
|
|
@@ -2992,32 +4282,81 @@ function defineTab(args) {
|
|
|
2992
4282
|
defineTextareaField,
|
|
2993
4283
|
defineTimeField,
|
|
2994
4284
|
defineUrlField,
|
|
4285
|
+
diffDays,
|
|
2995
4286
|
dispatchLifecycleEvent,
|
|
2996
4287
|
dispatchPendingLifecycleEvents,
|
|
4288
|
+
displayComputed,
|
|
4289
|
+
displayCustom,
|
|
4290
|
+
displayCustomComponent,
|
|
4291
|
+
displayDivider,
|
|
4292
|
+
displayField,
|
|
4293
|
+
displayGrid,
|
|
4294
|
+
displayRepeat,
|
|
4295
|
+
displaySection,
|
|
4296
|
+
displayTab,
|
|
4297
|
+
displayTabs,
|
|
4298
|
+
displayText,
|
|
4299
|
+
endsWith,
|
|
4300
|
+
evaluateDetailComputed,
|
|
4301
|
+
evaluateJexl,
|
|
4302
|
+
evaluateJexlSync,
|
|
2997
4303
|
executeFieldAfterRead,
|
|
2998
4304
|
executeFieldBeforeChange,
|
|
4305
|
+
first,
|
|
4306
|
+
floor,
|
|
2999
4307
|
formatConfigDiagnostics,
|
|
4308
|
+
formatDate,
|
|
4309
|
+
generateDefaultDetailSchema,
|
|
3000
4310
|
generateOpenApi,
|
|
4311
|
+
get,
|
|
3001
4312
|
getAdminAuthCollection,
|
|
3002
4313
|
getPublicAdminAuthConfig,
|
|
4314
|
+
includes,
|
|
3003
4315
|
initializeWorkflowDocument,
|
|
3004
4316
|
isConfigValidationError,
|
|
4317
|
+
isDetailItemVisible,
|
|
4318
|
+
isEmpty,
|
|
4319
|
+
isFuture,
|
|
4320
|
+
isPast,
|
|
4321
|
+
join,
|
|
4322
|
+
last,
|
|
4323
|
+
length,
|
|
4324
|
+
lower,
|
|
3005
4325
|
materializeWorkflowDocument,
|
|
4326
|
+
max,
|
|
4327
|
+
min,
|
|
3006
4328
|
normalizeConfig,
|
|
4329
|
+
normalizeDetailItem,
|
|
4330
|
+
now,
|
|
3007
4331
|
numericFieldNames,
|
|
3008
4332
|
parseMongoWhere,
|
|
3009
4333
|
parseSort,
|
|
3010
4334
|
parseSqlWhere,
|
|
4335
|
+
pow,
|
|
3011
4336
|
publishedStateName,
|
|
3012
4337
|
publishingWorkflow,
|
|
4338
|
+
readingTime,
|
|
4339
|
+
registerJexlHelpers,
|
|
4340
|
+
replace,
|
|
3013
4341
|
resolveAdminAuthCollection,
|
|
4342
|
+
round,
|
|
3014
4343
|
runCollectionHooks,
|
|
3015
4344
|
saveWorkflowDraft,
|
|
3016
4345
|
simplePublishingWorkflow,
|
|
4346
|
+
slugify,
|
|
4347
|
+
sqrt,
|
|
4348
|
+
startsWith,
|
|
4349
|
+
sum,
|
|
4350
|
+
today,
|
|
3017
4351
|
transitionWorkflow,
|
|
4352
|
+
trim,
|
|
4353
|
+
truncate,
|
|
4354
|
+
unique,
|
|
4355
|
+
upper,
|
|
3018
4356
|
validateAdminConditionExpression,
|
|
3019
4357
|
validateDeclarativeAccessExpression,
|
|
3020
4358
|
validateDeclarativeHookExpression,
|
|
3021
4359
|
validatePreviewUrlExpression,
|
|
4360
|
+
wordCount,
|
|
3022
4361
|
workflowCapabilities
|
|
3023
4362
|
});
|