@ghentcdh/crouton-api 0.0.1-alpha.3 → 0.0.1-alpha.30

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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
9
  var __export = (target, all) => {
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc2) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/index.ts
@@ -27,6 +37,8 @@ __export(index_exports, {
27
37
  ResourceConfigLoader: () => ResourceConfigLoader2,
28
38
  ResourceConfigRegistry: () => ResourceConfigRegistry,
29
39
  isOperationEnabled: () => isOperationEnabled,
40
+ isRowProcedureAction: () => isRowProcedureAction,
41
+ isTableProcedureAction: () => isTableProcedureAction,
30
42
  loadDataSourcesFromDir: () => loadDataSourcesFromDir,
31
43
  loadResourceConfigsFromDir: () => loadResourceConfigsFromDir,
32
44
  resolveDefinition: () => resolveDefinition,
@@ -35,21 +47,41 @@ __export(index_exports, {
35
47
  });
36
48
  module.exports = __toCommonJS(index_exports);
37
49
 
50
+ // ../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.58.8_@types+node@26.0.1__@swc+core@1.15.43_@swc+h_ec0dcc88aa461a763e37ec5746d3e7df/node_modules/tsup/assets/cjs_shims.js
51
+ var getImportMetaUrl = /* @__PURE__ */ __name(() => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href, "getImportMetaUrl");
52
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
53
+
38
54
  // src/lib/crouton-api.module.ts
39
- var import_common13 = require("@nestjs/common");
55
+ var import_common12 = require("@nestjs/common");
40
56
  var import_core = require("@nestjs/core");
41
57
 
42
- // src/lib/crud/app-layout.controller.ts
43
- var import_common2 = require("@nestjs/common");
44
- var import_swagger = require("@nestjs/swagger");
58
+ // src/lib/crud/app-layout/app-layout.types.ts
59
+ var import_zod = require("zod");
60
+ var SidebarLeafSchema = import_zod.z.object({
61
+ kind: import_zod.z.literal("item").default("item"),
62
+ id: import_zod.z.string(),
63
+ label: import_zod.z.string(),
64
+ position: import_zod.z.number().optional()
65
+ });
66
+ var SidebarGroupSchema = import_zod.z.object({
67
+ kind: import_zod.z.literal("group").default("group"),
68
+ id: import_zod.z.string(),
69
+ label: import_zod.z.string(),
70
+ position: import_zod.z.number().optional(),
71
+ children: import_zod.z.array(SidebarLeafSchema).default([])
72
+ });
73
+ var SidebarNodeSchema = import_zod.z.discriminatedUnion("kind", [
74
+ SidebarLeafSchema,
75
+ SidebarGroupSchema
76
+ ]);
45
77
 
46
78
  // ../crouton-core/src/lib/request.model.ts
47
- var import_zod2 = require("zod");
79
+ var import_zod3 = require("zod");
48
80
 
49
81
  // ../crouton-core/src/lib/zod.types.ts
50
- var import_zod = require("zod");
51
- var PositiveRequestNumber = /* @__PURE__ */ __name(() => import_zod.z.coerce.number().int().positive().nonnegative(), "PositiveRequestNumber");
52
- var StringOrArray = /* @__PURE__ */ __name(() => import_zod.z.string().or(import_zod.z.array(import_zod.z.string())).transform((val) => {
82
+ var import_zod2 = require("zod");
83
+ var PositiveRequestNumber = /* @__PURE__ */ __name(() => import_zod2.z.coerce.number().int().positive().nonnegative(), "PositiveRequestNumber");
84
+ var StringOrArray = /* @__PURE__ */ __name(() => import_zod2.z.string().or(import_zod2.z.array(import_zod2.z.string())).transform((val) => {
53
85
  if (Array.isArray(val)) return val;
54
86
  return [
55
87
  val
@@ -57,14 +89,14 @@ var StringOrArray = /* @__PURE__ */ __name(() => import_zod.z.string().or(import
57
89
  }), "StringOrArray");
58
90
 
59
91
  // ../crouton-core/src/lib/request.model.ts
60
- var SortDirEnum = import_zod2.z.enum([
92
+ var SortDirEnum = import_zod3.z.enum([
61
93
  "asc",
62
94
  "desc"
63
95
  ]);
64
- var RequestSchema = import_zod2.z.object({
96
+ var RequestSchema = import_zod3.z.object({
65
97
  page: PositiveRequestNumber().optional().default(1),
66
98
  pageSize: PositiveRequestNumber().optional().default(20),
67
- sort: import_zod2.z.string().optional().default("id"),
99
+ sort: import_zod3.z.string().optional().default("id"),
68
100
  sortDir: SortDirEnum.optional().default("asc"),
69
101
  // Filter is of the format key:value:operator (e.g. name:john:eq) operator is optional
70
102
  filter: StringOrArray().optional().default([])
@@ -115,18 +147,18 @@ var buildSortKey = /* @__PURE__ */ __name((keys, sortDir) => {
115
147
  }, "buildSortKey");
116
148
 
117
149
  // ../crouton-core/src/lib/response.model.ts
118
- var import_zod4 = require("zod");
150
+ var import_zod5 = require("zod");
119
151
  var ResponseRequestSchema = RequestSchema.extend({
120
152
  count: PositiveRequestNumber(),
121
153
  totalPages: PositiveRequestNumber()
122
154
  });
123
- var ResponseSchema = import_zod4.z.object({
124
- data: import_zod4.z.array(import_zod4.z.unknown()),
155
+ var ResponseSchema = import_zod5.z.object({
156
+ data: import_zod5.z.array(import_zod5.z.unknown()),
125
157
  request: ResponseRequestSchema
126
158
  });
127
159
 
128
160
  // ../crouton-core/src/lib/create-schema.ts
129
- var import_zod6 = require("zod");
161
+ var import_zod7 = require("zod");
130
162
 
131
163
  // ../crouton-core/src/lib/layout/base.builder.ts
132
164
  var Builder = class {
@@ -384,23 +416,6 @@ var LayoutBuilder = class _LayoutBuilder extends BuilderWithElements {
384
416
  }
385
417
  };
386
418
 
387
- // ../crouton-core/src/lib/json-config.types.ts
388
- var labelFromId = /* @__PURE__ */ __name((id) => {
389
- const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
390
- return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
391
- }, "labelFromId");
392
- var normalizeColumns = /* @__PURE__ */ __name((columns) => {
393
- if (!columns) return void 0;
394
- const raw = Array.isArray(columns) ? columns : Object.entries(columns).map(([id, col]) => ({
395
- id,
396
- ...col
397
- }));
398
- return raw.map((col) => ({
399
- ...col,
400
- label: col.label ?? labelFromId(col.id)
401
- }));
402
- }, "normalizeColumns");
403
-
404
419
  // ../crouton-core/src/lib/value-label.ts
405
420
  var wrapOne = /* @__PURE__ */ __name((value, values) => {
406
421
  const match = values.find((o) => o.value === value);
@@ -494,6 +509,515 @@ var TableBuilder = class _TableBuilder {
494
509
  }
495
510
  };
496
511
 
512
+ // ../crouton-core/src/lib/data-source/DataSourceSchema.ts
513
+ var import_zod8 = __toESM(require("zod"), 1);
514
+ var DataSourceShape = import_zod8.default.object({
515
+ /** Datasource name (folder + `name` in data-source.json). */
516
+ name: import_zod8.default.string(),
517
+ /** Env var holding the connection URL. */
518
+ urlEnv: import_zod8.default.string(),
519
+ /** Import path for this datasource's generated Zod types (for resource `schema.ts`). */
520
+ generatedTypesImport: import_zod8.default.string().optional(),
521
+ /** Datasource type tag. Default `postgres`. */
522
+ type: import_zod8.default.string().default("postgres"),
523
+ /** Mark this as the default datasource. Default `false`. */
524
+ default: import_zod8.default.boolean().default(false),
525
+ /** Prisma schema path, relative to project root. Default `prisma/<name>/schema.prisma`. */
526
+ prismaSchema: import_zod8.default.string().optional(),
527
+ /** Prisma config path, relative to project root. Default `prisma/<name>/prisma.config.ts`. */
528
+ prismaConfig: import_zod8.default.string().optional(),
529
+ /** zod-prisma-types output dir, relative to project root. Default `generated/<name>/src`. */
530
+ zodOutput: import_zod8.default.string().optional(),
531
+ /** Prisma client output dir, relative to project root. Default `generated/<name>/client`. */
532
+ clientOutput: import_zod8.default.string().optional()
533
+ });
534
+ var transformDataSource = /* @__PURE__ */ __name((data) => {
535
+ const name = data.name ?? "name";
536
+ return {
537
+ ...data,
538
+ generatedTypesImport: data.generatedTypesImport ?? `@your-scope/generated/${name}`,
539
+ prismaSchema: data.prismaSchema ?? `prisma/${name}/schema.prisma`,
540
+ prismaConfig: data.prismaConfig ?? `prisma/${name}/prisma.config.ts`,
541
+ zodOutput: data.zodOutput ?? `generated/${name}/src`,
542
+ clientOutput: data.clientOutput ?? `generated/${name}/client`
543
+ };
544
+ }, "transformDataSource");
545
+ var DataSourceSchema = DataSourceShape.transform(transformDataSource);
546
+
547
+ // ../crouton-core/src/lib/data-source/Operations.schema.ts
548
+ var import_zod9 = require("zod");
549
+ var BoolOrUpsertSchema = import_zod9.z.union([
550
+ import_zod9.z.boolean(),
551
+ import_zod9.z.object({
552
+ upsertOn: import_zod9.z.union([
553
+ import_zod9.z.string(),
554
+ import_zod9.z.array(import_zod9.z.string())
555
+ ])
556
+ })
557
+ ]);
558
+ var JsonOperationsSchema = import_zod9.z.object({
559
+ findAll: import_zod9.z.boolean().default(true),
560
+ findOne: import_zod9.z.boolean().default(true),
561
+ create: import_zod9.z.boolean().default(true),
562
+ update: import_zod9.z.boolean().default(true),
563
+ upsert: BoolOrUpsertSchema.default(false),
564
+ delete: import_zod9.z.boolean().default(true)
565
+ });
566
+
567
+ // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
568
+ var import_zod16 = require("zod");
569
+
570
+ // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
571
+ var import_zod11 = require("zod");
572
+
573
+ // ../crouton-core/src/lib/resource/FieldInput.schema.ts
574
+ var import_zod10 = require("zod");
575
+ var RelationType = import_zod10.z.enum([
576
+ "oneToOne",
577
+ "manyToOne",
578
+ "oneToMany",
579
+ "manyToMany"
580
+ ]);
581
+ var DetailControlSchema = import_zod10.z.object({
582
+ property: import_zod10.z.string(),
583
+ type: import_zod10.z.string().optional(),
584
+ options: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.unknown()).optional(),
585
+ hideLabel: import_zod10.z.boolean().optional(),
586
+ width: import_zod10.z.string().optional()
587
+ });
588
+ var DetailConfigSchema = import_zod10.z.object({
589
+ layout: import_zod10.z.enum([
590
+ "collapse",
591
+ "row"
592
+ ]),
593
+ titleKey: import_zod10.z.string().optional(),
594
+ controls: import_zod10.z.array(DetailControlSchema)
595
+ });
596
+ var RelationFieldInputOptionsSchema = import_zod10.z.object({
597
+ /** Field to sort related records by, e.g. `"title"` or `"author.name"`. */
598
+ sort: import_zod10.z.string().optional(),
599
+ /** Sort direction. Defaults to `"asc"` when omitted. */
600
+ sortDir: import_zod10.z.enum([
601
+ "asc",
602
+ "desc"
603
+ ]).optional(),
604
+ /** CSS flex direction for the relation control button layout. */
605
+ direction: import_zod10.z.string().optional(),
606
+ /** Key used as the display label in the relation control. */
607
+ displayKey: import_zod10.z.string().optional(),
608
+ /** Path to the related resource (resolved to a URI at load time). */
609
+ resource: import_zod10.z.string().optional()
610
+ }).catchall(import_zod10.z.unknown());
611
+ var FieldInputSchema = import_zod10.z.object({
612
+ type: import_zod10.z.string().optional(),
613
+ customRender: import_zod10.z.string().optional(),
614
+ /**
615
+ * Render format hint for the frontend (e.g. `"relation"` for sub-resource
616
+ * relations). When `format` is `"relation"`, `resource` must also be set.
617
+ */
618
+ format: import_zod10.z.string().optional(),
619
+ /**
620
+ * Relative path to the child resource definition, e.g. `"./author.resource"`.
621
+ * Only used when `format` is `"relation"`. Resolved at load time to inject
622
+ * sub-resource URIs into `options`.
623
+ */
624
+ resource: import_zod10.z.string().optional(),
625
+ /**
626
+ * Cardinality of the relation. Used by the frontend to determine the correct
627
+ * renderer and behaviour (e.g. single-select vs multi-select).
628
+ * - `manyToOne` / `oneToOne` — single FK reference (autocomplete / relation control)
629
+ * - `oneToMany` / `manyToMany` — collection (sub-resource table / multi-select)
630
+ */
631
+ relationType: RelationType.optional(),
632
+ /** Override the display order in form views. Lower values come first. */
633
+ position: import_zod10.z.number().optional(),
634
+ options: import_zod10.z.union([
635
+ RelationFieldInputOptionsSchema,
636
+ import_zod10.z.unknown()
637
+ ]).optional(),
638
+ /** Nested array detail layout (renders via `detailFixed`). */
639
+ detail: DetailConfigSchema.optional()
640
+ });
641
+
642
+ // ../crouton-core/src/lib/schema/label.helper.ts
643
+ var labelFromId = /* @__PURE__ */ __name((id) => {
644
+ const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
645
+ return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
646
+ }, "labelFromId");
647
+ var normalizeLabel = /* @__PURE__ */ __name((obj) => {
648
+ return {
649
+ label: labelFromId(obj.id),
650
+ ...obj
651
+ };
652
+ }, "normalizeLabel");
653
+
654
+ // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
655
+ var CalculatedColumnSchema = import_zod11.z.object({
656
+ /** Column identifier used in the response payload and UI schema. */
657
+ id: import_zod11.z.string(),
658
+ /** SQL alias — must match `id`. */
659
+ alias: import_zod11.z.string(),
660
+ /** Human-readable label shown in the table header. */
661
+ label: import_zod11.z.string().optional(),
662
+ sqlExpression: import_zod11.z.string(),
663
+ /** JSON schema / cast type for the computed value. Defaults to `"number"`. `"string"` skips CAST. */
664
+ type: import_zod11.z.enum([
665
+ "number",
666
+ "boolean",
667
+ "string"
668
+ ]).optional(),
669
+ /**
670
+ * Insertion position relative to the other visible columns.
671
+ * Uses the same semantics as `fieldInput.position` on regular columns:
672
+ * `position: N` places this column before the element currently at index N.
673
+ * Can also be set via `fieldInput.position` (takes precedence).
674
+ */
675
+ position: import_zod11.z.number().optional(),
676
+ hiddenInTable: import_zod11.z.boolean().optional(),
677
+ hiddenInView: import_zod11.z.boolean().optional(),
678
+ /**
679
+ * Field input options for rendering in form / view schemas.
680
+ * Supports `colspan`, `format`, and any other options passed to the Control renderer.
681
+ * `position` here takes precedence over the top-level `position`.
682
+ */
683
+ fieldInput: FieldInputSchema.optional()
684
+ }).transform(normalizeLabel);
685
+
686
+ // ../crouton-core/src/lib/resource/Column.ts
687
+ var import_zod12 = require("zod");
688
+ var WhenConditionSchema = import_zod12.z.object({
689
+ field: import_zod12.z.string(),
690
+ eq: import_zod12.z.unknown().optional(),
691
+ neq: import_zod12.z.unknown().optional(),
692
+ exists: import_zod12.z.boolean().optional(),
693
+ notExists: import_zod12.z.boolean().optional()
694
+ });
695
+ var JsonColumnSchema = import_zod12.z.object({
696
+ id: import_zod12.z.string(),
697
+ column: import_zod12.z.string().optional(),
698
+ label: import_zod12.z.string().optional(),
699
+ hiddenInTable: import_zod12.z.boolean().default(false),
700
+ hiddenInForm: import_zod12.z.boolean().default(false),
701
+ hiddenInView: import_zod12.z.boolean().default(false),
702
+ sortable: import_zod12.z.boolean().default(false),
703
+ defaultSort: import_zod12.z.boolean().default(false),
704
+ searchable: import_zod12.z.boolean().default(false),
705
+ filterable: import_zod12.z.boolean().default(false),
706
+ createable: import_zod12.z.boolean().default(true),
707
+ updateable: import_zod12.z.boolean().default(true),
708
+ hideLabel: import_zod12.z.boolean().default(false),
709
+ showWhen: WhenConditionSchema.optional(),
710
+ hideWhen: WhenConditionSchema.optional(),
711
+ disabledWhen: WhenConditionSchema.optional(),
712
+ displayKey: import_zod12.z.string().optional(),
713
+ sortId: import_zod12.z.string().optional(),
714
+ /**
715
+ * Name of a shared enum in the project enum registry (`crouton.enums.json`).
716
+ * At load time the loader injects that enum's `{ value, label }[]` into
717
+ * `fieldInput.options.values`, so columns don't duplicate the option list.
718
+ */
719
+ enum: import_zod12.z.string().optional(),
720
+ idField: import_zod12.z.boolean().default(false),
721
+ showInLookup: import_zod12.z.boolean().default(false),
722
+ columnType: import_zod12.z.string().default("string"),
723
+ fieldInput: FieldInputSchema.optional(),
724
+ /**
725
+ * Path to another `resource.json` whose columns are expanded as nested sub-columns
726
+ * under this column's object key.
727
+ *
728
+ * Example: `"extend": "../internalAuthor/resource.json"` on a column `author`
729
+ * auto-generates virtual sub-columns like `author_name` (column: "author", displayKey: "name").
730
+ *
731
+ * Visibility (`hiddenInTable/Form/View`) on this column is inherited by all sub-columns as a
732
+ * default; the referenced resource's own column visibility further restricts it.
733
+ */
734
+ extend: import_zod12.z.string().optional(),
735
+ /**
736
+ * Per-sub-column overrides when using `extend`.
737
+ * Key: the virtual column id (`"{extendId}_{refColId}"`) or just the referenced column id.
738
+ * Value: any `JsonColumn` fields to merge over the generated virtual column.
739
+ */
740
+ columns: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown())).optional()
741
+ });
742
+
743
+ // ../crouton-core/src/lib/resource/Sidebar.schema.ts
744
+ var import_zod13 = __toESM(require("zod"), 1);
745
+ var SidebarGroupSchema2 = import_zod13.default.object({
746
+ /** Human-readable heading shown in the sidebar. Defaults to a title-cased version of the slug. */
747
+ label: import_zod13.default.string().optional(),
748
+ /** Controls the order of this group among top-level sidebar items. */
749
+ position: import_zod13.default.number().optional()
750
+ });
751
+ var SidebarSchema = import_zod13.default.object({
752
+ hide: import_zod13.default.boolean().default(false),
753
+ position: import_zod13.default.number().optional(),
754
+ label: import_zod13.default.string().optional(),
755
+ /**
756
+ * Slug of the group this resource belongs to.
757
+ * Must match a key in `sidebarGroups` in `crouton.json`.
758
+ * Resources with the same `group` are nested under a shared collapsible section.
759
+ */
760
+ group: import_zod13.default.string().optional()
761
+ });
762
+
763
+ // ../crouton-core/src/lib/resource/TableAction.schema.ts
764
+ var import_zod14 = require("zod");
765
+ var ActionConditionSchema = import_zod14.z.object({
766
+ /** Row field to evaluate. */
767
+ field: import_zod14.z.string(),
768
+ /**
769
+ * Comparison operator. Defaults to `"eq"`.
770
+ * - `eq` / `neq` — strict equality
771
+ * - `gt` / `gte` / `lt` / `lte` — numeric or date comparison
772
+ * - `exists` — field is not null / undefined / empty string
773
+ * - `notExists` — field is null / undefined / empty string
774
+ */
775
+ op: import_zod14.z.enum([
776
+ "eq",
777
+ "neq",
778
+ "gt",
779
+ "gte",
780
+ "lt",
781
+ "lte",
782
+ "exists",
783
+ "notExists"
784
+ ]).default("eq"),
785
+ /** Comparison value. Not required for `exists` / `notExists`. */
786
+ value: import_zod14.z.unknown().optional()
787
+ });
788
+ var ActionSchema = import_zod14.z.object({
789
+ type: import_zod14.z.string(),
790
+ /** Unique identifier for the action. */
791
+ id: import_zod14.z.string(),
792
+ /** Human-readable button label. */
793
+ label: import_zod14.z.string(),
794
+ /** MDI icon name, e.g. `"mdi:open-in-new"`. */
795
+ icon: import_zod14.z.string().optional(),
796
+ /** Tooltip text. Falls back to `label` when omitted. */
797
+ tooltip: import_zod14.z.string().optional(),
798
+ condition: ActionConditionSchema.optional()
799
+ });
800
+ var JsonProcedureActionSchema = ActionSchema.extend({
801
+ type: import_zod14.z.literal("procedure").default("procedure"),
802
+ /**
803
+ * Filename (without extension) inside the resource's `actions/` directory
804
+ * that exports the table-action procedure, e.g. `"syncZotero"`.
805
+ */
806
+ procedure: import_zod14.z.string(),
807
+ /** HTTP method for the frontend. Defaults to `"post"`. */
808
+ method: import_zod14.z.string().optional().default("post"),
809
+ /** Static query / body params passed to the endpoint. */
810
+ data: import_zod14.z.record(import_zod14.z.string(), import_zod14.z.unknown()).optional()
811
+ });
812
+ var JsonLinkActionSchema = ActionSchema.extend({
813
+ /** Optional condition evaluated per row. Button is hidden when the condition is false. */
814
+ type: import_zod14.z.literal("link").default("link"),
815
+ /** URL to open. May contain `{env.VAR}` placeholders. */
816
+ href: import_zod14.z.string()
817
+ });
818
+ var transformAction = /* @__PURE__ */ __name((action) => {
819
+ const label = action.label;
820
+ return {
821
+ tooltip: label,
822
+ ...action
823
+ };
824
+ }, "transformAction");
825
+ var JsonActionSchema = import_zod14.z.union([
826
+ JsonProcedureActionSchema,
827
+ JsonLinkActionSchema
828
+ ]).transform(transformAction);
829
+
830
+ // ../crouton-core/src/lib/resource/include.schema.ts
831
+ var import_zod15 = require("zod");
832
+ var JsonIncludeEntrySchema = import_zod15.z.lazy(() => import_zod15.z.union([
833
+ import_zod15.z.string(),
834
+ import_zod15.z.object({
835
+ relation: import_zod15.z.string(),
836
+ include: import_zod15.z.array(JsonIncludeEntrySchema).optional(),
837
+ /** Prisma-format orderBy clause applied to the included records. */
838
+ orderBy: import_zod15.z.record(import_zod15.z.string(), import_zod15.z.unknown()).optional()
839
+ })
840
+ ]));
841
+
842
+ // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
843
+ var JsonColumnsMapSchema = import_zod16.z.record(import_zod16.z.string(), JsonColumnSchema.omit({
844
+ id: true
845
+ }).catchall(import_zod16.z.unknown()));
846
+ var ColumnsSchema = JsonColumnsMapSchema;
847
+ var JsonDisplaySchema = import_zod16.z.object({
848
+ mode: import_zod16.z.enum([
849
+ "page",
850
+ "modal"
851
+ ]).default("modal"),
852
+ customComponent: import_zod16.z.string().nullable().optional().default(null)
853
+ });
854
+ var normalizeColumns = /* @__PURE__ */ __name((columns) => {
855
+ if (!columns) return void 0;
856
+ const raw = Array.isArray(columns) ? columns : Object.entries(columns).map(([id, col]) => ({
857
+ id,
858
+ ...col
859
+ }));
860
+ return raw.map((col) => ({
861
+ ...col,
862
+ label: col.label ?? labelFromId(col.id)
863
+ }));
864
+ }, "normalizeColumns");
865
+ var ResourceJsonShape = import_zod16.z.object({
866
+ name: import_zod16.z.string(),
867
+ route: import_zod16.z.string(),
868
+ model: import_zod16.z.string(),
869
+ tag: import_zod16.z.string(),
870
+ title: import_zod16.z.string().optional(),
871
+ table: import_zod16.z.string().optional(),
872
+ database: import_zod16.z.string().optional(),
873
+ sidebar: SidebarSchema.default(SidebarSchema.parse({})),
874
+ display: JsonDisplaySchema.default(JsonDisplaySchema.parse({})),
875
+ operations: JsonOperationsSchema,
876
+ columns: ColumnsSchema.default(ColumnsSchema.parse({})),
877
+ calculatedColumns: import_zod16.z.array(CalculatedColumnSchema).default([]),
878
+ actions: import_zod16.z.array(JsonActionSchema).default([]),
879
+ /** Global table-level actions (no record id). Shown as toolbar buttons. */
880
+ tableActions: import_zod16.z.array(JsonActionSchema).default([]),
881
+ /** Modal width when opening a form for this resource. */
882
+ modalSize: import_zod16.z.enum([
883
+ "xs",
884
+ "sm",
885
+ "lg",
886
+ "xl"
887
+ ]).default("sm"),
888
+ /**
889
+ * Relations to include when querying this resource.
890
+ * Each entry is either a plain relation name (`"author"` → `include: { author: true }`)
891
+ * or an object for nested includes:
892
+ * `{ "relation": "text_author", "include": ["author"] }` →
893
+ * `include: { text_author: { include: { author: true } } }`
894
+ */
895
+ include: import_zod16.z.array(JsonIncludeEntrySchema).default([])
896
+ });
897
+ var ResourceJsonSchema = ResourceJsonShape.transform((obj) => {
898
+ const title = obj.title ?? labelFromId(obj.name);
899
+ return {
900
+ title,
901
+ ...obj,
902
+ columns: normalizeColumns(obj.columns)
903
+ };
904
+ });
905
+
906
+ // ../crouton-core/src/lib/config/CroutonConfig.schema.ts
907
+ var import_zod17 = require("zod");
908
+ var RulesetSchema = import_zod17.z.object({
909
+ hideIdInTable: import_zod17.z.boolean().default(true),
910
+ hideIdInForm: import_zod17.z.boolean().default(true),
911
+ hideIdInView: import_zod17.z.boolean().default(true),
912
+ hideTimestamps: import_zod17.z.boolean().default(true),
913
+ hideForeignKeys: import_zod17.z.boolean().default(true),
914
+ includeRelations: import_zod17.z.boolean().default(false),
915
+ hideRelationsInTable: import_zod17.z.boolean().default(true),
916
+ showRelationsInForm: import_zod17.z.boolean().default(true),
917
+ enumValueLabel: import_zod17.z.boolean().default(true),
918
+ sharedEnums: import_zod17.z.boolean().default(true),
919
+ defaultOperations: JsonOperationsSchema.default(JsonOperationsSchema.parse({}))
920
+ });
921
+ var CroutonConfigSchema = import_zod17.z.object({
922
+ /**
923
+ * Application title served to the frontend via `GET /_app/layout`.
924
+ * Displayed in the admin sidebar header.
925
+ */
926
+ title: import_zod17.z.string(),
927
+ /** Where resource directories live, relative to the project root. */
928
+ resourcesDir: import_zod17.z.string(),
929
+ /** Where datasource folders live (each with a `data-source.json`). */
930
+ dataSourcesDir: import_zod17.z.string(),
931
+ /**
932
+ * Template for a model's Zod export name. `{Model}` → Prisma model name.
933
+ * Defaults to `{Model}WithRelationsSchema` (the relations-aware schema
934
+ * emitted by zod-prisma-types when `createRelationValuesTypes` is on).
935
+ */
936
+ schemaExportName: import_zod17.z.string().default("{Model}WithRelationsSchema"),
937
+ /** Path to the shared enum registry, relative to project root. Default `crouton.enums.json`. */
938
+ enumsFile: import_zod17.z.string().default("crouton.enums.json"),
939
+ /** Optional overrides of the default visibility ruleset. */
940
+ rules: RulesetSchema.default(RulesetSchema.parse({})),
941
+ /**
942
+ * Sidebar group definitions, keyed by group slug (e.g. `"metadata"`).
943
+ * Resources reference a group via `sidebar.group` in their `resource.json`.
944
+ */
945
+ sidebarGroups: import_zod17.z.record(import_zod17.z.string(), SidebarGroupSchema2).default({}),
946
+ /**
947
+ * Whether form fields are saved automatically as the user edits them.
948
+ * @default true
949
+ */
950
+ autoSave: import_zod17.z.boolean().default(true)
951
+ });
952
+
953
+ // ../crouton-core/src/lib/config/readConfig.ts
954
+ var CONFIG_FILES = [
955
+ "crouton.json",
956
+ "crouton.mjs",
957
+ "crouton.js"
958
+ ];
959
+
960
+ // src/lib/crud/app-layout/app-layout.builder.ts
961
+ var byPosition = /* @__PURE__ */ __name((a, b) => {
962
+ if (a.position != null && b.position != null) return a.position - b.position;
963
+ if (a.position != null) return -1;
964
+ if (b.position != null) return 1;
965
+ return a.label.localeCompare(b.label);
966
+ }, "byPosition");
967
+ var buildLayoutPayload = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
968
+ const visible = configs.filter((c) => c.sidebar?.hide !== true && c.views?.["table"]);
969
+ const topLevel = [];
970
+ const groupMap = new Map(Object.entries(sidebarGroups).map(([slug, cfg]) => [
971
+ slug,
972
+ {
973
+ label: cfg.label ?? labelFromId(slug),
974
+ position: cfg.position,
975
+ children: []
976
+ }
977
+ ]));
978
+ for (const c of visible) {
979
+ const leaf = SidebarLeafSchema.parse({
980
+ id: c.name,
981
+ label: c.sidebar?.label ?? c.title ?? c.tag,
982
+ position: c.sidebar?.position
983
+ });
984
+ const groupSlug = c.sidebar?.group;
985
+ if (groupSlug) {
986
+ if (!groupMap.has(groupSlug)) {
987
+ groupMap.set(groupSlug, {
988
+ label: labelFromId(groupSlug),
989
+ children: []
990
+ });
991
+ }
992
+ groupMap.get(groupSlug).children.push(leaf);
993
+ } else {
994
+ topLevel.push(leaf);
995
+ }
996
+ }
997
+ const groups = [
998
+ ...groupMap.entries()
999
+ ].filter(([, g]) => g.children.length > 0).map(([id, g]) => ({
1000
+ kind: "group",
1001
+ id,
1002
+ label: g.label,
1003
+ position: g.position,
1004
+ children: g.children.sort(byPosition)
1005
+ }));
1006
+ const sidebar = [
1007
+ ...topLevel,
1008
+ ...groups
1009
+ ].sort(byPosition);
1010
+ return {
1011
+ sidebar,
1012
+ title,
1013
+ autoSave
1014
+ };
1015
+ }, "buildLayoutPayload");
1016
+
1017
+ // src/lib/crud/app-layout/app-layout.controller.ts
1018
+ var import_common2 = require("@nestjs/common");
1019
+ var import_swagger = require("@nestjs/swagger");
1020
+
497
1021
  // src/lib/crud/dev-mode.ts
498
1022
  var IS_DEV = process.env["NODE_ENV"] !== "production";
499
1023
 
@@ -542,7 +1066,7 @@ ResourceConfigRegistry = _ts_decorate([
542
1066
  ])
543
1067
  ], ResourceConfigRegistry);
544
1068
 
545
- // src/lib/crud/app-layout.controller.ts
1069
+ // src/lib/crud/app-layout/app-layout.controller.ts
546
1070
  function _ts_decorate2(decorators, target, key, desc2) {
547
1071
  var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
548
1072
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
@@ -554,62 +1078,6 @@ function _ts_metadata2(k, v) {
554
1078
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
555
1079
  }
556
1080
  __name(_ts_metadata2, "_ts_metadata");
557
- var byPosition = /* @__PURE__ */ __name((a, b) => {
558
- if (a.position != null && b.position != null) return a.position - b.position;
559
- if (a.position != null) return -1;
560
- if (b.position != null) return 1;
561
- return a.label.localeCompare(b.label);
562
- }, "byPosition");
563
- var buildLayoutPayload = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
564
- const visible = configs.filter((c) => c.sidebar?.hide !== true && c.views?.["table"]);
565
- const topLevel = [];
566
- const groupMap = new Map(Object.entries(sidebarGroups).map(([slug, cfg]) => [
567
- slug,
568
- {
569
- label: cfg.label ?? labelFromId(slug),
570
- position: cfg.position,
571
- children: []
572
- }
573
- ]));
574
- for (const c of visible) {
575
- const leaf = {
576
- kind: "item",
577
- id: c.name,
578
- label: c.sidebar?.label ?? c.title ?? c.tag,
579
- position: c.sidebar?.position
580
- };
581
- const groupSlug = c.sidebar?.group;
582
- if (groupSlug) {
583
- if (!groupMap.has(groupSlug)) {
584
- groupMap.set(groupSlug, {
585
- label: labelFromId(groupSlug),
586
- children: []
587
- });
588
- }
589
- groupMap.get(groupSlug).children.push(leaf);
590
- } else {
591
- topLevel.push(leaf);
592
- }
593
- }
594
- const groups = [
595
- ...groupMap.entries()
596
- ].filter(([, g]) => g.children.length > 0).map(([id, g]) => ({
597
- kind: "group",
598
- id,
599
- label: g.label,
600
- position: g.position,
601
- children: g.children.sort(byPosition)
602
- }));
603
- const sidebar = [
604
- ...topLevel,
605
- ...groups
606
- ].sort(byPosition);
607
- return {
608
- sidebar,
609
- title,
610
- autoSave
611
- };
612
- }, "buildLayoutPayload");
613
1081
  var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
614
1082
  const layoutPayload = buildLayoutPayload(configs, sidebarGroups, title, autoSave);
615
1083
  let AppLayoutController = class AppLayoutController {
@@ -655,6 +1123,45 @@ var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups =
655
1123
  return AppLayoutController;
656
1124
  }, "createAppLayoutController");
657
1125
 
1126
+ // src/lib/crud/config/read.ts
1127
+ var import_promises = require("fs/promises");
1128
+ var import_node_path = require("path");
1129
+ var fileExists = /* @__PURE__ */ __name(async (p) => {
1130
+ try {
1131
+ await (0, import_promises.access)(p);
1132
+ return true;
1133
+ } catch {
1134
+ return false;
1135
+ }
1136
+ }, "fileExists");
1137
+ var findConfigPath = /* @__PURE__ */ __name(async (cwd) => {
1138
+ let dir = (0, import_node_path.resolve)(cwd);
1139
+ while (true) {
1140
+ for (const name of CONFIG_FILES) {
1141
+ const candidate = (0, import_node_path.join)(dir, name);
1142
+ if (await fileExists(candidate)) return candidate;
1143
+ }
1144
+ const parent = (0, import_node_path.dirname)(dir);
1145
+ if (parent === dir) return void 0;
1146
+ dir = parent;
1147
+ }
1148
+ }, "findConfigPath");
1149
+ var loadConfig = /* @__PURE__ */ __name(async () => {
1150
+ const cwd = (0, import_node_path.resolve)(process.cwd());
1151
+ const path = await findConfigPath(cwd);
1152
+ if (!path) {
1153
+ throw new Error(`No crouton config found (looked for ${CONFIG_FILES.join(", ")} up from ${cwd}).`);
1154
+ }
1155
+ let config;
1156
+ if (path.endsWith(".json")) {
1157
+ config = JSON.parse(await (0, import_promises.readFile)(path, "utf-8"));
1158
+ } else {
1159
+ const mod = await import(path);
1160
+ config = mod.default ?? mod;
1161
+ }
1162
+ return CroutonConfigSchema.parse(config);
1163
+ }, "loadConfig");
1164
+
658
1165
  // src/lib/crud/crouton-validation.filter.ts
659
1166
  var import_common3 = require("@nestjs/common");
660
1167
 
@@ -697,24 +1204,156 @@ CroutonValidationExceptionFilter = _ts_decorate3([
697
1204
 
698
1205
  // src/lib/crud/crud-controller.factory.ts
699
1206
  var import_common11 = require("@nestjs/common");
700
- var import_common12 = require("@nestjs/common");
701
1207
  var import_swagger6 = require("@nestjs/swagger");
702
1208
 
703
- // src/lib/crud/crud.config.ts
704
- var resolveDefinition = /* @__PURE__ */ __name((config) => {
705
- const def2 = config.definition;
706
- return typeof def2 === "function" ? def2() : def2;
707
- }, "resolveDefinition");
708
- var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
709
- var schemaFor = /* @__PURE__ */ __name((def2, op) => {
710
- const entry = def2[op];
711
- if (!entry || entry === true) return void 0;
712
- return entry.schema;
713
- }, "schemaFor");
714
- var upsertOnFor = /* @__PURE__ */ __name((def2) => def2.upsert?.upsertOn, "upsertOnFor");
715
-
716
- // src/lib/crud/read.repository.ts
717
- var import_common4 = require("@nestjs/common");
1209
+ // src/lib/crud/action/action.types.ts
1210
+ var import_zod18 = require("zod");
1211
+ var ActionMetadataSchema = import_zod18.z.object({
1212
+ /** URL segment used in the endpoint. */
1213
+ id: import_zod18.z.string(),
1214
+ /** Human-readable label shown as a button. */
1215
+ label: import_zod18.z.string().optional(),
1216
+ /** MDI icon name, e.g. `"mdi:open-in-new"`. */
1217
+ icon: import_zod18.z.string().optional(),
1218
+ /** Tooltip text. Falls back to `label` when omitted. */
1219
+ tooltip: import_zod18.z.string().optional(),
1220
+ /** Per-row condition button is hidden when false. */
1221
+ condition: import_zod18.z.custom().optional()
1222
+ });
1223
+ var ResourceLinkActionSchema = ActionMetadataSchema.extend({
1224
+ type: import_zod18.z.literal("link"),
1225
+ /** URL to open. May contain `{id}` or `{env.VAR}` placeholders. */
1226
+ href: import_zod18.z.string()
1227
+ });
1228
+ var ResourceRowProcedureActionSchema = ActionMetadataSchema.extend({
1229
+ type: import_zod18.z.literal("procedure").optional(),
1230
+ /** HTTP method for the endpoint. Defaults to `"post"`. */
1231
+ method: import_zod18.z.string().optional(),
1232
+ /** Static data payload merged into the request body by the frontend. */
1233
+ data: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.unknown()).optional(),
1234
+ /** Procedure called with `(prisma, recordId)`. */
1235
+ procedure: import_zod18.z.custom((v) => typeof v === "function")
1236
+ });
1237
+ var ResourceTableProcedureActionSchema = ActionMetadataSchema.extend({
1238
+ type: import_zod18.z.literal("procedure").optional(),
1239
+ /** HTTP method for the endpoint. Defaults to `"post"`. */
1240
+ method: import_zod18.z.string().optional(),
1241
+ /** Static data payload merged into the request body by the frontend. */
1242
+ data: import_zod18.z.record(import_zod18.z.string(), import_zod18.z.unknown()).optional(),
1243
+ /** Procedure called with `(prisma)` — no record id. */
1244
+ procedure: import_zod18.z.custom((v) => typeof v === "function")
1245
+ });
1246
+ var ResourceRowActionSchema = import_zod18.z.union([
1247
+ ResourceRowProcedureActionSchema,
1248
+ ResourceLinkActionSchema
1249
+ ]);
1250
+ var ResourceTableActionSchema = import_zod18.z.union([
1251
+ ResourceTableProcedureActionSchema,
1252
+ ResourceLinkActionSchema
1253
+ ]);
1254
+ var isRowProcedureAction = /* @__PURE__ */ __name((action) => action.type !== "link", "isRowProcedureAction");
1255
+ var isTableProcedureAction = /* @__PURE__ */ __name((action) => action.type !== "link", "isTableProcedureAction");
1256
+
1257
+ // src/lib/crud/loader/module.loader.ts
1258
+ var import_node_fs = require("fs");
1259
+ var import_node_module = require("module");
1260
+ var import_node_path2 = require("path");
1261
+ var _require = (0, import_node_module.createRequire)(importMetaUrl ?? __filename);
1262
+ var findModule = /* @__PURE__ */ __name((dir, name) => {
1263
+ for (const ext of [
1264
+ ".ts",
1265
+ ".js"
1266
+ ]) {
1267
+ const p = (0, import_node_path2.join)(dir, `${name}${ext}`);
1268
+ if ((0, import_node_fs.existsSync)(p)) return p;
1269
+ }
1270
+ return void 0;
1271
+ }, "findModule");
1272
+ var IS_VITE = typeof globalThis.__vite_ssr_import__ === "function";
1273
+ var importDefault = /* @__PURE__ */ __name(async (filePath) => {
1274
+ try {
1275
+ if (IS_VITE) {
1276
+ const importPath = IS_DEV ? `${filePath}?t=${Date.now()}` : filePath;
1277
+ const mod = await import(importPath);
1278
+ return mod.default ?? mod;
1279
+ } else {
1280
+ const mod = _require(filePath);
1281
+ return mod.default;
1282
+ }
1283
+ } catch {
1284
+ return void 0;
1285
+ }
1286
+ }, "importDefault");
1287
+
1288
+ // src/lib/crud/action/action.loader.ts
1289
+ var import_node_path3 = require("path");
1290
+ var loadActions = /* @__PURE__ */ __name(async (jsonActions, basePath, scope) => {
1291
+ const tag = scope === "row" ? "actions" : "tableActions";
1292
+ const results = [];
1293
+ for (const action of jsonActions) {
1294
+ if (action.type === "link") {
1295
+ results.push({
1296
+ type: "link",
1297
+ id: action.id,
1298
+ label: action.label,
1299
+ href: action.href,
1300
+ ...action.icon && {
1301
+ icon: action.icon
1302
+ },
1303
+ ...action.tooltip && {
1304
+ tooltip: action.tooltip
1305
+ },
1306
+ ...action.condition && {
1307
+ condition: action.condition
1308
+ }
1309
+ });
1310
+ continue;
1311
+ }
1312
+ const file = findModule((0, import_node_path3.join)(basePath, "actions"), action.procedure);
1313
+ if (!file) {
1314
+ console.warn(`[${tag}] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
1315
+ continue;
1316
+ }
1317
+ const mod = await importDefault(file);
1318
+ if (!mod) {
1319
+ console.warn(`[${tag}] No default export in ${file}`);
1320
+ continue;
1321
+ }
1322
+ results.push({
1323
+ id: action.id,
1324
+ label: action.label,
1325
+ method: action.method,
1326
+ data: action.data,
1327
+ ...action.icon && {
1328
+ icon: action.icon
1329
+ },
1330
+ ...action.tooltip && {
1331
+ tooltip: action.tooltip
1332
+ },
1333
+ ...action.condition && {
1334
+ condition: action.condition
1335
+ },
1336
+ procedure: mod
1337
+ });
1338
+ }
1339
+ return results;
1340
+ }, "loadActions");
1341
+
1342
+ // src/lib/crud/crud.config.ts
1343
+ var resolveDefinition = /* @__PURE__ */ __name((config) => {
1344
+ const def2 = config.definition;
1345
+ return typeof def2 === "function" ? def2() : def2;
1346
+ }, "resolveDefinition");
1347
+ var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
1348
+ var schemaFor = /* @__PURE__ */ __name((def2, op) => {
1349
+ const entry = def2[op];
1350
+ if (!entry || entry === true) return void 0;
1351
+ return entry.schema;
1352
+ }, "schemaFor");
1353
+ var upsertOnFor = /* @__PURE__ */ __name((def2) => def2.upsert?.upsertOn, "upsertOnFor");
1354
+
1355
+ // src/lib/crud/read.repository.ts
1356
+ var import_common4 = require("@nestjs/common");
718
1357
 
719
1358
  // src/lib/crud/sql.helpers.ts
720
1359
  var castExpression = /* @__PURE__ */ __name((col) => {
@@ -1199,9 +1838,9 @@ var ReadRepository = class {
1199
1838
  };
1200
1839
 
1201
1840
  // src/lib/crud/schema.utils.ts
1202
- var import_zod7 = require("zod");
1841
+ var import_zod19 = require("zod");
1203
1842
  var dateOverride = /* @__PURE__ */ __name(({ zodSchema, jsonSchema }) => {
1204
- if (zodSchema instanceof import_zod7.z.ZodDate) {
1843
+ if (zodSchema instanceof import_zod19.z.ZodDate) {
1205
1844
  jsonSchema.type = "string";
1206
1845
  jsonSchema.format = "date-time";
1207
1846
  }
@@ -1211,7 +1850,7 @@ var jsonSchemaOpts = {
1211
1850
  override: dateOverride
1212
1851
  };
1213
1852
  function isZodSchema(schema) {
1214
- return schema instanceof import_zod7.ZodObject;
1853
+ return schema instanceof import_zod19.ZodObject;
1215
1854
  }
1216
1855
  __name(isZodSchema, "isZodSchema");
1217
1856
  var isNullableProperty = /* @__PURE__ */ __name((property) => {
@@ -1225,7 +1864,7 @@ var dropNullableFromRequired = /* @__PURE__ */ __name((jsonSchema) => {
1225
1864
  }, "dropNullableFromRequired");
1226
1865
  function toJsonSchema(schema) {
1227
1866
  if (isZodSchema(schema)) {
1228
- const jsonSchema = (0, import_zod7.toJSONSchema)(schema, {
1867
+ const jsonSchema = (0, import_zod19.toJSONSchema)(schema, {
1229
1868
  target: "openApi3",
1230
1869
  ...jsonSchemaOpts
1231
1870
  });
@@ -1600,21 +2239,27 @@ DataSourceRegistry = _ts_decorate4([
1600
2239
  ], DataSourceRegistry);
1601
2240
 
1602
2241
  // src/lib/crud/data-source/data-source.loader.ts
1603
- var import_node_fs = require("fs");
1604
- var import_node_path = require("path");
2242
+ var import_node_fs2 = require("fs");
2243
+ var import_node_path4 = require("path");
1605
2244
  var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
1606
- if (!(0, import_node_fs.existsSync)(dirPath)) return [];
1607
- const entries = (0, import_node_fs.readdirSync)(dirPath, {
2245
+ if (!(0, import_node_fs2.existsSync)(dirPath)) return [];
2246
+ const entries = (0, import_node_fs2.readdirSync)(dirPath, {
1608
2247
  withFileTypes: true
1609
2248
  });
1610
2249
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1611
2250
  const results = [];
1612
2251
  for (const dir of dirs) {
1613
- const basePath = (0, import_node_path.join)(dirPath, dir);
1614
- const jsonFile = (0, import_node_path.join)(basePath, "data-source.json");
1615
- if (!(0, import_node_fs.existsSync)(jsonFile)) continue;
1616
- const config = JSON.parse((0, import_node_fs.readFileSync)(jsonFile, "utf-8"));
1617
- const indexFile = findModule(basePath, "index");
2252
+ const basePath = (0, import_node_path4.join)(dirPath, dir);
2253
+ const jsonFile = (0, import_node_path4.join)(basePath, "data-source.json");
2254
+ if (!(0, import_node_fs2.existsSync)(jsonFile)) continue;
2255
+ const _config = JSON.parse((0, import_node_fs2.readFileSync)(jsonFile, "utf-8"));
2256
+ const datasource = DataSourceSchema.safeParse(_config);
2257
+ if (!datasource.success) {
2258
+ console.error(datasource.error);
2259
+ throw new Error(`Invalid datasource schema: ${jsonFile}`);
2260
+ }
2261
+ const config = datasource.data;
2262
+ const indexFile = findModule2(basePath, "index");
1618
2263
  if (!indexFile) continue;
1619
2264
  const mod = await import(indexFile);
1620
2265
  const client = mod.default;
@@ -1626,13 +2271,13 @@ var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
1626
2271
  }
1627
2272
  return results;
1628
2273
  }, "loadDataSourcesFromDir");
1629
- var findModule = /* @__PURE__ */ __name((dir, name) => {
2274
+ var findModule2 = /* @__PURE__ */ __name((dir, name) => {
1630
2275
  for (const ext of [
1631
2276
  ".ts",
1632
2277
  ".js"
1633
2278
  ]) {
1634
- const p = (0, import_node_path.join)(dir, `${name}${ext}`);
1635
- if ((0, import_node_fs.existsSync)(p)) return p;
2279
+ const p = (0, import_node_path4.join)(dir, `${name}${ext}`);
2280
+ if ((0, import_node_fs2.existsSync)(p)) return p;
1636
2281
  }
1637
2282
  return void 0;
1638
2283
  }, "findModule");
@@ -1655,7 +2300,7 @@ var desc = /* @__PURE__ */ __name((cls, method) => Object.getOwnPropertyDescript
1655
2300
  var registerActionRoutes = /* @__PURE__ */ __name((ctx) => {
1656
2301
  const { cls, config } = ctx;
1657
2302
  const { name } = config;
1658
- const procedureActions = (config.actions ?? []).filter((a) => a.type !== "link");
2303
+ const procedureActions = (config.actions ?? []).filter(isRowProcedureAction);
1659
2304
  for (const action of procedureActions) {
1660
2305
  const methodName = `procedure_${action.id}`;
1661
2306
  def(cls, methodName, async function(recordId) {
@@ -1680,7 +2325,7 @@ var registerActionRoutes = /* @__PURE__ */ __name((ctx) => {
1680
2325
  var registerTableActionRoutes = /* @__PURE__ */ __name((ctx) => {
1681
2326
  const { cls, config } = ctx;
1682
2327
  const { name } = config;
1683
- const procedureActions = (config.tableActions ?? []).filter((a) => a.type !== "link");
2328
+ const procedureActions = (config.tableActions ?? []).filter(isTableProcedureAction);
1684
2329
  for (const action of procedureActions) {
1685
2330
  const methodName = `tableAction_${action.id}`;
1686
2331
  def(cls, methodName, async function() {
@@ -1782,38 +2427,56 @@ var ZodValidationPipe = class {
1782
2427
  };
1783
2428
 
1784
2429
  // src/lib/crud/operations/register-crud.ts
2430
+ var findAll = /* @__PURE__ */ __name(async (repo, params, q, lookupLabel) => {
2431
+ const effectiveParams = {
2432
+ ...params
2433
+ };
2434
+ if (q && lookupLabel) {
2435
+ effectiveParams.filter = [
2436
+ ...params.filter ?? [],
2437
+ `${lookupLabel}:${q}`
2438
+ ];
2439
+ }
2440
+ const [data, count] = await Promise.all([
2441
+ repo.findAll(effectiveParams),
2442
+ repo.count(effectiveParams.filter)
2443
+ ]);
2444
+ const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2445
+ return {
2446
+ data,
2447
+ request: {
2448
+ count,
2449
+ page: params.page,
2450
+ pageSize: params.pageSize,
2451
+ totalPages,
2452
+ sort: params.sort,
2453
+ sortDir: params.sortDir,
2454
+ filter: params.filter
2455
+ }
2456
+ };
2457
+ }, "findAll");
2458
+ var findOne = /* @__PURE__ */ __name(async (repo, id) => {
2459
+ return repo.findOne(id);
2460
+ }, "findOne");
2461
+ var create = /* @__PURE__ */ __name(async (repo, body) => {
2462
+ return repo.create(body);
2463
+ }, "create");
2464
+ var update = /* @__PURE__ */ __name(async (repo, id, body) => {
2465
+ return repo.update(id, body);
2466
+ }, "update");
2467
+ var upsert = /* @__PURE__ */ __name(async (repo, body) => {
2468
+ return repo.upsert(body);
2469
+ }, "upsert");
2470
+ var del = /* @__PURE__ */ __name(async (repo, id) => {
2471
+ return repo.delete(id);
2472
+ }, "del");
1785
2473
  var registerFindAll = /* @__PURE__ */ __name((ctx) => {
1786
2474
  if (!isOperationEnabled(ctx.definition, "findAll")) return;
1787
2475
  const { cls, config, listSchema } = ctx;
1788
2476
  const { name } = config;
1789
2477
  const lookupLabel = config.lookup?.label;
1790
2478
  def(cls, "findAll", async function(params, q) {
1791
- const effectiveParams = {
1792
- ...params
1793
- };
1794
- if (q && lookupLabel) {
1795
- effectiveParams.filter = [
1796
- ...params.filter ?? [],
1797
- `${lookupLabel}:${q}`
1798
- ];
1799
- }
1800
- const [data, count] = await Promise.all([
1801
- this.repo.findAll(effectiveParams),
1802
- this.repo.count(effectiveParams.filter)
1803
- ]);
1804
- const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
1805
- return {
1806
- data,
1807
- request: {
1808
- count,
1809
- page: params.page,
1810
- pageSize: params.pageSize,
1811
- totalPages,
1812
- sort: params.sort,
1813
- sortDir: params.sortDir,
1814
- filter: params.filter
1815
- }
1816
- };
2479
+ return findAll(this.repo, params, q, lookupLabel);
1817
2480
  });
1818
2481
  const d = desc(cls, "findAll");
1819
2482
  (0, import_common8.Get)()(cls.prototype, "findAll", d);
@@ -1838,7 +2501,7 @@ var registerFindOne = /* @__PURE__ */ __name((ctx) => {
1838
2501
  const { cls, config, oneSchema, idParamMeta } = ctx;
1839
2502
  const { name } = config;
1840
2503
  def(cls, "findOne", function(id) {
1841
- return this.repo.findOne(id);
2504
+ return findOne(this.repo, id);
1842
2505
  });
1843
2506
  const d = desc(cls, "findOne");
1844
2507
  (0, import_common8.Get)(":id")(cls.prototype, "findOne", d);
@@ -1863,7 +2526,7 @@ var registerCreate = /* @__PURE__ */ __name((ctx) => {
1863
2526
  const { cls, config, createSchema, bodyDecorator } = ctx;
1864
2527
  const { name } = config;
1865
2528
  def(cls, "create", function(body) {
1866
- return this.repo.create(body);
2529
+ return create(this.repo, body);
1867
2530
  });
1868
2531
  const d = desc(cls, "create");
1869
2532
  (0, import_common8.Post)()(cls.prototype, "create", d);
@@ -1886,7 +2549,7 @@ var registerUpdate = /* @__PURE__ */ __name((ctx) => {
1886
2549
  const { cls, config, updateSchema, idParamMeta, bodyDecorator } = ctx;
1887
2550
  const { name } = config;
1888
2551
  def(cls, "update", function(id, body) {
1889
- return this.repo.update(id, body);
2552
+ return update(this.repo, id, body);
1890
2553
  });
1891
2554
  const d = desc(cls, "update");
1892
2555
  (0, import_common8.Patch)(":id")(cls.prototype, "update", d);
@@ -1912,7 +2575,7 @@ var registerUpsert = /* @__PURE__ */ __name((ctx) => {
1912
2575
  const { cls, config, upsertSchema, bodyDecorator } = ctx;
1913
2576
  const { name } = config;
1914
2577
  def(cls, "upsert", function(body) {
1915
- return this.repo.upsert(body);
2578
+ return upsert(this.repo, body);
1916
2579
  });
1917
2580
  const d = desc(cls, "upsert");
1918
2581
  (0, import_common8.Put)()(cls.prototype, "upsert", d);
@@ -1935,7 +2598,7 @@ var registerDelete = /* @__PURE__ */ __name((ctx) => {
1935
2598
  const { cls, config, idParamMeta } = ctx;
1936
2599
  const { name } = config;
1937
2600
  def(cls, "delete", function(id) {
1938
- return this.repo.delete(id);
2601
+ return del(this.repo, id);
1939
2602
  });
1940
2603
  const d = desc(cls, "delete");
1941
2604
  (0, import_common8.Delete)(":id")(cls.prototype, "delete", d);
@@ -2124,6 +2787,12 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2124
2787
  id: a.id,
2125
2788
  label: a.label,
2126
2789
  href: resolveEnvPlaceholders(a.href),
2790
+ ...a.icon && {
2791
+ icon: a.icon
2792
+ },
2793
+ ...a.tooltip && {
2794
+ tooltip: a.tooltip
2795
+ },
2127
2796
  ...a.condition && {
2128
2797
  condition: a.condition
2129
2798
  }
@@ -2135,6 +2804,12 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2135
2804
  ...a.data && {
2136
2805
  data: a.data
2137
2806
  },
2807
+ ...a.icon && {
2808
+ icon: a.icon
2809
+ },
2810
+ ...a.tooltip && {
2811
+ tooltip: a.tooltip
2812
+ },
2138
2813
  ...a.condition && {
2139
2814
  condition: a.condition
2140
2815
  }
@@ -2147,7 +2822,10 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2147
2822
  label: a.label,
2148
2823
  icon: a.icon,
2149
2824
  tooltip: a.tooltip,
2150
- href: resolveEnvPlaceholders(a.href)
2825
+ href: resolveEnvPlaceholders(a.href),
2826
+ ...a.condition && {
2827
+ condition: a.condition
2828
+ }
2151
2829
  } : {
2152
2830
  id: a.id,
2153
2831
  label: a.label,
@@ -2157,6 +2835,9 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2157
2835
  method: a.method ?? "post",
2158
2836
  ...a.data && {
2159
2837
  data: a.data
2838
+ },
2839
+ ...a.condition && {
2840
+ condition: a.condition
2160
2841
  }
2161
2842
  })
2162
2843
  }
@@ -2231,6 +2912,34 @@ var registerResourceJsonEndpoint = /* @__PURE__ */ __name((ctx) => {
2231
2912
  // src/lib/crud/operations/register-sub-resources.ts
2232
2913
  var import_common10 = require("@nestjs/common");
2233
2914
  var import_swagger5 = require("@nestjs/swagger");
2915
+ var findAllByParent = /* @__PURE__ */ __name(async (repo, id, childRoute, params) => {
2916
+ const { data, count } = await repo.findAllByParent(id, childRoute, params);
2917
+ const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2918
+ return {
2919
+ data,
2920
+ request: {
2921
+ count,
2922
+ page: params.page,
2923
+ pageSize: params.pageSize,
2924
+ totalPages,
2925
+ sort: params.sort,
2926
+ sortDir: params.sortDir,
2927
+ filter: params.filter
2928
+ }
2929
+ };
2930
+ }, "findAllByParent");
2931
+ var createChild = /* @__PURE__ */ __name(async (repo, id, sub, body) => {
2932
+ return repo.createChild(id, sub, body);
2933
+ }, "createChild");
2934
+ var findOneChild = /* @__PURE__ */ __name(async (repo, sub, childId, parentId) => {
2935
+ return repo.findOneChild(sub, childId, parentId);
2936
+ }, "findOneChild");
2937
+ var updateChild = /* @__PURE__ */ __name(async (repo, sub, childId, body) => {
2938
+ return repo.updateChild(sub, childId, body);
2939
+ }, "updateChild");
2940
+ var deleteChild = /* @__PURE__ */ __name(async (repo, sub, childId, parentId) => {
2941
+ return repo.deleteChild(sub, childId, parentId);
2942
+ }, "deleteChild");
2234
2943
  var registerSubResourceSchemas = /* @__PURE__ */ __name((ctx, sub) => {
2235
2944
  if (!sub.views) return;
2236
2945
  const { cls, config, baseUrl } = ctx;
@@ -2302,20 +3011,7 @@ var registerSubResourceFindAll = /* @__PURE__ */ __name((ctx, sub) => {
2302
3011
  const idParamMeta = ctx.idParamMeta;
2303
3012
  const methodName = `findAllBy_${sub.childRoute}`;
2304
3013
  def(cls, methodName, async function(id, params) {
2305
- const { data, count } = await this.repo.findAllByParent(id, sub.childRoute, params);
2306
- const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2307
- return {
2308
- data,
2309
- request: {
2310
- count,
2311
- page: params.page,
2312
- pageSize: params.pageSize,
2313
- totalPages,
2314
- sort: params.sort,
2315
- sortDir: params.sortDir,
2316
- filter: params.filter
2317
- }
2318
- };
3014
+ return findAllByParent(this.repo, id, sub.childRoute, params);
2319
3015
  });
2320
3016
  const d = desc(cls, methodName);
2321
3017
  (0, import_common10.Get)(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
@@ -2336,7 +3032,7 @@ var registerSubResourceCreate = /* @__PURE__ */ __name((ctx, sub) => {
2336
3032
  const { name } = config;
2337
3033
  const methodName = `createChild_${sub.childRoute}`;
2338
3034
  def(cls, methodName, async function(id, body) {
2339
- return this.repo.createChild(id, sub, body);
3035
+ return createChild(this.repo, id, sub, body);
2340
3036
  });
2341
3037
  const d = desc(cls, methodName);
2342
3038
  (0, import_common10.Post)(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
@@ -2356,7 +3052,7 @@ var registerSubResourceFindOne = /* @__PURE__ */ __name((ctx, sub) => {
2356
3052
  const { cls } = ctx;
2357
3053
  const methodName = `findOneChild_${sub.childRoute}`;
2358
3054
  def(cls, methodName, async function(parentId, childId) {
2359
- return this.repo.findOneChild(sub, childId, parentId);
3055
+ return findOneChild(this.repo, sub, childId, parentId);
2360
3056
  });
2361
3057
  const d = desc(cls, methodName);
2362
3058
  (0, import_common10.Get)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2376,7 +3072,7 @@ var registerSubResourceUpdate = /* @__PURE__ */ __name((ctx, sub) => {
2376
3072
  const { cls } = ctx;
2377
3073
  const methodName = `updateChild_${sub.childRoute}`;
2378
3074
  def(cls, methodName, async function(_id, childId, body) {
2379
- return this.repo.updateChild(sub, childId, body);
3075
+ return updateChild(this.repo, sub, childId, body);
2380
3076
  });
2381
3077
  const d = desc(cls, methodName);
2382
3078
  (0, import_common10.Patch)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2397,7 +3093,7 @@ var registerSubResourceDelete = /* @__PURE__ */ __name((ctx, sub) => {
2397
3093
  const { cls } = ctx;
2398
3094
  const methodName = `deleteChild_${sub.childRoute}`;
2399
3095
  def(cls, methodName, async function(parentId, childId) {
2400
- return this.repo.deleteChild(sub, childId, parentId);
3096
+ return deleteChild(this.repo, sub, childId, parentId);
2401
3097
  });
2402
3098
  const d = desc(cls, methodName);
2403
3099
  (0, import_common10.Delete)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2436,9 +3132,9 @@ function createCrudController(config, baseUrl) {
2436
3132
  throw new Error(`Resource "${name}" declares 'upsert' but no upsertOn`);
2437
3133
  }
2438
3134
  const bodyDecorator = /* @__PURE__ */ __name((schema, options) => {
2439
- if (!schema) return (0, import_common12.Body)();
2440
- if (isZodSchema(schema)) return (0, import_common12.Body)(new ZodValidationPipe(schema, options));
2441
- return (0, import_common12.Body)();
3135
+ if (!schema) return (0, import_common11.Body)();
3136
+ if (isZodSchema(schema)) return (0, import_common11.Body)(new ZodValidationPipe(schema, options));
3137
+ return (0, import_common11.Body)();
2442
3138
  }, "bodyDecorator");
2443
3139
  let CrudControllerBase = class CrudControllerBase {
2444
3140
  static {
@@ -2493,165 +3189,115 @@ function createCrudController(config, baseUrl) {
2493
3189
  }
2494
3190
  __name(createCrudController, "createCrudController");
2495
3191
 
2496
- // src/lib/crud/loader/module.loader.ts
2497
- var import_node_fs2 = require("fs");
2498
- var import_node_module = require("module");
2499
- var import_node_path2 = require("path");
2500
- var import_meta = {};
2501
- var _require = (0, import_node_module.createRequire)(import_meta.url);
2502
- var findModule2 = /* @__PURE__ */ __name((dir, name) => {
2503
- for (const ext of [
2504
- ".ts",
2505
- ".js"
2506
- ]) {
2507
- const p = (0, import_node_path2.join)(dir, `${name}${ext}`);
2508
- if ((0, import_node_fs2.existsSync)(p)) return p;
3192
+ // src/lib/crud/builder/column-predicates.ts
3193
+ var isBoolean = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "boolean" || col.columnType === "boolean", "isBoolean");
3194
+ var isRelation = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "relation", "isRelation");
3195
+ var isAutocomplete = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "autocomplete", "isAutocomplete");
3196
+ var isRecordCell = /* @__PURE__ */ __name((col) => isRelation(col) || isAutocomplete(col), "isRecordCell");
3197
+ var isDateRange = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "date-range", "isDateRange");
3198
+
3199
+ // src/lib/crud/builder/column.utils.ts
3200
+ var colPosition = /* @__PURE__ */ __name((col, i) => col.fieldInput?.position ?? i, "colPosition");
3201
+ var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
3202
+ col,
3203
+ i
3204
+ })).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
3205
+ var toViewColumn = /* @__PURE__ */ __name((col) => ({
3206
+ id: col.id,
3207
+ ...col.label && {
3208
+ label: col.label
3209
+ },
3210
+ ...col.sortable != null && {
3211
+ sortable: col.sortable
3212
+ },
3213
+ ...col.searchable != null && {
3214
+ searchable: col.searchable
3215
+ },
3216
+ ...col.fieldInput && {
3217
+ fieldInput: col.fieldInput
2509
3218
  }
2510
- return void 0;
2511
- }, "findModule");
2512
- var IS_VITE = typeof globalThis.__vite_ssr_import__ === "function";
2513
- var importDefault = /* @__PURE__ */ __name(async (filePath) => {
2514
- try {
2515
- if (IS_VITE) {
2516
- const importPath = IS_DEV ? `${filePath}?t=${Date.now()}` : filePath;
2517
- const mod = await import(importPath);
2518
- return mod.default ?? mod;
2519
- } else {
2520
- const mod = _require(filePath);
2521
- return mod.default;
2522
- }
2523
- } catch {
2524
- return void 0;
3219
+ }), "toViewColumn");
3220
+
3221
+ // src/lib/crud/builder/sort.helpers.ts
3222
+ var deriveSortId = /* @__PURE__ */ __name((col) => {
3223
+ if (col.sortable === false) return null;
3224
+ if (col.sortId) return col.sortId;
3225
+ const base = col.column ?? col.id;
3226
+ const fi = col.fieldInput;
3227
+ const opts = fi?.options ?? {};
3228
+ const isRelation2 = fi?.format === "relation" || !!fi?.relationType || fi?.type === "autocomplete" || typeof opts.resource === "string";
3229
+ if (isRelation2) {
3230
+ const key = (typeof col.displayKey === "string" ? col.displayKey : void 0) ?? (typeof opts.displayKey === "string" ? opts.displayKey : void 0) ?? (typeof opts.labelKey === "string" ? opts.labelKey : void 0);
3231
+ if (!key) return null;
3232
+ const path2 = key.includes(".") ? key : `${base}.${key}`;
3233
+ return path2.endsWith(".label") ? path2.slice(0, -".label".length) : path2;
2525
3234
  }
2526
- }, "importDefault");
3235
+ if (!col.displayKey) return base;
3236
+ const path = `${base}.${col.displayKey}`;
3237
+ const isValueLabel = !!col.enum || opts.emitObject === true;
3238
+ if (isValueLabel && path.endsWith(".label")) return path.slice(0, -".label".length);
3239
+ return path;
3240
+ }, "deriveSortId");
3241
+ var resolveDefaultSort = /* @__PURE__ */ __name((tableCols, allColumns) => {
3242
+ const isSortable = /* @__PURE__ */ __name((c) => c.sortable !== false, "isSortable");
3243
+ const sortCol = tableCols.find((c) => c.defaultSort && isSortable(c)) ?? tableCols.find(isSortable) ?? allColumns?.find((c) => c.idField);
3244
+ return sortCol ? deriveSortId(sortCol) ?? sortCol.id : void 0;
3245
+ }, "resolveDefaultSort");
2527
3246
 
2528
- // src/lib/crud/loader/action.loader.ts
2529
- var import_node_path3 = require("path");
2530
- var loadActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
2531
- const results = [];
2532
- for (const action of jsonActions) {
2533
- if (action.type === "link") {
2534
- results.push({
2535
- type: "link",
2536
- id: action.id,
2537
- label: action.label,
2538
- href: action.href,
2539
- ...action.condition && {
2540
- condition: action.condition
2541
- }
2542
- });
2543
- continue;
2544
- }
2545
- const file = findModule2((0, import_node_path3.join)(basePath, "actions"), action.procedure);
2546
- if (!file) {
2547
- console.warn(`[actions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
2548
- continue;
2549
- }
2550
- const mod = await importDefault(file);
2551
- if (!mod) {
2552
- console.warn(`[actions] No default export in ${file}`);
2553
- continue;
3247
+ // src/lib/crud/builder/schema-transforms.ts
3248
+ var allowAdditionalProperties = /* @__PURE__ */ __name((schema) => {
3249
+ if (schema["type"] === "object") {
3250
+ schema["additionalProperties"] = true;
3251
+ const props = schema["properties"];
3252
+ if (props) {
3253
+ for (const value of Object.values(props)) {
3254
+ allowAdditionalProperties(value);
3255
+ }
2554
3256
  }
2555
- results.push({
2556
- id: action.id,
2557
- label: action.label,
2558
- method: action.method,
2559
- data: action.data,
2560
- ...action.condition && {
2561
- condition: action.condition
2562
- },
2563
- procedure: mod
2564
- });
2565
3257
  }
2566
- return results;
2567
- }, "loadActions");
2568
- var loadTableActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
2569
- const results = [];
2570
- for (const action of jsonActions) {
2571
- if (action.type === "link") {
2572
- results.push({
2573
- type: "link",
2574
- id: action.id,
2575
- label: action.label,
2576
- icon: action.icon,
2577
- tooltip: action.tooltip,
2578
- href: action.href
2579
- });
2580
- continue;
2581
- }
2582
- const file = findModule2((0, import_node_path3.join)(basePath, "actions"), action.procedure);
2583
- if (!file) {
2584
- console.warn(`[tableActions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
2585
- continue;
2586
- }
2587
- const mod = await importDefault(file);
2588
- if (!mod) {
2589
- console.warn(`[tableActions] No default export in ${file}`);
2590
- continue;
2591
- }
2592
- results.push({
2593
- id: action.id,
2594
- label: action.label,
2595
- icon: action.icon,
2596
- tooltip: action.tooltip,
2597
- method: action.method,
2598
- data: action.data,
2599
- procedure: mod
2600
- });
3258
+ if (schema["type"] === "array" && schema["items"]) {
3259
+ allowAdditionalProperties(schema["items"]);
2601
3260
  }
2602
- return results;
2603
- }, "loadTableActions");
2604
-
2605
- // src/lib/crud/loader/enum-registry.ts
2606
- var import_node_fs3 = require("fs");
2607
- var import_node_path4 = require("path");
2608
- var ENUMS_FILE = "crouton.enums.json";
2609
- var loadEnumRegistry = /* @__PURE__ */ __name((startDir, enumsFile) => {
2610
- let file = enumsFile;
2611
- if (!file) {
2612
- let dir = startDir;
2613
- while (true) {
2614
- const candidate = (0, import_node_path4.join)(dir, ENUMS_FILE);
2615
- if ((0, import_node_fs3.existsSync)(candidate)) {
2616
- file = candidate;
2617
- break;
2618
- }
2619
- const parent = (0, import_node_path4.dirname)(dir);
2620
- if (parent === dir) break;
2621
- dir = parent;
2622
- }
3261
+ }, "allowAdditionalProperties");
3262
+ var isNullableProperty2 = /* @__PURE__ */ __name((prop) => {
3263
+ const anyOf = prop?.["anyOf"];
3264
+ return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
3265
+ }, "isNullableProperty");
3266
+ var dropNullableFromRequired2 = /* @__PURE__ */ __name((schema) => {
3267
+ if (schema["type"] !== "object") return;
3268
+ const props = schema["properties"];
3269
+ if (!props) return;
3270
+ const required = schema["required"];
3271
+ if (Array.isArray(required)) {
3272
+ schema["required"] = required.filter((key) => !isNullableProperty2(props[key]));
2623
3273
  }
2624
- if (!file || !(0, import_node_fs3.existsSync)(file)) return {};
2625
- try {
2626
- return JSON.parse((0, import_node_fs3.readFileSync)(file, "utf-8"));
2627
- } catch {
2628
- return {};
3274
+ for (const value of Object.values(props)) {
3275
+ dropNullableFromRequired2(value);
2629
3276
  }
2630
- }, "loadEnumRegistry");
2631
- var injectEnumValues = /* @__PURE__ */ __name((columns, enums) => {
2632
- if (!columns) return;
2633
- for (const col of columns) {
2634
- if (!col.enum) continue;
2635
- const values = enums[col.enum];
2636
- if (!values) continue;
2637
- col.fieldInput = col.fieldInput ?? {
2638
- type: "select"
2639
- };
2640
- const options = col.fieldInput.options ?? {};
2641
- if (!("values" in options)) options.values = values;
2642
- col.fieldInput.options = options;
3277
+ }, "dropNullableFromRequired");
3278
+ var enforceRequiredMinLength = /* @__PURE__ */ __name((schema) => {
3279
+ if (schema["type"] !== "object") return;
3280
+ const props = schema["properties"];
3281
+ if (!props) return;
3282
+ for (const prop of Object.values(props)) {
3283
+ if (prop?.["type"] === "string" && !("minLength" in prop)) {
3284
+ prop["minLength"] = 1;
3285
+ } else if (prop?.["type"] === "object") {
3286
+ enforceRequiredMinLength(prop);
3287
+ }
2643
3288
  }
2644
- }, "injectEnumValues");
2645
-
2646
- // src/lib/crud/loader/json-adapter.ts
2647
- var import_zod9 = require("zod");
3289
+ }, "enforceRequiredMinLength");
3290
+ var applySchemaTransforms = /* @__PURE__ */ __name((schema) => {
3291
+ allowAdditionalProperties(schema);
3292
+ dropNullableFromRequired2(schema);
3293
+ enforceRequiredMinLength(schema);
3294
+ }, "applySchemaTransforms");
2648
3295
 
2649
- // src/lib/crud/loader/schema.helpers.ts
3296
+ // src/lib/crud/builder/schema.helpers.ts
2650
3297
  var pickByColumns = /* @__PURE__ */ __name((schema, columns, filter) => {
2651
3298
  if (!schema) return void 0;
2652
3299
  if (!columns?.length) return schema;
2653
- const isRelation2 = /* @__PURE__ */ __name((c) => c.fieldInput?.format === "relation", "isRelation");
2654
- const baseFilter = /* @__PURE__ */ __name((c) => !isRelation2(c) && (filter ? filter(c) : true), "baseFilter");
3300
+ const baseFilter = /* @__PURE__ */ __name((c) => !isRelation(c) && (filter ? filter(c) : true), "baseFilter");
2655
3301
  const filtered = columns.filter(baseFilter);
2656
3302
  if (!filtered.length) return void 0;
2657
3303
  const mask = Object.fromEntries(filtered.map((c) => [
@@ -2682,10 +3328,75 @@ var upsertOp = /* @__PURE__ */ __name((entry, schema) => {
2682
3328
  return void 0;
2683
3329
  }, "upsertOp");
2684
3330
 
2685
- // src/lib/crud/loader/view.builders.ts
2686
- var import_zod8 = require("zod");
3331
+ // src/lib/crud/builder/table-schema.builder.ts
3332
+ var SHARED_CELL_OPTION_KEYS = [
3333
+ "values",
3334
+ "storeValue",
3335
+ "uri",
3336
+ "resourceUri",
3337
+ "schemasUri"
3338
+ ];
3339
+ var pickSharedCellOptions = /* @__PURE__ */ __name((col) => {
3340
+ const options = col.fieldInput?.options ?? {};
3341
+ return Object.fromEntries(SHARED_CELL_OPTION_KEYS.filter((key) => options[key] !== void 0).map((key) => [
3342
+ key,
3343
+ options[key]
3344
+ ]));
3345
+ }, "pickSharedCellOptions");
3346
+ var buildTableUiSchema = /* @__PURE__ */ __name((cols) => {
3347
+ const layout = TableBuilder.init().addControls(...cols.map((col) => {
3348
+ const cellBuilder = isBoolean(col) ? BooleanCellBuilder : TextCellBuilder;
3349
+ let builder = cellBuilder.properties(col.id);
3350
+ if (col.displayKey) builder = builder.key(col.displayKey);
3351
+ if (col.sortId) builder = builder.setSortId(col.sortId);
3352
+ return builder;
3353
+ })).build();
3354
+ const colMap = Object.fromEntries(cols.map((c) => [
3355
+ c.id,
3356
+ c
3357
+ ]));
3358
+ layout.elements = layout.elements.map((el) => {
3359
+ const id = el.scope?.replace("#/properties/", "");
3360
+ const col = id ? colMap[id] : void 0;
3361
+ if (!col) return el;
3362
+ const fieldInputOptions = isRecordCell(col) || isDateRange(col) ? col.fieldInput?.options ?? {} : pickSharedCellOptions(col);
3363
+ const dataPathOption = col.column ? {
3364
+ dataPath: col.column
3365
+ } : {};
3366
+ const derivedSortId = isRecordCell(col) || isDateRange(col) ? null : deriveSortId(col);
3367
+ const sortOptions = derivedSortId ? {
3368
+ sortId: derivedSortId
3369
+ } : {
3370
+ sortable: false
3371
+ };
3372
+ const relationTypeOption = col.fieldInput?.relationType ? {
3373
+ relationType: col.fieldInput.relationType
3374
+ } : {};
3375
+ return {
3376
+ ...el,
3377
+ options: {
3378
+ ...el.options ?? {},
3379
+ ...fieldInputOptions,
3380
+ ...dataPathOption,
3381
+ ...sortOptions,
3382
+ ...relationTypeOption,
3383
+ ...isDateRange(col) && {
3384
+ format: "date-range"
3385
+ },
3386
+ label: col.label
3387
+ },
3388
+ ...isRecordCell(col) && {
3389
+ type: "RecordCell"
3390
+ },
3391
+ ...isDateRange(col) && {
3392
+ type: "Control"
3393
+ }
3394
+ };
3395
+ });
3396
+ return layout;
3397
+ }, "buildTableUiSchema");
2687
3398
 
2688
- // src/lib/crud/loader/form-schema.builder.ts
3399
+ // src/lib/crud/builder/form-schema.builder.ts
2689
3400
  var buildConditionSchema = /* @__PURE__ */ __name((when) => {
2690
3401
  if (when.notExists) return {
2691
3402
  not: {
@@ -2805,232 +3516,7 @@ var buildFormUiSchema = /* @__PURE__ */ __name((cols) => {
2805
3516
  return layout;
2806
3517
  }, "buildFormUiSchema");
2807
3518
 
2808
- // src/lib/crud/loader/schema-transforms.ts
2809
- var allowAdditionalProperties = /* @__PURE__ */ __name((schema) => {
2810
- if (schema["type"] === "object") {
2811
- schema["additionalProperties"] = true;
2812
- const props = schema["properties"];
2813
- if (props) {
2814
- for (const value of Object.values(props)) {
2815
- allowAdditionalProperties(value);
2816
- }
2817
- }
2818
- }
2819
- if (schema["type"] === "array" && schema["items"]) {
2820
- allowAdditionalProperties(schema["items"]);
2821
- }
2822
- }, "allowAdditionalProperties");
2823
- var isNullableProperty2 = /* @__PURE__ */ __name((prop) => {
2824
- const anyOf = prop?.["anyOf"];
2825
- return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
2826
- }, "isNullableProperty");
2827
- var dropNullableFromRequired2 = /* @__PURE__ */ __name((schema) => {
2828
- if (schema["type"] !== "object") return;
2829
- const props = schema["properties"];
2830
- if (!props) return;
2831
- const required = schema["required"];
2832
- if (Array.isArray(required)) {
2833
- schema["required"] = required.filter((key) => !isNullableProperty2(props[key]));
2834
- }
2835
- for (const value of Object.values(props)) {
2836
- dropNullableFromRequired2(value);
2837
- }
2838
- }, "dropNullableFromRequired");
2839
- var enforceRequiredMinLength = /* @__PURE__ */ __name((schema) => {
2840
- if (schema["type"] !== "object") return;
2841
- const props = schema["properties"];
2842
- if (!props) return;
2843
- for (const prop of Object.values(props)) {
2844
- if (prop?.["type"] === "string" && !("minLength" in prop)) {
2845
- prop["minLength"] = 1;
2846
- } else if (prop?.["type"] === "object") {
2847
- enforceRequiredMinLength(prop);
2848
- }
2849
- }
2850
- }, "enforceRequiredMinLength");
2851
-
2852
- // src/lib/crud/loader/table-schema.builder.ts
2853
- var isBoolean = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "boolean" || col.columnType === "boolean", "isBoolean");
2854
- var isRelation = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "relation", "isRelation");
2855
- var isAutocomplete = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "autocomplete", "isAutocomplete");
2856
- var isRecordCell = /* @__PURE__ */ __name((col) => isRelation(col) || isAutocomplete(col), "isRecordCell");
2857
- var isDateRange = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "date-range", "isDateRange");
2858
- var deriveSortId = /* @__PURE__ */ __name((col) => {
2859
- if (col.sortable === false) return null;
2860
- if (col.sortId) return col.sortId;
2861
- const base = col.column ?? col.id;
2862
- const fi = col.fieldInput;
2863
- const opts = fi?.options ?? {};
2864
- const isRelation2 = fi?.format === "relation" || !!fi?.relationType || fi?.type === "autocomplete" || typeof opts.resource === "string";
2865
- if (isRelation2) {
2866
- const key = (typeof col.displayKey === "string" ? col.displayKey : void 0) ?? (typeof opts.displayKey === "string" ? opts.displayKey : void 0) ?? (typeof opts.labelKey === "string" ? opts.labelKey : void 0);
2867
- if (!key) return null;
2868
- const path2 = key.includes(".") ? key : `${base}.${key}`;
2869
- return path2.endsWith(".label") ? path2.slice(0, -".label".length) : path2;
2870
- }
2871
- if (!col.displayKey) return base;
2872
- const path = `${base}.${col.displayKey}`;
2873
- const isValueLabel = !!col.enum || opts.emitObject === true;
2874
- if (isValueLabel && path.endsWith(".label")) return path.slice(0, -".label".length);
2875
- return path;
2876
- }, "deriveSortId");
2877
- var resolveDefaultSort = /* @__PURE__ */ __name((tableCols, allColumns) => {
2878
- const isSortable = /* @__PURE__ */ __name((c) => c.sortable !== false, "isSortable");
2879
- const sortCol = tableCols.find((c) => c.defaultSort && isSortable(c)) ?? tableCols.find(isSortable) ?? allColumns?.find((c) => c.idField);
2880
- return sortCol ? deriveSortId(sortCol) ?? sortCol.id : void 0;
2881
- }, "resolveDefaultSort");
2882
- var SHARED_CELL_OPTION_KEYS = [
2883
- "values",
2884
- "storeValue",
2885
- "uri",
2886
- "resourceUri",
2887
- "schemasUri"
2888
- ];
2889
- var pickSharedCellOptions = /* @__PURE__ */ __name((col) => {
2890
- const options = col.fieldInput?.options ?? {};
2891
- return Object.fromEntries(SHARED_CELL_OPTION_KEYS.filter((key) => options[key] !== void 0).map((key) => [
2892
- key,
2893
- options[key]
2894
- ]));
2895
- }, "pickSharedCellOptions");
2896
- var buildTableUiSchema = /* @__PURE__ */ __name((cols) => {
2897
- const layout = TableBuilder.init().addControls(...cols.map((col) => {
2898
- const cellBuilder = isBoolean(col) ? BooleanCellBuilder : TextCellBuilder;
2899
- let builder = cellBuilder.properties(col.id);
2900
- if (col.displayKey) builder = builder.key(col.displayKey);
2901
- if (col.sortId) builder = builder.setSortId(col.sortId);
2902
- return builder;
2903
- })).build();
2904
- const colMap = Object.fromEntries(cols.map((c) => [
2905
- c.id,
2906
- c
2907
- ]));
2908
- layout.elements = layout.elements.map((el) => {
2909
- const id = el.scope?.replace("#/properties/", "");
2910
- const col = id ? colMap[id] : void 0;
2911
- if (!col) return el;
2912
- const fieldInputOptions = isRecordCell(col) || isDateRange(col) ? col.fieldInput?.options ?? {} : pickSharedCellOptions(col);
2913
- const dataPathOption = col.column ? {
2914
- dataPath: col.column
2915
- } : {};
2916
- const derivedSortId = isRecordCell(col) || isDateRange(col) ? null : deriveSortId(col);
2917
- const sortOptions = derivedSortId ? {
2918
- sortId: derivedSortId
2919
- } : {
2920
- sortable: false
2921
- };
2922
- const relationTypeOption = col.fieldInput?.relationType ? {
2923
- relationType: col.fieldInput.relationType
2924
- } : {};
2925
- return {
2926
- ...el,
2927
- options: {
2928
- ...el.options ?? {},
2929
- ...fieldInputOptions,
2930
- ...dataPathOption,
2931
- ...sortOptions,
2932
- ...relationTypeOption,
2933
- ...isDateRange(col) && {
2934
- format: "date-range"
2935
- },
2936
- label: col.label
2937
- },
2938
- ...isRecordCell(col) && {
2939
- type: "RecordCell"
2940
- },
2941
- ...isDateRange(col) && {
2942
- type: "Control"
2943
- }
2944
- };
2945
- });
2946
- return layout;
2947
- }, "buildTableUiSchema");
2948
-
2949
- // src/lib/crud/loader/view.builders.ts
2950
- var patchFilterProperties = /* @__PURE__ */ __name((jsonSchema, columns) => {
2951
- if (!columns?.length) return;
2952
- const properties = jsonSchema.properties;
2953
- if (!properties) return;
2954
- for (const col of columns) {
2955
- if (col.fieldInput?.format === "date-range") {
2956
- delete properties[col.id];
2957
- const opts = col.fieldInput?.options ?? {};
2958
- const base = col.label ?? col.id;
2959
- const fromField = opts["fromField"] ?? "from";
2960
- const toField = opts["toField"] ?? "to";
2961
- const dateProp = /* @__PURE__ */ __name((title) => ({
2962
- type: "string",
2963
- format: "date",
2964
- title,
2965
- "x-field-type": "date"
2966
- }), "dateProp");
2967
- properties[`${col.id}->${fromField}`] = dateProp(opts["fromLabel"] ?? `${base} (from)`);
2968
- properties[`${col.id}->${toField}`] = dateProp(opts["toLabel"] ?? `${base} (to)`);
2969
- continue;
2970
- }
2971
- const prop = properties[col.id];
2972
- if (!prop) continue;
2973
- prop.title = col.label ?? col.id;
2974
- const values = col.fieldInput?.options?.["values"];
2975
- if (values) {
2976
- prop["x-values"] = values;
2977
- }
2978
- const ft = col.fieldInput?.type;
2979
- if (ft === "select" || col.enum) {
2980
- prop["x-field-type"] = "enum";
2981
- } else if (ft === "number" || prop.type === "number" || prop.type === "integer") {
2982
- prop["x-field-type"] = "number";
2983
- } else if (prop.format === "date-time" || prop.format === "date") {
2984
- prop["x-field-type"] = "date";
2985
- } else if (prop.type === "boolean") {
2986
- prop["x-field-type"] = "boolean";
2987
- }
2988
- }
2989
- }, "patchFilterProperties");
2990
- var colPosition = /* @__PURE__ */ __name((col, i) => col.fieldInput?.position ?? i, "colPosition");
2991
- var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
2992
- col,
2993
- i
2994
- })).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
2995
- var toViewColumn = /* @__PURE__ */ __name((col) => ({
2996
- id: col.id,
2997
- ...col.label && {
2998
- label: col.label
2999
- },
3000
- ...col.sortable != null && {
3001
- sortable: col.sortable
3002
- },
3003
- ...col.searchable != null && {
3004
- searchable: col.searchable
3005
- },
3006
- ...col.fieldInput && {
3007
- fieldInput: col.fieldInput
3008
- }
3009
- }), "toViewColumn");
3010
- var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema, sort = false, schemaVisible) => {
3011
- if (!schema || !columns?.length) return void 0;
3012
- const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
3013
- if (!visibleCols.length) return void 0;
3014
- const schemaCols = schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols;
3015
- const schemaIds = schemaCols.map((c) => c.id);
3016
- const mask = Object.fromEntries(schemaIds.map((id) => [
3017
- id,
3018
- true
3019
- ]));
3020
- const picked = schema.pick(mask);
3021
- const jsonSchema = (0, import_zod8.toJSONSchema)(picked, {
3022
- target: "draft-07",
3023
- ...jsonSchemaOpts
3024
- });
3025
- allowAdditionalProperties(jsonSchema);
3026
- dropNullableFromRequired2(jsonSchema);
3027
- enforceRequiredMinLength(jsonSchema);
3028
- return {
3029
- json_schema: jsonSchema,
3030
- ui_schema: buildUiSchema(visibleCols),
3031
- columns: visibleCols.map(toViewColumn)
3032
- };
3033
- }, "buildView");
3519
+ // src/lib/crud/builder/calculated-columns.builder.ts
3034
3520
  var isVisibleInMode = /* @__PURE__ */ __name((c, mode) => mode === "table" ? !c.hiddenInTable : !c.hiddenInView, "isVisibleInMode");
3035
3521
  var buildCalculatedElement = /* @__PURE__ */ __name((c, mode) => mode === "table" ? {
3036
3522
  type: c.type === "boolean" ? "BooleanCell" : "TextCell",
@@ -3109,6 +3595,78 @@ var injectCalculatedColumnsIntoView = /* @__PURE__ */ __name((viewConfig, calcul
3109
3595
  }, "injectCalculatedColumnsIntoView");
3110
3596
  var injectCalculatedColumns = /* @__PURE__ */ __name((tableView, calculated) => injectCalculatedColumnsIntoView(tableView, calculated, "table"), "injectCalculatedColumns");
3111
3597
  var injectCalculatedColumnsToView = /* @__PURE__ */ __name((viewConfig, calculated) => injectCalculatedColumnsIntoView(viewConfig, calculated, "view"), "injectCalculatedColumnsToView");
3598
+
3599
+ // src/lib/crud/builder/view.builder.ts
3600
+ var import_zod20 = require("zod");
3601
+ var patchFilterProperties = /* @__PURE__ */ __name((jsonSchema, columns) => {
3602
+ if (!columns?.length) return;
3603
+ const properties = jsonSchema.properties;
3604
+ if (!properties) return;
3605
+ for (const col of columns) {
3606
+ if (col.fieldInput?.format === "date-range") {
3607
+ delete properties[col.id];
3608
+ const opts = col.fieldInput?.options ?? {};
3609
+ const base = col.label ?? col.id;
3610
+ const fromField = opts["fromField"] ?? "from";
3611
+ const toField = opts["toField"] ?? "to";
3612
+ const dateProp = /* @__PURE__ */ __name((title) => ({
3613
+ type: "string",
3614
+ format: "date",
3615
+ title,
3616
+ "x-field-type": "date"
3617
+ }), "dateProp");
3618
+ properties[`${col.id}->${fromField}`] = dateProp(opts["fromLabel"] ?? `${base} (from)`);
3619
+ properties[`${col.id}->${toField}`] = dateProp(opts["toLabel"] ?? `${base} (to)`);
3620
+ continue;
3621
+ }
3622
+ const prop = properties[col.id];
3623
+ if (!prop) continue;
3624
+ prop.title = col.label ?? col.id;
3625
+ const values = col.fieldInput?.options?.["values"];
3626
+ if (values) {
3627
+ prop["x-values"] = values;
3628
+ }
3629
+ const ft = col.fieldInput?.type;
3630
+ if (ft === "select" || col.enum) {
3631
+ prop["x-field-type"] = "enum";
3632
+ } else if (ft === "number" || prop.type === "number" || prop.type === "integer") {
3633
+ prop["x-field-type"] = "number";
3634
+ } else if (prop.format === "date-time" || prop.format === "date") {
3635
+ prop["x-field-type"] = "date";
3636
+ } else if (prop.type === "boolean") {
3637
+ prop["x-field-type"] = "boolean";
3638
+ }
3639
+ }
3640
+ }, "patchFilterProperties");
3641
+ var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema, sort = false, schemaVisible) => {
3642
+ if (!schema || !columns?.length) return void 0;
3643
+ const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
3644
+ if (!visibleCols.length) return void 0;
3645
+ const schemaCols = (schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols).filter((c) => !isRelation(c));
3646
+ const schemaIds = schemaCols.map((c) => c.id);
3647
+ const mask = Object.fromEntries(schemaIds.map((id) => [
3648
+ id,
3649
+ true
3650
+ ]));
3651
+ const picked = schema.pick(mask);
3652
+ const jsonSchema = (0, import_zod20.toJSONSchema)(picked, {
3653
+ target: "draft-07",
3654
+ ...jsonSchemaOpts
3655
+ });
3656
+ applySchemaTransforms(jsonSchema);
3657
+ if (schemaVisible) {
3658
+ const nonEditableIds = new Set(schemaCols.filter((c) => c.idField || c.createable === false && c.updateable === false).map((c) => c.id));
3659
+ const required = jsonSchema["required"];
3660
+ if (Array.isArray(required) && nonEditableIds.size) {
3661
+ jsonSchema["required"] = required.filter((k) => !nonEditableIds.has(k));
3662
+ }
3663
+ }
3664
+ return {
3665
+ json_schema: jsonSchema,
3666
+ ui_schema: buildUiSchema(visibleCols),
3667
+ columns: visibleCols.map(toViewColumn)
3668
+ };
3669
+ }, "buildView");
3112
3670
  var emptyTableView = /* @__PURE__ */ __name(() => ({
3113
3671
  json_schema: {
3114
3672
  type: "object",
@@ -3127,7 +3685,7 @@ var buildViews = /* @__PURE__ */ __name((schema, columns) => {
3127
3685
  } else if (columns?.length) {
3128
3686
  views.table = emptyTableView();
3129
3687
  }
3130
- const form = buildView(schema, columns, (c) => !c.hiddenInForm, buildFormUiSchema, true, (c) => c.createable === true || c.updateable === true);
3688
+ const form = buildView(schema, columns, (c) => !c.hiddenInForm, buildFormUiSchema, true, (c) => !c.idField && (c.createable === true || c.updateable === true));
3131
3689
  if (form) views.form = form;
3132
3690
  const filter = buildView(schema, columns, (c) => !!c.filterable, buildFormUiSchema, true);
3133
3691
  if (filter) {
@@ -3147,7 +3705,6 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3147
3705
  if (!properties[c.column]) {
3148
3706
  properties[c.column] = {
3149
3707
  type: "object",
3150
- additionalProperties: true,
3151
3708
  properties: {}
3152
3709
  };
3153
3710
  }
@@ -3158,7 +3715,6 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3158
3715
  if (!target[segment]) {
3159
3716
  target[segment] = {
3160
3717
  type: "object",
3161
- additionalProperties: true,
3162
3718
  properties: {}
3163
3719
  };
3164
3720
  }
@@ -3169,17 +3725,22 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3169
3725
  title: c.label ?? c.id
3170
3726
  };
3171
3727
  } else {
3728
+ const opts = c.fieldInput?.options;
3729
+ const isObject = opts?.emitObject === true || c.fieldInput?.type === "autocomplete";
3172
3730
  properties[c.id] = {
3173
- type: c.columnType ?? "string",
3731
+ ...isObject ? {} : {
3732
+ type: c.columnType ?? "string"
3733
+ },
3174
3734
  title: c.label ?? c.id
3175
3735
  };
3176
3736
  }
3177
3737
  }
3178
- return {
3738
+ const schema = {
3179
3739
  type: "object",
3180
- additionalProperties: true,
3181
3740
  properties
3182
3741
  };
3742
+ applySchemaTransforms(schema);
3743
+ return schema;
3183
3744
  }, "buildJsonSchema");
3184
3745
  const fixNestedScopes = /* @__PURE__ */ __name((uiSchema, cols) => {
3185
3746
  const colMap = Object.fromEntries(cols.map((c) => [
@@ -3229,33 +3790,223 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3229
3790
  return Object.keys(views).length ? views : void 0;
3230
3791
  }, "buildViewsFromColumns");
3231
3792
 
3232
- // src/lib/crud/loader/json-adapter.ts
3793
+ // src/lib/crud/enum-registry/enum-registry.types.ts
3794
+ var import_zod21 = require("zod");
3795
+ var EnumEntrySchema = import_zod21.z.object({
3796
+ value: import_zod21.z.unknown(),
3797
+ label: import_zod21.z.string()
3798
+ });
3799
+ var EnumRegistrySchema = import_zod21.z.record(import_zod21.z.string(), import_zod21.z.array(EnumEntrySchema)).default({});
3800
+
3801
+ // src/lib/crud/enum-registry/enum-registry.loader.ts
3802
+ var import_node_fs3 = require("fs");
3803
+ var import_node_path5 = require("path");
3804
+ var ENUMS_FILE = "crouton.enums.json";
3805
+ var loadEnumRegistry = /* @__PURE__ */ __name((startDir, enumsFile) => {
3806
+ let file = enumsFile;
3807
+ if (!file) {
3808
+ let dir = startDir;
3809
+ while (true) {
3810
+ const candidate = (0, import_node_path5.join)(dir, ENUMS_FILE);
3811
+ if ((0, import_node_fs3.existsSync)(candidate)) {
3812
+ file = candidate;
3813
+ break;
3814
+ }
3815
+ const parent = (0, import_node_path5.dirname)(dir);
3816
+ if (parent === dir) break;
3817
+ dir = parent;
3818
+ }
3819
+ }
3820
+ if (!file || !(0, import_node_fs3.existsSync)(file)) return EnumRegistrySchema.parse(void 0);
3821
+ try {
3822
+ return EnumRegistrySchema.parse(JSON.parse((0, import_node_fs3.readFileSync)(file, "utf-8")));
3823
+ } catch {
3824
+ return EnumRegistrySchema.parse(void 0);
3825
+ }
3826
+ }, "loadEnumRegistry");
3827
+
3828
+ // src/lib/crud/enum-registry/enum-registry.injector.ts
3829
+ var injectEnumValues = /* @__PURE__ */ __name((columns, enums) => {
3830
+ if (!columns) return;
3831
+ for (const col of columns) {
3832
+ if (!col.enum) continue;
3833
+ const values = enums[col.enum];
3834
+ if (!values) continue;
3835
+ col.fieldInput = col.fieldInput ?? {
3836
+ type: "select"
3837
+ };
3838
+ const options = col.fieldInput.options ?? {};
3839
+ if (!("values" in options)) options.values = values;
3840
+ col.fieldInput.options = options;
3841
+ }
3842
+ }, "injectEnumValues");
3843
+
3844
+ // src/lib/crud/adapter/relation-type.ts
3845
+ var import_zod22 = require("zod");
3846
+ var unwrapZodType = /* @__PURE__ */ __name((type) => {
3847
+ if (type instanceof import_zod22.ZodOptional || type instanceof import_zod22.ZodNullable) {
3848
+ return unwrapZodType(type.unwrap());
3849
+ }
3850
+ return type;
3851
+ }, "unwrapZodType");
3852
+ var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
3853
+ if (!schema) return void 0;
3854
+ const field = schema.shape[columnId];
3855
+ if (!field) return void 0;
3856
+ const inner = unwrapZodType(field);
3857
+ return inner instanceof import_zod22.ZodArray ? "oneToMany" : "manyToOne";
3858
+ }, "deriveRelationType");
3859
+ var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
3860
+ const base = col.column ?? col.id;
3861
+ const fkNames = /* @__PURE__ */ new Set([
3862
+ `${base}_id`,
3863
+ `${col.id}_id`
3864
+ ]);
3865
+ return cols.some((c) => fkNames.has(c.id)) ? "manyToOne" : "oneToMany";
3866
+ }, "deriveRelationTypeFromColumns");
3867
+ var enrichRelationTypes = /* @__PURE__ */ __name((columns, schema) => {
3868
+ if (!schema) return columns;
3869
+ return columns.map((col) => {
3870
+ const fi = col.fieldInput;
3871
+ if (!fi) return col;
3872
+ const isRelationField = fi.type === "autocomplete" || fi.format === "relation";
3873
+ if (!isRelationField) return col;
3874
+ if (fi.relationType) return col;
3875
+ const derived = deriveRelationType(schema, col.id) ?? deriveRelationTypeFromColumns(col, columns);
3876
+ if (!derived) return col;
3877
+ return {
3878
+ ...col,
3879
+ fieldInput: {
3880
+ ...fi,
3881
+ relationType: derived
3882
+ }
3883
+ };
3884
+ });
3885
+ }, "enrichRelationTypes");
3886
+
3887
+ // src/lib/crud/resource/ReadResourceJson.ts
3233
3888
  var import_node_fs4 = require("fs");
3234
- var import_node_path5 = require("path");
3235
- var resolveChildResource = /* @__PURE__ */ __name((resourcePath, parentDir) => {
3236
- const directPath = (0, import_node_path5.resolve)(parentDir, resourcePath);
3237
- if (resourcePath.endsWith(".json") && (0, import_node_fs4.existsSync)(directPath)) {
3238
- try {
3239
- return {
3240
- json: JSON.parse((0, import_node_fs4.readFileSync)(directPath, "utf-8")),
3241
- dir: (0, import_node_path5.dirname)(directPath)
3242
- };
3243
- } catch {
3244
- return void 0;
3245
- }
3889
+ var import_node_path6 = require("path");
3890
+ var readResourceJson = /* @__PURE__ */ __name((jsonPath) => {
3891
+ if (!(0, import_node_fs4.existsSync)(jsonPath)) return void 0;
3892
+ const fileContent = JSON.parse((0, import_node_fs4.readFileSync)(jsonPath, "utf-8"));
3893
+ const resource = ResourceJsonSchema.safeParse(fileContent);
3894
+ if (resource.error) {
3895
+ console.error(resource.error);
3896
+ throw new Error(`Resource cannot be parsed ${jsonPath}`);
3246
3897
  }
3247
- const childName = resourcePath.replace(/^\.\//, "").replace(/\.resource$/, "");
3248
- const childJsonPath = (0, import_node_path5.resolve)((0, import_node_path5.dirname)(parentDir), childName, "resource.json");
3249
- if (!(0, import_node_fs4.existsSync)(childJsonPath)) return void 0;
3898
+ return {
3899
+ json: resource.data,
3900
+ dir: (0, import_node_path6.dirname)(jsonPath)
3901
+ };
3902
+ }, "readResourceJson");
3903
+
3904
+ // src/lib/crud/adapter/resource-resolver.ts
3905
+ var import_node_fs5 = require("fs");
3906
+ var import_node_path7 = require("path");
3907
+ var resolveChildResource = /* @__PURE__ */ __name((resourcePath, parentDir) => {
3908
+ const directPath = (0, import_node_path7.resolve)(parentDir, resourcePath);
3250
3909
  try {
3251
- return {
3252
- json: JSON.parse((0, import_node_fs4.readFileSync)(childJsonPath, "utf-8")),
3253
- dir: (0, import_node_path5.dirname)(childJsonPath)
3254
- };
3910
+ if (resourcePath.endsWith(".json") && (0, import_node_fs5.existsSync)(directPath)) {
3911
+ return readResourceJson(directPath);
3912
+ }
3913
+ const childName = resourcePath.replace(/^\.\//, "").replace(/\.resource$/, "");
3914
+ const childJsonPath = (0, import_node_path7.resolve)((0, import_node_path7.dirname)(parentDir), childName, "resource.json");
3915
+ return readResourceJson(childJsonPath);
3255
3916
  } catch {
3256
3917
  return void 0;
3257
3918
  }
3258
3919
  }, "resolveChildResource");
3920
+
3921
+ // src/lib/crud/adapter/column-enrichment.ts
3922
+ var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResources, baseUrl) => {
3923
+ if (!columns) return columns;
3924
+ const base = baseUrl ?? "";
3925
+ return columns.map((col) => {
3926
+ if (col.fieldInput?.format !== "relation") return col;
3927
+ const sub = subResources.find((s) => s.column === col.id);
3928
+ if (!sub) return col;
3929
+ return {
3930
+ ...col,
3931
+ fieldInput: {
3932
+ ...col.fieldInput,
3933
+ options: {
3934
+ ...col.fieldInput.options,
3935
+ uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
3936
+ resourceUri: `${base}/${sub.childRoute}`,
3937
+ ...sub.views && {
3938
+ resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
3939
+ }
3940
+ }
3941
+ }
3942
+ };
3943
+ });
3944
+ }, "enrichActionColumns");
3945
+ var enrichResourceRefColumns = /* @__PURE__ */ __name((columns, parentDir, baseUrl) => {
3946
+ if (!columns || !parentDir) return columns;
3947
+ const base = baseUrl ?? "";
3948
+ return columns.map((col) => {
3949
+ if (!col.fieldInput?.resource || col.fieldInput.format === "relation") return col;
3950
+ const childResolved = resolveChildResource(col.fieldInput.resource, parentDir);
3951
+ const childRoute = childResolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3952
+ return {
3953
+ ...col,
3954
+ fieldInput: {
3955
+ ...col.fieldInput,
3956
+ options: {
3957
+ ...col.fieldInput.options,
3958
+ resourceUri: `${base}/${childRoute}`,
3959
+ schemasUri: `${base}/${childRoute}/schemas`
3960
+ }
3961
+ }
3962
+ };
3963
+ });
3964
+ }, "enrichResourceRefColumns");
3965
+ var enrichNestedRelationColumns = /* @__PURE__ */ __name((cols, dir, baseUrl) => {
3966
+ if (!cols || !dir) return cols;
3967
+ const base = baseUrl ?? "";
3968
+ return cols.map((col) => {
3969
+ if (col.fieldInput?.format !== "relation" || !col.fieldInput.resource) return col;
3970
+ const resolved = resolveChildResource(col.fieldInput.resource, dir);
3971
+ const targetRoute = resolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\/resource\.json$/, "").replace(/\.resource$/, "");
3972
+ const relationType = col.fieldInput.relationType ?? deriveRelationTypeFromColumns(col, cols);
3973
+ return {
3974
+ ...col,
3975
+ fieldInput: {
3976
+ ...col.fieldInput,
3977
+ relationType,
3978
+ options: {
3979
+ ...col.fieldInput.options,
3980
+ uri: `${base}/${targetRoute}`,
3981
+ resourceUri: `${base}/${targetRoute}`,
3982
+ resource: `${base}/${targetRoute}/schemas`
3983
+ }
3984
+ }
3985
+ };
3986
+ });
3987
+ }, "enrichNestedRelationColumns");
3988
+ var enrichIncludeWithSort = /* @__PURE__ */ __name((include, columns) => {
3989
+ if (!include?.length) return include;
3990
+ return include.map((entry) => {
3991
+ const relationName = typeof entry === "string" ? entry : entry.relation;
3992
+ const col = columns.find((c) => {
3993
+ const opts2 = c.fieldInput?.options;
3994
+ return (c.column ?? c.id) === relationName && opts2?.sort;
3995
+ });
3996
+ if (!col) return entry;
3997
+ const opts = col.fieldInput.options;
3998
+ const orderBy = buildChildSortClause(opts.sort, opts.sortDir ?? "asc");
3999
+ return typeof entry === "string" ? {
4000
+ relation: entry,
4001
+ orderBy
4002
+ } : {
4003
+ ...entry,
4004
+ orderBy
4005
+ };
4006
+ });
4007
+ }, "enrichIncludeWithSort");
4008
+
4009
+ // src/lib/crud/adapter/column-transforms.ts
3259
4010
  var expandExtendColumns = /* @__PURE__ */ __name((columns, dirPath) => {
3260
4011
  if (!dirPath) return columns;
3261
4012
  const result = [];
@@ -3270,7 +4021,7 @@ var expandExtendColumns = /* @__PURE__ */ __name((columns, dirPath) => {
3270
4021
  result.push(col);
3271
4022
  continue;
3272
4023
  }
3273
- const refColumns = normalizeColumns(resolved.json.columns) ?? [];
4024
+ const refColumns = resolved.json.columns;
3274
4025
  const parentColumnKey = col.column ?? col.id;
3275
4026
  for (const refCol of refColumns) {
3276
4027
  if (refCol.idField) continue;
@@ -3331,37 +4082,8 @@ var applyRelationFormatDefault = /* @__PURE__ */ __name((cols) => cols?.map((col
3331
4082
  }
3332
4083
  return col;
3333
4084
  }), "applyRelationFormatDefault");
3334
- var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
3335
- const base = col.column ?? col.id;
3336
- const fkNames = /* @__PURE__ */ new Set([
3337
- `${base}_id`,
3338
- `${col.id}_id`
3339
- ]);
3340
- return cols.some((c) => fkNames.has(c.id)) ? "manyToOne" : "oneToMany";
3341
- }, "deriveRelationTypeFromColumns");
3342
- var enrichNestedRelationColumns = /* @__PURE__ */ __name((cols, dir, baseUrl) => {
3343
- if (!cols || !dir) return cols;
3344
- const base = baseUrl ?? "";
3345
- return cols.map((col) => {
3346
- if (col.fieldInput?.format !== "relation" || !col.fieldInput.resource) return col;
3347
- const resolved = resolveChildResource(col.fieldInput.resource, dir);
3348
- const targetRoute = resolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\/resource\.json$/, "").replace(/\.resource$/, "");
3349
- const relationType = col.fieldInput.relationType ?? deriveRelationTypeFromColumns(col, cols);
3350
- return {
3351
- ...col,
3352
- fieldInput: {
3353
- ...col.fieldInput,
3354
- relationType,
3355
- options: {
3356
- ...col.fieldInput.options,
3357
- uri: `${base}/${targetRoute}`,
3358
- resourceUri: `${base}/${targetRoute}`,
3359
- resource: `${base}/${targetRoute}/schemas`
3360
- }
3361
- }
3362
- };
3363
- });
3364
- }, "enrichNestedRelationColumns");
4085
+
4086
+ // src/lib/crud/adapter/sub-resource.builder.ts
3365
4087
  var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentModel, parentDir, enums = {}, baseUrl) => {
3366
4088
  if (!columns || !parentDir) return [];
3367
4089
  return columns.filter((c) => c.fieldInput?.format === "relation" && c.fieldInput?.resource).map((c) => {
@@ -3369,7 +4091,7 @@ var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentMode
3369
4091
  const childJson = childResolved?.json;
3370
4092
  const childDir = childResolved?.dir;
3371
4093
  const childRoute = childJson?.route ?? c.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3372
- const rawChildColumns = childJson ? normalizeColumns(childJson.columns) : void 0;
4094
+ const rawChildColumns = childJson?.columns;
3373
4095
  const expandedChildColumns = rawChildColumns ? expandExtendColumns(rawChildColumns, childDir) : void 0;
3374
4096
  const childColumns = applyRelationFormatDefault(expandedChildColumns) ?? expandedChildColumns;
3375
4097
  injectEnumValues(childColumns, enums);
@@ -3431,71 +4153,10 @@ var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentMode
3431
4153
  };
3432
4154
  });
3433
4155
  }, "buildSubResources");
3434
- var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResources, baseUrl) => {
3435
- if (!columns) return columns;
3436
- const base = baseUrl ?? "";
3437
- return columns.map((col) => {
3438
- if (col.fieldInput?.format !== "relation") return col;
3439
- const sub = subResources.find((s) => s.column === col.id);
3440
- if (!sub) return col;
3441
- return {
3442
- ...col,
3443
- fieldInput: {
3444
- ...col.fieldInput,
3445
- options: {
3446
- ...col.fieldInput.options,
3447
- uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
3448
- resourceUri: `${base}/${sub.childRoute}`,
3449
- ...sub.views && {
3450
- resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
3451
- }
3452
- }
3453
- }
3454
- };
3455
- });
3456
- }, "enrichActionColumns");
3457
- var enrichResourceRefColumns = /* @__PURE__ */ __name((columns, parentDir, baseUrl) => {
3458
- if (!columns || !parentDir) return columns;
3459
- const base = baseUrl ?? "";
3460
- return columns.map((col) => {
3461
- if (!col.fieldInput?.resource || col.fieldInput.format === "relation") return col;
3462
- const childResolved = resolveChildResource(col.fieldInput.resource, parentDir);
3463
- const childRoute = childResolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3464
- return {
3465
- ...col,
3466
- fieldInput: {
3467
- ...col.fieldInput,
3468
- options: {
3469
- ...col.fieldInput.options,
3470
- resourceUri: `${base}/${childRoute}`,
3471
- schemasUri: `${base}/${childRoute}/schemas`
3472
- }
3473
- }
3474
- };
3475
- });
3476
- }, "enrichResourceRefColumns");
3477
- var enrichIncludeWithSort = /* @__PURE__ */ __name((include, columns) => {
3478
- if (!include?.length) return include;
3479
- return include.map((entry) => {
3480
- const relationName = typeof entry === "string" ? entry : entry.relation;
3481
- const col = columns.find((c) => {
3482
- const opts2 = c.fieldInput?.options;
3483
- return (c.column ?? c.id) === relationName && opts2?.sort;
3484
- });
3485
- if (!col) return entry;
3486
- const opts = col.fieldInput.options;
3487
- const orderBy = buildChildSortClause(opts.sort, opts.sortDir ?? "asc");
3488
- return typeof entry === "string" ? {
3489
- relation: entry,
3490
- orderBy
3491
- } : {
3492
- ...entry,
3493
- orderBy
3494
- };
3495
- });
3496
- }, "enrichIncludeWithSort");
4156
+
4157
+ // src/lib/crud/adapter/json-adapter.ts
3497
4158
  var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, actions, tableActions, enums = {}) => {
3498
- const rawColumns = expandExtendColumns(normalizeColumns(json.columns) ?? [], dirPath);
4159
+ const rawColumns = expandExtendColumns(json.columns, dirPath);
3499
4160
  const columns = enrichRelationTypes(applyRelationFormatDefault(rawColumns) ?? rawColumns, schema);
3500
4161
  injectEnumValues(columns, enums);
3501
4162
  const subResources = buildSubResources(columns, json.route, json.model, dirPath, enums, baseUrl);
@@ -3539,22 +4200,14 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
3539
4200
  delete: true
3540
4201
  }
3541
4202
  };
3542
- const display = {
3543
- mode: json.display?.mode === "page" ? "page" : "modal",
3544
- customComponent: json.display?.customComponent ?? null
3545
- };
3546
4203
  return {
4204
+ ...json,
3547
4205
  name: json.name,
3548
4206
  route: json.route,
3549
4207
  model: json.model,
3550
4208
  tag: json.tag,
3551
- display,
3552
- ...json.title && {
3553
- title: json.title
3554
- },
3555
- ...json.sidebar && {
3556
- sidebar: json.sidebar
3557
- },
4209
+ sidebar: json.sidebar,
4210
+ display: json.display,
3558
4211
  ...json.idType && {
3559
4212
  idType: json.idType
3560
4213
  },
@@ -3597,38 +4250,6 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
3597
4250
  }
3598
4251
  };
3599
4252
  }, "fromJson");
3600
- var unwrapZodType = /* @__PURE__ */ __name((type) => {
3601
- if (type instanceof import_zod9.ZodOptional || type instanceof import_zod9.ZodNullable) {
3602
- return unwrapZodType(type.unwrap());
3603
- }
3604
- return type;
3605
- }, "unwrapZodType");
3606
- var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
3607
- if (!schema) return void 0;
3608
- const field = schema.shape[columnId];
3609
- if (!field) return void 0;
3610
- const inner = unwrapZodType(field);
3611
- return inner instanceof import_zod9.ZodArray ? "oneToMany" : "manyToOne";
3612
- }, "deriveRelationType");
3613
- var enrichRelationTypes = /* @__PURE__ */ __name((columns, schema) => {
3614
- if (!schema) return columns;
3615
- return columns.map((col) => {
3616
- const fi = col.fieldInput;
3617
- if (!fi) return col;
3618
- const isRelationField = fi.type === "autocomplete" || fi.format === "relation";
3619
- if (!isRelationField) return col;
3620
- if (fi.relationType) return col;
3621
- const derived = deriveRelationType(schema, col.id) ?? deriveRelationTypeFromColumns(col, columns);
3622
- if (!derived) return col;
3623
- return {
3624
- ...col,
3625
- fieldInput: {
3626
- ...fi,
3627
- relationType: derived
3628
- }
3629
- };
3630
- });
3631
- }, "enrichRelationTypes");
3632
4253
  var buildLookup = /* @__PURE__ */ __name((columns) => {
3633
4254
  if (!columns) return void 0;
3634
4255
  const keyCol = columns.find((c) => c.idField);
@@ -3642,41 +4263,66 @@ var buildLookup = /* @__PURE__ */ __name((columns) => {
3642
4263
  };
3643
4264
  }, "buildLookup");
3644
4265
 
3645
- // src/lib/crud/loader/index.ts
3646
- var import_node_fs5 = require("fs");
3647
- var import_node_path6 = require("path");
4266
+ // src/lib/crud/hooks/hooks.types.ts
4267
+ var import_zod23 = require("zod");
4268
+ var WriteOpSchema = import_zod23.z.enum([
4269
+ "create",
4270
+ "update",
4271
+ "upsert",
4272
+ "delete"
4273
+ ]);
4274
+ var ReadOpSchema = import_zod23.z.enum([
4275
+ "findAll",
4276
+ "findOne"
4277
+ ]);
4278
+ var ResourceHooksSchema = import_zod23.z.object({
4279
+ beforeWrite: import_zod23.z.custom().optional(),
4280
+ afterWrite: import_zod23.z.custom().optional(),
4281
+ afterRead: import_zod23.z.custom().optional()
4282
+ });
4283
+
4284
+ // src/lib/crud/hooks/hooks.loader.ts
4285
+ var import_node_path8 = require("path");
4286
+ var loadResourceHooks = /* @__PURE__ */ __name(async (basePath) => {
4287
+ const file = findModule(basePath, "hooks");
4288
+ return file ? importDefault(file) : void 0;
4289
+ }, "loadResourceHooks");
3648
4290
  var loadSubResourceHooks = /* @__PURE__ */ __name(async (subResources, basePath) => {
3649
4291
  for (const sub of subResources) {
3650
- const file = sub.name ? findModule2((0, import_node_path6.join)(basePath, "hooks"), sub.name) : void 0;
4292
+ const file = sub.name ? findModule((0, import_node_path8.join)(basePath, "hooks"), sub.name) : void 0;
3651
4293
  if (!file) continue;
3652
4294
  const hooks = await importDefault(file);
3653
4295
  if (hooks) sub.hooks = hooks;
3654
4296
  }
3655
4297
  }, "loadSubResourceHooks");
4298
+
4299
+ // src/lib/crud/loader/index.ts
4300
+ var import_node_fs6 = require("fs");
4301
+ var import_node_path9 = require("path");
3656
4302
  var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl, enumsFile) => {
4303
+ if (!(0, import_node_fs6.existsSync)(dirPath)) return [];
3657
4304
  const enums = loadEnumRegistry(dirPath, enumsFile);
3658
- const entries = (0, import_node_fs5.readdirSync)(dirPath, {
4305
+ const entries = (0, import_node_fs6.readdirSync)(dirPath, {
3659
4306
  withFileTypes: true
3660
4307
  });
3661
4308
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
3662
4309
  const configs = [];
3663
4310
  for (const dir of dirs) {
3664
- const basePath = (0, import_node_path6.join)(dirPath, dir);
3665
- const schemaFile = findModule2(basePath, "schema");
4311
+ const basePath = (0, import_node_path9.join)(dirPath, dir);
4312
+ const schemaFile = findModule(basePath, "schema");
3666
4313
  const schema = schemaFile ? await importDefault(schemaFile) : void 0;
3667
- const hooksFile = findModule2(basePath, "hooks");
3668
- const hooks = hooksFile ? await importDefault(hooksFile) : void 0;
3669
- const jsonFile = (0, import_node_path6.join)(basePath, "resource.json");
3670
- if ((0, import_node_fs5.existsSync)(jsonFile)) {
3671
- const json = JSON.parse((0, import_node_fs5.readFileSync)(jsonFile, "utf-8"));
3672
- const actions = await loadActions(json.actions ?? [], basePath);
3673
- const tableActions = await loadTableActions(json.tableActions ?? [], basePath);
4314
+ const hooks = await loadResourceHooks(basePath);
4315
+ const jsonFile = (0, import_node_path9.join)(basePath, "resource.json");
4316
+ if ((0, import_node_fs6.existsSync)(jsonFile)) {
4317
+ const json = readResourceJson(jsonFile).json;
4318
+ const actions = await loadActions(json.actions ?? [], basePath, "row");
4319
+ const tableActions = await loadActions(json.tableActions ?? [], basePath, "table");
3674
4320
  const config = fromJson(json, schema, hooks, basePath, baseUrl, actions, tableActions, enums);
3675
4321
  await loadSubResourceHooks(config.subResources ?? [], basePath);
3676
4322
  configs.push(config);
3677
4323
  continue;
3678
4324
  }
3679
- const tsFile = findModule2(basePath, "resource");
4325
+ const tsFile = findModule(basePath, "resource");
3680
4326
  if (tsFile) {
3681
4327
  const config = await importDefault(tsFile);
3682
4328
  if (config) configs.push(hooks ? {
@@ -3727,11 +4373,18 @@ var CroutonApiModule = class _CroutonApiModule {
3727
4373
  static {
3728
4374
  __name(this, "CroutonApiModule");
3729
4375
  }
3730
- static forResources(configs, dataSources, loader, config) {
4376
+ onModuleInit() {
4377
+ if (!BigInt.prototype.toJSON) {
4378
+ BigInt.prototype.toJSON = function() {
4379
+ return Number(this);
4380
+ };
4381
+ }
4382
+ }
4383
+ static forResources(configs, dataSources, loader, { baseUrl }, config) {
3731
4384
  const dataSourceRegistry = new DataSourceRegistry(dataSources);
3732
4385
  const configRegistry = new ResourceConfigRegistry(loader, configs);
3733
4386
  const controllers = [
3734
- ...configs.map((c) => createCrudController(c, config.baseUrl)),
4387
+ ...configs.map((c) => createCrudController(c, baseUrl)),
3735
4388
  createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true)
3736
4389
  ];
3737
4390
  return {
@@ -3753,18 +4406,22 @@ var CroutonApiModule = class _CroutonApiModule {
3753
4406
  ]
3754
4407
  };
3755
4408
  }
3756
- static async forResourceDir(dirPath, dataSourcesPath, config) {
3757
- const loader = new FileSystemResourceConfigLoader(dirPath, config.baseUrl, config.enumsFile);
3758
- const configs = await loadResourceConfigsFromDir(dirPath, config.baseUrl, config.enumsFile);
4409
+ static async forResourceDir(dirPath, dataSourcesPath, { baseUrl }) {
4410
+ const config = await loadConfig();
4411
+ const loader = new FileSystemResourceConfigLoader(dirPath, baseUrl, config.enumsFile);
4412
+ const configs = await loadResourceConfigsFromDir(dirPath, baseUrl, config.enumsFile);
3759
4413
  const dataSources = await loadDataSourcesFromDir(dataSourcesPath);
3760
- return _CroutonApiModule.forResources(configs, dataSources, loader, config);
4414
+ return _CroutonApiModule.forResources(configs, dataSources, loader, {
4415
+ baseUrl
4416
+ }, config);
3761
4417
  }
3762
- static forLoader(loader, configs, dataSources, config) {
3763
- return _CroutonApiModule.forResources(configs, dataSources, loader, config);
4418
+ static async forLoader(loader, configs, dataSources, appConfig) {
4419
+ const config = await loadConfig();
4420
+ return _CroutonApiModule.forResources(configs, dataSources, loader, appConfig, config);
3764
4421
  }
3765
4422
  };
3766
4423
  CroutonApiModule = _ts_decorate5([
3767
- (0, import_common13.Module)({
4424
+ (0, import_common12.Module)({
3768
4425
  controllers: [],
3769
4426
  providers: [],
3770
4427
  exports: []
@@ -3778,6 +4435,8 @@ CroutonApiModule = _ts_decorate5([
3778
4435
  ResourceConfigLoader,
3779
4436
  ResourceConfigRegistry,
3780
4437
  isOperationEnabled,
4438
+ isRowProcedureAction,
4439
+ isTableProcedureAction,
3781
4440
  loadDataSourcesFromDir,
3782
4441
  loadResourceConfigsFromDir,
3783
4442
  resolveDefinition,