@dyrected/core 2.10.0 → 2.11.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.js CHANGED
@@ -101,7 +101,7 @@ import {
101
101
  validatePreviewUrlExpression,
102
102
  wordCount,
103
103
  workflowCapabilities
104
- } from "./chunk-TYPDQTE2.js";
104
+ } from "./chunk-H7DVVW2P.js";
105
105
  import "./chunk-JSBRDJBE.js";
106
106
 
107
107
  // src/types/workflows.ts
@@ -389,6 +389,346 @@ function buildSqlOrderBy(sort, existingColumns, numericFields, dialect) {
389
389
  }).join(", ");
390
390
  }
391
391
 
392
+ // src/utils/conditions.ts
393
+ function formatJexlValue(v) {
394
+ if (v === null || v === void 0) return "null";
395
+ if (typeof v === "boolean" || typeof v === "number") return String(v);
396
+ if (typeof v === "string") {
397
+ if (v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"') || v.includes("+") || v.includes("?") || v.includes("(") || v.includes(" ")) {
398
+ return v;
399
+ }
400
+ if (v.startsWith("/")) {
401
+ return `'${v}'`;
402
+ }
403
+ if (v === "null" || v === "true" || v === "false") {
404
+ return v;
405
+ }
406
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(v)) {
407
+ return v;
408
+ }
409
+ return `'${v}'`;
410
+ }
411
+ return String(v);
412
+ }
413
+ var MatchBuilder = class {
414
+ branches = [];
415
+ /**
416
+ * Adds a conditional branch (`condition ? value : ...`).
417
+ */
418
+ case(condition, value) {
419
+ this.branches.push({ condition, value });
420
+ return this;
421
+ }
422
+ /**
423
+ * Final fallback value when no cases match.
424
+ */
425
+ otherwise(fallback = "null") {
426
+ if (this.branches.length === 0) return formatJexlValue(fallback);
427
+ let expr = formatJexlValue(fallback);
428
+ for (let i = this.branches.length - 1; i >= 0; i--) {
429
+ const b = this.branches[i];
430
+ expr = `${b.condition} ? ${formatJexlValue(b.value)} : ${expr}`;
431
+ }
432
+ return expr;
433
+ }
434
+ };
435
+ var FieldConditionBuilder = class {
436
+ constructor(field) {
437
+ this.field = field;
438
+ }
439
+ field;
440
+ /**
441
+ * Generates equality condition (`field == value`).
442
+ */
443
+ equals(value) {
444
+ return `${this.field} == ${JSON.stringify(value)}`;
445
+ }
446
+ /**
447
+ * Generates inequality condition (`field != value`).
448
+ */
449
+ notEquals(value) {
450
+ return `${this.field} != ${JSON.stringify(value)}`;
451
+ }
452
+ /**
453
+ * Generates set membership condition (`field in [val1, val2]`).
454
+ */
455
+ in(...values) {
456
+ const flat = values.flat();
457
+ return `${this.field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}]`;
458
+ }
459
+ /**
460
+ * Generates inverted set membership condition (`!(field in [val1, val2])`).
461
+ */
462
+ notIn(...values) {
463
+ const flat = values.flat();
464
+ return `!(${this.field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}])`;
465
+ }
466
+ /**
467
+ * Generates boolean true check (`field == true`).
468
+ */
469
+ isTrue() {
470
+ return `${this.field} == true`;
471
+ }
472
+ /**
473
+ * Generates boolean false check (`field != true`).
474
+ */
475
+ isFalse() {
476
+ return `${this.field} != true`;
477
+ }
478
+ /**
479
+ * Checks that the field is neither null/undefined nor an empty string.
480
+ */
481
+ notEmpty() {
482
+ return `${this.field} != null && ${this.field} != ''`;
483
+ }
484
+ /**
485
+ * Checks that the field is null, undefined, or an empty string.
486
+ */
487
+ isEmpty() {
488
+ return `${this.field} == null || ${this.field} == ''`;
489
+ }
490
+ /**
491
+ * Numerical greater than (`field > num`).
492
+ */
493
+ greaterThan(num) {
494
+ return `${this.field} > ${typeof num === "number" ? num : num}`;
495
+ }
496
+ /**
497
+ * Numerical greater than or equal (`field >= num`).
498
+ */
499
+ greaterThanOrEqual(num) {
500
+ return `${this.field} >= ${typeof num === "number" ? num : num}`;
501
+ }
502
+ /**
503
+ * Numerical less than (`field < num`).
504
+ */
505
+ lessThan(num) {
506
+ return `${this.field} < ${typeof num === "number" ? num : num}`;
507
+ }
508
+ /**
509
+ * Numerical less than or equal (`field <= num`).
510
+ */
511
+ lessThanOrEqual(num) {
512
+ return `${this.field} <= ${typeof num === "number" ? num : num}`;
513
+ }
514
+ /**
515
+ * Numerical range check (`field >= min && field <= max`).
516
+ */
517
+ between(min2, max2) {
518
+ return `${this.field} >= ${min2} && ${this.field} <= ${max2}`;
519
+ }
520
+ /**
521
+ * String prefix check using JEXL `startsWith(field, prefix)`.
522
+ */
523
+ startsWith(prefix) {
524
+ return `startsWith(${this.field}, ${JSON.stringify(prefix)})`;
525
+ }
526
+ /**
527
+ * String suffix check using JEXL `endsWith(field, suffix)`.
528
+ */
529
+ endsWith(suffix) {
530
+ return `endsWith(${this.field}, ${JSON.stringify(suffix)})`;
531
+ }
532
+ /**
533
+ * Substring or array item check using JEXL `includes(field, item)`.
534
+ */
535
+ contains(val) {
536
+ return `includes(${this.field}, ${JSON.stringify(val)})`;
537
+ }
538
+ /**
539
+ * Checks if array/string length exceeds a threshold.
540
+ */
541
+ hasLengthGreaterThan(length2) {
542
+ return `length(${this.field}) > ${length2}`;
543
+ }
544
+ /**
545
+ * Checks if array/string length is at least a threshold.
546
+ */
547
+ hasLengthAtLeast(length2) {
548
+ return `length(${this.field}) >= ${length2}`;
549
+ }
550
+ };
551
+ function createWhen() {
552
+ const whenFn = ((fieldName) => new FieldConditionBuilder(fieldName));
553
+ whenFn.user = (userProp) => new FieldConditionBuilder(`user.${userProp}`);
554
+ whenFn.sibling = (siblingProp) => new FieldConditionBuilder(`siblingData.${siblingProp}`);
555
+ whenFn.concat = (...parts) => {
556
+ if (parts.length === 0) return "''";
557
+ return parts.map((p) => formatJexlValue(p)).join(" + ");
558
+ };
559
+ whenFn.slugify = (field) => `slugify(${field})`;
560
+ whenFn.lower = (field) => `lower(${field})`;
561
+ whenFn.upper = (field) => `upper(${field})`;
562
+ whenFn.trim = (field) => `trim(${field})`;
563
+ whenFn.access = {
564
+ isOwner: (ownerField = "author") => {
565
+ return `${ownerField} == user.id || ${ownerField}.id == user.id`;
566
+ },
567
+ isAdmin: () => {
568
+ return "user.role == 'admin' || (user.roles != null && 'admin' in user.roles)";
569
+ },
570
+ hasRole: (...roles) => {
571
+ return whenFn.userRole(...roles);
572
+ },
573
+ isPublishedOrAdmin: (statusField = "status") => {
574
+ return `${statusField} == 'published' || user.role == 'admin' || (user.roles != null && 'admin' in user.roles)`;
575
+ }
576
+ };
577
+ whenFn.then = (condition, ifTrue, ifFalse = "null") => {
578
+ return `${condition} ? ${formatJexlValue(ifTrue)} : ${formatJexlValue(ifFalse)}`;
579
+ };
580
+ whenFn.match = () => new MatchBuilder();
581
+ whenFn.cases = (...branches) => {
582
+ const builder = new MatchBuilder();
583
+ let fallback = "null";
584
+ for (const b of branches) {
585
+ if (Array.isArray(b)) {
586
+ builder.case(b[0], b[1]);
587
+ } else {
588
+ fallback = b;
589
+ }
590
+ }
591
+ return builder.otherwise(fallback);
592
+ };
593
+ whenFn.all = (...conditions) => {
594
+ const valid = conditions.filter(Boolean);
595
+ if (valid.length === 0) return "true";
596
+ if (valid.length === 1) return valid[0];
597
+ return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" && ");
598
+ };
599
+ whenFn.and = whenFn.all;
600
+ whenFn.any = (...conditions) => {
601
+ const valid = conditions.filter(Boolean);
602
+ if (valid.length === 0) return "true";
603
+ if (valid.length === 1) return valid[0];
604
+ return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" || ");
605
+ };
606
+ whenFn.or = whenFn.any;
607
+ whenFn.not = (condition) => {
608
+ if (!condition) return "false";
609
+ return `!(${condition})`;
610
+ };
611
+ whenFn.variant = (...variants) => {
612
+ const flat = variants.flat();
613
+ if (flat.length === 0) return "true";
614
+ if (flat.length === 1) return `variant == '${flat[0]}'`;
615
+ return `variant in [${flat.map((v) => `'${v}'`).join(", ")}]`;
616
+ };
617
+ whenFn.notVariant = (...variants) => {
618
+ const flat = variants.flat();
619
+ if (flat.length === 0) return "false";
620
+ if (flat.length === 1) return `variant != '${flat[0]}'`;
621
+ return `!(variant in [${flat.map((v) => `'${v}'`).join(", ")}])`;
622
+ };
623
+ whenFn.block = (...blocks) => {
624
+ const flat = blocks.flat();
625
+ if (flat.length === 0) return "true";
626
+ if (flat.length === 1) return `block == '${flat[0]}'`;
627
+ return `block in [${flat.map((b) => `'${b}'`).join(", ")}]`;
628
+ };
629
+ whenFn.notBlock = (...blocks) => {
630
+ const flat = blocks.flat();
631
+ if (flat.length === 0) return "false";
632
+ if (flat.length === 1) return `block != '${flat[0]}'`;
633
+ return `!(block in [${flat.map((b) => `'${b}'`).join(", ")}])`;
634
+ };
635
+ whenFn.fieldEquals = (field, value) => {
636
+ return `${field} == ${JSON.stringify(value)}`;
637
+ };
638
+ whenFn.fieldNotEquals = (field, value) => {
639
+ return `${field} != ${JSON.stringify(value)}`;
640
+ };
641
+ whenFn.fieldIn = (field, ...values) => {
642
+ const flat = values.flat();
643
+ return `${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}]`;
644
+ };
645
+ whenFn.fieldNotIn = (field, ...values) => {
646
+ const flat = values.flat();
647
+ return `!(${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}])`;
648
+ };
649
+ whenFn.fieldNotEmpty = (field) => {
650
+ return `${field} != null && ${field} != ''`;
651
+ };
652
+ whenFn.fieldEmpty = (field) => {
653
+ return `${field} == null || ${field} == ''`;
654
+ };
655
+ whenFn.fieldIsTrue = (field) => {
656
+ return `${field} == true`;
657
+ };
658
+ whenFn.fieldIsFalse = (field) => {
659
+ return `${field} != true`;
660
+ };
661
+ whenFn.fieldGreaterThan = (field, value) => {
662
+ return `${field} > ${value}`;
663
+ };
664
+ whenFn.fieldGreaterThanOrEqual = (field, value) => {
665
+ return `${field} >= ${value}`;
666
+ };
667
+ whenFn.fieldLessThan = (field, value) => {
668
+ return `${field} < ${value}`;
669
+ };
670
+ whenFn.fieldLessThanOrEqual = (field, value) => {
671
+ return `${field} <= ${value}`;
672
+ };
673
+ whenFn.fieldBetween = (field, min2, max2) => {
674
+ return `${field} >= ${min2} && ${field} <= ${max2}`;
675
+ };
676
+ whenFn.fieldStartsWith = (field, prefix) => {
677
+ return `startsWith(${field}, ${JSON.stringify(prefix)})`;
678
+ };
679
+ whenFn.fieldEndsWith = (field, suffix) => {
680
+ return `endsWith(${field}, ${JSON.stringify(suffix)})`;
681
+ };
682
+ whenFn.fieldContains = (field, item) => {
683
+ return `includes(${field}, ${JSON.stringify(item)})`;
684
+ };
685
+ whenFn.arrayNotEmpty = (field) => {
686
+ return `length(${field}) > 0`;
687
+ };
688
+ whenFn.arrayEmpty = (field) => {
689
+ return `length(${field}) == 0`;
690
+ };
691
+ whenFn.arrayCountGreaterThan = (field, count2) => {
692
+ return `length(${field}) > ${count2}`;
693
+ };
694
+ whenFn.arrayCountAtLeast = (field, count2) => {
695
+ return `length(${field}) >= ${count2}`;
696
+ };
697
+ whenFn.arrayCountLessThan = (field, count2) => {
698
+ return `length(${field}) < ${count2}`;
699
+ };
700
+ whenFn.userRole = (...roles) => {
701
+ const flat = roles.flat();
702
+ if (flat.length === 1) {
703
+ return `user.role == '${flat[0]}' || (user.roles != null && '${flat[0]}' in user.roles)`;
704
+ }
705
+ const roleArray = `[${flat.map((r) => `'${r}'`).join(", ")}]`;
706
+ return `user.role in ${roleArray} || (user.roles != null && includes(${roleArray}, user.role))`;
707
+ };
708
+ whenFn.userEmailDomain = (domain) => {
709
+ const clean = domain.startsWith("@") ? domain : `@${domain}`;
710
+ return `endsWith(user.email, '${clean}')`;
711
+ };
712
+ whenFn.userAttributeEquals = (attr, value) => {
713
+ return `user.${attr} == ${JSON.stringify(value)}`;
714
+ };
715
+ whenFn.isNewDocument = () => {
716
+ return "id == null";
717
+ };
718
+ whenFn.isExistingDocument = () => {
719
+ return "id != null";
720
+ };
721
+ whenFn.statusEquals = (status) => {
722
+ return `status == '${status}'`;
723
+ };
724
+ whenFn.statusIn = (...statuses) => {
725
+ const flat = statuses.flat();
726
+ return `status in [${flat.map((s) => `'${s}'`).join(", ")}]`;
727
+ };
728
+ return whenFn;
729
+ }
730
+ var when = createWhen();
731
+
392
732
  // src/index.ts
393
733
  function defineCollection(config) {
394
734
  return config;
@@ -439,8 +779,10 @@ function defineTab(args) {
439
779
  export {
440
780
  BUILTIN_JEXL_HELPERS,
441
781
  ConfigValidationError,
782
+ FieldConditionBuilder,
442
783
  LIFECYCLE_EVENTS_COLLECTION,
443
784
  LIFECYCLE_EVENT_NAMES,
785
+ MatchBuilder,
444
786
  WORKFLOW_HISTORY_COLLECTION,
445
787
  abs,
446
788
  addDays,
@@ -522,6 +864,7 @@ export {
522
864
  floor,
523
865
  formatConfigDiagnostics,
524
866
  formatDate,
867
+ formatJexlValue,
525
868
  generateDefaultDetailSchema,
526
869
  generateOpenApi,
527
870
  get,
@@ -573,6 +916,7 @@ export {
573
916
  validateDeclarativeAccessExpression,
574
917
  validateDeclarativeHookExpression,
575
918
  validatePreviewUrlExpression,
919
+ when,
576
920
  wordCount,
577
921
  workflowCapabilities
578
922
  };
package/dist/server.cjs CHANGED
@@ -2318,11 +2318,12 @@ function sanitizeWhereClause(where, fields) {
2318
2318
  function walk(node) {
2319
2319
  const result = {};
2320
2320
  for (const [key, value] of Object.entries(node)) {
2321
- if (key === "AND" || key === "OR") {
2321
+ const upperKey = key.toUpperCase();
2322
+ if (upperKey === "AND" || upperKey === "OR") {
2322
2323
  if (Array.isArray(value)) {
2323
2324
  const processed = value.map((v) => walk(v)).filter((v) => Object.keys(v).length > 0);
2324
2325
  if (processed.length > 0) {
2325
- result[key] = processed;
2326
+ result[upperKey] = processed;
2326
2327
  }
2327
2328
  }
2328
2329
  continue;
@@ -3144,6 +3145,11 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
3144
3145
  try {
3145
3146
  jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
3146
3147
  jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
3148
+ jexlInstance.addBinaryOp("in", 30, (left, right) => {
3149
+ if (Array.isArray(right)) return right.includes(left);
3150
+ if (typeof right === "string") return right.includes(String(left ?? ""));
3151
+ return false;
3152
+ });
3147
3153
  } catch {
3148
3154
  }
3149
3155
  }
@@ -4036,6 +4042,17 @@ function validateUpload(file, config) {
4036
4042
  }
4037
4043
  return null;
4038
4044
  }
4045
+ function generateUniqueUploadFilename(filename) {
4046
+ const lastDotIndex = filename.lastIndexOf(".");
4047
+ const ext = lastDotIndex !== -1 ? filename.substring(lastDotIndex).toLowerCase() : "";
4048
+ const baseName = lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename;
4049
+ const sanitizedBase = baseName.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "file";
4050
+ if (/-\d{10,13}-[a-z0-9]{4,10}$/.test(sanitizedBase)) {
4051
+ return `${sanitizedBase}${ext}`;
4052
+ }
4053
+ const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
4054
+ return `${sanitizedBase}-${uniqueSuffix}${ext}`;
4055
+ }
4039
4056
 
4040
4057
  // src/auth/jexl.ts
4041
4058
  var import_jexl5 = __toESM(require_Jexl(), 1);
@@ -5366,6 +5383,7 @@ var CollectionController = class {
5366
5383
  createdBy: user?.sub ?? null,
5367
5384
  updatedBy: user?.sub ?? null
5368
5385
  };
5386
+ data = DefaultsService.apply(this.collection.fields, data);
5369
5387
  if (this.collection.workflow) {
5370
5388
  data = initializeWorkflowDocument(data, this.collection.workflow);
5371
5389
  }
@@ -5487,8 +5505,9 @@ var CollectionController = class {
5487
5505
  const siteId = c.get("siteId");
5488
5506
  const workspaceId = c.get("workspaceId");
5489
5507
  const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId;
5508
+ const uniqueFilename = generateUniqueUploadFilename(file.name);
5490
5509
  const fileData = await storage.upload({
5491
- filename: file.name,
5510
+ filename: uniqueFilename,
5492
5511
  buffer,
5493
5512
  mimeType: file.type,
5494
5513
  prefix
@@ -6685,6 +6704,7 @@ var MediaController = class {
6685
6704
  const buffer = new Uint8Array(await file.arrayBuffer());
6686
6705
  const workspaceId = c.get("workspaceId");
6687
6706
  const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId || "default";
6707
+ const uniqueFilename = generateUniqueUploadFilename(file.name);
6688
6708
  let imageMetadata = {};
6689
6709
  let imageSizes = {};
6690
6710
  if (imageService && file.type.startsWith("image/")) {
@@ -6707,7 +6727,7 @@ var MediaController = class {
6707
6727
  }
6708
6728
  }
6709
6729
  const fileData = await storage.upload({
6710
- filename: file.name,
6730
+ filename: uniqueFilename,
6711
6731
  buffer,
6712
6732
  mimeType: file.type,
6713
6733
  prefix
@@ -6720,9 +6740,10 @@ var MediaController = class {
6720
6740
  };
6721
6741
  if (imageSizes) {
6722
6742
  for (const [sizeName, sizeData] of Object.entries(imageSizes)) {
6723
- const ext = file.name.split(".").pop();
6724
- const baseName = file.name.substring(0, file.name.lastIndexOf("."));
6725
- const sizeFilename = `${baseName}-${sizeName}.${ext}`;
6743
+ const lastDot = uniqueFilename.lastIndexOf(".");
6744
+ const ext = lastDot !== -1 ? uniqueFilename.substring(lastDot) : "";
6745
+ const baseName = lastDot !== -1 ? uniqueFilename.substring(0, lastDot) : uniqueFilename;
6746
+ const sizeFilename = `${baseName}-${sizeName}${ext}`;
6726
6747
  try {
6727
6748
  const sizeFileData = await storage.upload({
6728
6749
  filename: sizeFilename,
@@ -9850,6 +9871,7 @@ async function serializeViewForApi(view, serializeAccess) {
9850
9871
  metrics: view.metrics,
9851
9872
  features: view.features,
9852
9873
  actionOrder: view.actionOrder,
9874
+ components: view.components,
9853
9875
  actions: await Promise.all(
9854
9876
  (view.actions || []).map(async (action) => ({
9855
9877
  name: action.name,
@@ -9857,6 +9879,7 @@ async function serializeViewForApi(view, serializeAccess) {
9857
9879
  icon: action.icon,
9858
9880
  type: action.type ?? "row",
9859
9881
  confirm: action.confirm,
9882
+ submitLabel: action.submitLabel,
9860
9883
  fields: action.fields?.map(serializeFieldForApi),
9861
9884
  mutation: action.mutation,
9862
9885
  // Self-hosted handlers are intentionally omitted: they never leave the server.
package/dist/server.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Context, Hono } from 'hono';
4
4
  import { Logger } from 'pino';
5
- import { D as DyrectedConfig, b4 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DQhcrGDE.cjs';
5
+ import { D as DyrectedConfig, b5 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-6U_Kum-Z.cjs';
6
6
  import { JWTPayload } from 'jose';
7
7
  import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
8
8
  import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
package/dist/server.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Context, Hono } from 'hono';
4
4
  import { Logger } from 'pino';
5
- import { D as DyrectedConfig, b4 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DQhcrGDE.js';
5
+ import { D as DyrectedConfig, b5 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-6U_Kum-Z.js';
6
6
  import { JWTPayload } from 'jose';
7
7
  import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
8
8
  import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
package/dist/server.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  touchAuthSession,
31
31
  transitionWorkflow,
32
32
  verifyCollectionToken
33
- } from "./chunk-TYPDQTE2.js";
33
+ } from "./chunk-H7DVVW2P.js";
34
34
  import {
35
35
  attachRequestMetrics,
36
36
  bindObservabilityRuntime,
@@ -436,6 +436,17 @@ function validateUpload(file, config) {
436
436
  }
437
437
  return null;
438
438
  }
439
+ function generateUniqueUploadFilename(filename) {
440
+ const lastDotIndex = filename.lastIndexOf(".");
441
+ const ext = lastDotIndex !== -1 ? filename.substring(lastDotIndex).toLowerCase() : "";
442
+ const baseName = lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename;
443
+ const sanitizedBase = baseName.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "file";
444
+ if (/-\d{10,13}-[a-z0-9]{4,10}$/.test(sanitizedBase)) {
445
+ return `${sanitizedBase}${ext}`;
446
+ }
447
+ const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
448
+ return `${sanitizedBase}-${uniqueSuffix}${ext}`;
449
+ }
439
450
 
440
451
  // src/auth/jexl.ts
441
452
  var import_jexl2 = __toESM(require_Jexl(), 1);
@@ -1021,7 +1032,7 @@ var CollectionController = class {
1021
1032
  if (this.collection.admin?.filterable === false) {
1022
1033
  where = void 0;
1023
1034
  } else {
1024
- const { sanitizeWhereClause } = await import("./where-sanitizer-3MNFMYA2.js");
1035
+ const { sanitizeWhereClause } = await import("./where-sanitizer-47QPJQ6O.js");
1025
1036
  where = sanitizeWhereClause(where, this.collection.fields);
1026
1037
  if (Object.keys(where).length === 0) {
1027
1038
  where = void 0;
@@ -1319,6 +1330,7 @@ var CollectionController = class {
1319
1330
  createdBy: user?.sub ?? null,
1320
1331
  updatedBy: user?.sub ?? null
1321
1332
  };
1333
+ data = DefaultsService.apply(this.collection.fields, data);
1322
1334
  if (this.collection.workflow) {
1323
1335
  data = initializeWorkflowDocument(data, this.collection.workflow);
1324
1336
  }
@@ -1440,8 +1452,9 @@ var CollectionController = class {
1440
1452
  const siteId = c.get("siteId");
1441
1453
  const workspaceId = c.get("workspaceId");
1442
1454
  const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId;
1455
+ const uniqueFilename = generateUniqueUploadFilename(file.name);
1443
1456
  const fileData = await storage.upload({
1444
- filename: file.name,
1457
+ filename: uniqueFilename,
1445
1458
  buffer,
1446
1459
  mimeType: file.type,
1447
1460
  prefix
@@ -2214,7 +2227,7 @@ var CollectionController = class {
2214
2227
  groupBy = queryGroupBy.trim();
2215
2228
  }
2216
2229
  }
2217
- const { sanitizeWhereClause } = await import("./where-sanitizer-3MNFMYA2.js");
2230
+ const { sanitizeWhereClause } = await import("./where-sanitizer-47QPJQ6O.js");
2218
2231
  const sanitizedAggregates = {};
2219
2232
  for (const [key, op] of Object.entries(rawAggregates)) {
2220
2233
  if (!op || typeof op !== "object" || Array.isArray(op)) {
@@ -2524,6 +2537,7 @@ var MediaController = class {
2524
2537
  const buffer = new Uint8Array(await file.arrayBuffer());
2525
2538
  const workspaceId = c.get("workspaceId");
2526
2539
  const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId || "default";
2540
+ const uniqueFilename = generateUniqueUploadFilename(file.name);
2527
2541
  let imageMetadata = {};
2528
2542
  let imageSizes = {};
2529
2543
  if (imageService && file.type.startsWith("image/")) {
@@ -2546,7 +2560,7 @@ var MediaController = class {
2546
2560
  }
2547
2561
  }
2548
2562
  const fileData = await storage.upload({
2549
- filename: file.name,
2563
+ filename: uniqueFilename,
2550
2564
  buffer,
2551
2565
  mimeType: file.type,
2552
2566
  prefix
@@ -2559,9 +2573,10 @@ var MediaController = class {
2559
2573
  };
2560
2574
  if (imageSizes) {
2561
2575
  for (const [sizeName, sizeData] of Object.entries(imageSizes)) {
2562
- const ext = file.name.split(".").pop();
2563
- const baseName = file.name.substring(0, file.name.lastIndexOf("."));
2564
- const sizeFilename = `${baseName}-${sizeName}.${ext}`;
2576
+ const lastDot = uniqueFilename.lastIndexOf(".");
2577
+ const ext = lastDot !== -1 ? uniqueFilename.substring(lastDot) : "";
2578
+ const baseName = lastDot !== -1 ? uniqueFilename.substring(0, lastDot) : uniqueFilename;
2579
+ const sizeFilename = `${baseName}-${sizeName}${ext}`;
2565
2580
  try {
2566
2581
  const sizeFileData = await storage.upload({
2567
2582
  filename: sizeFilename,
@@ -4581,6 +4596,7 @@ async function serializeViewForApi(view, serializeAccess) {
4581
4596
  metrics: view.metrics,
4582
4597
  features: view.features,
4583
4598
  actionOrder: view.actionOrder,
4599
+ components: view.components,
4584
4600
  actions: await Promise.all(
4585
4601
  (view.actions || []).map(async (action) => ({
4586
4602
  name: action.name,
@@ -4588,6 +4604,7 @@ async function serializeViewForApi(view, serializeAccess) {
4588
4604
  icon: action.icon,
4589
4605
  type: action.type ?? "row",
4590
4606
  confirm: action.confirm,
4607
+ submitLabel: action.submitLabel,
4591
4608
  fields: action.fields?.map(serializeFieldForApi),
4592
4609
  mutation: action.mutation,
4593
4610
  // Self-hosted handlers are intentionally omitted: they never leave the server.
@@ -28,11 +28,12 @@ function sanitizeWhereClause(where, fields) {
28
28
  function walk(node) {
29
29
  const result = {};
30
30
  for (const [key, value] of Object.entries(node)) {
31
- if (key === "AND" || key === "OR") {
31
+ const upperKey = key.toUpperCase();
32
+ if (upperKey === "AND" || upperKey === "OR") {
32
33
  if (Array.isArray(value)) {
33
34
  const processed = value.map((v) => walk(v)).filter((v) => Object.keys(v).length > 0);
34
35
  if (processed.length > 0) {
35
- result[key] = processed;
36
+ result[upperKey] = processed;
36
37
  }
37
38
  }
38
39
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",