@dyrected/core 2.11.0 → 2.13.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/access-P6Z2XAFS.js +10 -0
- package/dist/ai-X3M2SE5A.js +17 -0
- package/dist/ai-tools-UPKJY3TX.js +14 -0
- package/dist/{app-config-6U_Kum-Z.d.cts → app-D_ODsBWA.d.cts} +524 -5
- package/dist/{app-config-6U_Kum-Z.d.ts → app-D_ODsBWA.d.ts} +524 -5
- package/dist/chunk-254THFDS.js +202 -0
- package/dist/chunk-27YRIGK5.js +1758 -0
- package/dist/chunk-4RAQJVJM.js +18 -0
- package/dist/{chunk-H7DVVW2P.js → chunk-DZMBVIMN.js} +285 -1760
- package/dist/chunk-HRRJ3RYT.js +70 -0
- package/dist/chunk-W7GKJM2X.js +1136 -0
- package/dist/chunk-ZYYDTHF3.js +635 -0
- package/dist/index.cjs +634 -588
- package/dist/index.d.cts +180 -46
- package/dist/index.d.ts +180 -46
- package/dist/index.js +148 -60
- package/dist/rag.service-YDGEYTXS.js +11 -0
- package/dist/server.cjs +5127 -1135
- package/dist/server.d.cts +371 -329
- package/dist/server.d.ts +371 -329
- package/dist/server.js +2416 -273
- package/dist/{where-sanitizer-47QPJQ6O.js → where-sanitizer-CAAIYSSK.js} +20 -1
- package/package.json +12 -9
- package/LICENSE.md +0 -50
- package/dist/chunk-FRSJJO27.js +0 -519
- package/dist/observability-HQF4EUSV.js +0 -35
package/dist/index.cjs
CHANGED
|
@@ -5,9 +5,6 @@ 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
|
-
};
|
|
11
8
|
var __commonJS = (cb, mod) => function __require() {
|
|
12
9
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
10
|
};
|
|
@@ -33,517 +30,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
30
|
));
|
|
34
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
32
|
|
|
36
|
-
// src/observability.ts
|
|
37
|
-
var observability_exports = {};
|
|
38
|
-
__export(observability_exports, {
|
|
39
|
-
attachRequestMetrics: () => attachRequestMetrics,
|
|
40
|
-
bindObservabilityRuntime: () => bindObservabilityRuntime,
|
|
41
|
-
buildRequestLogger: () => buildRequestLogger,
|
|
42
|
-
captureRequestBody: () => captureRequestBody,
|
|
43
|
-
createObservabilityRuntime: () => createObservabilityRuntime,
|
|
44
|
-
createRequestSpan: () => createRequestSpan,
|
|
45
|
-
endRequestSpan: () => endRequestSpan,
|
|
46
|
-
getConfigLogger: () => getConfigLogger,
|
|
47
|
-
getObservabilityRuntime: () => getObservabilityRuntime,
|
|
48
|
-
getRequestLogger: () => getRequestLogger,
|
|
49
|
-
redactBody: () => redactBody,
|
|
50
|
-
redactHeaders: () => redactHeaders,
|
|
51
|
-
renderPrometheusMetrics: () => renderPrometheusMetrics,
|
|
52
|
-
resolveObservabilityConfig: () => resolveObservabilityConfig,
|
|
53
|
-
shouldSampleRequest: () => shouldSampleRequest
|
|
54
|
-
});
|
|
55
|
-
function resolveObservabilityConfig(config) {
|
|
56
|
-
const requestLogging = config?.requestLogging;
|
|
57
|
-
const sampling = config?.sampling;
|
|
58
|
-
const tracing = config?.tracing;
|
|
59
|
-
const metrics = config?.metrics;
|
|
60
|
-
return {
|
|
61
|
-
requestLogging: {
|
|
62
|
-
enabled: requestLogging?.enabled ?? true,
|
|
63
|
-
logBodies: requestLogging?.logBodies ?? false,
|
|
64
|
-
maxBodyBytes: requestLogging?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
|
|
65
|
-
redactPaths: uniqueValues([
|
|
66
|
-
...DEFAULT_REDACT_PATHS,
|
|
67
|
-
...requestLogging?.redactPaths ?? []
|
|
68
|
-
]),
|
|
69
|
-
includeHeaders: normalizeHeaderNames(requestLogging?.includeHeaders ?? []),
|
|
70
|
-
redactHeaders: uniqueValues(
|
|
71
|
-
normalizeHeaderNames([
|
|
72
|
-
...DEFAULT_REDACT_HEADERS,
|
|
73
|
-
...requestLogging?.redactHeaders ?? []
|
|
74
|
-
])
|
|
75
|
-
)
|
|
76
|
-
},
|
|
77
|
-
sampling: {
|
|
78
|
-
successRate: clampRate(sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE),
|
|
79
|
-
traceSuccessRate: clampRate(
|
|
80
|
-
sampling?.traceSuccessRate ?? sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE
|
|
81
|
-
),
|
|
82
|
-
bodySuccessRate: clampRate(
|
|
83
|
-
sampling?.bodySuccessRate ?? Math.min(DEFAULT_BODY_SAMPLE_RATE, sampling?.successRate ?? DEFAULT_SUCCESS_SAMPLE_RATE)
|
|
84
|
-
),
|
|
85
|
-
alwaysKeep4xx: sampling?.alwaysKeep4xx ?? true,
|
|
86
|
-
alwaysKeep5xx: sampling?.alwaysKeep5xx ?? true
|
|
87
|
-
},
|
|
88
|
-
tracing: {
|
|
89
|
-
enabled: tracing?.enabled ?? false,
|
|
90
|
-
serviceName: tracing?.serviceName ?? "dyrected",
|
|
91
|
-
exporter: tracing?.exporter ?? "none",
|
|
92
|
-
headers: tracing?.headers ?? {},
|
|
93
|
-
endpoint: tracing?.endpoint
|
|
94
|
-
},
|
|
95
|
-
metrics: {
|
|
96
|
-
enabled: metrics?.enabled ?? false,
|
|
97
|
-
exporter: metrics?.exporter ?? "none",
|
|
98
|
-
endpoint: metrics?.endpoint,
|
|
99
|
-
path: metrics?.path ?? "/metrics"
|
|
100
|
-
},
|
|
101
|
-
transports: {
|
|
102
|
-
targets: config?.transports?.targets && config.transports.targets.length > 0 ? config.transports.targets : [{ type: "stdout" }]
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function createObservabilityRuntime(config) {
|
|
107
|
-
const resolved = resolveObservabilityConfig(config.observability);
|
|
108
|
-
const logger = createRootLogger(config.logger, resolved);
|
|
109
|
-
const shutdownTasks = [];
|
|
110
|
-
const meterRuntime = resolved.metrics.enabled || resolved.metrics.exporter === "prometheus" ? createMeterProvider(resolved) : void 0;
|
|
111
|
-
const tracerProvider = resolved.tracing.enabled ? createTracerProvider(resolved) : void 0;
|
|
112
|
-
const tracer = tracerProvider?.getTracer("dyrected");
|
|
113
|
-
const meter = meterRuntime?.provider.getMeter("dyrected");
|
|
114
|
-
const metrics = meter ? {
|
|
115
|
-
requestCount: meter.createCounter("dyrected_http_requests_total", {
|
|
116
|
-
description: "Total HTTP requests served by Dyrected"
|
|
117
|
-
}),
|
|
118
|
-
authFailureCount: meter.createCounter("dyrected_auth_failures_total", {
|
|
119
|
-
description: "Authentication failures"
|
|
120
|
-
}),
|
|
121
|
-
uncaughtErrorCount: meter.createCounter("dyrected_uncaught_errors_total", {
|
|
122
|
-
description: "Uncaught application errors"
|
|
123
|
-
}),
|
|
124
|
-
auditWriteFailureCount: meter.createCounter("dyrected_audit_write_failures_total", {
|
|
125
|
-
description: "Audit log persistence failures"
|
|
126
|
-
}),
|
|
127
|
-
emailSendFailureCount: meter.createCounter("dyrected_email_send_failures_total", {
|
|
128
|
-
description: "Transactional email delivery failures"
|
|
129
|
-
}),
|
|
130
|
-
workflowHookFailureCount: meter.createCounter("dyrected_workflow_hook_failures_total", {
|
|
131
|
-
description: "Workflow hook failures isolated after successful writes"
|
|
132
|
-
}),
|
|
133
|
-
requestDuration: meter.createHistogram("dyrected_http_request_duration_ms", {
|
|
134
|
-
description: "HTTP request duration in milliseconds",
|
|
135
|
-
unit: "ms"
|
|
136
|
-
})
|
|
137
|
-
} : void 0;
|
|
138
|
-
if (meterRuntime) {
|
|
139
|
-
shutdownTasks.push(() => meterRuntime.provider.shutdown());
|
|
140
|
-
}
|
|
141
|
-
if (tracerProvider) {
|
|
142
|
-
shutdownTasks.push(() => tracerProvider.shutdown());
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
logger,
|
|
146
|
-
config: resolved,
|
|
147
|
-
tracer,
|
|
148
|
-
metrics,
|
|
149
|
-
prometheusExporter: meterRuntime?.prometheusExporter,
|
|
150
|
-
shutdown: async () => {
|
|
151
|
-
for (const task of shutdownTasks) {
|
|
152
|
-
await task();
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
recordAuthFailure: (attributes) => metrics?.authFailureCount.add(1, attributes),
|
|
156
|
-
recordUncaughtError: (attributes) => metrics?.uncaughtErrorCount.add(1, attributes),
|
|
157
|
-
recordAuditWriteFailure: (attributes) => metrics?.auditWriteFailureCount.add(1, attributes),
|
|
158
|
-
recordEmailSendFailure: (attributes) => metrics?.emailSendFailureCount.add(1, attributes),
|
|
159
|
-
recordWorkflowHookFailure: (attributes) => metrics?.workflowHookFailureCount.add(1, attributes)
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function bindObservabilityRuntime(config, runtime) {
|
|
163
|
-
runtimeByConfig.set(config, runtime);
|
|
164
|
-
}
|
|
165
|
-
function getObservabilityRuntime(config) {
|
|
166
|
-
if (!config || typeof config !== "object") return void 0;
|
|
167
|
-
return runtimeByConfig.get(config);
|
|
168
|
-
}
|
|
169
|
-
function createRootLogger(loggerConfig, observability) {
|
|
170
|
-
if (loggerConfig && !("options" in loggerConfig)) {
|
|
171
|
-
return loggerConfig;
|
|
172
|
-
}
|
|
173
|
-
const options = loggerConfig?.options ?? {};
|
|
174
|
-
const enabled = options.enabled ?? process.env.DISABLE_LOGGING !== "true";
|
|
175
|
-
const name = options.name ?? "dyrected";
|
|
176
|
-
if (loggerConfig?.destination) {
|
|
177
|
-
return (0, import_pino.default)(
|
|
178
|
-
{
|
|
179
|
-
...options,
|
|
180
|
-
enabled,
|
|
181
|
-
name,
|
|
182
|
-
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
183
|
-
},
|
|
184
|
-
loggerConfig.destination
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
const streams = buildTransportStreams(observability);
|
|
188
|
-
const destination = streams.length === 1 ? streams[0] : (0, import_pino.multistream)(streams.map((stream) => ({ stream })));
|
|
189
|
-
return (0, import_pino.default)(
|
|
190
|
-
{
|
|
191
|
-
...options,
|
|
192
|
-
enabled,
|
|
193
|
-
name,
|
|
194
|
-
timestamp: options.timestamp ?? import_pino.stdTimeFunctions.isoTime
|
|
195
|
-
},
|
|
196
|
-
destination
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
function buildTransportStreams(observability) {
|
|
200
|
-
const targets = observability.transports.targets;
|
|
201
|
-
if (targets.length === 0) {
|
|
202
|
-
return [defaultLoggerDestination()];
|
|
203
|
-
}
|
|
204
|
-
return targets.map((target) => {
|
|
205
|
-
switch (target.type) {
|
|
206
|
-
case "stdout":
|
|
207
|
-
return defaultLoggerDestination();
|
|
208
|
-
case "stderr":
|
|
209
|
-
return (0, import_pino.destination)(2);
|
|
210
|
-
case "file":
|
|
211
|
-
return (0, import_pino.destination)(target.path);
|
|
212
|
-
case "otlp":
|
|
213
|
-
return new OtlpLogWritable(target.endpoint, target.headers);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function defaultLoggerDestination() {
|
|
218
|
-
if (process.env.NODE_ENV !== "production" && typeof window === "undefined") {
|
|
219
|
-
try {
|
|
220
|
-
const req = globalThis.require;
|
|
221
|
-
if (req) {
|
|
222
|
-
const pinoPretty = req("pino-pretty");
|
|
223
|
-
return pinoPretty({
|
|
224
|
-
colorize: true,
|
|
225
|
-
ignore: "pid,hostname",
|
|
226
|
-
translateTime: "SYS:HH:MM:ss",
|
|
227
|
-
destination: 1,
|
|
228
|
-
sync: true
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return (0, import_pino.destination)(1);
|
|
235
|
-
}
|
|
236
|
-
function createTracerProvider(observability) {
|
|
237
|
-
const exporter = createTraceExporter(observability);
|
|
238
|
-
const provider = new import_sdk_trace_base.BasicTracerProvider({
|
|
239
|
-
resource: (0, import_resources.resourceFromAttributes)({
|
|
240
|
-
"service.name": observability.tracing.serviceName
|
|
241
|
-
}),
|
|
242
|
-
spanProcessors: exporter ? [new import_sdk_trace_base.BatchSpanProcessor(exporter)] : []
|
|
243
|
-
});
|
|
244
|
-
import_api.trace.setGlobalTracerProvider(provider);
|
|
245
|
-
return provider;
|
|
246
|
-
}
|
|
247
|
-
function createTraceExporter(observability) {
|
|
248
|
-
switch (observability.tracing.exporter) {
|
|
249
|
-
case "console":
|
|
250
|
-
return new import_sdk_trace_base.ConsoleSpanExporter();
|
|
251
|
-
case "otlp":
|
|
252
|
-
return new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
253
|
-
url: observability.tracing.endpoint,
|
|
254
|
-
headers: observability.tracing.headers
|
|
255
|
-
});
|
|
256
|
-
default:
|
|
257
|
-
return void 0;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
function createMeterProvider(observability) {
|
|
261
|
-
const readers = [];
|
|
262
|
-
let prometheusExporter;
|
|
263
|
-
switch (observability.metrics.exporter) {
|
|
264
|
-
case "otlp":
|
|
265
|
-
readers.push(
|
|
266
|
-
new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
267
|
-
exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
268
|
-
url: observability.metrics.endpoint
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
break;
|
|
273
|
-
case "prometheus":
|
|
274
|
-
prometheusExporter = new import_exporter_prometheus.PrometheusExporter({
|
|
275
|
-
endpoint: observability.metrics.path,
|
|
276
|
-
preventServerStart: true
|
|
277
|
-
});
|
|
278
|
-
readers.push(prometheusExporter);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
const provider = new import_sdk_metrics.MeterProvider({
|
|
282
|
-
resource: (0, import_resources.resourceFromAttributes)({
|
|
283
|
-
"service.name": observability.tracing.serviceName
|
|
284
|
-
}),
|
|
285
|
-
readers
|
|
286
|
-
});
|
|
287
|
-
return { provider, prometheusExporter };
|
|
288
|
-
}
|
|
289
|
-
function getConfigLogger(config, component) {
|
|
290
|
-
const base = config?.logger && !("options" in config.logger) ? config.logger : fallbackLogger;
|
|
291
|
-
return base.child({ component });
|
|
292
|
-
}
|
|
293
|
-
function getRequestLogger(c, component) {
|
|
294
|
-
const base = c.get("logger") ?? fallbackLogger;
|
|
295
|
-
return component ? base.child({ component }) : base;
|
|
296
|
-
}
|
|
297
|
-
function buildRequestLogger(runtime, vars, requestId) {
|
|
298
|
-
const trace2 = vars.requestTrace;
|
|
299
|
-
return runtime.logger.child({
|
|
300
|
-
requestId,
|
|
301
|
-
siteId: vars.siteId,
|
|
302
|
-
workspaceId: vars.workspaceId,
|
|
303
|
-
traceId: trace2?.traceId,
|
|
304
|
-
spanId: trace2?.spanId
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
function shouldSampleRequest(requestId, statusCode, sampling, kind) {
|
|
308
|
-
if (statusCode >= 500 && sampling.alwaysKeep5xx) return true;
|
|
309
|
-
if (statusCode >= 400 && sampling.alwaysKeep4xx) return true;
|
|
310
|
-
const rate = kind === "trace" ? sampling.traceSuccessRate : kind === "body" ? sampling.bodySuccessRate : sampling.successRate;
|
|
311
|
-
return deterministicSample(requestId ?? "no-request-id", rate);
|
|
312
|
-
}
|
|
313
|
-
function redactHeaders(headers, observability) {
|
|
314
|
-
const includeHeaders = new Set(observability.requestLogging.includeHeaders);
|
|
315
|
-
const redactHeaders2 = new Set(observability.requestLogging.redactHeaders);
|
|
316
|
-
const output = {};
|
|
317
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
318
|
-
const normalizedKey = key.toLowerCase();
|
|
319
|
-
if (includeHeaders.size > 0 && !includeHeaders.has(normalizedKey)) continue;
|
|
320
|
-
output[normalizedKey] = redactHeaders2.has(normalizedKey) ? REDACTED_VALUE : value;
|
|
321
|
-
}
|
|
322
|
-
return output;
|
|
323
|
-
}
|
|
324
|
-
async function captureRequestBody(request, observability) {
|
|
325
|
-
const contentType = request.headers.get("content-type") ?? void 0;
|
|
326
|
-
const contentLength = toNumber(request.headers.get("content-length"));
|
|
327
|
-
const jsonBody = contentType?.includes("application/json");
|
|
328
|
-
if (!jsonBody) {
|
|
329
|
-
return {
|
|
330
|
-
attempted: false,
|
|
331
|
-
contentType,
|
|
332
|
-
contentLength
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const text = await request.clone().text();
|
|
337
|
-
if (!text) {
|
|
338
|
-
return { attempted: true, contentType, contentLength };
|
|
339
|
-
}
|
|
340
|
-
const truncated = text.length > observability.requestLogging.maxBodyBytes;
|
|
341
|
-
const raw = truncated ? text.slice(0, observability.requestLogging.maxBodyBytes) : text;
|
|
342
|
-
try {
|
|
343
|
-
const parsed = JSON.parse(raw);
|
|
344
|
-
return {
|
|
345
|
-
attempted: true,
|
|
346
|
-
contentType,
|
|
347
|
-
contentLength: contentLength ?? text.length,
|
|
348
|
-
truncated,
|
|
349
|
-
body: redactBody(parsed, observability.requestLogging.redactPaths)
|
|
350
|
-
};
|
|
351
|
-
} catch {
|
|
352
|
-
return {
|
|
353
|
-
attempted: true,
|
|
354
|
-
contentType,
|
|
355
|
-
contentLength: contentLength ?? text.length,
|
|
356
|
-
truncated,
|
|
357
|
-
parseFailed: true
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
return {
|
|
362
|
-
attempted: true,
|
|
363
|
-
contentType,
|
|
364
|
-
contentLength,
|
|
365
|
-
parseFailed: true
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function redactBody(value, redactPaths) {
|
|
370
|
-
const cloned = deepClone(value);
|
|
371
|
-
for (const path of redactPaths) {
|
|
372
|
-
applyRedaction(cloned, path.split("."));
|
|
373
|
-
}
|
|
374
|
-
return cloned;
|
|
375
|
-
}
|
|
376
|
-
function applyRedaction(target, segments) {
|
|
377
|
-
if (!target || segments.length === 0) return;
|
|
378
|
-
const [segment, ...rest] = segments;
|
|
379
|
-
if (Array.isArray(target)) {
|
|
380
|
-
for (const item of target) {
|
|
381
|
-
if (segment === "*") {
|
|
382
|
-
applyRedaction(item, rest);
|
|
383
|
-
} else {
|
|
384
|
-
applyRedaction(item, segments);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (typeof target !== "object") return;
|
|
390
|
-
const record = target;
|
|
391
|
-
if (segment === "*") {
|
|
392
|
-
for (const value of Object.values(record)) {
|
|
393
|
-
applyRedaction(value, rest);
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!(segment in record)) return;
|
|
398
|
-
if (rest.length === 0) {
|
|
399
|
-
record[segment] = REDACTED_VALUE;
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
applyRedaction(record[segment], rest);
|
|
403
|
-
}
|
|
404
|
-
function deepClone(value) {
|
|
405
|
-
if (typeof structuredClone === "function") {
|
|
406
|
-
return structuredClone(value);
|
|
407
|
-
}
|
|
408
|
-
return JSON.parse(JSON.stringify(value));
|
|
409
|
-
}
|
|
410
|
-
function createRequestSpan(runtime, name, attributes) {
|
|
411
|
-
if (!runtime.tracer) return void 0;
|
|
412
|
-
const span = runtime.tracer.startSpan(name, { attributes }, import_api.context.active());
|
|
413
|
-
const spanContext = span.spanContext();
|
|
414
|
-
return {
|
|
415
|
-
span,
|
|
416
|
-
traceId: spanContext.traceId,
|
|
417
|
-
spanId: spanContext.spanId,
|
|
418
|
-
sampled: spanContext.traceFlags === import_api.TraceFlags.SAMPLED
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
function endRequestSpan(requestTrace, statusCode, error) {
|
|
422
|
-
if (!requestTrace) return;
|
|
423
|
-
requestTrace.span.setAttribute("http.status_code", statusCode);
|
|
424
|
-
if (error) {
|
|
425
|
-
requestTrace.span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
426
|
-
}
|
|
427
|
-
requestTrace.span.end();
|
|
428
|
-
}
|
|
429
|
-
function attachRequestMetrics(runtime, args) {
|
|
430
|
-
runtime.metrics?.requestCount.add(1, {
|
|
431
|
-
method: args.method,
|
|
432
|
-
route: args.route,
|
|
433
|
-
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
434
|
-
});
|
|
435
|
-
runtime.metrics?.requestDuration.record(args.durationMs, {
|
|
436
|
-
method: args.method,
|
|
437
|
-
route: args.route,
|
|
438
|
-
status_class: `${Math.floor(args.statusCode / 100)}xx`
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
async function renderPrometheusMetrics(exporter) {
|
|
442
|
-
let statusCode = 200;
|
|
443
|
-
const headers = {};
|
|
444
|
-
let body = "";
|
|
445
|
-
await new Promise((resolve) => {
|
|
446
|
-
const response = {
|
|
447
|
-
statusCode,
|
|
448
|
-
setHeader(name, value) {
|
|
449
|
-
headers[name.toLowerCase()] = value;
|
|
450
|
-
},
|
|
451
|
-
end(chunk) {
|
|
452
|
-
if (chunk) body += chunk;
|
|
453
|
-
statusCode = this.statusCode;
|
|
454
|
-
resolve();
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
exporter.getMetricsRequestHandler({}, response);
|
|
458
|
-
});
|
|
459
|
-
return { body, headers, statusCode };
|
|
460
|
-
}
|
|
461
|
-
function normalizeHeaderNames(headers) {
|
|
462
|
-
return headers.map((value) => value.toLowerCase());
|
|
463
|
-
}
|
|
464
|
-
function uniqueValues(values) {
|
|
465
|
-
return [...new Set(values)];
|
|
466
|
-
}
|
|
467
|
-
function clampRate(value) {
|
|
468
|
-
if (Number.isNaN(value)) return 0;
|
|
469
|
-
return Math.max(0, Math.min(1, value));
|
|
470
|
-
}
|
|
471
|
-
function deterministicSample(seed, rate) {
|
|
472
|
-
if (rate >= 1) return true;
|
|
473
|
-
if (rate <= 0) return false;
|
|
474
|
-
let hash = 0;
|
|
475
|
-
for (let index = 0; index < seed.length; index += 1) {
|
|
476
|
-
hash = hash * 31 + seed.charCodeAt(index) >>> 0;
|
|
477
|
-
}
|
|
478
|
-
return hash / 4294967295 <= rate;
|
|
479
|
-
}
|
|
480
|
-
function toNumber(value) {
|
|
481
|
-
if (!value) return void 0;
|
|
482
|
-
const parsed = Number(value);
|
|
483
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
484
|
-
}
|
|
485
|
-
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;
|
|
486
|
-
var init_observability = __esm({
|
|
487
|
-
"src/observability.ts"() {
|
|
488
|
-
"use strict";
|
|
489
|
-
import_api = require("@opentelemetry/api");
|
|
490
|
-
import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
491
|
-
import_exporter_prometheus = require("@opentelemetry/exporter-prometheus");
|
|
492
|
-
import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
493
|
-
import_resources = require("@opentelemetry/resources");
|
|
494
|
-
import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
495
|
-
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
496
|
-
import_pino = __toESM(require("pino"), 1);
|
|
497
|
-
import_node_stream = require("stream");
|
|
498
|
-
DEFAULT_REDACT_HEADERS = ["authorization", "cookie", "set-cookie", "x-api-key"];
|
|
499
|
-
DEFAULT_REDACT_PATHS = [
|
|
500
|
-
"password",
|
|
501
|
-
"currentPassword",
|
|
502
|
-
"newPassword",
|
|
503
|
-
"confirmPassword",
|
|
504
|
-
"token",
|
|
505
|
-
"refreshToken",
|
|
506
|
-
"accessToken",
|
|
507
|
-
"secret",
|
|
508
|
-
"apiKey",
|
|
509
|
-
"inviteToken",
|
|
510
|
-
"resetToken"
|
|
511
|
-
];
|
|
512
|
-
DEFAULT_MAX_BODY_BYTES = 8192;
|
|
513
|
-
DEFAULT_SUCCESS_SAMPLE_RATE = 0.1;
|
|
514
|
-
DEFAULT_BODY_SAMPLE_RATE = 0.02;
|
|
515
|
-
REDACTED_VALUE = "[REDACTED]";
|
|
516
|
-
runtimeByConfig = /* @__PURE__ */ new WeakMap();
|
|
517
|
-
fallbackLogger = (0, import_pino.default)({
|
|
518
|
-
name: "dyrected",
|
|
519
|
-
enabled: process.env.DISABLE_LOGGING !== "true",
|
|
520
|
-
timestamp: import_pino.stdTimeFunctions.isoTime
|
|
521
|
-
});
|
|
522
|
-
OtlpLogWritable = class extends import_node_stream.Writable {
|
|
523
|
-
constructor(endpoint, headers = {}) {
|
|
524
|
-
super();
|
|
525
|
-
this.endpoint = endpoint;
|
|
526
|
-
this.headers = headers;
|
|
527
|
-
}
|
|
528
|
-
endpoint;
|
|
529
|
-
headers;
|
|
530
|
-
_write(chunk, _encoding, callback) {
|
|
531
|
-
const payload = chunk.toString();
|
|
532
|
-
void fetch(this.endpoint, {
|
|
533
|
-
method: "POST",
|
|
534
|
-
headers: {
|
|
535
|
-
"content-type": "application/json",
|
|
536
|
-
...this.headers
|
|
537
|
-
},
|
|
538
|
-
body: JSON.stringify({ logs: payload.trim().split("\n").filter(Boolean) })
|
|
539
|
-
}).then(() => callback()).catch(
|
|
540
|
-
(error) => callback(error instanceof Error ? error : new Error(String(error)))
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
|
|
547
33
|
// ../../node_modules/.pnpm/@babel+runtime@7.29.7/node_modules/@babel/runtime/helpers/interopRequireDefault.js
|
|
548
34
|
var require_interopRequireDefault = __commonJS({
|
|
549
35
|
"../../node_modules/.pnpm/@babel+runtime@7.29.7/node_modules/@babel/runtime/helpers/interopRequireDefault.js"(exports2, module2) {
|
|
@@ -911,7 +397,7 @@ var require_Evaluator = __commonJS({
|
|
|
911
397
|
*/
|
|
912
398
|
}, {
|
|
913
399
|
key: "_filterRelative",
|
|
914
|
-
value: function _filterRelative(subject,
|
|
400
|
+
value: function _filterRelative(subject, expr2) {
|
|
915
401
|
var _this4 = this;
|
|
916
402
|
var promises = [];
|
|
917
403
|
if (!Array.isArray(subject)) {
|
|
@@ -919,7 +405,7 @@ var require_Evaluator = __commonJS({
|
|
|
919
405
|
}
|
|
920
406
|
subject.forEach(function(elem) {
|
|
921
407
|
var evalInst = new Evaluator2(_this4._grammar, _this4._context, elem, _this4.Promise);
|
|
922
|
-
promises.push(evalInst.eval(
|
|
408
|
+
promises.push(evalInst.eval(expr2));
|
|
923
409
|
});
|
|
924
410
|
return this.Promise.all(promises).then(function(values) {
|
|
925
411
|
var results = [];
|
|
@@ -948,8 +434,8 @@ var require_Evaluator = __commonJS({
|
|
|
948
434
|
*/
|
|
949
435
|
}, {
|
|
950
436
|
key: "_filterStatic",
|
|
951
|
-
value: function _filterStatic(subject,
|
|
952
|
-
return this.eval(
|
|
437
|
+
value: function _filterStatic(subject, expr2) {
|
|
438
|
+
return this.eval(expr2).then(function(res) {
|
|
953
439
|
if (typeof res === "boolean") {
|
|
954
440
|
return res ? subject : void 0;
|
|
955
441
|
}
|
|
@@ -2253,7 +1739,7 @@ var require_Jexl = __commonJS({
|
|
|
2253
1739
|
*/
|
|
2254
1740
|
}, {
|
|
2255
1741
|
key: "expr",
|
|
2256
|
-
value: function
|
|
1742
|
+
value: function expr2(strs) {
|
|
2257
1743
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
2258
1744
|
args[_key - 1] = arguments[_key];
|
|
2259
1745
|
}
|
|
@@ -2298,12 +1784,20 @@ var require_Jexl = __commonJS({
|
|
|
2298
1784
|
// src/index.ts
|
|
2299
1785
|
var index_exports = {};
|
|
2300
1786
|
__export(index_exports, {
|
|
1787
|
+
AI_ACTIONS_COLLECTION: () => AI_ACTIONS_COLLECTION,
|
|
1788
|
+
AI_AUDIT_COLLECTION: () => AI_AUDIT_COLLECTION,
|
|
1789
|
+
AI_CHUNKS_COLLECTION: () => AI_CHUNKS_COLLECTION,
|
|
1790
|
+
AI_MESSAGES_COLLECTION: () => AI_MESSAGES_COLLECTION,
|
|
1791
|
+
AI_THREADS_COLLECTION: () => AI_THREADS_COLLECTION,
|
|
2301
1792
|
BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
|
|
2302
1793
|
ConfigValidationError: () => ConfigValidationError,
|
|
1794
|
+
DyrectedAIError: () => DyrectedAIError,
|
|
2303
1795
|
FieldConditionBuilder: () => FieldConditionBuilder,
|
|
2304
1796
|
LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
|
|
2305
1797
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
2306
1798
|
MatchBuilder: () => MatchBuilder,
|
|
1799
|
+
NON_NEGOTIABLE_CREDENTIAL_FIELDS: () => NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
1800
|
+
PII_REGEX_PATTERNS: () => PII_REGEX_PATTERNS,
|
|
2307
1801
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
2308
1802
|
abs: () => abs,
|
|
2309
1803
|
addDays: () => addDays,
|
|
@@ -2381,6 +1875,7 @@ __export(index_exports, {
|
|
|
2381
1875
|
evaluateJexlSync: () => evaluateJexlSync,
|
|
2382
1876
|
executeFieldAfterRead: () => executeFieldAfterRead,
|
|
2383
1877
|
executeFieldBeforeChange: () => executeFieldBeforeChange,
|
|
1878
|
+
expr: () => expr,
|
|
2384
1879
|
first: () => first,
|
|
2385
1880
|
floor: () => floor,
|
|
2386
1881
|
formatConfigDiagnostics: () => formatConfigDiagnostics,
|
|
@@ -2390,18 +1885,24 @@ __export(index_exports, {
|
|
|
2390
1885
|
generateOpenApi: () => generateOpenApi,
|
|
2391
1886
|
get: () => get,
|
|
2392
1887
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
1888
|
+
getAllowedSitesForUser: () => getAllowedSitesForUser,
|
|
2393
1889
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
2394
1890
|
includes: () => includes,
|
|
2395
1891
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
1892
|
+
isAICollection: () => isAICollection,
|
|
2396
1893
|
isConfigValidationError: () => isConfigValidationError,
|
|
2397
1894
|
isDetailItemVisible: () => isDetailItemVisible,
|
|
2398
1895
|
isEmpty: () => isEmpty,
|
|
2399
1896
|
isFuture: () => isFuture,
|
|
2400
1897
|
isPast: () => isPast,
|
|
1898
|
+
isSiteAuthorized: () => isSiteAuthorized,
|
|
2401
1899
|
join: () => join,
|
|
2402
1900
|
last: () => last,
|
|
2403
1901
|
length: () => length,
|
|
2404
1902
|
lower: () => lower,
|
|
1903
|
+
maskEmail: () => maskEmail,
|
|
1904
|
+
maskPhone: () => maskPhone,
|
|
1905
|
+
maskTextPII: () => maskTextPII,
|
|
2405
1906
|
materializeWorkflowDocument: () => materializeWorkflowDocument,
|
|
2406
1907
|
max: () => max,
|
|
2407
1908
|
min: () => min,
|
|
@@ -2419,8 +1920,10 @@ __export(index_exports, {
|
|
|
2419
1920
|
registerJexlHelpers: () => registerJexlHelpers,
|
|
2420
1921
|
replace: () => replace,
|
|
2421
1922
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
1923
|
+
resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
|
|
2422
1924
|
round: () => round,
|
|
2423
1925
|
runCollectionHooks: () => runCollectionHooks,
|
|
1926
|
+
sanitizeDocForAI: () => sanitizeDocForAI,
|
|
2424
1927
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
2425
1928
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
2426
1929
|
slugify: () => slugify,
|
|
@@ -2439,7 +1942,8 @@ __export(index_exports, {
|
|
|
2439
1942
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
2440
1943
|
when: () => when,
|
|
2441
1944
|
wordCount: () => wordCount,
|
|
2442
|
-
workflowCapabilities: () => workflowCapabilities
|
|
1945
|
+
workflowCapabilities: () => workflowCapabilities,
|
|
1946
|
+
z: () => import_zod.z
|
|
2443
1947
|
});
|
|
2444
1948
|
module.exports = __toCommonJS(index_exports);
|
|
2445
1949
|
|
|
@@ -2459,6 +1963,40 @@ function defineAction(config) {
|
|
|
2459
1963
|
return config;
|
|
2460
1964
|
}
|
|
2461
1965
|
|
|
1966
|
+
// src/types/ai.ts
|
|
1967
|
+
var AI_THREADS_COLLECTION = "_dyrected_ai_threads";
|
|
1968
|
+
var AI_MESSAGES_COLLECTION = "_dyrected_ai_messages";
|
|
1969
|
+
var AI_CHUNKS_COLLECTION = "_dyrected_ai_chunks";
|
|
1970
|
+
var AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
|
|
1971
|
+
var AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
|
|
1972
|
+
function isAICollection(slug) {
|
|
1973
|
+
return slug === AI_THREADS_COLLECTION || slug === AI_MESSAGES_COLLECTION || slug === AI_CHUNKS_COLLECTION || slug === AI_ACTIONS_COLLECTION || slug === AI_AUDIT_COLLECTION;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// src/types/ai-errors.ts
|
|
1977
|
+
var DyrectedAIError = class _DyrectedAIError extends Error {
|
|
1978
|
+
code;
|
|
1979
|
+
status;
|
|
1980
|
+
details;
|
|
1981
|
+
constructor(code, message, status = 500, details) {
|
|
1982
|
+
super(message);
|
|
1983
|
+
this.name = "DyrectedAIError";
|
|
1984
|
+
this.code = code;
|
|
1985
|
+
this.status = status;
|
|
1986
|
+
this.details = details;
|
|
1987
|
+
Object.setPrototypeOf(this, _DyrectedAIError.prototype);
|
|
1988
|
+
}
|
|
1989
|
+
toJSON() {
|
|
1990
|
+
return {
|
|
1991
|
+
error: true,
|
|
1992
|
+
code: this.code,
|
|
1993
|
+
message: this.message,
|
|
1994
|
+
status: this.status,
|
|
1995
|
+
details: this.details
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2462
2000
|
// src/auth/token.ts
|
|
2463
2001
|
var import_jose = require("jose");
|
|
2464
2002
|
|
|
@@ -2861,18 +2399,31 @@ async function transitionWorkflow(args) {
|
|
|
2861
2399
|
await hook({ ...hookContext, doc: updated, event: events[0] });
|
|
2862
2400
|
} catch (error) {
|
|
2863
2401
|
try {
|
|
2864
|
-
const
|
|
2865
|
-
|
|
2402
|
+
const obs = config?.__observability;
|
|
2403
|
+
obs?.recordWorkflowHookFailure?.({
|
|
2866
2404
|
collection: collection.slug,
|
|
2867
2405
|
transition: transition.name
|
|
2868
2406
|
});
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2407
|
+
const logger = config?.logger;
|
|
2408
|
+
if (logger && typeof logger.child === "function") {
|
|
2409
|
+
logger.child({ component: "workflow" }).error({
|
|
2410
|
+
err: error,
|
|
2411
|
+
msg: "afterTransition hook failed",
|
|
2412
|
+
collection: collection.slug,
|
|
2413
|
+
transition: transition.name,
|
|
2414
|
+
documentId: id
|
|
2415
|
+
});
|
|
2416
|
+
} else if (logger && typeof logger.error === "function") {
|
|
2417
|
+
logger.error({
|
|
2418
|
+
err: error,
|
|
2419
|
+
msg: "afterTransition hook failed",
|
|
2420
|
+
collection: collection.slug,
|
|
2421
|
+
transition: transition.name,
|
|
2422
|
+
documentId: id
|
|
2423
|
+
});
|
|
2424
|
+
} else {
|
|
2425
|
+
console.error("afterTransition hook failed:", error);
|
|
2426
|
+
}
|
|
2876
2427
|
} catch {
|
|
2877
2428
|
console.error("afterTransition hook failed:", error);
|
|
2878
2429
|
}
|
|
@@ -3977,6 +3528,21 @@ var SYSTEM_FIELDS = [
|
|
|
3977
3528
|
admin: { readOnly: true, hidden: true }
|
|
3978
3529
|
}
|
|
3979
3530
|
];
|
|
3531
|
+
var MEDIA_SYSTEM_FIELDS = [
|
|
3532
|
+
{ name: "folderId", type: "text", label: "Folder", admin: { hidden: true } },
|
|
3533
|
+
{ name: "filename", type: "text", label: "Filename", required: true },
|
|
3534
|
+
{ name: "originalFilename", type: "text", label: "Original Filename", admin: { hidden: true } },
|
|
3535
|
+
{ name: "mimeType", type: "text", label: "MIME Type", admin: { hidden: true } },
|
|
3536
|
+
{ name: "filesize", type: "number", label: "File Size", admin: { hidden: true } },
|
|
3537
|
+
{ name: "url", type: "text", label: "URL", admin: { hidden: true } },
|
|
3538
|
+
{ name: "width", type: "number", label: "Width", admin: { hidden: true } },
|
|
3539
|
+
{ name: "height", type: "number", label: "Height", admin: { hidden: true } },
|
|
3540
|
+
{ name: "aspectRatio", type: "number", label: "Aspect Ratio", admin: { hidden: true } },
|
|
3541
|
+
{ name: "blurhash", type: "text", label: "Blurhash", admin: { hidden: true } },
|
|
3542
|
+
{ name: "focalPoint", type: "json", label: "Focal Point", admin: { hidden: true } },
|
|
3543
|
+
{ name: "alt", type: "text", label: "Alt Text" },
|
|
3544
|
+
{ name: "caption", type: "text", label: "Caption" }
|
|
3545
|
+
];
|
|
3980
3546
|
var AUDIT_COLLECTION = {
|
|
3981
3547
|
slug: AUDIT_COLLECTION_SLUG,
|
|
3982
3548
|
labels: { singular: "Audit Log", plural: "Audit Logs" },
|
|
@@ -4248,6 +3814,11 @@ function normalizeConfig(config) {
|
|
|
4248
3814
|
}
|
|
4249
3815
|
}
|
|
4250
3816
|
}
|
|
3817
|
+
if (col.upload || col.slug === "media") {
|
|
3818
|
+
const currentNames = new Set(fields.map((f) => f.name));
|
|
3819
|
+
const mediaFieldsToInject = MEDIA_SYSTEM_FIELDS.filter((f) => !currentNames.has(f.name));
|
|
3820
|
+
fields = [...fields, ...mediaFieldsToInject];
|
|
3821
|
+
}
|
|
4251
3822
|
const updatedFieldNames = new Set(fields.map((f) => f.name));
|
|
4252
3823
|
const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
|
|
4253
3824
|
const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
|
|
@@ -4540,8 +4111,8 @@ function buildSqlOrderBy(sort, existingColumns, numericFields, dialect) {
|
|
|
4540
4111
|
return `${dialect.quoteIdent(field)} ${direction}`;
|
|
4541
4112
|
}
|
|
4542
4113
|
const extracted = dialect.jsonExtract(field);
|
|
4543
|
-
const
|
|
4544
|
-
return `${
|
|
4114
|
+
const expr2 = numericFields.has(field) ? dialect.castNumeric(extracted) : extracted;
|
|
4115
|
+
return `${expr2} ${direction}`;
|
|
4545
4116
|
}).join(", ");
|
|
4546
4117
|
}
|
|
4547
4118
|
|
|
@@ -5002,6 +4573,182 @@ function generateOpenApi(config) {
|
|
|
5002
4573
|
}
|
|
5003
4574
|
}
|
|
5004
4575
|
};
|
|
4576
|
+
spec.paths["/api/ai/chat"] = {
|
|
4577
|
+
post: {
|
|
4578
|
+
tags: ["AI"],
|
|
4579
|
+
summary: "Send prompt and stream AI assistant response",
|
|
4580
|
+
requestBody: {
|
|
4581
|
+
required: true,
|
|
4582
|
+
content: {
|
|
4583
|
+
"application/json": {
|
|
4584
|
+
schema: {
|
|
4585
|
+
type: "object",
|
|
4586
|
+
properties: {
|
|
4587
|
+
content: { type: "string", description: "User prompt message" },
|
|
4588
|
+
prompt: { type: "string", description: "Alias for content" },
|
|
4589
|
+
threadId: { type: "string", description: "Existing conversation thread ID" },
|
|
4590
|
+
messages: { type: "array", items: { type: "object" }, description: "Conversation history" }
|
|
4591
|
+
},
|
|
4592
|
+
required: []
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
responses: {
|
|
4598
|
+
200: {
|
|
4599
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4600
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
spec.paths["/api/ai/threads"] = {
|
|
4606
|
+
get: {
|
|
4607
|
+
tags: ["AI"],
|
|
4608
|
+
summary: "List conversation threads",
|
|
4609
|
+
parameters: [
|
|
4610
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } }
|
|
4611
|
+
],
|
|
4612
|
+
responses: { 200: { description: "Array of conversation threads" } }
|
|
4613
|
+
},
|
|
4614
|
+
post: {
|
|
4615
|
+
tags: ["AI"],
|
|
4616
|
+
summary: "Create a new conversation thread",
|
|
4617
|
+
requestBody: {
|
|
4618
|
+
content: {
|
|
4619
|
+
"application/json": {
|
|
4620
|
+
schema: {
|
|
4621
|
+
type: "object",
|
|
4622
|
+
properties: { title: { type: "string" } }
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
},
|
|
4627
|
+
responses: { 201: { description: "Created conversation thread" } }
|
|
4628
|
+
},
|
|
4629
|
+
delete: {
|
|
4630
|
+
tags: ["AI"],
|
|
4631
|
+
summary: "Clear all conversation threads",
|
|
4632
|
+
responses: { 200: { description: "Count of deleted threads" } }
|
|
4633
|
+
}
|
|
4634
|
+
};
|
|
4635
|
+
spec.paths["/api/ai/threads/{threadId}"] = {
|
|
4636
|
+
get: {
|
|
4637
|
+
tags: ["AI"],
|
|
4638
|
+
summary: "Get conversation thread and historical messages",
|
|
4639
|
+
parameters: [
|
|
4640
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4641
|
+
],
|
|
4642
|
+
responses: { 200: { description: "Thread details and messages" } }
|
|
4643
|
+
},
|
|
4644
|
+
delete: {
|
|
4645
|
+
tags: ["AI"],
|
|
4646
|
+
summary: "Delete a specific conversation thread",
|
|
4647
|
+
parameters: [
|
|
4648
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4649
|
+
],
|
|
4650
|
+
responses: { 200: { description: "Deletion success status" } }
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
spec.paths["/api/ai/threads/{threadId}/messages"] = {
|
|
4654
|
+
post: {
|
|
4655
|
+
tags: ["AI"],
|
|
4656
|
+
summary: "Post a message to an existing thread and stream reply",
|
|
4657
|
+
parameters: [
|
|
4658
|
+
{ name: "threadId", in: "path", required: true, schema: { type: "string" } }
|
|
4659
|
+
],
|
|
4660
|
+
requestBody: {
|
|
4661
|
+
required: true,
|
|
4662
|
+
content: {
|
|
4663
|
+
"application/json": {
|
|
4664
|
+
schema: {
|
|
4665
|
+
type: "object",
|
|
4666
|
+
properties: { content: { type: "string" } },
|
|
4667
|
+
required: ["content"]
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
},
|
|
4672
|
+
responses: {
|
|
4673
|
+
200: {
|
|
4674
|
+
description: "Server-Sent Events (SSE) AI message stream",
|
|
4675
|
+
content: { "text/event-stream": { schema: { type: "string" } } }
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
};
|
|
4680
|
+
spec.paths["/api/ai/actions/{actionId}"] = {
|
|
4681
|
+
get: {
|
|
4682
|
+
tags: ["AI"],
|
|
4683
|
+
summary: "Get an AI mutation action proposal",
|
|
4684
|
+
parameters: [
|
|
4685
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4686
|
+
],
|
|
4687
|
+
responses: { 200: { description: "AI Action details and diff payload" } }
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
spec.paths["/api/ai/actions/{actionId}/execute"] = {
|
|
4691
|
+
post: {
|
|
4692
|
+
tags: ["AI"],
|
|
4693
|
+
summary: "Approve and execute an AI mutation proposal",
|
|
4694
|
+
parameters: [
|
|
4695
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4696
|
+
],
|
|
4697
|
+
responses: { 200: { description: "Execution result" } }
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
spec.paths["/api/ai/actions/{actionId}/reject"] = {
|
|
4701
|
+
post: {
|
|
4702
|
+
tags: ["AI"],
|
|
4703
|
+
summary: "Reject an AI mutation proposal",
|
|
4704
|
+
parameters: [
|
|
4705
|
+
{ name: "actionId", in: "path", required: true, schema: { type: "string" } }
|
|
4706
|
+
],
|
|
4707
|
+
responses: { 200: { description: "Rejection result" } }
|
|
4708
|
+
}
|
|
4709
|
+
};
|
|
4710
|
+
spec.paths["/api/ai/rag/reindex"] = {
|
|
4711
|
+
post: {
|
|
4712
|
+
tags: ["AI"],
|
|
4713
|
+
summary: "Trigger vector reindexing for semantic search",
|
|
4714
|
+
requestBody: {
|
|
4715
|
+
content: {
|
|
4716
|
+
"application/json": {
|
|
4717
|
+
schema: {
|
|
4718
|
+
type: "object",
|
|
4719
|
+
properties: {
|
|
4720
|
+
collection: { type: "string" },
|
|
4721
|
+
force: { type: "boolean" }
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
},
|
|
4727
|
+
responses: { 200: { description: "Reindexing results and chunk counts" } }
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
spec.paths["/api/ai/rag/search"] = {
|
|
4731
|
+
post: {
|
|
4732
|
+
tags: ["AI"],
|
|
4733
|
+
summary: "Perform semantic vector search against indexed content",
|
|
4734
|
+
requestBody: {
|
|
4735
|
+
required: true,
|
|
4736
|
+
content: {
|
|
4737
|
+
"application/json": {
|
|
4738
|
+
schema: {
|
|
4739
|
+
type: "object",
|
|
4740
|
+
properties: {
|
|
4741
|
+
query: { type: "string" },
|
|
4742
|
+
limit: { type: "integer", default: 5 }
|
|
4743
|
+
},
|
|
4744
|
+
required: ["query"]
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
},
|
|
4749
|
+
responses: { 200: { description: "Semantic search results" } }
|
|
4750
|
+
}
|
|
4751
|
+
};
|
|
5005
4752
|
for (const collection of config.collections) {
|
|
5006
4753
|
spec.components.schemas[collection.slug] = collectionToSchema(collection);
|
|
5007
4754
|
}
|
|
@@ -6122,12 +5869,12 @@ var MatchBuilder = class {
|
|
|
6122
5869
|
*/
|
|
6123
5870
|
otherwise(fallback = "null") {
|
|
6124
5871
|
if (this.branches.length === 0) return formatJexlValue(fallback);
|
|
6125
|
-
let
|
|
5872
|
+
let expr2 = formatJexlValue(fallback);
|
|
6126
5873
|
for (let i = this.branches.length - 1; i >= 0; i--) {
|
|
6127
5874
|
const b = this.branches[i];
|
|
6128
|
-
|
|
5875
|
+
expr2 = `${b.condition} ? ${formatJexlValue(b.value)} : ${expr2}`;
|
|
6129
5876
|
}
|
|
6130
|
-
return
|
|
5877
|
+
return expr2;
|
|
6131
5878
|
}
|
|
6132
5879
|
};
|
|
6133
5880
|
var FieldConditionBuilder = class {
|
|
@@ -6185,6 +5932,12 @@ var FieldConditionBuilder = class {
|
|
|
6185
5932
|
isEmpty() {
|
|
6186
5933
|
return `${this.field} == null || ${this.field} == ''`;
|
|
6187
5934
|
}
|
|
5935
|
+
/**
|
|
5936
|
+
* Alias for `isEmpty()`.
|
|
5937
|
+
*/
|
|
5938
|
+
empty() {
|
|
5939
|
+
return this.isEmpty();
|
|
5940
|
+
}
|
|
6188
5941
|
/**
|
|
6189
5942
|
* Numerical greater than (`field > num`).
|
|
6190
5943
|
*/
|
|
@@ -6245,20 +5998,44 @@ var FieldConditionBuilder = class {
|
|
|
6245
5998
|
hasLengthAtLeast(length2) {
|
|
6246
5999
|
return `length(${this.field}) >= ${length2}`;
|
|
6247
6000
|
}
|
|
6001
|
+
/**
|
|
6002
|
+
* Generates a JEXL `slugify(field)` transform expression.
|
|
6003
|
+
*/
|
|
6004
|
+
slugify() {
|
|
6005
|
+
return `slugify(${this.field})`;
|
|
6006
|
+
}
|
|
6007
|
+
/**
|
|
6008
|
+
* Generates a JEXL `lower(field)` transform expression.
|
|
6009
|
+
*/
|
|
6010
|
+
lower() {
|
|
6011
|
+
return `lower(${this.field})`;
|
|
6012
|
+
}
|
|
6013
|
+
/**
|
|
6014
|
+
* Generates a JEXL `upper(field)` transform expression.
|
|
6015
|
+
*/
|
|
6016
|
+
upper() {
|
|
6017
|
+
return `upper(${this.field})`;
|
|
6018
|
+
}
|
|
6019
|
+
/**
|
|
6020
|
+
* Generates a JEXL `trim(field)` transform expression.
|
|
6021
|
+
*/
|
|
6022
|
+
trim() {
|
|
6023
|
+
return `trim(${this.field})`;
|
|
6024
|
+
}
|
|
6248
6025
|
};
|
|
6249
|
-
function
|
|
6250
|
-
const
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6026
|
+
function createExpr() {
|
|
6027
|
+
const exprFn = ((fieldName) => new FieldConditionBuilder(fieldName));
|
|
6028
|
+
exprFn.user = (userProp) => new FieldConditionBuilder(`user.${userProp}`);
|
|
6029
|
+
exprFn.sibling = (siblingProp) => new FieldConditionBuilder(`siblingData.${siblingProp}`);
|
|
6030
|
+
exprFn.concat = (...parts) => {
|
|
6254
6031
|
if (parts.length === 0) return "''";
|
|
6255
6032
|
return parts.map((p) => formatJexlValue(p)).join(" + ");
|
|
6256
6033
|
};
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6034
|
+
exprFn.slugify = (field) => `slugify(${field})`;
|
|
6035
|
+
exprFn.lower = (field) => `lower(${field})`;
|
|
6036
|
+
exprFn.upper = (field) => `upper(${field})`;
|
|
6037
|
+
exprFn.trim = (field) => `trim(${field})`;
|
|
6038
|
+
exprFn.access = {
|
|
6262
6039
|
isOwner: (ownerField = "author") => {
|
|
6263
6040
|
return `${ownerField} == user.id || ${ownerField}.id == user.id`;
|
|
6264
6041
|
},
|
|
@@ -6266,17 +6043,18 @@ function createWhen() {
|
|
|
6266
6043
|
return "user.role == 'admin' || (user.roles != null && 'admin' in user.roles)";
|
|
6267
6044
|
},
|
|
6268
6045
|
hasRole: (...roles) => {
|
|
6269
|
-
return
|
|
6046
|
+
return exprFn.userRole(...roles);
|
|
6270
6047
|
},
|
|
6271
6048
|
isPublishedOrAdmin: (statusField = "status") => {
|
|
6272
6049
|
return `${statusField} == 'published' || user.role == 'admin' || (user.roles != null && 'admin' in user.roles)`;
|
|
6273
6050
|
}
|
|
6274
6051
|
};
|
|
6275
|
-
|
|
6052
|
+
exprFn.ifElse = (condition, ifTrue, ifFalse = "null") => {
|
|
6276
6053
|
return `${condition} ? ${formatJexlValue(ifTrue)} : ${formatJexlValue(ifFalse)}`;
|
|
6277
6054
|
};
|
|
6278
|
-
|
|
6279
|
-
|
|
6055
|
+
exprFn.then = exprFn.ifElse;
|
|
6056
|
+
exprFn.match = () => new MatchBuilder();
|
|
6057
|
+
exprFn.cases = (...branches) => {
|
|
6280
6058
|
const builder = new MatchBuilder();
|
|
6281
6059
|
let fallback = "null";
|
|
6282
6060
|
for (const b of branches) {
|
|
@@ -6288,114 +6066,130 @@ function createWhen() {
|
|
|
6288
6066
|
}
|
|
6289
6067
|
return builder.otherwise(fallback);
|
|
6290
6068
|
};
|
|
6291
|
-
|
|
6069
|
+
exprFn.all = (...conditions) => {
|
|
6292
6070
|
const valid = conditions.filter(Boolean);
|
|
6293
6071
|
if (valid.length === 0) return "true";
|
|
6294
6072
|
if (valid.length === 1) return valid[0];
|
|
6295
6073
|
return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" && ");
|
|
6296
6074
|
};
|
|
6297
|
-
|
|
6298
|
-
|
|
6075
|
+
exprFn.and = exprFn.all;
|
|
6076
|
+
exprFn.any = (...conditions) => {
|
|
6299
6077
|
const valid = conditions.filter(Boolean);
|
|
6300
6078
|
if (valid.length === 0) return "true";
|
|
6301
6079
|
if (valid.length === 1) return valid[0];
|
|
6302
6080
|
return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" || ");
|
|
6303
6081
|
};
|
|
6304
|
-
|
|
6305
|
-
|
|
6082
|
+
exprFn.or = exprFn.any;
|
|
6083
|
+
exprFn.not = (condition) => {
|
|
6306
6084
|
if (!condition) return "false";
|
|
6307
6085
|
return `!(${condition})`;
|
|
6308
6086
|
};
|
|
6309
|
-
|
|
6087
|
+
exprFn.variant = (...variants) => {
|
|
6310
6088
|
const flat = variants.flat();
|
|
6311
6089
|
if (flat.length === 0) return "true";
|
|
6312
6090
|
if (flat.length === 1) return `variant == '${flat[0]}'`;
|
|
6313
6091
|
return `variant in [${flat.map((v) => `'${v}'`).join(", ")}]`;
|
|
6314
6092
|
};
|
|
6315
|
-
|
|
6093
|
+
exprFn.notVariant = (...variants) => {
|
|
6316
6094
|
const flat = variants.flat();
|
|
6317
6095
|
if (flat.length === 0) return "false";
|
|
6318
6096
|
if (flat.length === 1) return `variant != '${flat[0]}'`;
|
|
6319
6097
|
return `!(variant in [${flat.map((v) => `'${v}'`).join(", ")}])`;
|
|
6320
6098
|
};
|
|
6321
|
-
|
|
6099
|
+
exprFn.block = (...blocks) => {
|
|
6322
6100
|
const flat = blocks.flat();
|
|
6323
6101
|
if (flat.length === 0) return "true";
|
|
6324
6102
|
if (flat.length === 1) return `block == '${flat[0]}'`;
|
|
6325
6103
|
return `block in [${flat.map((b) => `'${b}'`).join(", ")}]`;
|
|
6326
6104
|
};
|
|
6327
|
-
|
|
6105
|
+
exprFn.notBlock = (...blocks) => {
|
|
6328
6106
|
const flat = blocks.flat();
|
|
6329
6107
|
if (flat.length === 0) return "false";
|
|
6330
6108
|
if (flat.length === 1) return `block != '${flat[0]}'`;
|
|
6331
6109
|
return `!(block in [${flat.map((b) => `'${b}'`).join(", ")}])`;
|
|
6332
6110
|
};
|
|
6333
|
-
|
|
6111
|
+
exprFn.equals = (field, value) => {
|
|
6334
6112
|
return `${field} == ${JSON.stringify(value)}`;
|
|
6335
6113
|
};
|
|
6336
|
-
|
|
6114
|
+
exprFn.fieldEquals = exprFn.equals;
|
|
6115
|
+
exprFn.notEquals = (field, value) => {
|
|
6337
6116
|
return `${field} != ${JSON.stringify(value)}`;
|
|
6338
6117
|
};
|
|
6339
|
-
|
|
6118
|
+
exprFn.fieldNotEquals = exprFn.notEquals;
|
|
6119
|
+
exprFn.in = (field, ...values) => {
|
|
6340
6120
|
const flat = values.flat();
|
|
6341
6121
|
return `${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
6342
6122
|
};
|
|
6343
|
-
|
|
6123
|
+
exprFn.fieldIn = exprFn.in;
|
|
6124
|
+
exprFn.notIn = (field, ...values) => {
|
|
6344
6125
|
const flat = values.flat();
|
|
6345
6126
|
return `!(${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
6346
6127
|
};
|
|
6347
|
-
|
|
6128
|
+
exprFn.fieldNotIn = exprFn.notIn;
|
|
6129
|
+
exprFn.notEmpty = (field) => {
|
|
6348
6130
|
return `${field} != null && ${field} != ''`;
|
|
6349
6131
|
};
|
|
6350
|
-
|
|
6132
|
+
exprFn.fieldNotEmpty = exprFn.notEmpty;
|
|
6133
|
+
exprFn.empty = (field) => {
|
|
6351
6134
|
return `${field} == null || ${field} == ''`;
|
|
6352
6135
|
};
|
|
6353
|
-
|
|
6136
|
+
exprFn.fieldEmpty = exprFn.empty;
|
|
6137
|
+
exprFn.isTrue = (field) => {
|
|
6354
6138
|
return `${field} == true`;
|
|
6355
6139
|
};
|
|
6356
|
-
|
|
6140
|
+
exprFn.fieldIsTrue = exprFn.isTrue;
|
|
6141
|
+
exprFn.isFalse = (field) => {
|
|
6357
6142
|
return `${field} != true`;
|
|
6358
6143
|
};
|
|
6359
|
-
|
|
6144
|
+
exprFn.fieldIsFalse = exprFn.isFalse;
|
|
6145
|
+
exprFn.greaterThan = (field, value) => {
|
|
6360
6146
|
return `${field} > ${value}`;
|
|
6361
6147
|
};
|
|
6362
|
-
|
|
6148
|
+
exprFn.fieldGreaterThan = exprFn.greaterThan;
|
|
6149
|
+
exprFn.greaterThanOrEqual = (field, value) => {
|
|
6363
6150
|
return `${field} >= ${value}`;
|
|
6364
6151
|
};
|
|
6365
|
-
|
|
6152
|
+
exprFn.fieldGreaterThanOrEqual = exprFn.greaterThanOrEqual;
|
|
6153
|
+
exprFn.lessThan = (field, value) => {
|
|
6366
6154
|
return `${field} < ${value}`;
|
|
6367
6155
|
};
|
|
6368
|
-
|
|
6156
|
+
exprFn.fieldLessThan = exprFn.lessThan;
|
|
6157
|
+
exprFn.lessThanOrEqual = (field, value) => {
|
|
6369
6158
|
return `${field} <= ${value}`;
|
|
6370
6159
|
};
|
|
6371
|
-
|
|
6160
|
+
exprFn.fieldLessThanOrEqual = exprFn.lessThanOrEqual;
|
|
6161
|
+
exprFn.between = (field, min2, max2) => {
|
|
6372
6162
|
return `${field} >= ${min2} && ${field} <= ${max2}`;
|
|
6373
6163
|
};
|
|
6374
|
-
|
|
6164
|
+
exprFn.fieldBetween = exprFn.between;
|
|
6165
|
+
exprFn.startsWith = (field, prefix) => {
|
|
6375
6166
|
return `startsWith(${field}, ${JSON.stringify(prefix)})`;
|
|
6376
6167
|
};
|
|
6377
|
-
|
|
6168
|
+
exprFn.fieldStartsWith = exprFn.startsWith;
|
|
6169
|
+
exprFn.endsWith = (field, suffix) => {
|
|
6378
6170
|
return `endsWith(${field}, ${JSON.stringify(suffix)})`;
|
|
6379
6171
|
};
|
|
6380
|
-
|
|
6172
|
+
exprFn.fieldEndsWith = exprFn.endsWith;
|
|
6173
|
+
exprFn.contains = (field, item) => {
|
|
6381
6174
|
return `includes(${field}, ${JSON.stringify(item)})`;
|
|
6382
6175
|
};
|
|
6383
|
-
|
|
6176
|
+
exprFn.fieldContains = exprFn.contains;
|
|
6177
|
+
exprFn.arrayNotEmpty = (field) => {
|
|
6384
6178
|
return `length(${field}) > 0`;
|
|
6385
6179
|
};
|
|
6386
|
-
|
|
6180
|
+
exprFn.arrayEmpty = (field) => {
|
|
6387
6181
|
return `length(${field}) == 0`;
|
|
6388
6182
|
};
|
|
6389
|
-
|
|
6183
|
+
exprFn.arrayCountGreaterThan = (field, count2) => {
|
|
6390
6184
|
return `length(${field}) > ${count2}`;
|
|
6391
6185
|
};
|
|
6392
|
-
|
|
6186
|
+
exprFn.arrayCountAtLeast = (field, count2) => {
|
|
6393
6187
|
return `length(${field}) >= ${count2}`;
|
|
6394
6188
|
};
|
|
6395
|
-
|
|
6189
|
+
exprFn.arrayCountLessThan = (field, count2) => {
|
|
6396
6190
|
return `length(${field}) < ${count2}`;
|
|
6397
6191
|
};
|
|
6398
|
-
|
|
6192
|
+
exprFn.userRole = (...roles) => {
|
|
6399
6193
|
const flat = roles.flat();
|
|
6400
6194
|
if (flat.length === 1) {
|
|
6401
6195
|
return `user.role == '${flat[0]}' || (user.roles != null && '${flat[0]}' in user.roles)`;
|
|
@@ -6403,31 +6197,265 @@ function createWhen() {
|
|
|
6403
6197
|
const roleArray = `[${flat.map((r) => `'${r}'`).join(", ")}]`;
|
|
6404
6198
|
return `user.role in ${roleArray} || (user.roles != null && includes(${roleArray}, user.role))`;
|
|
6405
6199
|
};
|
|
6406
|
-
|
|
6200
|
+
exprFn.userEmailDomain = (domain) => {
|
|
6407
6201
|
const clean = domain.startsWith("@") ? domain : `@${domain}`;
|
|
6408
6202
|
return `endsWith(user.email, '${clean}')`;
|
|
6409
6203
|
};
|
|
6410
|
-
|
|
6204
|
+
exprFn.userAttributeEquals = (attr, value) => {
|
|
6411
6205
|
return `user.${attr} == ${JSON.stringify(value)}`;
|
|
6412
6206
|
};
|
|
6413
|
-
|
|
6207
|
+
exprFn.isNewDocument = () => {
|
|
6414
6208
|
return "id == null";
|
|
6415
6209
|
};
|
|
6416
|
-
|
|
6210
|
+
exprFn.isExistingDocument = () => {
|
|
6417
6211
|
return "id != null";
|
|
6418
6212
|
};
|
|
6419
|
-
|
|
6213
|
+
exprFn.statusEquals = (status) => {
|
|
6420
6214
|
return `status == '${status}'`;
|
|
6421
6215
|
};
|
|
6422
|
-
|
|
6216
|
+
exprFn.statusIn = (...statuses) => {
|
|
6423
6217
|
const flat = statuses.flat();
|
|
6424
6218
|
return `status in [${flat.map((s) => `'${s}'`).join(", ")}]`;
|
|
6425
6219
|
};
|
|
6426
|
-
return
|
|
6220
|
+
return exprFn;
|
|
6221
|
+
}
|
|
6222
|
+
var expr = createExpr();
|
|
6223
|
+
var when = expr;
|
|
6224
|
+
|
|
6225
|
+
// src/utils/tenant.ts
|
|
6226
|
+
var import_http_exception = require("hono/http-exception");
|
|
6227
|
+
function getAllowedSitesForUser(user, payload) {
|
|
6228
|
+
const raw = user?.allowedSites ?? user?.siteIds ?? user?.siteAccess ?? (user?.siteId ? [user.siteId] : void 0) ?? payload?.allowedSites ?? payload?.siteIds ?? payload?.siteAccess ?? (payload?.siteId ? [payload.siteId] : void 0);
|
|
6229
|
+
if (Array.isArray(raw)) {
|
|
6230
|
+
return raw.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
6231
|
+
}
|
|
6232
|
+
return void 0;
|
|
6233
|
+
}
|
|
6234
|
+
function isSiteAuthorized(user, payload, requestedSiteId) {
|
|
6235
|
+
const allowedSites = getAllowedSitesForUser(user, payload);
|
|
6236
|
+
if (!allowedSites || allowedSites.length === 0) {
|
|
6237
|
+
return true;
|
|
6238
|
+
}
|
|
6239
|
+
if (!requestedSiteId) {
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
return allowedSites.includes(requestedSiteId);
|
|
6243
|
+
}
|
|
6244
|
+
function resolveAuthorizedSiteId(c) {
|
|
6245
|
+
const user = typeof c.get === "function" ? c.get("user") : void 0;
|
|
6246
|
+
const tokenPayload = typeof c.get === "function" ? c.get("authTokenPayload") : void 0;
|
|
6247
|
+
const requestedSiteId = (typeof c.req?.header === "function" ? c.req.header("X-Site-Id") : void 0) || (typeof c.get === "function" ? c.get("siteId") : void 0);
|
|
6248
|
+
const allowedSites = getAllowedSitesForUser(user, tokenPayload);
|
|
6249
|
+
if (Array.isArray(allowedSites) && allowedSites.length > 0) {
|
|
6250
|
+
if (!requestedSiteId) {
|
|
6251
|
+
return allowedSites[0];
|
|
6252
|
+
}
|
|
6253
|
+
if (!allowedSites.includes(requestedSiteId)) {
|
|
6254
|
+
throw new import_http_exception.HTTPException(403, {
|
|
6255
|
+
message: `Forbidden: User is not authorized to access site "${requestedSiteId}".`
|
|
6256
|
+
});
|
|
6257
|
+
}
|
|
6258
|
+
return requestedSiteId;
|
|
6259
|
+
}
|
|
6260
|
+
return requestedSiteId || "default";
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
// src/utils/ai-pii.ts
|
|
6264
|
+
var NON_NEGOTIABLE_CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
|
|
6265
|
+
"password",
|
|
6266
|
+
"salt",
|
|
6267
|
+
"hash",
|
|
6268
|
+
"resetPasswordToken",
|
|
6269
|
+
"apiKey",
|
|
6270
|
+
"token",
|
|
6271
|
+
"secret",
|
|
6272
|
+
"accessToken",
|
|
6273
|
+
"refreshToken",
|
|
6274
|
+
"privateKey",
|
|
6275
|
+
"private_key",
|
|
6276
|
+
"webhookSecret",
|
|
6277
|
+
"webhook_secret",
|
|
6278
|
+
"authSecret",
|
|
6279
|
+
"auth_secret",
|
|
6280
|
+
"cvv",
|
|
6281
|
+
"cvc",
|
|
6282
|
+
"pin"
|
|
6283
|
+
]);
|
|
6284
|
+
function maskEmail(email, strategy = "mask") {
|
|
6285
|
+
if (!email || typeof email !== "string") return email;
|
|
6286
|
+
if (strategy === "token") return "[REDACTED_EMAIL]";
|
|
6287
|
+
const atIdx = email.indexOf("@");
|
|
6288
|
+
if (atIdx <= 1) {
|
|
6289
|
+
return "***@" + (email.slice(atIdx + 1) || "example.com");
|
|
6290
|
+
}
|
|
6291
|
+
const prefix = email[0];
|
|
6292
|
+
const domain = email.slice(atIdx);
|
|
6293
|
+
return `${prefix}***${domain}`;
|
|
6294
|
+
}
|
|
6295
|
+
function maskPhone(phone, strategy = "mask") {
|
|
6296
|
+
if (!phone || typeof phone !== "string") return phone;
|
|
6297
|
+
if (strategy === "token") return "[REDACTED_PHONE]";
|
|
6298
|
+
const digits = phone.replace(/\D/g, "");
|
|
6299
|
+
if (digits.length < 4) return "***-***-****";
|
|
6300
|
+
const lastFour = digits.slice(-4);
|
|
6301
|
+
return `***-***-${lastFour}`;
|
|
6302
|
+
}
|
|
6303
|
+
var PII_REGEX_PATTERNS = {
|
|
6304
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
6305
|
+
phone_number: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6306
|
+
phoneNumber: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6307
|
+
phone: /(?:\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-\s]?\d{4}\b/g,
|
|
6308
|
+
credit_card: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
6309
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
6310
|
+
ipv4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
6311
|
+
ipv6: /(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}/g,
|
|
6312
|
+
iban: /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g,
|
|
6313
|
+
api_key: /\b(?:sk_(?:live|test)_[0-9a-zA-Z_]{16,}|ghp_[0-9a-zA-Z_]{20,}|AKIA[0-9A-Z]{16})\b/g,
|
|
6314
|
+
jwt: /\beyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g,
|
|
6315
|
+
passport: /\b[A-Z]{1,2}[0-9]{6,9}\b/g
|
|
6316
|
+
};
|
|
6317
|
+
var PII_PATTERN_PRIORITY = {
|
|
6318
|
+
api_key: 1,
|
|
6319
|
+
jwt: 2,
|
|
6320
|
+
iban: 3,
|
|
6321
|
+
credit_card: 4,
|
|
6322
|
+
ipv6: 5,
|
|
6323
|
+
ipv4: 6,
|
|
6324
|
+
email: 7,
|
|
6325
|
+
passport: 8,
|
|
6326
|
+
ssn: 9,
|
|
6327
|
+
phone: 10,
|
|
6328
|
+
phone_number: 10,
|
|
6329
|
+
phoneNumber: 10
|
|
6330
|
+
};
|
|
6331
|
+
function maskTextPII(text, piiConfig) {
|
|
6332
|
+
if (!text || typeof text !== "string") return text;
|
|
6333
|
+
if (!piiConfig || piiConfig.enabled === false) return text;
|
|
6334
|
+
let result = text;
|
|
6335
|
+
const strategy = piiConfig.strategy || "mask";
|
|
6336
|
+
const rawPatterns = piiConfig.patterns || [
|
|
6337
|
+
"email",
|
|
6338
|
+
"phone",
|
|
6339
|
+
"phone_number",
|
|
6340
|
+
"phoneNumber",
|
|
6341
|
+
"ssn",
|
|
6342
|
+
"credit_card",
|
|
6343
|
+
"ipv4"
|
|
6344
|
+
];
|
|
6345
|
+
const patterns = [...rawPatterns].sort(
|
|
6346
|
+
(a, b) => (PII_PATTERN_PRIORITY[a] ?? 99) - (PII_PATTERN_PRIORITY[b] ?? 99)
|
|
6347
|
+
);
|
|
6348
|
+
for (const patternName of patterns) {
|
|
6349
|
+
const regex = PII_REGEX_PATTERNS[patternName];
|
|
6350
|
+
if (!regex) continue;
|
|
6351
|
+
result = result.replace(regex, (match) => {
|
|
6352
|
+
switch (patternName) {
|
|
6353
|
+
case "email":
|
|
6354
|
+
return maskEmail(match, strategy);
|
|
6355
|
+
case "phone":
|
|
6356
|
+
case "phone_number":
|
|
6357
|
+
case "phoneNumber":
|
|
6358
|
+
return maskPhone(match, strategy);
|
|
6359
|
+
case "credit_card":
|
|
6360
|
+
return strategy === "token" ? "[REDACTED_CREDIT_CARD]" : "****-****-****-" + match.replace(/\D/g, "").slice(-4);
|
|
6361
|
+
case "ssn":
|
|
6362
|
+
return strategy === "token" ? "[REDACTED_SSN]" : "***-**-" + match.replace(/\D/g, "").slice(-4);
|
|
6363
|
+
case "ipv4":
|
|
6364
|
+
return "[REDACTED_IP]";
|
|
6365
|
+
case "ipv6":
|
|
6366
|
+
return "[REDACTED_IPV6]";
|
|
6367
|
+
case "iban":
|
|
6368
|
+
return strategy === "token" ? "[REDACTED_IBAN]" : "****-****-" + match.replace(/\s/g, "").slice(-4);
|
|
6369
|
+
case "api_key":
|
|
6370
|
+
return "[REDACTED_API_KEY]";
|
|
6371
|
+
case "jwt":
|
|
6372
|
+
return "[REDACTED_JWT]";
|
|
6373
|
+
case "passport":
|
|
6374
|
+
return strategy === "token" ? "[REDACTED_PASSPORT]" : "***" + match.slice(-3);
|
|
6375
|
+
default:
|
|
6376
|
+
return "[REDACTED]";
|
|
6377
|
+
}
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
if (typeof piiConfig.customScrubber === "function") {
|
|
6381
|
+
result = piiConfig.customScrubber(result);
|
|
6382
|
+
}
|
|
6383
|
+
return result;
|
|
6384
|
+
}
|
|
6385
|
+
function sanitizeDocForAI(options) {
|
|
6386
|
+
const { doc, collectionConfig, globalAIConfig } = options;
|
|
6387
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
6388
|
+
const copy = { ...doc };
|
|
6389
|
+
for (const cred of NON_NEGOTIABLE_CREDENTIAL_FIELDS) {
|
|
6390
|
+
delete copy[cred];
|
|
6391
|
+
}
|
|
6392
|
+
if (collectionConfig?.ai?.excludeFields) {
|
|
6393
|
+
for (const field of collectionConfig.ai.excludeFields) {
|
|
6394
|
+
delete copy[field];
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
const fields = collectionConfig?.fields || [];
|
|
6398
|
+
for (const field of fields) {
|
|
6399
|
+
const fieldName = field.name;
|
|
6400
|
+
if (!fieldName || copy[fieldName] === void 0) continue;
|
|
6401
|
+
if (field.ai?.exclude === true) {
|
|
6402
|
+
delete copy[fieldName];
|
|
6403
|
+
continue;
|
|
6404
|
+
}
|
|
6405
|
+
const val = copy[fieldName];
|
|
6406
|
+
const normalizedName = fieldName.toLowerCase().replace(/[_\s-]/g, "");
|
|
6407
|
+
const isEmail = field.type === "email" || normalizedName.includes("email");
|
|
6408
|
+
const isPhone = field.type === "phone" || normalizedName.includes("phone") || normalizedName === "tel" || normalizedName === "telephone" || normalizedName === "mobile" || normalizedName === "cell";
|
|
6409
|
+
if (isEmail) {
|
|
6410
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6411
|
+
copy[fieldName] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6412
|
+
}
|
|
6413
|
+
continue;
|
|
6414
|
+
}
|
|
6415
|
+
if (isPhone) {
|
|
6416
|
+
if (globalAIConfig?.pii?.enabled !== false && field.ai?.allowRaw !== true && typeof val === "string") {
|
|
6417
|
+
copy[fieldName] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6418
|
+
}
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
if (field.ai?.redact === true || field.ai?.redact === "mask") {
|
|
6422
|
+
if (typeof val === "string") {
|
|
6423
|
+
copy[fieldName] = "[REDACTED]";
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (collectionConfig?.ai?.redactFields) {
|
|
6428
|
+
for (const field of collectionConfig.ai.redactFields) {
|
|
6429
|
+
if (copy[field] !== void 0) {
|
|
6430
|
+
const val = copy[field];
|
|
6431
|
+
if (typeof val === "string") {
|
|
6432
|
+
if (field.toLowerCase().includes("email")) {
|
|
6433
|
+
copy[field] = maskEmail(val, globalAIConfig?.pii?.strategy);
|
|
6434
|
+
} else if (field.toLowerCase().includes("phone")) {
|
|
6435
|
+
copy[field] = maskPhone(val, globalAIConfig?.pii?.strategy);
|
|
6436
|
+
} else {
|
|
6437
|
+
copy[field] = "[REDACTED]";
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
let result = copy;
|
|
6444
|
+
if (typeof collectionConfig?.ai?.sanitizeDoc === "function") {
|
|
6445
|
+
result = collectionConfig.ai.sanitizeDoc(copy);
|
|
6446
|
+
}
|
|
6447
|
+
if (globalAIConfig?.pii?.enabled === true) {
|
|
6448
|
+
for (const [k, v] of Object.entries(result)) {
|
|
6449
|
+
if (typeof v === "string") {
|
|
6450
|
+
result[k] = maskTextPII(v, globalAIConfig.pii);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
return result;
|
|
6427
6455
|
}
|
|
6428
|
-
var when = createWhen();
|
|
6429
6456
|
|
|
6430
6457
|
// src/index.ts
|
|
6458
|
+
var import_zod = require("zod");
|
|
6431
6459
|
function defineCollection(config) {
|
|
6432
6460
|
return config;
|
|
6433
6461
|
}
|
|
@@ -6476,12 +6504,20 @@ function defineTab(args) {
|
|
|
6476
6504
|
}
|
|
6477
6505
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6478
6506
|
0 && (module.exports = {
|
|
6507
|
+
AI_ACTIONS_COLLECTION,
|
|
6508
|
+
AI_AUDIT_COLLECTION,
|
|
6509
|
+
AI_CHUNKS_COLLECTION,
|
|
6510
|
+
AI_MESSAGES_COLLECTION,
|
|
6511
|
+
AI_THREADS_COLLECTION,
|
|
6479
6512
|
BUILTIN_JEXL_HELPERS,
|
|
6480
6513
|
ConfigValidationError,
|
|
6514
|
+
DyrectedAIError,
|
|
6481
6515
|
FieldConditionBuilder,
|
|
6482
6516
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
6483
6517
|
LIFECYCLE_EVENT_NAMES,
|
|
6484
6518
|
MatchBuilder,
|
|
6519
|
+
NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
6520
|
+
PII_REGEX_PATTERNS,
|
|
6485
6521
|
WORKFLOW_HISTORY_COLLECTION,
|
|
6486
6522
|
abs,
|
|
6487
6523
|
addDays,
|
|
@@ -6559,6 +6595,7 @@ function defineTab(args) {
|
|
|
6559
6595
|
evaluateJexlSync,
|
|
6560
6596
|
executeFieldAfterRead,
|
|
6561
6597
|
executeFieldBeforeChange,
|
|
6598
|
+
expr,
|
|
6562
6599
|
first,
|
|
6563
6600
|
floor,
|
|
6564
6601
|
formatConfigDiagnostics,
|
|
@@ -6568,18 +6605,24 @@ function defineTab(args) {
|
|
|
6568
6605
|
generateOpenApi,
|
|
6569
6606
|
get,
|
|
6570
6607
|
getAdminAuthCollection,
|
|
6608
|
+
getAllowedSitesForUser,
|
|
6571
6609
|
getPublicAdminAuthConfig,
|
|
6572
6610
|
includes,
|
|
6573
6611
|
initializeWorkflowDocument,
|
|
6612
|
+
isAICollection,
|
|
6574
6613
|
isConfigValidationError,
|
|
6575
6614
|
isDetailItemVisible,
|
|
6576
6615
|
isEmpty,
|
|
6577
6616
|
isFuture,
|
|
6578
6617
|
isPast,
|
|
6618
|
+
isSiteAuthorized,
|
|
6579
6619
|
join,
|
|
6580
6620
|
last,
|
|
6581
6621
|
length,
|
|
6582
6622
|
lower,
|
|
6623
|
+
maskEmail,
|
|
6624
|
+
maskPhone,
|
|
6625
|
+
maskTextPII,
|
|
6583
6626
|
materializeWorkflowDocument,
|
|
6584
6627
|
max,
|
|
6585
6628
|
min,
|
|
@@ -6597,8 +6640,10 @@ function defineTab(args) {
|
|
|
6597
6640
|
registerJexlHelpers,
|
|
6598
6641
|
replace,
|
|
6599
6642
|
resolveAdminAuthCollection,
|
|
6643
|
+
resolveAuthorizedSiteId,
|
|
6600
6644
|
round,
|
|
6601
6645
|
runCollectionHooks,
|
|
6646
|
+
sanitizeDocForAI,
|
|
6602
6647
|
saveWorkflowDraft,
|
|
6603
6648
|
simplePublishingWorkflow,
|
|
6604
6649
|
slugify,
|
|
@@ -6617,5 +6662,6 @@ function defineTab(args) {
|
|
|
6617
6662
|
validatePreviewUrlExpression,
|
|
6618
6663
|
when,
|
|
6619
6664
|
wordCount,
|
|
6620
|
-
workflowCapabilities
|
|
6665
|
+
workflowCapabilities,
|
|
6666
|
+
z
|
|
6621
6667
|
});
|