@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/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, expr) {
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(expr));
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, expr) {
952
- return this.eval(expr).then(function(res) {
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 expr(strs) {
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 { getConfigLogger: getConfigLogger2, getObservabilityRuntime: getObservabilityRuntime2 } = await Promise.resolve().then(() => (init_observability(), observability_exports));
2865
- getObservabilityRuntime2(config)?.recordWorkflowHookFailure({
2402
+ const obs = config?.__observability;
2403
+ obs?.recordWorkflowHookFailure?.({
2866
2404
  collection: collection.slug,
2867
2405
  transition: transition.name
2868
2406
  });
2869
- getConfigLogger2(config, "workflow").error({
2870
- err: error,
2871
- msg: "afterTransition hook failed",
2872
- collection: collection.slug,
2873
- transition: transition.name,
2874
- documentId: id
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 expr = numericFields.has(field) ? dialect.castNumeric(extracted) : extracted;
4544
- return `${expr} ${direction}`;
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 expr = formatJexlValue(fallback);
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
- expr = `${b.condition} ? ${formatJexlValue(b.value)} : ${expr}`;
5875
+ expr2 = `${b.condition} ? ${formatJexlValue(b.value)} : ${expr2}`;
6129
5876
  }
6130
- return expr;
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 createWhen() {
6250
- const whenFn = ((fieldName) => new FieldConditionBuilder(fieldName));
6251
- whenFn.user = (userProp) => new FieldConditionBuilder(`user.${userProp}`);
6252
- whenFn.sibling = (siblingProp) => new FieldConditionBuilder(`siblingData.${siblingProp}`);
6253
- whenFn.concat = (...parts) => {
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
- whenFn.slugify = (field) => `slugify(${field})`;
6258
- whenFn.lower = (field) => `lower(${field})`;
6259
- whenFn.upper = (field) => `upper(${field})`;
6260
- whenFn.trim = (field) => `trim(${field})`;
6261
- whenFn.access = {
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 whenFn.userRole(...roles);
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
- whenFn.then = (condition, ifTrue, ifFalse = "null") => {
6052
+ exprFn.ifElse = (condition, ifTrue, ifFalse = "null") => {
6276
6053
  return `${condition} ? ${formatJexlValue(ifTrue)} : ${formatJexlValue(ifFalse)}`;
6277
6054
  };
6278
- whenFn.match = () => new MatchBuilder();
6279
- whenFn.cases = (...branches) => {
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
- whenFn.all = (...conditions) => {
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
- whenFn.and = whenFn.all;
6298
- whenFn.any = (...conditions) => {
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
- whenFn.or = whenFn.any;
6305
- whenFn.not = (condition) => {
6082
+ exprFn.or = exprFn.any;
6083
+ exprFn.not = (condition) => {
6306
6084
  if (!condition) return "false";
6307
6085
  return `!(${condition})`;
6308
6086
  };
6309
- whenFn.variant = (...variants) => {
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
- whenFn.notVariant = (...variants) => {
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
- whenFn.block = (...blocks) => {
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
- whenFn.notBlock = (...blocks) => {
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
- whenFn.fieldEquals = (field, value) => {
6111
+ exprFn.equals = (field, value) => {
6334
6112
  return `${field} == ${JSON.stringify(value)}`;
6335
6113
  };
6336
- whenFn.fieldNotEquals = (field, value) => {
6114
+ exprFn.fieldEquals = exprFn.equals;
6115
+ exprFn.notEquals = (field, value) => {
6337
6116
  return `${field} != ${JSON.stringify(value)}`;
6338
6117
  };
6339
- whenFn.fieldIn = (field, ...values) => {
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
- whenFn.fieldNotIn = (field, ...values) => {
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
- whenFn.fieldNotEmpty = (field) => {
6128
+ exprFn.fieldNotIn = exprFn.notIn;
6129
+ exprFn.notEmpty = (field) => {
6348
6130
  return `${field} != null && ${field} != ''`;
6349
6131
  };
6350
- whenFn.fieldEmpty = (field) => {
6132
+ exprFn.fieldNotEmpty = exprFn.notEmpty;
6133
+ exprFn.empty = (field) => {
6351
6134
  return `${field} == null || ${field} == ''`;
6352
6135
  };
6353
- whenFn.fieldIsTrue = (field) => {
6136
+ exprFn.fieldEmpty = exprFn.empty;
6137
+ exprFn.isTrue = (field) => {
6354
6138
  return `${field} == true`;
6355
6139
  };
6356
- whenFn.fieldIsFalse = (field) => {
6140
+ exprFn.fieldIsTrue = exprFn.isTrue;
6141
+ exprFn.isFalse = (field) => {
6357
6142
  return `${field} != true`;
6358
6143
  };
6359
- whenFn.fieldGreaterThan = (field, value) => {
6144
+ exprFn.fieldIsFalse = exprFn.isFalse;
6145
+ exprFn.greaterThan = (field, value) => {
6360
6146
  return `${field} > ${value}`;
6361
6147
  };
6362
- whenFn.fieldGreaterThanOrEqual = (field, value) => {
6148
+ exprFn.fieldGreaterThan = exprFn.greaterThan;
6149
+ exprFn.greaterThanOrEqual = (field, value) => {
6363
6150
  return `${field} >= ${value}`;
6364
6151
  };
6365
- whenFn.fieldLessThan = (field, value) => {
6152
+ exprFn.fieldGreaterThanOrEqual = exprFn.greaterThanOrEqual;
6153
+ exprFn.lessThan = (field, value) => {
6366
6154
  return `${field} < ${value}`;
6367
6155
  };
6368
- whenFn.fieldLessThanOrEqual = (field, value) => {
6156
+ exprFn.fieldLessThan = exprFn.lessThan;
6157
+ exprFn.lessThanOrEqual = (field, value) => {
6369
6158
  return `${field} <= ${value}`;
6370
6159
  };
6371
- whenFn.fieldBetween = (field, min2, max2) => {
6160
+ exprFn.fieldLessThanOrEqual = exprFn.lessThanOrEqual;
6161
+ exprFn.between = (field, min2, max2) => {
6372
6162
  return `${field} >= ${min2} && ${field} <= ${max2}`;
6373
6163
  };
6374
- whenFn.fieldStartsWith = (field, prefix) => {
6164
+ exprFn.fieldBetween = exprFn.between;
6165
+ exprFn.startsWith = (field, prefix) => {
6375
6166
  return `startsWith(${field}, ${JSON.stringify(prefix)})`;
6376
6167
  };
6377
- whenFn.fieldEndsWith = (field, suffix) => {
6168
+ exprFn.fieldStartsWith = exprFn.startsWith;
6169
+ exprFn.endsWith = (field, suffix) => {
6378
6170
  return `endsWith(${field}, ${JSON.stringify(suffix)})`;
6379
6171
  };
6380
- whenFn.fieldContains = (field, item) => {
6172
+ exprFn.fieldEndsWith = exprFn.endsWith;
6173
+ exprFn.contains = (field, item) => {
6381
6174
  return `includes(${field}, ${JSON.stringify(item)})`;
6382
6175
  };
6383
- whenFn.arrayNotEmpty = (field) => {
6176
+ exprFn.fieldContains = exprFn.contains;
6177
+ exprFn.arrayNotEmpty = (field) => {
6384
6178
  return `length(${field}) > 0`;
6385
6179
  };
6386
- whenFn.arrayEmpty = (field) => {
6180
+ exprFn.arrayEmpty = (field) => {
6387
6181
  return `length(${field}) == 0`;
6388
6182
  };
6389
- whenFn.arrayCountGreaterThan = (field, count2) => {
6183
+ exprFn.arrayCountGreaterThan = (field, count2) => {
6390
6184
  return `length(${field}) > ${count2}`;
6391
6185
  };
6392
- whenFn.arrayCountAtLeast = (field, count2) => {
6186
+ exprFn.arrayCountAtLeast = (field, count2) => {
6393
6187
  return `length(${field}) >= ${count2}`;
6394
6188
  };
6395
- whenFn.arrayCountLessThan = (field, count2) => {
6189
+ exprFn.arrayCountLessThan = (field, count2) => {
6396
6190
  return `length(${field}) < ${count2}`;
6397
6191
  };
6398
- whenFn.userRole = (...roles) => {
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
- whenFn.userEmailDomain = (domain) => {
6200
+ exprFn.userEmailDomain = (domain) => {
6407
6201
  const clean = domain.startsWith("@") ? domain : `@${domain}`;
6408
6202
  return `endsWith(user.email, '${clean}')`;
6409
6203
  };
6410
- whenFn.userAttributeEquals = (attr, value) => {
6204
+ exprFn.userAttributeEquals = (attr, value) => {
6411
6205
  return `user.${attr} == ${JSON.stringify(value)}`;
6412
6206
  };
6413
- whenFn.isNewDocument = () => {
6207
+ exprFn.isNewDocument = () => {
6414
6208
  return "id == null";
6415
6209
  };
6416
- whenFn.isExistingDocument = () => {
6210
+ exprFn.isExistingDocument = () => {
6417
6211
  return "id != null";
6418
6212
  };
6419
- whenFn.statusEquals = (status) => {
6213
+ exprFn.statusEquals = (status) => {
6420
6214
  return `status == '${status}'`;
6421
6215
  };
6422
- whenFn.statusIn = (...statuses) => {
6216
+ exprFn.statusIn = (...statuses) => {
6423
6217
  const flat = statuses.flat();
6424
6218
  return `status in [${flat.map((s) => `'${s}'`).join(", ")}]`;
6425
6219
  };
6426
- return whenFn;
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
  });