@ghentcdh/crouton-api 0.0.1-alpha.2 → 0.0.1-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,17 +5,33 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
5
5
  import { Module } from "@nestjs/common";
6
6
  import { APP_FILTER } from "@nestjs/core";
7
7
 
8
- // src/lib/crud/app-layout.controller.ts
9
- import { Controller, Get } from "@nestjs/common";
10
- import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
8
+ // src/lib/crud/app-layout/app-layout.types.ts
9
+ import { z } from "zod";
10
+ var SidebarLeafSchema = z.object({
11
+ kind: z.literal("item").default("item"),
12
+ id: z.string(),
13
+ label: z.string(),
14
+ position: z.number().optional()
15
+ });
16
+ var SidebarGroupSchema = z.object({
17
+ kind: z.literal("group").default("group"),
18
+ id: z.string(),
19
+ label: z.string(),
20
+ position: z.number().optional(),
21
+ children: z.array(SidebarLeafSchema).default([])
22
+ });
23
+ var SidebarNodeSchema = z.discriminatedUnion("kind", [
24
+ SidebarLeafSchema,
25
+ SidebarGroupSchema
26
+ ]);
11
27
 
12
28
  // ../crouton-core/src/lib/request.model.ts
13
- import { z as z2 } from "zod";
29
+ import { z as z3 } from "zod";
14
30
 
15
31
  // ../crouton-core/src/lib/zod.types.ts
16
- import { z } from "zod";
17
- var PositiveRequestNumber = /* @__PURE__ */ __name(() => z.coerce.number().int().positive().nonnegative(), "PositiveRequestNumber");
18
- var StringOrArray = /* @__PURE__ */ __name(() => z.string().or(z.array(z.string())).transform((val) => {
32
+ import { z as z2 } from "zod";
33
+ var PositiveRequestNumber = /* @__PURE__ */ __name(() => z2.coerce.number().int().positive().nonnegative(), "PositiveRequestNumber");
34
+ var StringOrArray = /* @__PURE__ */ __name(() => z2.string().or(z2.array(z2.string())).transform((val) => {
19
35
  if (Array.isArray(val)) return val;
20
36
  return [
21
37
  val
@@ -23,14 +39,14 @@ var StringOrArray = /* @__PURE__ */ __name(() => z.string().or(z.array(z.string(
23
39
  }), "StringOrArray");
24
40
 
25
41
  // ../crouton-core/src/lib/request.model.ts
26
- var SortDirEnum = z2.enum([
42
+ var SortDirEnum = z3.enum([
27
43
  "asc",
28
44
  "desc"
29
45
  ]);
30
- var RequestSchema = z2.object({
46
+ var RequestSchema = z3.object({
31
47
  page: PositiveRequestNumber().optional().default(1),
32
48
  pageSize: PositiveRequestNumber().optional().default(20),
33
- sort: z2.string().optional().default("id"),
49
+ sort: z3.string().optional().default("id"),
34
50
  sortDir: SortDirEnum.optional().default("asc"),
35
51
  // Filter is of the format key:value:operator (e.g. name:john:eq) operator is optional
36
52
  filter: StringOrArray().optional().default([])
@@ -81,13 +97,13 @@ var buildSortKey = /* @__PURE__ */ __name((keys, sortDir) => {
81
97
  }, "buildSortKey");
82
98
 
83
99
  // ../crouton-core/src/lib/response.model.ts
84
- import { z as z3 } from "zod";
100
+ import { z as z4 } from "zod";
85
101
  var ResponseRequestSchema = RequestSchema.extend({
86
102
  count: PositiveRequestNumber(),
87
103
  totalPages: PositiveRequestNumber()
88
104
  });
89
- var ResponseSchema = z3.object({
90
- data: z3.array(z3.unknown()),
105
+ var ResponseSchema = z4.object({
106
+ data: z4.array(z4.unknown()),
91
107
  request: ResponseRequestSchema
92
108
  });
93
109
 
@@ -350,23 +366,6 @@ var LayoutBuilder = class _LayoutBuilder extends BuilderWithElements {
350
366
  }
351
367
  };
352
368
 
353
- // ../crouton-core/src/lib/json-config.types.ts
354
- var labelFromId = /* @__PURE__ */ __name((id) => {
355
- const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
356
- return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
357
- }, "labelFromId");
358
- var normalizeColumns = /* @__PURE__ */ __name((columns) => {
359
- if (!columns) return void 0;
360
- const raw = Array.isArray(columns) ? columns : Object.entries(columns).map(([id, col]) => ({
361
- id,
362
- ...col
363
- }));
364
- return raw.map((col) => ({
365
- ...col,
366
- label: col.label ?? labelFromId(col.id)
367
- }));
368
- }, "normalizeColumns");
369
-
370
369
  // ../crouton-core/src/lib/value-label.ts
371
370
  var wrapOne = /* @__PURE__ */ __name((value, values) => {
372
371
  const match = values.find((o) => o.value === value);
@@ -460,6 +459,515 @@ var TableBuilder = class _TableBuilder {
460
459
  }
461
460
  };
462
461
 
462
+ // ../crouton-core/src/lib/data-source/DataSourceSchema.ts
463
+ import z5 from "zod";
464
+ var DataSourceShape = z5.object({
465
+ /** Datasource name (folder + `name` in data-source.json). */
466
+ name: z5.string(),
467
+ /** Env var holding the connection URL. */
468
+ urlEnv: z5.string(),
469
+ /** Import path for this datasource's generated Zod types (for resource `schema.ts`). */
470
+ generatedTypesImport: z5.string().optional(),
471
+ /** Datasource type tag. Default `postgres`. */
472
+ type: z5.string().default("postgres"),
473
+ /** Mark this as the default datasource. Default `false`. */
474
+ default: z5.boolean().default(false),
475
+ /** Prisma schema path, relative to project root. Default `prisma/<name>/schema.prisma`. */
476
+ prismaSchema: z5.string().optional(),
477
+ /** Prisma config path, relative to project root. Default `prisma/<name>/prisma.config.ts`. */
478
+ prismaConfig: z5.string().optional(),
479
+ /** zod-prisma-types output dir, relative to project root. Default `generated/<name>/src`. */
480
+ zodOutput: z5.string().optional(),
481
+ /** Prisma client output dir, relative to project root. Default `generated/<name>/client`. */
482
+ clientOutput: z5.string().optional()
483
+ });
484
+ var transformDataSource = /* @__PURE__ */ __name((data) => {
485
+ const name = data.name ?? "name";
486
+ return {
487
+ ...data,
488
+ generatedTypesImport: data.generatedTypesImport ?? `@your-scope/generated/${name}`,
489
+ prismaSchema: data.prismaSchema ?? `prisma/${name}/schema.prisma`,
490
+ prismaConfig: data.prismaConfig ?? `prisma/${name}/prisma.config.ts`,
491
+ zodOutput: data.zodOutput ?? `generated/${name}/src`,
492
+ clientOutput: data.clientOutput ?? `generated/${name}/client`
493
+ };
494
+ }, "transformDataSource");
495
+ var DataSourceSchema = DataSourceShape.transform(transformDataSource);
496
+
497
+ // ../crouton-core/src/lib/data-source/Operations.schema.ts
498
+ import { z as z6 } from "zod";
499
+ var BoolOrUpsertSchema = z6.union([
500
+ z6.boolean(),
501
+ z6.object({
502
+ upsertOn: z6.union([
503
+ z6.string(),
504
+ z6.array(z6.string())
505
+ ])
506
+ })
507
+ ]);
508
+ var JsonOperationsSchema = z6.object({
509
+ findAll: z6.boolean().default(true),
510
+ findOne: z6.boolean().default(true),
511
+ create: z6.boolean().default(true),
512
+ update: z6.boolean().default(true),
513
+ upsert: BoolOrUpsertSchema.default(false),
514
+ delete: z6.boolean().default(true)
515
+ });
516
+
517
+ // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
518
+ import { z as z13 } from "zod";
519
+
520
+ // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
521
+ import { z as z8 } from "zod";
522
+
523
+ // ../crouton-core/src/lib/resource/FieldInput.schema.ts
524
+ import { z as z7 } from "zod";
525
+ var RelationType = z7.enum([
526
+ "oneToOne",
527
+ "manyToOne",
528
+ "oneToMany",
529
+ "manyToMany"
530
+ ]);
531
+ var DetailControlSchema = z7.object({
532
+ property: z7.string(),
533
+ type: z7.string().optional(),
534
+ options: z7.record(z7.string(), z7.unknown()).optional(),
535
+ hideLabel: z7.boolean().optional(),
536
+ width: z7.string().optional()
537
+ });
538
+ var DetailConfigSchema = z7.object({
539
+ layout: z7.enum([
540
+ "collapse",
541
+ "row"
542
+ ]),
543
+ titleKey: z7.string().optional(),
544
+ controls: z7.array(DetailControlSchema)
545
+ });
546
+ var RelationFieldInputOptionsSchema = z7.object({
547
+ /** Field to sort related records by, e.g. `"title"` or `"author.name"`. */
548
+ sort: z7.string().optional(),
549
+ /** Sort direction. Defaults to `"asc"` when omitted. */
550
+ sortDir: z7.enum([
551
+ "asc",
552
+ "desc"
553
+ ]).optional(),
554
+ /** CSS flex direction for the relation control button layout. */
555
+ direction: z7.string().optional(),
556
+ /** Key used as the display label in the relation control. */
557
+ displayKey: z7.string().optional(),
558
+ /** Path to the related resource (resolved to a URI at load time). */
559
+ resource: z7.string().optional()
560
+ }).catchall(z7.unknown());
561
+ var FieldInputSchema = z7.object({
562
+ type: z7.string().optional(),
563
+ customRender: z7.string().optional(),
564
+ /**
565
+ * Render format hint for the frontend (e.g. `"relation"` for sub-resource
566
+ * relations). When `format` is `"relation"`, `resource` must also be set.
567
+ */
568
+ format: z7.string().optional(),
569
+ /**
570
+ * Relative path to the child resource definition, e.g. `"./author.resource"`.
571
+ * Only used when `format` is `"relation"`. Resolved at load time to inject
572
+ * sub-resource URIs into `options`.
573
+ */
574
+ resource: z7.string().optional(),
575
+ /**
576
+ * Cardinality of the relation. Used by the frontend to determine the correct
577
+ * renderer and behaviour (e.g. single-select vs multi-select).
578
+ * - `manyToOne` / `oneToOne` — single FK reference (autocomplete / relation control)
579
+ * - `oneToMany` / `manyToMany` — collection (sub-resource table / multi-select)
580
+ */
581
+ relationType: RelationType.optional(),
582
+ /** Override the display order in form views. Lower values come first. */
583
+ position: z7.number().optional(),
584
+ options: z7.union([
585
+ RelationFieldInputOptionsSchema,
586
+ z7.unknown()
587
+ ]).optional(),
588
+ /** Nested array detail layout (renders via `detailFixed`). */
589
+ detail: DetailConfigSchema.optional()
590
+ });
591
+
592
+ // ../crouton-core/src/lib/schema/label.helper.ts
593
+ var labelFromId = /* @__PURE__ */ __name((id) => {
594
+ const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
595
+ return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
596
+ }, "labelFromId");
597
+ var normalizeLabel = /* @__PURE__ */ __name((obj) => {
598
+ return {
599
+ label: labelFromId(obj.id),
600
+ ...obj
601
+ };
602
+ }, "normalizeLabel");
603
+
604
+ // ../crouton-core/src/lib/resource/CalculatedColumn.schema.ts
605
+ var CalculatedColumnSchema = z8.object({
606
+ /** Column identifier used in the response payload and UI schema. */
607
+ id: z8.string(),
608
+ /** SQL alias — must match `id`. */
609
+ alias: z8.string(),
610
+ /** Human-readable label shown in the table header. */
611
+ label: z8.string().optional(),
612
+ sqlExpression: z8.string(),
613
+ /** JSON schema / cast type for the computed value. Defaults to `"number"`. `"string"` skips CAST. */
614
+ type: z8.enum([
615
+ "number",
616
+ "boolean",
617
+ "string"
618
+ ]).optional(),
619
+ /**
620
+ * Insertion position relative to the other visible columns.
621
+ * Uses the same semantics as `fieldInput.position` on regular columns:
622
+ * `position: N` places this column before the element currently at index N.
623
+ * Can also be set via `fieldInput.position` (takes precedence).
624
+ */
625
+ position: z8.number().optional(),
626
+ hiddenInTable: z8.boolean().optional(),
627
+ hiddenInView: z8.boolean().optional(),
628
+ /**
629
+ * Field input options for rendering in form / view schemas.
630
+ * Supports `colspan`, `format`, and any other options passed to the Control renderer.
631
+ * `position` here takes precedence over the top-level `position`.
632
+ */
633
+ fieldInput: FieldInputSchema.optional()
634
+ }).transform(normalizeLabel);
635
+
636
+ // ../crouton-core/src/lib/resource/Column.ts
637
+ import { z as z9 } from "zod";
638
+ var WhenConditionSchema = z9.object({
639
+ field: z9.string(),
640
+ eq: z9.unknown().optional(),
641
+ neq: z9.unknown().optional(),
642
+ exists: z9.boolean().optional(),
643
+ notExists: z9.boolean().optional()
644
+ });
645
+ var JsonColumnSchema = z9.object({
646
+ id: z9.string(),
647
+ column: z9.string().optional(),
648
+ label: z9.string().optional(),
649
+ hiddenInTable: z9.boolean().default(false),
650
+ hiddenInForm: z9.boolean().default(false),
651
+ hiddenInView: z9.boolean().default(false),
652
+ sortable: z9.boolean().default(false),
653
+ defaultSort: z9.boolean().default(false),
654
+ searchable: z9.boolean().default(false),
655
+ filterable: z9.boolean().default(false),
656
+ createable: z9.boolean().default(true),
657
+ updateable: z9.boolean().default(true),
658
+ hideLabel: z9.boolean().default(false),
659
+ showWhen: WhenConditionSchema.optional(),
660
+ hideWhen: WhenConditionSchema.optional(),
661
+ disabledWhen: WhenConditionSchema.optional(),
662
+ displayKey: z9.string().optional(),
663
+ sortId: z9.string().optional(),
664
+ /**
665
+ * Name of a shared enum in the project enum registry (`crouton.enums.json`).
666
+ * At load time the loader injects that enum's `{ value, label }[]` into
667
+ * `fieldInput.options.values`, so columns don't duplicate the option list.
668
+ */
669
+ enum: z9.string().optional(),
670
+ idField: z9.boolean().default(false),
671
+ showInLookup: z9.boolean().default(false),
672
+ columnType: z9.string().default("string"),
673
+ fieldInput: FieldInputSchema.optional(),
674
+ /**
675
+ * Path to another `resource.json` whose columns are expanded as nested sub-columns
676
+ * under this column's object key.
677
+ *
678
+ * Example: `"extend": "../internalAuthor/resource.json"` on a column `author`
679
+ * auto-generates virtual sub-columns like `author_name` (column: "author", displayKey: "name").
680
+ *
681
+ * Visibility (`hiddenInTable/Form/View`) on this column is inherited by all sub-columns as a
682
+ * default; the referenced resource's own column visibility further restricts it.
683
+ */
684
+ extend: z9.string().optional(),
685
+ /**
686
+ * Per-sub-column overrides when using `extend`.
687
+ * Key: the virtual column id (`"{extendId}_{refColId}"`) or just the referenced column id.
688
+ * Value: any `JsonColumn` fields to merge over the generated virtual column.
689
+ */
690
+ columns: z9.record(z9.string(), z9.record(z9.string(), z9.unknown())).optional()
691
+ });
692
+
693
+ // ../crouton-core/src/lib/resource/Sidebar.schema.ts
694
+ import z10 from "zod";
695
+ var SidebarGroupSchema2 = z10.object({
696
+ /** Human-readable heading shown in the sidebar. Defaults to a title-cased version of the slug. */
697
+ label: z10.string().optional(),
698
+ /** Controls the order of this group among top-level sidebar items. */
699
+ position: z10.string().optional()
700
+ });
701
+ var SidebarSchema = z10.object({
702
+ hide: z10.boolean().default(false),
703
+ position: z10.number().optional(),
704
+ label: z10.string().optional(),
705
+ /**
706
+ * Slug of the group this resource belongs to.
707
+ * Must match a key in `sidebarGroups` in `crouton.json`.
708
+ * Resources with the same `group` are nested under a shared collapsible section.
709
+ */
710
+ group: z10.string().optional()
711
+ });
712
+
713
+ // ../crouton-core/src/lib/resource/TableAction.schema.ts
714
+ import { z as z11 } from "zod";
715
+ var ActionConditionSchema = z11.object({
716
+ /** Row field to evaluate. */
717
+ field: z11.string(),
718
+ /**
719
+ * Comparison operator. Defaults to `"eq"`.
720
+ * - `eq` / `neq` — strict equality
721
+ * - `gt` / `gte` / `lt` / `lte` — numeric or date comparison
722
+ * - `exists` — field is not null / undefined / empty string
723
+ * - `notExists` — field is null / undefined / empty string
724
+ */
725
+ op: z11.enum([
726
+ "eq",
727
+ "neq",
728
+ "gt",
729
+ "gte",
730
+ "lt",
731
+ "lte",
732
+ "exists",
733
+ "notExists"
734
+ ]).default("eq"),
735
+ /** Comparison value. Not required for `exists` / `notExists`. */
736
+ value: z11.unknown().optional()
737
+ });
738
+ var ActionSchema = z11.object({
739
+ type: z11.string(),
740
+ /** Unique identifier for the action. */
741
+ id: z11.string(),
742
+ /** Human-readable button label. */
743
+ label: z11.string(),
744
+ /** MDI icon name, e.g. `"mdi:open-in-new"`. */
745
+ icon: z11.string().optional(),
746
+ /** Tooltip text. Falls back to `label` when omitted. */
747
+ tooltip: z11.string().optional(),
748
+ condition: ActionConditionSchema.optional()
749
+ });
750
+ var JsonProcedureActionSchema = ActionSchema.extend({
751
+ type: z11.literal("procedure").default("procedure"),
752
+ /**
753
+ * Filename (without extension) inside the resource's `actions/` directory
754
+ * that exports the table-action procedure, e.g. `"syncZotero"`.
755
+ */
756
+ procedure: z11.string(),
757
+ /** HTTP method for the frontend. Defaults to `"post"`. */
758
+ method: z11.string().optional().default("post"),
759
+ /** Static query / body params passed to the endpoint. */
760
+ data: z11.record(z11.string(), z11.unknown()).optional()
761
+ });
762
+ var JsonLinkActionSchema = ActionSchema.extend({
763
+ /** Optional condition evaluated per row. Button is hidden when the condition is false. */
764
+ type: z11.literal("link").default("link"),
765
+ /** URL to open. May contain `{env.VAR}` placeholders. */
766
+ href: z11.string()
767
+ });
768
+ var transformAction = /* @__PURE__ */ __name((action) => {
769
+ const label = action.label;
770
+ return {
771
+ tooltip: label,
772
+ ...action
773
+ };
774
+ }, "transformAction");
775
+ var JsonActionSchema = z11.union([
776
+ JsonProcedureActionSchema,
777
+ JsonLinkActionSchema
778
+ ]).transform(transformAction);
779
+
780
+ // ../crouton-core/src/lib/resource/include.schema.ts
781
+ import { z as z12 } from "zod";
782
+ var JsonIncludeEntrySchema = z12.lazy(() => z12.union([
783
+ z12.string(),
784
+ z12.object({
785
+ relation: z12.string(),
786
+ include: z12.array(JsonIncludeEntrySchema).optional(),
787
+ /** Prisma-format orderBy clause applied to the included records. */
788
+ orderBy: z12.record(z12.string(), z12.unknown()).optional()
789
+ })
790
+ ]));
791
+
792
+ // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
793
+ var JsonColumnsMapSchema = z13.record(z13.string(), JsonColumnSchema.omit({
794
+ id: true
795
+ }).catchall(z13.unknown()));
796
+ var ColumnsSchema = JsonColumnsMapSchema;
797
+ var JsonDisplaySchema = z13.object({
798
+ mode: z13.enum([
799
+ "page",
800
+ "modal"
801
+ ]).default("modal"),
802
+ customComponent: z13.string().nullable().optional().default(null)
803
+ });
804
+ var normalizeColumns = /* @__PURE__ */ __name((columns) => {
805
+ if (!columns) return void 0;
806
+ const raw = Array.isArray(columns) ? columns : Object.entries(columns).map(([id, col]) => ({
807
+ id,
808
+ ...col
809
+ }));
810
+ return raw.map((col) => ({
811
+ ...col,
812
+ label: col.label ?? labelFromId(col.id)
813
+ }));
814
+ }, "normalizeColumns");
815
+ var ResourceJsonShape = z13.object({
816
+ name: z13.string(),
817
+ route: z13.string(),
818
+ model: z13.string(),
819
+ tag: z13.string(),
820
+ title: z13.string().optional(),
821
+ table: z13.string().optional(),
822
+ database: z13.string().optional(),
823
+ sidebar: SidebarSchema.default(SidebarSchema.parse({})),
824
+ display: JsonDisplaySchema.default(JsonDisplaySchema.parse({})),
825
+ operations: JsonOperationsSchema,
826
+ columns: ColumnsSchema.default(ColumnsSchema.parse({})),
827
+ calculatedColumns: z13.array(CalculatedColumnSchema).default([]),
828
+ actions: z13.array(JsonActionSchema).default([]),
829
+ /** Global table-level actions (no record id). Shown as toolbar buttons. */
830
+ tableActions: z13.array(JsonActionSchema).default([]),
831
+ /** Modal width when opening a form for this resource. */
832
+ modalSize: z13.enum([
833
+ "xs",
834
+ "sm",
835
+ "lg",
836
+ "xl"
837
+ ]).default("sm"),
838
+ /**
839
+ * Relations to include when querying this resource.
840
+ * Each entry is either a plain relation name (`"author"` → `include: { author: true }`)
841
+ * or an object for nested includes:
842
+ * `{ "relation": "text_author", "include": ["author"] }` →
843
+ * `include: { text_author: { include: { author: true } } }`
844
+ */
845
+ include: z13.array(JsonIncludeEntrySchema).default([])
846
+ });
847
+ var ResourceJsonSchema = ResourceJsonShape.transform((obj) => {
848
+ const title = obj.title ?? labelFromId(obj.name);
849
+ return {
850
+ title,
851
+ ...obj,
852
+ columns: normalizeColumns(obj.columns)
853
+ };
854
+ });
855
+
856
+ // ../crouton-core/src/lib/config/CroutonConfig.schema.ts
857
+ import { z as z14 } from "zod";
858
+ var RulesetSchema = z14.object({
859
+ hideIdInTable: z14.boolean().default(true),
860
+ hideIdInForm: z14.boolean().default(true),
861
+ hideIdInView: z14.boolean().default(true),
862
+ hideTimestamps: z14.boolean().default(true),
863
+ hideForeignKeys: z14.boolean().default(true),
864
+ includeRelations: z14.boolean().default(false),
865
+ hideRelationsInTable: z14.boolean().default(true),
866
+ showRelationsInForm: z14.boolean().default(true),
867
+ enumValueLabel: z14.boolean().default(true),
868
+ sharedEnums: z14.boolean().default(true),
869
+ defaultOperations: JsonOperationsSchema.default(JsonOperationsSchema.parse({}))
870
+ });
871
+ var CroutonConfigSchema = z14.object({
872
+ /**
873
+ * Application title served to the frontend via `GET /_app/layout`.
874
+ * Displayed in the admin sidebar header.
875
+ */
876
+ title: z14.string(),
877
+ /** Where resource directories live, relative to the project root. */
878
+ resourcesDir: z14.string(),
879
+ /** Where datasource folders live (each with a `data-source.json`). */
880
+ dataSourcesDir: z14.string(),
881
+ /**
882
+ * Template for a model's Zod export name. `{Model}` → Prisma model name.
883
+ * Defaults to `{Model}WithRelationsSchema` (the relations-aware schema
884
+ * emitted by zod-prisma-types when `createRelationValuesTypes` is on).
885
+ */
886
+ schemaExportName: z14.string().default("{Model}WithRelationsSchema"),
887
+ /** Path to the shared enum registry, relative to project root. Default `crouton.enums.json`. */
888
+ enumsFile: z14.string().default("croutons.enums.json"),
889
+ /** Optional overrides of the default visibility ruleset. */
890
+ rules: RulesetSchema.default(RulesetSchema.parse({})),
891
+ /**
892
+ * Sidebar group definitions, keyed by group slug (e.g. `"metadata"`).
893
+ * Resources reference a group via `sidebar.group` in their `resource.json`.
894
+ */
895
+ sidebarGroups: z14.record(z14.string(), SidebarGroupSchema2).default({}),
896
+ /**
897
+ * Whether form fields are saved automatically as the user edits them.
898
+ * @default true
899
+ */
900
+ autoSave: z14.boolean().default(true)
901
+ });
902
+
903
+ // ../crouton-core/src/lib/config/readConfig.ts
904
+ var CONFIG_FILES = [
905
+ "crouton.json",
906
+ "crouton.mjs",
907
+ "crouton.js"
908
+ ];
909
+
910
+ // src/lib/crud/app-layout/app-layout.builder.ts
911
+ var byPosition = /* @__PURE__ */ __name((a, b) => {
912
+ if (a.position != null && b.position != null) return a.position - b.position;
913
+ if (a.position != null) return -1;
914
+ if (b.position != null) return 1;
915
+ return a.label.localeCompare(b.label);
916
+ }, "byPosition");
917
+ var buildLayoutPayload = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
918
+ const visible = configs.filter((c) => c.sidebar?.hide !== true && c.views?.["table"]);
919
+ const topLevel = [];
920
+ const groupMap = new Map(Object.entries(sidebarGroups).map(([slug, cfg]) => [
921
+ slug,
922
+ {
923
+ label: cfg.label ?? labelFromId(slug),
924
+ position: cfg.position,
925
+ children: []
926
+ }
927
+ ]));
928
+ for (const c of visible) {
929
+ const leaf = SidebarLeafSchema.parse({
930
+ id: c.name,
931
+ label: c.sidebar?.label ?? c.title ?? c.tag,
932
+ position: c.sidebar?.position
933
+ });
934
+ const groupSlug = c.sidebar?.group;
935
+ if (groupSlug) {
936
+ if (!groupMap.has(groupSlug)) {
937
+ groupMap.set(groupSlug, {
938
+ label: labelFromId(groupSlug),
939
+ children: []
940
+ });
941
+ }
942
+ groupMap.get(groupSlug).children.push(leaf);
943
+ } else {
944
+ topLevel.push(leaf);
945
+ }
946
+ }
947
+ const groups = [
948
+ ...groupMap.entries()
949
+ ].filter(([, g]) => g.children.length > 0).map(([id, g]) => ({
950
+ kind: "group",
951
+ id,
952
+ label: g.label,
953
+ position: g.position,
954
+ children: g.children.sort(byPosition)
955
+ }));
956
+ const sidebar = [
957
+ ...topLevel,
958
+ ...groups
959
+ ].sort(byPosition);
960
+ return {
961
+ sidebar,
962
+ title,
963
+ autoSave
964
+ };
965
+ }, "buildLayoutPayload");
966
+
967
+ // src/lib/crud/app-layout/app-layout.controller.ts
968
+ import { Controller, Get } from "@nestjs/common";
969
+ import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
970
+
463
971
  // src/lib/crud/dev-mode.ts
464
972
  var IS_DEV = process.env["NODE_ENV"] !== "production";
465
973
 
@@ -508,7 +1016,7 @@ ResourceConfigRegistry = _ts_decorate([
508
1016
  ])
509
1017
  ], ResourceConfigRegistry);
510
1018
 
511
- // src/lib/crud/app-layout.controller.ts
1019
+ // src/lib/crud/app-layout/app-layout.controller.ts
512
1020
  function _ts_decorate2(decorators, target, key, desc2) {
513
1021
  var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
514
1022
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
@@ -520,62 +1028,6 @@ function _ts_metadata2(k, v) {
520
1028
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
521
1029
  }
522
1030
  __name(_ts_metadata2, "_ts_metadata");
523
- var byPosition = /* @__PURE__ */ __name((a, b) => {
524
- if (a.position != null && b.position != null) return a.position - b.position;
525
- if (a.position != null) return -1;
526
- if (b.position != null) return 1;
527
- return a.label.localeCompare(b.label);
528
- }, "byPosition");
529
- var buildLayoutPayload = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
530
- const visible = configs.filter((c) => c.sidebar?.hide !== true && c.views?.["table"]);
531
- const topLevel = [];
532
- const groupMap = new Map(Object.entries(sidebarGroups).map(([slug, cfg]) => [
533
- slug,
534
- {
535
- label: cfg.label ?? labelFromId(slug),
536
- position: cfg.position,
537
- children: []
538
- }
539
- ]));
540
- for (const c of visible) {
541
- const leaf = {
542
- kind: "item",
543
- id: c.name,
544
- label: c.sidebar?.label ?? c.title ?? c.tag,
545
- position: c.sidebar?.position
546
- };
547
- const groupSlug = c.sidebar?.group;
548
- if (groupSlug) {
549
- if (!groupMap.has(groupSlug)) {
550
- groupMap.set(groupSlug, {
551
- label: labelFromId(groupSlug),
552
- children: []
553
- });
554
- }
555
- groupMap.get(groupSlug).children.push(leaf);
556
- } else {
557
- topLevel.push(leaf);
558
- }
559
- }
560
- const groups = [
561
- ...groupMap.entries()
562
- ].filter(([, g]) => g.children.length > 0).map(([id, g]) => ({
563
- kind: "group",
564
- id,
565
- label: g.label,
566
- position: g.position,
567
- children: g.children.sort(byPosition)
568
- }));
569
- const sidebar = [
570
- ...topLevel,
571
- ...groups
572
- ].sort(byPosition);
573
- return {
574
- sidebar,
575
- title,
576
- autoSave
577
- };
578
- }, "buildLayoutPayload");
579
1031
  var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
580
1032
  const layoutPayload = buildLayoutPayload(configs, sidebarGroups, title, autoSave);
581
1033
  let AppLayoutController = class AppLayoutController {
@@ -621,6 +1073,45 @@ var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups =
621
1073
  return AppLayoutController;
622
1074
  }, "createAppLayoutController");
623
1075
 
1076
+ // src/lib/crud/config/read.ts
1077
+ import { access, readFile } from "fs/promises";
1078
+ import { dirname, join, resolve, resolve as pathResolve } from "path";
1079
+ var fileExists = /* @__PURE__ */ __name(async (p) => {
1080
+ try {
1081
+ await access(p);
1082
+ return true;
1083
+ } catch {
1084
+ return false;
1085
+ }
1086
+ }, "fileExists");
1087
+ var findConfigPath = /* @__PURE__ */ __name(async (cwd) => {
1088
+ let dir = resolve(cwd);
1089
+ while (true) {
1090
+ for (const name of CONFIG_FILES) {
1091
+ const candidate = join(dir, name);
1092
+ if (await fileExists(candidate)) return candidate;
1093
+ }
1094
+ const parent = dirname(dir);
1095
+ if (parent === dir) return void 0;
1096
+ dir = parent;
1097
+ }
1098
+ }, "findConfigPath");
1099
+ var loadConfig = /* @__PURE__ */ __name(async () => {
1100
+ const cwd = pathResolve(process.cwd());
1101
+ const path = await findConfigPath(cwd);
1102
+ if (!path) {
1103
+ throw new Error(`No crouton config found (looked for ${CONFIG_FILES.join(", ")} up from ${cwd}).`);
1104
+ }
1105
+ let config;
1106
+ if (path.endsWith(".json")) {
1107
+ config = JSON.parse(await readFile(path, "utf-8"));
1108
+ } else {
1109
+ const mod = await import(path);
1110
+ config = mod.default ?? mod;
1111
+ }
1112
+ return CroutonConfigSchema.parse(config);
1113
+ }, "loadConfig");
1114
+
624
1115
  // src/lib/crud/crouton-validation.filter.ts
625
1116
  import { Catch } from "@nestjs/common";
626
1117
 
@@ -662,24 +1153,156 @@ CroutonValidationExceptionFilter = _ts_decorate3([
662
1153
  ], CroutonValidationExceptionFilter);
663
1154
 
664
1155
  // src/lib/crud/crud-controller.factory.ts
665
- import { Controller as Controller2 } from "@nestjs/common";
666
- import { Body as Body2 } from "@nestjs/common";
1156
+ import { Body as Body2, Controller as Controller2 } from "@nestjs/common";
667
1157
  import { ApiTags as ApiTags2 } from "@nestjs/swagger";
668
1158
 
669
- // src/lib/crud/crud.config.ts
670
- var resolveDefinition = /* @__PURE__ */ __name((config) => {
671
- const def2 = config.definition;
672
- return typeof def2 === "function" ? def2() : def2;
673
- }, "resolveDefinition");
674
- var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
675
- var schemaFor = /* @__PURE__ */ __name((def2, op) => {
676
- const entry = def2[op];
677
- if (!entry || entry === true) return void 0;
678
- return entry.schema;
679
- }, "schemaFor");
680
- var upsertOnFor = /* @__PURE__ */ __name((def2) => def2.upsert?.upsertOn, "upsertOnFor");
1159
+ // src/lib/crud/action/action.types.ts
1160
+ import { z as z15 } from "zod";
1161
+ var ActionMetadataSchema = z15.object({
1162
+ /** URL segment used in the endpoint. */
1163
+ id: z15.string(),
1164
+ /** Human-readable label shown as a button. */
1165
+ label: z15.string().optional(),
1166
+ /** MDI icon name, e.g. `"mdi:open-in-new"`. */
1167
+ icon: z15.string().optional(),
1168
+ /** Tooltip text. Falls back to `label` when omitted. */
1169
+ tooltip: z15.string().optional(),
1170
+ /** Per-row condition — button is hidden when false. */
1171
+ condition: z15.custom().optional()
1172
+ });
1173
+ var ResourceLinkActionSchema = ActionMetadataSchema.extend({
1174
+ type: z15.literal("link"),
1175
+ /** URL to open. May contain `{id}` or `{env.VAR}` placeholders. */
1176
+ href: z15.string()
1177
+ });
1178
+ var ResourceRowProcedureActionSchema = ActionMetadataSchema.extend({
1179
+ type: z15.literal("procedure").optional(),
1180
+ /** HTTP method for the endpoint. Defaults to `"post"`. */
1181
+ method: z15.string().optional(),
1182
+ /** Static data payload merged into the request body by the frontend. */
1183
+ data: z15.record(z15.string(), z15.unknown()).optional(),
1184
+ /** Procedure called with `(prisma, recordId)`. */
1185
+ procedure: z15.custom((v) => typeof v === "function")
1186
+ });
1187
+ var ResourceTableProcedureActionSchema = ActionMetadataSchema.extend({
1188
+ type: z15.literal("procedure").optional(),
1189
+ /** HTTP method for the endpoint. Defaults to `"post"`. */
1190
+ method: z15.string().optional(),
1191
+ /** Static data payload merged into the request body by the frontend. */
1192
+ data: z15.record(z15.string(), z15.unknown()).optional(),
1193
+ /** Procedure called with `(prisma)` — no record id. */
1194
+ procedure: z15.custom((v) => typeof v === "function")
1195
+ });
1196
+ var ResourceRowActionSchema = z15.union([
1197
+ ResourceRowProcedureActionSchema,
1198
+ ResourceLinkActionSchema
1199
+ ]);
1200
+ var ResourceTableActionSchema = z15.union([
1201
+ ResourceTableProcedureActionSchema,
1202
+ ResourceLinkActionSchema
1203
+ ]);
1204
+ var isRowProcedureAction = /* @__PURE__ */ __name((action) => action.type !== "link", "isRowProcedureAction");
1205
+ var isTableProcedureAction = /* @__PURE__ */ __name((action) => action.type !== "link", "isTableProcedureAction");
681
1206
 
682
- // src/lib/crud/read.repository.ts
1207
+ // src/lib/crud/loader/module.loader.ts
1208
+ import { existsSync } from "fs";
1209
+ import { createRequire } from "module";
1210
+ import { join as join2 } from "path";
1211
+ var _require = createRequire(import.meta.url ?? __filename);
1212
+ var findModule = /* @__PURE__ */ __name((dir, name) => {
1213
+ for (const ext of [
1214
+ ".ts",
1215
+ ".js"
1216
+ ]) {
1217
+ const p = join2(dir, `${name}${ext}`);
1218
+ if (existsSync(p)) return p;
1219
+ }
1220
+ return void 0;
1221
+ }, "findModule");
1222
+ var IS_VITE = typeof globalThis.__vite_ssr_import__ === "function";
1223
+ var importDefault = /* @__PURE__ */ __name(async (filePath) => {
1224
+ try {
1225
+ if (IS_VITE) {
1226
+ const importPath = IS_DEV ? `${filePath}?t=${Date.now()}` : filePath;
1227
+ const mod = await import(importPath);
1228
+ return mod.default ?? mod;
1229
+ } else {
1230
+ const mod = _require(filePath);
1231
+ return mod.default;
1232
+ }
1233
+ } catch {
1234
+ return void 0;
1235
+ }
1236
+ }, "importDefault");
1237
+
1238
+ // src/lib/crud/action/action.loader.ts
1239
+ import { join as join3 } from "path";
1240
+ var loadActions = /* @__PURE__ */ __name(async (jsonActions, basePath, scope) => {
1241
+ const tag = scope === "row" ? "actions" : "tableActions";
1242
+ const results = [];
1243
+ for (const action of jsonActions) {
1244
+ if (action.type === "link") {
1245
+ results.push({
1246
+ type: "link",
1247
+ id: action.id,
1248
+ label: action.label,
1249
+ href: action.href,
1250
+ ...action.icon && {
1251
+ icon: action.icon
1252
+ },
1253
+ ...action.tooltip && {
1254
+ tooltip: action.tooltip
1255
+ },
1256
+ ...action.condition && {
1257
+ condition: action.condition
1258
+ }
1259
+ });
1260
+ continue;
1261
+ }
1262
+ const file = findModule(join3(basePath, "actions"), action.procedure);
1263
+ if (!file) {
1264
+ console.warn(`[${tag}] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
1265
+ continue;
1266
+ }
1267
+ const mod = await importDefault(file);
1268
+ if (!mod) {
1269
+ console.warn(`[${tag}] No default export in ${file}`);
1270
+ continue;
1271
+ }
1272
+ results.push({
1273
+ id: action.id,
1274
+ label: action.label,
1275
+ method: action.method,
1276
+ data: action.data,
1277
+ ...action.icon && {
1278
+ icon: action.icon
1279
+ },
1280
+ ...action.tooltip && {
1281
+ tooltip: action.tooltip
1282
+ },
1283
+ ...action.condition && {
1284
+ condition: action.condition
1285
+ },
1286
+ procedure: mod
1287
+ });
1288
+ }
1289
+ return results;
1290
+ }, "loadActions");
1291
+
1292
+ // src/lib/crud/crud.config.ts
1293
+ var resolveDefinition = /* @__PURE__ */ __name((config) => {
1294
+ const def2 = config.definition;
1295
+ return typeof def2 === "function" ? def2() : def2;
1296
+ }, "resolveDefinition");
1297
+ var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
1298
+ var schemaFor = /* @__PURE__ */ __name((def2, op) => {
1299
+ const entry = def2[op];
1300
+ if (!entry || entry === true) return void 0;
1301
+ return entry.schema;
1302
+ }, "schemaFor");
1303
+ var upsertOnFor = /* @__PURE__ */ __name((def2) => def2.upsert?.upsertOn, "upsertOnFor");
1304
+
1305
+ // src/lib/crud/read.repository.ts
683
1306
  import { NotFoundException } from "@nestjs/common";
684
1307
 
685
1308
  // src/lib/crud/sql.helpers.ts
@@ -1165,9 +1788,9 @@ var ReadRepository = class {
1165
1788
  };
1166
1789
 
1167
1790
  // src/lib/crud/schema.utils.ts
1168
- import { ZodObject, toJSONSchema as toJSONSchema2, z as z4 } from "zod";
1791
+ import { ZodObject, toJSONSchema as toJSONSchema2, z as z16 } from "zod";
1169
1792
  var dateOverride = /* @__PURE__ */ __name(({ zodSchema, jsonSchema }) => {
1170
- if (zodSchema instanceof z4.ZodDate) {
1793
+ if (zodSchema instanceof z16.ZodDate) {
1171
1794
  jsonSchema.type = "string";
1172
1795
  jsonSchema.format = "date-time";
1173
1796
  }
@@ -1566,21 +2189,27 @@ DataSourceRegistry = _ts_decorate4([
1566
2189
  ], DataSourceRegistry);
1567
2190
 
1568
2191
  // src/lib/crud/data-source/data-source.loader.ts
1569
- import { existsSync, readFileSync, readdirSync } from "fs";
1570
- import { join } from "path";
2192
+ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
2193
+ import { join as join4 } from "path";
1571
2194
  var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
1572
- if (!existsSync(dirPath)) return [];
2195
+ if (!existsSync2(dirPath)) return [];
1573
2196
  const entries = readdirSync(dirPath, {
1574
2197
  withFileTypes: true
1575
2198
  });
1576
2199
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1577
2200
  const results = [];
1578
2201
  for (const dir of dirs) {
1579
- const basePath = join(dirPath, dir);
1580
- const jsonFile = join(basePath, "data-source.json");
1581
- if (!existsSync(jsonFile)) continue;
1582
- const config = JSON.parse(readFileSync(jsonFile, "utf-8"));
1583
- const indexFile = findModule(basePath, "index");
2202
+ const basePath = join4(dirPath, dir);
2203
+ const jsonFile = join4(basePath, "data-source.json");
2204
+ if (!existsSync2(jsonFile)) continue;
2205
+ const _config = JSON.parse(readFileSync(jsonFile, "utf-8"));
2206
+ const datasource = DataSourceSchema.safeParse(_config);
2207
+ if (!datasource.success) {
2208
+ console.error(datasource.error);
2209
+ throw new Error(`Invalid datasource schema: ${jsonFile}`);
2210
+ }
2211
+ const config = datasource.data;
2212
+ const indexFile = findModule2(basePath, "index");
1584
2213
  if (!indexFile) continue;
1585
2214
  const mod = await import(indexFile);
1586
2215
  const client = mod.default;
@@ -1592,13 +2221,13 @@ var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
1592
2221
  }
1593
2222
  return results;
1594
2223
  }, "loadDataSourcesFromDir");
1595
- var findModule = /* @__PURE__ */ __name((dir, name) => {
2224
+ var findModule2 = /* @__PURE__ */ __name((dir, name) => {
1596
2225
  for (const ext of [
1597
2226
  ".ts",
1598
2227
  ".js"
1599
2228
  ]) {
1600
- const p = join(dir, `${name}${ext}`);
1601
- if (existsSync(p)) return p;
2229
+ const p = join4(dir, `${name}${ext}`);
2230
+ if (existsSync2(p)) return p;
1602
2231
  }
1603
2232
  return void 0;
1604
2233
  }, "findModule");
@@ -1621,7 +2250,7 @@ var desc = /* @__PURE__ */ __name((cls, method) => Object.getOwnPropertyDescript
1621
2250
  var registerActionRoutes = /* @__PURE__ */ __name((ctx) => {
1622
2251
  const { cls, config } = ctx;
1623
2252
  const { name } = config;
1624
- const procedureActions = (config.actions ?? []).filter((a) => a.type !== "link");
2253
+ const procedureActions = (config.actions ?? []).filter(isRowProcedureAction);
1625
2254
  for (const action of procedureActions) {
1626
2255
  const methodName = `procedure_${action.id}`;
1627
2256
  def(cls, methodName, async function(recordId) {
@@ -1646,7 +2275,7 @@ var registerActionRoutes = /* @__PURE__ */ __name((ctx) => {
1646
2275
  var registerTableActionRoutes = /* @__PURE__ */ __name((ctx) => {
1647
2276
  const { cls, config } = ctx;
1648
2277
  const { name } = config;
1649
- const procedureActions = (config.tableActions ?? []).filter((a) => a.type !== "link");
2278
+ const procedureActions = (config.tableActions ?? []).filter(isTableProcedureAction);
1650
2279
  for (const action of procedureActions) {
1651
2280
  const methodName = `tableAction_${action.id}`;
1652
2281
  def(cls, methodName, async function() {
@@ -1748,38 +2377,56 @@ var ZodValidationPipe = class {
1748
2377
  };
1749
2378
 
1750
2379
  // src/lib/crud/operations/register-crud.ts
2380
+ var findAll = /* @__PURE__ */ __name(async (repo, params, q, lookupLabel) => {
2381
+ const effectiveParams = {
2382
+ ...params
2383
+ };
2384
+ if (q && lookupLabel) {
2385
+ effectiveParams.filter = [
2386
+ ...params.filter ?? [],
2387
+ `${lookupLabel}:${q}`
2388
+ ];
2389
+ }
2390
+ const [data, count] = await Promise.all([
2391
+ repo.findAll(effectiveParams),
2392
+ repo.count(effectiveParams.filter)
2393
+ ]);
2394
+ const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2395
+ return {
2396
+ data,
2397
+ request: {
2398
+ count,
2399
+ page: params.page,
2400
+ pageSize: params.pageSize,
2401
+ totalPages,
2402
+ sort: params.sort,
2403
+ sortDir: params.sortDir,
2404
+ filter: params.filter
2405
+ }
2406
+ };
2407
+ }, "findAll");
2408
+ var findOne = /* @__PURE__ */ __name(async (repo, id) => {
2409
+ return repo.findOne(id);
2410
+ }, "findOne");
2411
+ var create = /* @__PURE__ */ __name(async (repo, body) => {
2412
+ return repo.create(body);
2413
+ }, "create");
2414
+ var update = /* @__PURE__ */ __name(async (repo, id, body) => {
2415
+ return repo.update(id, body);
2416
+ }, "update");
2417
+ var upsert = /* @__PURE__ */ __name(async (repo, body) => {
2418
+ return repo.upsert(body);
2419
+ }, "upsert");
2420
+ var del = /* @__PURE__ */ __name(async (repo, id) => {
2421
+ return repo.delete(id);
2422
+ }, "del");
1751
2423
  var registerFindAll = /* @__PURE__ */ __name((ctx) => {
1752
2424
  if (!isOperationEnabled(ctx.definition, "findAll")) return;
1753
2425
  const { cls, config, listSchema } = ctx;
1754
2426
  const { name } = config;
1755
2427
  const lookupLabel = config.lookup?.label;
1756
2428
  def(cls, "findAll", async function(params, q) {
1757
- const effectiveParams = {
1758
- ...params
1759
- };
1760
- if (q && lookupLabel) {
1761
- effectiveParams.filter = [
1762
- ...params.filter ?? [],
1763
- `${lookupLabel}:${q}`
1764
- ];
1765
- }
1766
- const [data, count] = await Promise.all([
1767
- this.repo.findAll(effectiveParams),
1768
- this.repo.count(effectiveParams.filter)
1769
- ]);
1770
- const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
1771
- return {
1772
- data,
1773
- request: {
1774
- count,
1775
- page: params.page,
1776
- pageSize: params.pageSize,
1777
- totalPages,
1778
- sort: params.sort,
1779
- sortDir: params.sortDir,
1780
- filter: params.filter
1781
- }
1782
- };
2429
+ return findAll(this.repo, params, q, lookupLabel);
1783
2430
  });
1784
2431
  const d = desc(cls, "findAll");
1785
2432
  Get2()(cls.prototype, "findAll", d);
@@ -1804,7 +2451,7 @@ var registerFindOne = /* @__PURE__ */ __name((ctx) => {
1804
2451
  const { cls, config, oneSchema, idParamMeta } = ctx;
1805
2452
  const { name } = config;
1806
2453
  def(cls, "findOne", function(id) {
1807
- return this.repo.findOne(id);
2454
+ return findOne(this.repo, id);
1808
2455
  });
1809
2456
  const d = desc(cls, "findOne");
1810
2457
  Get2(":id")(cls.prototype, "findOne", d);
@@ -1829,7 +2476,7 @@ var registerCreate = /* @__PURE__ */ __name((ctx) => {
1829
2476
  const { cls, config, createSchema, bodyDecorator } = ctx;
1830
2477
  const { name } = config;
1831
2478
  def(cls, "create", function(body) {
1832
- return this.repo.create(body);
2479
+ return create(this.repo, body);
1833
2480
  });
1834
2481
  const d = desc(cls, "create");
1835
2482
  Post2()(cls.prototype, "create", d);
@@ -1852,7 +2499,7 @@ var registerUpdate = /* @__PURE__ */ __name((ctx) => {
1852
2499
  const { cls, config, updateSchema, idParamMeta, bodyDecorator } = ctx;
1853
2500
  const { name } = config;
1854
2501
  def(cls, "update", function(id, body) {
1855
- return this.repo.update(id, body);
2502
+ return update(this.repo, id, body);
1856
2503
  });
1857
2504
  const d = desc(cls, "update");
1858
2505
  Patch(":id")(cls.prototype, "update", d);
@@ -1878,7 +2525,7 @@ var registerUpsert = /* @__PURE__ */ __name((ctx) => {
1878
2525
  const { cls, config, upsertSchema, bodyDecorator } = ctx;
1879
2526
  const { name } = config;
1880
2527
  def(cls, "upsert", function(body) {
1881
- return this.repo.upsert(body);
2528
+ return upsert(this.repo, body);
1882
2529
  });
1883
2530
  const d = desc(cls, "upsert");
1884
2531
  Put()(cls.prototype, "upsert", d);
@@ -1901,7 +2548,7 @@ var registerDelete = /* @__PURE__ */ __name((ctx) => {
1901
2548
  const { cls, config, idParamMeta } = ctx;
1902
2549
  const { name } = config;
1903
2550
  def(cls, "delete", function(id) {
1904
- return this.repo.delete(id);
2551
+ return del(this.repo, id);
1905
2552
  });
1906
2553
  const d = desc(cls, "delete");
1907
2554
  Delete(":id")(cls.prototype, "delete", d);
@@ -2090,6 +2737,12 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2090
2737
  id: a.id,
2091
2738
  label: a.label,
2092
2739
  href: resolveEnvPlaceholders(a.href),
2740
+ ...a.icon && {
2741
+ icon: a.icon
2742
+ },
2743
+ ...a.tooltip && {
2744
+ tooltip: a.tooltip
2745
+ },
2093
2746
  ...a.condition && {
2094
2747
  condition: a.condition
2095
2748
  }
@@ -2101,6 +2754,12 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2101
2754
  ...a.data && {
2102
2755
  data: a.data
2103
2756
  },
2757
+ ...a.icon && {
2758
+ icon: a.icon
2759
+ },
2760
+ ...a.tooltip && {
2761
+ tooltip: a.tooltip
2762
+ },
2104
2763
  ...a.condition && {
2105
2764
  condition: a.condition
2106
2765
  }
@@ -2113,7 +2772,10 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2113
2772
  label: a.label,
2114
2773
  icon: a.icon,
2115
2774
  tooltip: a.tooltip,
2116
- href: resolveEnvPlaceholders(a.href)
2775
+ href: resolveEnvPlaceholders(a.href),
2776
+ ...a.condition && {
2777
+ condition: a.condition
2778
+ }
2117
2779
  } : {
2118
2780
  id: a.id,
2119
2781
  label: a.label,
@@ -2123,6 +2785,9 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
2123
2785
  method: a.method ?? "post",
2124
2786
  ...a.data && {
2125
2787
  data: a.data
2788
+ },
2789
+ ...a.condition && {
2790
+ condition: a.condition
2126
2791
  }
2127
2792
  })
2128
2793
  }
@@ -2197,6 +2862,34 @@ var registerResourceJsonEndpoint = /* @__PURE__ */ __name((ctx) => {
2197
2862
  // src/lib/crud/operations/register-sub-resources.ts
2198
2863
  import { Body, Delete as Delete2, Get as Get4, Param as Param3, Patch as Patch2, Post as Post3, Query as Query2 } from "@nestjs/common";
2199
2864
  import { ApiOperation as ApiOperation5, ApiParam as ApiParam3, ApiResponse as ApiResponse5 } from "@nestjs/swagger";
2865
+ var findAllByParent = /* @__PURE__ */ __name(async (repo, id, childRoute, params) => {
2866
+ const { data, count } = await repo.findAllByParent(id, childRoute, params);
2867
+ const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2868
+ return {
2869
+ data,
2870
+ request: {
2871
+ count,
2872
+ page: params.page,
2873
+ pageSize: params.pageSize,
2874
+ totalPages,
2875
+ sort: params.sort,
2876
+ sortDir: params.sortDir,
2877
+ filter: params.filter
2878
+ }
2879
+ };
2880
+ }, "findAllByParent");
2881
+ var createChild = /* @__PURE__ */ __name(async (repo, id, sub, body) => {
2882
+ return repo.createChild(id, sub, body);
2883
+ }, "createChild");
2884
+ var findOneChild = /* @__PURE__ */ __name(async (repo, sub, childId, parentId) => {
2885
+ return repo.findOneChild(sub, childId, parentId);
2886
+ }, "findOneChild");
2887
+ var updateChild = /* @__PURE__ */ __name(async (repo, sub, childId, body) => {
2888
+ return repo.updateChild(sub, childId, body);
2889
+ }, "updateChild");
2890
+ var deleteChild = /* @__PURE__ */ __name(async (repo, sub, childId, parentId) => {
2891
+ return repo.deleteChild(sub, childId, parentId);
2892
+ }, "deleteChild");
2200
2893
  var registerSubResourceSchemas = /* @__PURE__ */ __name((ctx, sub) => {
2201
2894
  if (!sub.views) return;
2202
2895
  const { cls, config, baseUrl } = ctx;
@@ -2268,20 +2961,7 @@ var registerSubResourceFindAll = /* @__PURE__ */ __name((ctx, sub) => {
2268
2961
  const idParamMeta = ctx.idParamMeta;
2269
2962
  const methodName = `findAllBy_${sub.childRoute}`;
2270
2963
  def(cls, methodName, async function(id, params) {
2271
- const { data, count } = await this.repo.findAllByParent(id, sub.childRoute, params);
2272
- const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
2273
- return {
2274
- data,
2275
- request: {
2276
- count,
2277
- page: params.page,
2278
- pageSize: params.pageSize,
2279
- totalPages,
2280
- sort: params.sort,
2281
- sortDir: params.sortDir,
2282
- filter: params.filter
2283
- }
2284
- };
2964
+ return findAllByParent(this.repo, id, sub.childRoute, params);
2285
2965
  });
2286
2966
  const d = desc(cls, methodName);
2287
2967
  Get4(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
@@ -2302,7 +2982,7 @@ var registerSubResourceCreate = /* @__PURE__ */ __name((ctx, sub) => {
2302
2982
  const { name } = config;
2303
2983
  const methodName = `createChild_${sub.childRoute}`;
2304
2984
  def(cls, methodName, async function(id, body) {
2305
- return this.repo.createChild(id, sub, body);
2985
+ return createChild(this.repo, id, sub, body);
2306
2986
  });
2307
2987
  const d = desc(cls, methodName);
2308
2988
  Post3(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
@@ -2322,7 +3002,7 @@ var registerSubResourceFindOne = /* @__PURE__ */ __name((ctx, sub) => {
2322
3002
  const { cls } = ctx;
2323
3003
  const methodName = `findOneChild_${sub.childRoute}`;
2324
3004
  def(cls, methodName, async function(parentId, childId) {
2325
- return this.repo.findOneChild(sub, childId, parentId);
3005
+ return findOneChild(this.repo, sub, childId, parentId);
2326
3006
  });
2327
3007
  const d = desc(cls, methodName);
2328
3008
  Get4(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2342,7 +3022,7 @@ var registerSubResourceUpdate = /* @__PURE__ */ __name((ctx, sub) => {
2342
3022
  const { cls } = ctx;
2343
3023
  const methodName = `updateChild_${sub.childRoute}`;
2344
3024
  def(cls, methodName, async function(_id, childId, body) {
2345
- return this.repo.updateChild(sub, childId, body);
3025
+ return updateChild(this.repo, sub, childId, body);
2346
3026
  });
2347
3027
  const d = desc(cls, methodName);
2348
3028
  Patch2(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2363,7 +3043,7 @@ var registerSubResourceDelete = /* @__PURE__ */ __name((ctx, sub) => {
2363
3043
  const { cls } = ctx;
2364
3044
  const methodName = `deleteChild_${sub.childRoute}`;
2365
3045
  def(cls, methodName, async function(parentId, childId) {
2366
- return this.repo.deleteChild(sub, childId, parentId);
3046
+ return deleteChild(this.repo, sub, childId, parentId);
2367
3047
  });
2368
3048
  const d = desc(cls, methodName);
2369
3049
  Delete2(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
@@ -2459,164 +3139,115 @@ function createCrudController(config, baseUrl) {
2459
3139
  }
2460
3140
  __name(createCrudController, "createCrudController");
2461
3141
 
2462
- // src/lib/crud/loader/module.loader.ts
2463
- import { existsSync as existsSync2 } from "fs";
2464
- import { createRequire } from "module";
2465
- import { join as join2 } from "path";
2466
- var _require = createRequire(import.meta.url);
2467
- var findModule2 = /* @__PURE__ */ __name((dir, name) => {
2468
- for (const ext of [
2469
- ".ts",
2470
- ".js"
2471
- ]) {
2472
- const p = join2(dir, `${name}${ext}`);
2473
- if (existsSync2(p)) return p;
3142
+ // src/lib/crud/builder/column-predicates.ts
3143
+ var isBoolean = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "boolean" || col.columnType === "boolean", "isBoolean");
3144
+ var isRelation = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "relation", "isRelation");
3145
+ var isAutocomplete = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "autocomplete", "isAutocomplete");
3146
+ var isRecordCell = /* @__PURE__ */ __name((col) => isRelation(col) || isAutocomplete(col), "isRecordCell");
3147
+ var isDateRange = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "date-range", "isDateRange");
3148
+
3149
+ // src/lib/crud/builder/column.utils.ts
3150
+ var colPosition = /* @__PURE__ */ __name((col, i) => col.fieldInput?.position ?? i, "colPosition");
3151
+ var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
3152
+ col,
3153
+ i
3154
+ })).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
3155
+ var toViewColumn = /* @__PURE__ */ __name((col) => ({
3156
+ id: col.id,
3157
+ ...col.label && {
3158
+ label: col.label
3159
+ },
3160
+ ...col.sortable != null && {
3161
+ sortable: col.sortable
3162
+ },
3163
+ ...col.searchable != null && {
3164
+ searchable: col.searchable
3165
+ },
3166
+ ...col.fieldInput && {
3167
+ fieldInput: col.fieldInput
2474
3168
  }
2475
- return void 0;
2476
- }, "findModule");
2477
- var IS_VITE = typeof globalThis.__vite_ssr_import__ === "function";
2478
- var importDefault = /* @__PURE__ */ __name(async (filePath) => {
2479
- try {
2480
- if (IS_VITE) {
2481
- const importPath = IS_DEV ? `${filePath}?t=${Date.now()}` : filePath;
2482
- const mod = await import(importPath);
2483
- return mod.default ?? mod;
2484
- } else {
2485
- const mod = _require(filePath);
2486
- return mod.default;
2487
- }
2488
- } catch {
2489
- return void 0;
3169
+ }), "toViewColumn");
3170
+
3171
+ // src/lib/crud/builder/sort.helpers.ts
3172
+ var deriveSortId = /* @__PURE__ */ __name((col) => {
3173
+ if (col.sortable === false) return null;
3174
+ if (col.sortId) return col.sortId;
3175
+ const base = col.column ?? col.id;
3176
+ const fi = col.fieldInput;
3177
+ const opts = fi?.options ?? {};
3178
+ const isRelation2 = fi?.format === "relation" || !!fi?.relationType || fi?.type === "autocomplete" || typeof opts.resource === "string";
3179
+ if (isRelation2) {
3180
+ 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);
3181
+ if (!key) return null;
3182
+ const path2 = key.includes(".") ? key : `${base}.${key}`;
3183
+ return path2.endsWith(".label") ? path2.slice(0, -".label".length) : path2;
2490
3184
  }
2491
- }, "importDefault");
3185
+ if (!col.displayKey) return base;
3186
+ const path = `${base}.${col.displayKey}`;
3187
+ const isValueLabel = !!col.enum || opts.emitObject === true;
3188
+ if (isValueLabel && path.endsWith(".label")) return path.slice(0, -".label".length);
3189
+ return path;
3190
+ }, "deriveSortId");
3191
+ var resolveDefaultSort = /* @__PURE__ */ __name((tableCols, allColumns) => {
3192
+ const isSortable = /* @__PURE__ */ __name((c) => c.sortable !== false, "isSortable");
3193
+ const sortCol = tableCols.find((c) => c.defaultSort && isSortable(c)) ?? tableCols.find(isSortable) ?? allColumns?.find((c) => c.idField);
3194
+ return sortCol ? deriveSortId(sortCol) ?? sortCol.id : void 0;
3195
+ }, "resolveDefaultSort");
2492
3196
 
2493
- // src/lib/crud/loader/action.loader.ts
2494
- import { join as join3 } from "path";
2495
- var loadActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
2496
- const results = [];
2497
- for (const action of jsonActions) {
2498
- if (action.type === "link") {
2499
- results.push({
2500
- type: "link",
2501
- id: action.id,
2502
- label: action.label,
2503
- href: action.href,
2504
- ...action.condition && {
2505
- condition: action.condition
2506
- }
2507
- });
2508
- continue;
2509
- }
2510
- const file = findModule2(join3(basePath, "actions"), action.procedure);
2511
- if (!file) {
2512
- console.warn(`[actions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
2513
- continue;
2514
- }
2515
- const mod = await importDefault(file);
2516
- if (!mod) {
2517
- console.warn(`[actions] No default export in ${file}`);
2518
- continue;
3197
+ // src/lib/crud/builder/schema-transforms.ts
3198
+ var allowAdditionalProperties = /* @__PURE__ */ __name((schema) => {
3199
+ if (schema["type"] === "object") {
3200
+ schema["additionalProperties"] = true;
3201
+ const props = schema["properties"];
3202
+ if (props) {
3203
+ for (const value of Object.values(props)) {
3204
+ allowAdditionalProperties(value);
3205
+ }
2519
3206
  }
2520
- results.push({
2521
- id: action.id,
2522
- label: action.label,
2523
- method: action.method,
2524
- data: action.data,
2525
- ...action.condition && {
2526
- condition: action.condition
2527
- },
2528
- procedure: mod
2529
- });
2530
3207
  }
2531
- return results;
2532
- }, "loadActions");
2533
- var loadTableActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
2534
- const results = [];
2535
- for (const action of jsonActions) {
2536
- if (action.type === "link") {
2537
- results.push({
2538
- type: "link",
2539
- id: action.id,
2540
- label: action.label,
2541
- icon: action.icon,
2542
- tooltip: action.tooltip,
2543
- href: action.href
2544
- });
2545
- continue;
2546
- }
2547
- const file = findModule2(join3(basePath, "actions"), action.procedure);
2548
- if (!file) {
2549
- console.warn(`[tableActions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
2550
- continue;
2551
- }
2552
- const mod = await importDefault(file);
2553
- if (!mod) {
2554
- console.warn(`[tableActions] No default export in ${file}`);
2555
- continue;
2556
- }
2557
- results.push({
2558
- id: action.id,
2559
- label: action.label,
2560
- icon: action.icon,
2561
- tooltip: action.tooltip,
2562
- method: action.method,
2563
- data: action.data,
2564
- procedure: mod
2565
- });
3208
+ if (schema["type"] === "array" && schema["items"]) {
3209
+ allowAdditionalProperties(schema["items"]);
2566
3210
  }
2567
- return results;
2568
- }, "loadTableActions");
2569
-
2570
- // src/lib/crud/loader/enum-registry.ts
2571
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2572
- import { dirname, join as join4 } from "path";
2573
- var ENUMS_FILE = "crouton.enums.json";
2574
- var loadEnumRegistry = /* @__PURE__ */ __name((startDir, enumsFile) => {
2575
- let file = enumsFile;
2576
- if (!file) {
2577
- let dir = startDir;
2578
- while (true) {
2579
- const candidate = join4(dir, ENUMS_FILE);
2580
- if (existsSync3(candidate)) {
2581
- file = candidate;
2582
- break;
2583
- }
2584
- const parent = dirname(dir);
2585
- if (parent === dir) break;
2586
- dir = parent;
2587
- }
3211
+ }, "allowAdditionalProperties");
3212
+ var isNullableProperty2 = /* @__PURE__ */ __name((prop) => {
3213
+ const anyOf = prop?.["anyOf"];
3214
+ return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
3215
+ }, "isNullableProperty");
3216
+ var dropNullableFromRequired2 = /* @__PURE__ */ __name((schema) => {
3217
+ if (schema["type"] !== "object") return;
3218
+ const props = schema["properties"];
3219
+ if (!props) return;
3220
+ const required = schema["required"];
3221
+ if (Array.isArray(required)) {
3222
+ schema["required"] = required.filter((key) => !isNullableProperty2(props[key]));
2588
3223
  }
2589
- if (!file || !existsSync3(file)) return {};
2590
- try {
2591
- return JSON.parse(readFileSync2(file, "utf-8"));
2592
- } catch {
2593
- return {};
3224
+ for (const value of Object.values(props)) {
3225
+ dropNullableFromRequired2(value);
2594
3226
  }
2595
- }, "loadEnumRegistry");
2596
- var injectEnumValues = /* @__PURE__ */ __name((columns, enums) => {
2597
- if (!columns) return;
2598
- for (const col of columns) {
2599
- if (!col.enum) continue;
2600
- const values = enums[col.enum];
2601
- if (!values) continue;
2602
- col.fieldInput = col.fieldInput ?? {
2603
- type: "select"
2604
- };
2605
- const options = col.fieldInput.options ?? {};
2606
- if (!("values" in options)) options.values = values;
2607
- col.fieldInput.options = options;
3227
+ }, "dropNullableFromRequired");
3228
+ var enforceRequiredMinLength = /* @__PURE__ */ __name((schema) => {
3229
+ if (schema["type"] !== "object") return;
3230
+ const props = schema["properties"];
3231
+ if (!props) return;
3232
+ for (const prop of Object.values(props)) {
3233
+ if (prop?.["type"] === "string" && !("minLength" in prop)) {
3234
+ prop["minLength"] = 1;
3235
+ } else if (prop?.["type"] === "object") {
3236
+ enforceRequiredMinLength(prop);
3237
+ }
2608
3238
  }
2609
- }, "injectEnumValues");
2610
-
2611
- // src/lib/crud/loader/json-adapter.ts
2612
- import { ZodArray, ZodNullable, ZodOptional } from "zod";
3239
+ }, "enforceRequiredMinLength");
3240
+ var applySchemaTransforms = /* @__PURE__ */ __name((schema) => {
3241
+ allowAdditionalProperties(schema);
3242
+ dropNullableFromRequired2(schema);
3243
+ enforceRequiredMinLength(schema);
3244
+ }, "applySchemaTransforms");
2613
3245
 
2614
- // src/lib/crud/loader/schema.helpers.ts
3246
+ // src/lib/crud/builder/schema.helpers.ts
2615
3247
  var pickByColumns = /* @__PURE__ */ __name((schema, columns, filter) => {
2616
3248
  if (!schema) return void 0;
2617
3249
  if (!columns?.length) return schema;
2618
- const isRelation2 = /* @__PURE__ */ __name((c) => c.fieldInput?.format === "relation", "isRelation");
2619
- const baseFilter = /* @__PURE__ */ __name((c) => !isRelation2(c) && (filter ? filter(c) : true), "baseFilter");
3250
+ const baseFilter = /* @__PURE__ */ __name((c) => !isRelation(c) && (filter ? filter(c) : true), "baseFilter");
2620
3251
  const filtered = columns.filter(baseFilter);
2621
3252
  if (!filtered.length) return void 0;
2622
3253
  const mask = Object.fromEntries(filtered.map((c) => [
@@ -2647,10 +3278,75 @@ var upsertOp = /* @__PURE__ */ __name((entry, schema) => {
2647
3278
  return void 0;
2648
3279
  }, "upsertOp");
2649
3280
 
2650
- // src/lib/crud/loader/view.builders.ts
2651
- import { toJSONSchema as toJSONSchema3 } from "zod";
3281
+ // src/lib/crud/builder/table-schema.builder.ts
3282
+ var SHARED_CELL_OPTION_KEYS = [
3283
+ "values",
3284
+ "storeValue",
3285
+ "uri",
3286
+ "resourceUri",
3287
+ "schemasUri"
3288
+ ];
3289
+ var pickSharedCellOptions = /* @__PURE__ */ __name((col) => {
3290
+ const options = col.fieldInput?.options ?? {};
3291
+ return Object.fromEntries(SHARED_CELL_OPTION_KEYS.filter((key) => options[key] !== void 0).map((key) => [
3292
+ key,
3293
+ options[key]
3294
+ ]));
3295
+ }, "pickSharedCellOptions");
3296
+ var buildTableUiSchema = /* @__PURE__ */ __name((cols) => {
3297
+ const layout = TableBuilder.init().addControls(...cols.map((col) => {
3298
+ const cellBuilder = isBoolean(col) ? BooleanCellBuilder : TextCellBuilder;
3299
+ let builder = cellBuilder.properties(col.id);
3300
+ if (col.displayKey) builder = builder.key(col.displayKey);
3301
+ if (col.sortId) builder = builder.setSortId(col.sortId);
3302
+ return builder;
3303
+ })).build();
3304
+ const colMap = Object.fromEntries(cols.map((c) => [
3305
+ c.id,
3306
+ c
3307
+ ]));
3308
+ layout.elements = layout.elements.map((el) => {
3309
+ const id = el.scope?.replace("#/properties/", "");
3310
+ const col = id ? colMap[id] : void 0;
3311
+ if (!col) return el;
3312
+ const fieldInputOptions = isRecordCell(col) || isDateRange(col) ? col.fieldInput?.options ?? {} : pickSharedCellOptions(col);
3313
+ const dataPathOption = col.column ? {
3314
+ dataPath: col.column
3315
+ } : {};
3316
+ const derivedSortId = isRecordCell(col) || isDateRange(col) ? null : deriveSortId(col);
3317
+ const sortOptions = derivedSortId ? {
3318
+ sortId: derivedSortId
3319
+ } : {
3320
+ sortable: false
3321
+ };
3322
+ const relationTypeOption = col.fieldInput?.relationType ? {
3323
+ relationType: col.fieldInput.relationType
3324
+ } : {};
3325
+ return {
3326
+ ...el,
3327
+ options: {
3328
+ ...el.options ?? {},
3329
+ ...fieldInputOptions,
3330
+ ...dataPathOption,
3331
+ ...sortOptions,
3332
+ ...relationTypeOption,
3333
+ ...isDateRange(col) && {
3334
+ format: "date-range"
3335
+ },
3336
+ label: col.label
3337
+ },
3338
+ ...isRecordCell(col) && {
3339
+ type: "RecordCell"
3340
+ },
3341
+ ...isDateRange(col) && {
3342
+ type: "Control"
3343
+ }
3344
+ };
3345
+ });
3346
+ return layout;
3347
+ }, "buildTableUiSchema");
2652
3348
 
2653
- // src/lib/crud/loader/form-schema.builder.ts
3349
+ // src/lib/crud/builder/form-schema.builder.ts
2654
3350
  var buildConditionSchema = /* @__PURE__ */ __name((when) => {
2655
3351
  if (when.notExists) return {
2656
3352
  not: {
@@ -2770,232 +3466,7 @@ var buildFormUiSchema = /* @__PURE__ */ __name((cols) => {
2770
3466
  return layout;
2771
3467
  }, "buildFormUiSchema");
2772
3468
 
2773
- // src/lib/crud/loader/schema-transforms.ts
2774
- var allowAdditionalProperties = /* @__PURE__ */ __name((schema) => {
2775
- if (schema["type"] === "object") {
2776
- schema["additionalProperties"] = true;
2777
- const props = schema["properties"];
2778
- if (props) {
2779
- for (const value of Object.values(props)) {
2780
- allowAdditionalProperties(value);
2781
- }
2782
- }
2783
- }
2784
- if (schema["type"] === "array" && schema["items"]) {
2785
- allowAdditionalProperties(schema["items"]);
2786
- }
2787
- }, "allowAdditionalProperties");
2788
- var isNullableProperty2 = /* @__PURE__ */ __name((prop) => {
2789
- const anyOf = prop?.["anyOf"];
2790
- return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
2791
- }, "isNullableProperty");
2792
- var dropNullableFromRequired2 = /* @__PURE__ */ __name((schema) => {
2793
- if (schema["type"] !== "object") return;
2794
- const props = schema["properties"];
2795
- if (!props) return;
2796
- const required = schema["required"];
2797
- if (Array.isArray(required)) {
2798
- schema["required"] = required.filter((key) => !isNullableProperty2(props[key]));
2799
- }
2800
- for (const value of Object.values(props)) {
2801
- dropNullableFromRequired2(value);
2802
- }
2803
- }, "dropNullableFromRequired");
2804
- var enforceRequiredMinLength = /* @__PURE__ */ __name((schema) => {
2805
- if (schema["type"] !== "object") return;
2806
- const props = schema["properties"];
2807
- if (!props) return;
2808
- for (const prop of Object.values(props)) {
2809
- if (prop?.["type"] === "string" && !("minLength" in prop)) {
2810
- prop["minLength"] = 1;
2811
- } else if (prop?.["type"] === "object") {
2812
- enforceRequiredMinLength(prop);
2813
- }
2814
- }
2815
- }, "enforceRequiredMinLength");
2816
-
2817
- // src/lib/crud/loader/table-schema.builder.ts
2818
- var isBoolean = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "boolean" || col.columnType === "boolean", "isBoolean");
2819
- var isRelation = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "relation", "isRelation");
2820
- var isAutocomplete = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "autocomplete", "isAutocomplete");
2821
- var isRecordCell = /* @__PURE__ */ __name((col) => isRelation(col) || isAutocomplete(col), "isRecordCell");
2822
- var isDateRange = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "date-range", "isDateRange");
2823
- var deriveSortId = /* @__PURE__ */ __name((col) => {
2824
- if (col.sortable === false) return null;
2825
- if (col.sortId) return col.sortId;
2826
- const base = col.column ?? col.id;
2827
- const fi = col.fieldInput;
2828
- const opts = fi?.options ?? {};
2829
- const isRelation2 = fi?.format === "relation" || !!fi?.relationType || fi?.type === "autocomplete" || typeof opts.resource === "string";
2830
- if (isRelation2) {
2831
- 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);
2832
- if (!key) return null;
2833
- const path2 = key.includes(".") ? key : `${base}.${key}`;
2834
- return path2.endsWith(".label") ? path2.slice(0, -".label".length) : path2;
2835
- }
2836
- if (!col.displayKey) return base;
2837
- const path = `${base}.${col.displayKey}`;
2838
- const isValueLabel = !!col.enum || opts.emitObject === true;
2839
- if (isValueLabel && path.endsWith(".label")) return path.slice(0, -".label".length);
2840
- return path;
2841
- }, "deriveSortId");
2842
- var resolveDefaultSort = /* @__PURE__ */ __name((tableCols, allColumns) => {
2843
- const isSortable = /* @__PURE__ */ __name((c) => c.sortable !== false, "isSortable");
2844
- const sortCol = tableCols.find((c) => c.defaultSort && isSortable(c)) ?? tableCols.find(isSortable) ?? allColumns?.find((c) => c.idField);
2845
- return sortCol ? deriveSortId(sortCol) ?? sortCol.id : void 0;
2846
- }, "resolveDefaultSort");
2847
- var SHARED_CELL_OPTION_KEYS = [
2848
- "values",
2849
- "storeValue",
2850
- "uri",
2851
- "resourceUri",
2852
- "schemasUri"
2853
- ];
2854
- var pickSharedCellOptions = /* @__PURE__ */ __name((col) => {
2855
- const options = col.fieldInput?.options ?? {};
2856
- return Object.fromEntries(SHARED_CELL_OPTION_KEYS.filter((key) => options[key] !== void 0).map((key) => [
2857
- key,
2858
- options[key]
2859
- ]));
2860
- }, "pickSharedCellOptions");
2861
- var buildTableUiSchema = /* @__PURE__ */ __name((cols) => {
2862
- const layout = TableBuilder.init().addControls(...cols.map((col) => {
2863
- const cellBuilder = isBoolean(col) ? BooleanCellBuilder : TextCellBuilder;
2864
- let builder = cellBuilder.properties(col.id);
2865
- if (col.displayKey) builder = builder.key(col.displayKey);
2866
- if (col.sortId) builder = builder.setSortId(col.sortId);
2867
- return builder;
2868
- })).build();
2869
- const colMap = Object.fromEntries(cols.map((c) => [
2870
- c.id,
2871
- c
2872
- ]));
2873
- layout.elements = layout.elements.map((el) => {
2874
- const id = el.scope?.replace("#/properties/", "");
2875
- const col = id ? colMap[id] : void 0;
2876
- if (!col) return el;
2877
- const fieldInputOptions = isRecordCell(col) || isDateRange(col) ? col.fieldInput?.options ?? {} : pickSharedCellOptions(col);
2878
- const dataPathOption = col.column ? {
2879
- dataPath: col.column
2880
- } : {};
2881
- const derivedSortId = isRecordCell(col) || isDateRange(col) ? null : deriveSortId(col);
2882
- const sortOptions = derivedSortId ? {
2883
- sortId: derivedSortId
2884
- } : {
2885
- sortable: false
2886
- };
2887
- const relationTypeOption = col.fieldInput?.relationType ? {
2888
- relationType: col.fieldInput.relationType
2889
- } : {};
2890
- return {
2891
- ...el,
2892
- options: {
2893
- ...el.options ?? {},
2894
- ...fieldInputOptions,
2895
- ...dataPathOption,
2896
- ...sortOptions,
2897
- ...relationTypeOption,
2898
- ...isDateRange(col) && {
2899
- format: "date-range"
2900
- },
2901
- label: col.label
2902
- },
2903
- ...isRecordCell(col) && {
2904
- type: "RecordCell"
2905
- },
2906
- ...isDateRange(col) && {
2907
- type: "Control"
2908
- }
2909
- };
2910
- });
2911
- return layout;
2912
- }, "buildTableUiSchema");
2913
-
2914
- // src/lib/crud/loader/view.builders.ts
2915
- var patchFilterProperties = /* @__PURE__ */ __name((jsonSchema, columns) => {
2916
- if (!columns?.length) return;
2917
- const properties = jsonSchema.properties;
2918
- if (!properties) return;
2919
- for (const col of columns) {
2920
- if (col.fieldInput?.format === "date-range") {
2921
- delete properties[col.id];
2922
- const opts = col.fieldInput?.options ?? {};
2923
- const base = col.label ?? col.id;
2924
- const fromField = opts["fromField"] ?? "from";
2925
- const toField = opts["toField"] ?? "to";
2926
- const dateProp = /* @__PURE__ */ __name((title) => ({
2927
- type: "string",
2928
- format: "date",
2929
- title,
2930
- "x-field-type": "date"
2931
- }), "dateProp");
2932
- properties[`${col.id}->${fromField}`] = dateProp(opts["fromLabel"] ?? `${base} (from)`);
2933
- properties[`${col.id}->${toField}`] = dateProp(opts["toLabel"] ?? `${base} (to)`);
2934
- continue;
2935
- }
2936
- const prop = properties[col.id];
2937
- if (!prop) continue;
2938
- prop.title = col.label ?? col.id;
2939
- const values = col.fieldInput?.options?.["values"];
2940
- if (values) {
2941
- prop["x-values"] = values;
2942
- }
2943
- const ft = col.fieldInput?.type;
2944
- if (ft === "select" || col.enum) {
2945
- prop["x-field-type"] = "enum";
2946
- } else if (ft === "number" || prop.type === "number" || prop.type === "integer") {
2947
- prop["x-field-type"] = "number";
2948
- } else if (prop.format === "date-time" || prop.format === "date") {
2949
- prop["x-field-type"] = "date";
2950
- } else if (prop.type === "boolean") {
2951
- prop["x-field-type"] = "boolean";
2952
- }
2953
- }
2954
- }, "patchFilterProperties");
2955
- var colPosition = /* @__PURE__ */ __name((col, i) => col.fieldInput?.position ?? i, "colPosition");
2956
- var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
2957
- col,
2958
- i
2959
- })).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
2960
- var toViewColumn = /* @__PURE__ */ __name((col) => ({
2961
- id: col.id,
2962
- ...col.label && {
2963
- label: col.label
2964
- },
2965
- ...col.sortable != null && {
2966
- sortable: col.sortable
2967
- },
2968
- ...col.searchable != null && {
2969
- searchable: col.searchable
2970
- },
2971
- ...col.fieldInput && {
2972
- fieldInput: col.fieldInput
2973
- }
2974
- }), "toViewColumn");
2975
- var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema, sort = false, schemaVisible) => {
2976
- if (!schema || !columns?.length) return void 0;
2977
- const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
2978
- if (!visibleCols.length) return void 0;
2979
- const schemaCols = schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols;
2980
- const schemaIds = schemaCols.map((c) => c.id);
2981
- const mask = Object.fromEntries(schemaIds.map((id) => [
2982
- id,
2983
- true
2984
- ]));
2985
- const picked = schema.pick(mask);
2986
- const jsonSchema = toJSONSchema3(picked, {
2987
- target: "draft-07",
2988
- ...jsonSchemaOpts
2989
- });
2990
- allowAdditionalProperties(jsonSchema);
2991
- dropNullableFromRequired2(jsonSchema);
2992
- enforceRequiredMinLength(jsonSchema);
2993
- return {
2994
- json_schema: jsonSchema,
2995
- ui_schema: buildUiSchema(visibleCols),
2996
- columns: visibleCols.map(toViewColumn)
2997
- };
2998
- }, "buildView");
3469
+ // src/lib/crud/builder/calculated-columns.builder.ts
2999
3470
  var isVisibleInMode = /* @__PURE__ */ __name((c, mode) => mode === "table" ? !c.hiddenInTable : !c.hiddenInView, "isVisibleInMode");
3000
3471
  var buildCalculatedElement = /* @__PURE__ */ __name((c, mode) => mode === "table" ? {
3001
3472
  type: c.type === "boolean" ? "BooleanCell" : "TextCell",
@@ -3074,6 +3545,78 @@ var injectCalculatedColumnsIntoView = /* @__PURE__ */ __name((viewConfig, calcul
3074
3545
  }, "injectCalculatedColumnsIntoView");
3075
3546
  var injectCalculatedColumns = /* @__PURE__ */ __name((tableView, calculated) => injectCalculatedColumnsIntoView(tableView, calculated, "table"), "injectCalculatedColumns");
3076
3547
  var injectCalculatedColumnsToView = /* @__PURE__ */ __name((viewConfig, calculated) => injectCalculatedColumnsIntoView(viewConfig, calculated, "view"), "injectCalculatedColumnsToView");
3548
+
3549
+ // src/lib/crud/builder/view.builder.ts
3550
+ import { toJSONSchema as toJSONSchema3 } from "zod";
3551
+ var patchFilterProperties = /* @__PURE__ */ __name((jsonSchema, columns) => {
3552
+ if (!columns?.length) return;
3553
+ const properties = jsonSchema.properties;
3554
+ if (!properties) return;
3555
+ for (const col of columns) {
3556
+ if (col.fieldInput?.format === "date-range") {
3557
+ delete properties[col.id];
3558
+ const opts = col.fieldInput?.options ?? {};
3559
+ const base = col.label ?? col.id;
3560
+ const fromField = opts["fromField"] ?? "from";
3561
+ const toField = opts["toField"] ?? "to";
3562
+ const dateProp = /* @__PURE__ */ __name((title) => ({
3563
+ type: "string",
3564
+ format: "date",
3565
+ title,
3566
+ "x-field-type": "date"
3567
+ }), "dateProp");
3568
+ properties[`${col.id}->${fromField}`] = dateProp(opts["fromLabel"] ?? `${base} (from)`);
3569
+ properties[`${col.id}->${toField}`] = dateProp(opts["toLabel"] ?? `${base} (to)`);
3570
+ continue;
3571
+ }
3572
+ const prop = properties[col.id];
3573
+ if (!prop) continue;
3574
+ prop.title = col.label ?? col.id;
3575
+ const values = col.fieldInput?.options?.["values"];
3576
+ if (values) {
3577
+ prop["x-values"] = values;
3578
+ }
3579
+ const ft = col.fieldInput?.type;
3580
+ if (ft === "select" || col.enum) {
3581
+ prop["x-field-type"] = "enum";
3582
+ } else if (ft === "number" || prop.type === "number" || prop.type === "integer") {
3583
+ prop["x-field-type"] = "number";
3584
+ } else if (prop.format === "date-time" || prop.format === "date") {
3585
+ prop["x-field-type"] = "date";
3586
+ } else if (prop.type === "boolean") {
3587
+ prop["x-field-type"] = "boolean";
3588
+ }
3589
+ }
3590
+ }, "patchFilterProperties");
3591
+ var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema, sort = false, schemaVisible) => {
3592
+ if (!schema || !columns?.length) return void 0;
3593
+ const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
3594
+ if (!visibleCols.length) return void 0;
3595
+ const schemaCols = (schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols).filter((c) => !isRelation(c));
3596
+ const schemaIds = schemaCols.map((c) => c.id);
3597
+ const mask = Object.fromEntries(schemaIds.map((id) => [
3598
+ id,
3599
+ true
3600
+ ]));
3601
+ const picked = schema.pick(mask);
3602
+ const jsonSchema = toJSONSchema3(picked, {
3603
+ target: "draft-07",
3604
+ ...jsonSchemaOpts
3605
+ });
3606
+ applySchemaTransforms(jsonSchema);
3607
+ if (schemaVisible) {
3608
+ const nonEditableIds = new Set(schemaCols.filter((c) => c.idField || c.createable === false && c.updateable === false).map((c) => c.id));
3609
+ const required = jsonSchema["required"];
3610
+ if (Array.isArray(required) && nonEditableIds.size) {
3611
+ jsonSchema["required"] = required.filter((k) => !nonEditableIds.has(k));
3612
+ }
3613
+ }
3614
+ return {
3615
+ json_schema: jsonSchema,
3616
+ ui_schema: buildUiSchema(visibleCols),
3617
+ columns: visibleCols.map(toViewColumn)
3618
+ };
3619
+ }, "buildView");
3077
3620
  var emptyTableView = /* @__PURE__ */ __name(() => ({
3078
3621
  json_schema: {
3079
3622
  type: "object",
@@ -3092,7 +3635,7 @@ var buildViews = /* @__PURE__ */ __name((schema, columns) => {
3092
3635
  } else if (columns?.length) {
3093
3636
  views.table = emptyTableView();
3094
3637
  }
3095
- const form = buildView(schema, columns, (c) => !c.hiddenInForm, buildFormUiSchema, true, (c) => c.createable === true || c.updateable === true);
3638
+ const form = buildView(schema, columns, (c) => !c.hiddenInForm, buildFormUiSchema, true, (c) => !c.idField && (c.createable === true || c.updateable === true));
3096
3639
  if (form) views.form = form;
3097
3640
  const filter = buildView(schema, columns, (c) => !!c.filterable, buildFormUiSchema, true);
3098
3641
  if (filter) {
@@ -3112,7 +3655,6 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3112
3655
  if (!properties[c.column]) {
3113
3656
  properties[c.column] = {
3114
3657
  type: "object",
3115
- additionalProperties: true,
3116
3658
  properties: {}
3117
3659
  };
3118
3660
  }
@@ -3123,7 +3665,6 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3123
3665
  if (!target[segment]) {
3124
3666
  target[segment] = {
3125
3667
  type: "object",
3126
- additionalProperties: true,
3127
3668
  properties: {}
3128
3669
  };
3129
3670
  }
@@ -3134,17 +3675,22 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3134
3675
  title: c.label ?? c.id
3135
3676
  };
3136
3677
  } else {
3678
+ const opts = c.fieldInput?.options;
3679
+ const isObject = opts?.emitObject === true || c.fieldInput?.type === "autocomplete";
3137
3680
  properties[c.id] = {
3138
- type: c.columnType ?? "string",
3681
+ ...isObject ? {} : {
3682
+ type: c.columnType ?? "string"
3683
+ },
3139
3684
  title: c.label ?? c.id
3140
3685
  };
3141
3686
  }
3142
3687
  }
3143
- return {
3688
+ const schema = {
3144
3689
  type: "object",
3145
- additionalProperties: true,
3146
3690
  properties
3147
3691
  };
3692
+ applySchemaTransforms(schema);
3693
+ return schema;
3148
3694
  }, "buildJsonSchema");
3149
3695
  const fixNestedScopes = /* @__PURE__ */ __name((uiSchema, cols) => {
3150
3696
  const colMap = Object.fromEntries(cols.map((c) => [
@@ -3194,33 +3740,223 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
3194
3740
  return Object.keys(views).length ? views : void 0;
3195
3741
  }, "buildViewsFromColumns");
3196
3742
 
3197
- // src/lib/crud/loader/json-adapter.ts
3743
+ // src/lib/crud/enum-registry/enum-registry.types.ts
3744
+ import { z as z17 } from "zod";
3745
+ var EnumEntrySchema = z17.object({
3746
+ value: z17.unknown(),
3747
+ label: z17.string()
3748
+ });
3749
+ var EnumRegistrySchema = z17.record(z17.string(), z17.array(EnumEntrySchema)).default({});
3750
+
3751
+ // src/lib/crud/enum-registry/enum-registry.loader.ts
3752
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
3753
+ import { dirname as dirname2, join as join5 } from "path";
3754
+ var ENUMS_FILE = "crouton.enums.json";
3755
+ var loadEnumRegistry = /* @__PURE__ */ __name((startDir, enumsFile) => {
3756
+ let file = enumsFile;
3757
+ if (!file) {
3758
+ let dir = startDir;
3759
+ while (true) {
3760
+ const candidate = join5(dir, ENUMS_FILE);
3761
+ if (existsSync3(candidate)) {
3762
+ file = candidate;
3763
+ break;
3764
+ }
3765
+ const parent = dirname2(dir);
3766
+ if (parent === dir) break;
3767
+ dir = parent;
3768
+ }
3769
+ }
3770
+ if (!file || !existsSync3(file)) return EnumRegistrySchema.parse(void 0);
3771
+ try {
3772
+ return EnumRegistrySchema.parse(JSON.parse(readFileSync2(file, "utf-8")));
3773
+ } catch {
3774
+ return EnumRegistrySchema.parse(void 0);
3775
+ }
3776
+ }, "loadEnumRegistry");
3777
+
3778
+ // src/lib/crud/enum-registry/enum-registry.injector.ts
3779
+ var injectEnumValues = /* @__PURE__ */ __name((columns, enums) => {
3780
+ if (!columns) return;
3781
+ for (const col of columns) {
3782
+ if (!col.enum) continue;
3783
+ const values = enums[col.enum];
3784
+ if (!values) continue;
3785
+ col.fieldInput = col.fieldInput ?? {
3786
+ type: "select"
3787
+ };
3788
+ const options = col.fieldInput.options ?? {};
3789
+ if (!("values" in options)) options.values = values;
3790
+ col.fieldInput.options = options;
3791
+ }
3792
+ }, "injectEnumValues");
3793
+
3794
+ // src/lib/crud/adapter/relation-type.ts
3795
+ import { ZodArray, ZodNullable, ZodOptional } from "zod";
3796
+ var unwrapZodType = /* @__PURE__ */ __name((type) => {
3797
+ if (type instanceof ZodOptional || type instanceof ZodNullable) {
3798
+ return unwrapZodType(type.unwrap());
3799
+ }
3800
+ return type;
3801
+ }, "unwrapZodType");
3802
+ var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
3803
+ if (!schema) return void 0;
3804
+ const field = schema.shape[columnId];
3805
+ if (!field) return void 0;
3806
+ const inner = unwrapZodType(field);
3807
+ return inner instanceof ZodArray ? "oneToMany" : "manyToOne";
3808
+ }, "deriveRelationType");
3809
+ var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
3810
+ const base = col.column ?? col.id;
3811
+ const fkNames = /* @__PURE__ */ new Set([
3812
+ `${base}_id`,
3813
+ `${col.id}_id`
3814
+ ]);
3815
+ return cols.some((c) => fkNames.has(c.id)) ? "manyToOne" : "oneToMany";
3816
+ }, "deriveRelationTypeFromColumns");
3817
+ var enrichRelationTypes = /* @__PURE__ */ __name((columns, schema) => {
3818
+ if (!schema) return columns;
3819
+ return columns.map((col) => {
3820
+ const fi = col.fieldInput;
3821
+ if (!fi) return col;
3822
+ const isRelationField = fi.type === "autocomplete" || fi.format === "relation";
3823
+ if (!isRelationField) return col;
3824
+ if (fi.relationType) return col;
3825
+ const derived = deriveRelationType(schema, col.id) ?? deriveRelationTypeFromColumns(col, columns);
3826
+ if (!derived) return col;
3827
+ return {
3828
+ ...col,
3829
+ fieldInput: {
3830
+ ...fi,
3831
+ relationType: derived
3832
+ }
3833
+ };
3834
+ });
3835
+ }, "enrichRelationTypes");
3836
+
3837
+ // src/lib/crud/resource/ReadResourceJson.ts
3198
3838
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
3199
- import { dirname as dirname2, resolve } from "path";
3200
- var resolveChildResource = /* @__PURE__ */ __name((resourcePath, parentDir) => {
3201
- const directPath = resolve(parentDir, resourcePath);
3202
- if (resourcePath.endsWith(".json") && existsSync4(directPath)) {
3203
- try {
3204
- return {
3205
- json: JSON.parse(readFileSync3(directPath, "utf-8")),
3206
- dir: dirname2(directPath)
3207
- };
3208
- } catch {
3209
- return void 0;
3210
- }
3839
+ import { dirname as dirname3, resolve as resolve2 } from "path";
3840
+ var readResourceJson = /* @__PURE__ */ __name((jsonPath) => {
3841
+ if (!existsSync4(jsonPath)) return void 0;
3842
+ const fileContent = JSON.parse(readFileSync3(jsonPath, "utf-8"));
3843
+ const resource = ResourceJsonSchema.safeParse(fileContent);
3844
+ if (resource.error) {
3845
+ console.error(resource.error);
3846
+ throw new Error(`Resource cannot be parsed ${jsonPath}`);
3211
3847
  }
3212
- const childName = resourcePath.replace(/^\.\//, "").replace(/\.resource$/, "");
3213
- const childJsonPath = resolve(dirname2(parentDir), childName, "resource.json");
3214
- if (!existsSync4(childJsonPath)) return void 0;
3848
+ return {
3849
+ json: resource.data,
3850
+ dir: dirname3(jsonPath)
3851
+ };
3852
+ }, "readResourceJson");
3853
+
3854
+ // src/lib/crud/adapter/resource-resolver.ts
3855
+ import { existsSync as existsSync5 } from "fs";
3856
+ import { dirname as dirname4, resolve as resolve3 } from "path";
3857
+ var resolveChildResource = /* @__PURE__ */ __name((resourcePath, parentDir) => {
3858
+ const directPath = resolve3(parentDir, resourcePath);
3215
3859
  try {
3216
- return {
3217
- json: JSON.parse(readFileSync3(childJsonPath, "utf-8")),
3218
- dir: dirname2(childJsonPath)
3219
- };
3860
+ if (resourcePath.endsWith(".json") && existsSync5(directPath)) {
3861
+ return readResourceJson(directPath);
3862
+ }
3863
+ const childName = resourcePath.replace(/^\.\//, "").replace(/\.resource$/, "");
3864
+ const childJsonPath = resolve3(dirname4(parentDir), childName, "resource.json");
3865
+ return readResourceJson(childJsonPath);
3220
3866
  } catch {
3221
3867
  return void 0;
3222
3868
  }
3223
3869
  }, "resolveChildResource");
3870
+
3871
+ // src/lib/crud/adapter/column-enrichment.ts
3872
+ var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResources, baseUrl) => {
3873
+ if (!columns) return columns;
3874
+ const base = baseUrl ?? "";
3875
+ return columns.map((col) => {
3876
+ if (col.fieldInput?.format !== "relation") return col;
3877
+ const sub = subResources.find((s) => s.column === col.id);
3878
+ if (!sub) return col;
3879
+ return {
3880
+ ...col,
3881
+ fieldInput: {
3882
+ ...col.fieldInput,
3883
+ options: {
3884
+ ...col.fieldInput.options,
3885
+ uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
3886
+ resourceUri: `${base}/${sub.childRoute}`,
3887
+ ...sub.views && {
3888
+ resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
3889
+ }
3890
+ }
3891
+ }
3892
+ };
3893
+ });
3894
+ }, "enrichActionColumns");
3895
+ var enrichResourceRefColumns = /* @__PURE__ */ __name((columns, parentDir, baseUrl) => {
3896
+ if (!columns || !parentDir) return columns;
3897
+ const base = baseUrl ?? "";
3898
+ return columns.map((col) => {
3899
+ if (!col.fieldInput?.resource || col.fieldInput.format === "relation") return col;
3900
+ const childResolved = resolveChildResource(col.fieldInput.resource, parentDir);
3901
+ const childRoute = childResolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3902
+ return {
3903
+ ...col,
3904
+ fieldInput: {
3905
+ ...col.fieldInput,
3906
+ options: {
3907
+ ...col.fieldInput.options,
3908
+ resourceUri: `${base}/${childRoute}`,
3909
+ schemasUri: `${base}/${childRoute}/schemas`
3910
+ }
3911
+ }
3912
+ };
3913
+ });
3914
+ }, "enrichResourceRefColumns");
3915
+ var enrichNestedRelationColumns = /* @__PURE__ */ __name((cols, dir, baseUrl) => {
3916
+ if (!cols || !dir) return cols;
3917
+ const base = baseUrl ?? "";
3918
+ return cols.map((col) => {
3919
+ if (col.fieldInput?.format !== "relation" || !col.fieldInput.resource) return col;
3920
+ const resolved = resolveChildResource(col.fieldInput.resource, dir);
3921
+ const targetRoute = resolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\/resource\.json$/, "").replace(/\.resource$/, "");
3922
+ const relationType = col.fieldInput.relationType ?? deriveRelationTypeFromColumns(col, cols);
3923
+ return {
3924
+ ...col,
3925
+ fieldInput: {
3926
+ ...col.fieldInput,
3927
+ relationType,
3928
+ options: {
3929
+ ...col.fieldInput.options,
3930
+ uri: `${base}/${targetRoute}`,
3931
+ resourceUri: `${base}/${targetRoute}`,
3932
+ resource: `${base}/${targetRoute}/schemas`
3933
+ }
3934
+ }
3935
+ };
3936
+ });
3937
+ }, "enrichNestedRelationColumns");
3938
+ var enrichIncludeWithSort = /* @__PURE__ */ __name((include, columns) => {
3939
+ if (!include?.length) return include;
3940
+ return include.map((entry) => {
3941
+ const relationName = typeof entry === "string" ? entry : entry.relation;
3942
+ const col = columns.find((c) => {
3943
+ const opts2 = c.fieldInput?.options;
3944
+ return (c.column ?? c.id) === relationName && opts2?.sort;
3945
+ });
3946
+ if (!col) return entry;
3947
+ const opts = col.fieldInput.options;
3948
+ const orderBy = buildChildSortClause(opts.sort, opts.sortDir ?? "asc");
3949
+ return typeof entry === "string" ? {
3950
+ relation: entry,
3951
+ orderBy
3952
+ } : {
3953
+ ...entry,
3954
+ orderBy
3955
+ };
3956
+ });
3957
+ }, "enrichIncludeWithSort");
3958
+
3959
+ // src/lib/crud/adapter/column-transforms.ts
3224
3960
  var expandExtendColumns = /* @__PURE__ */ __name((columns, dirPath) => {
3225
3961
  if (!dirPath) return columns;
3226
3962
  const result = [];
@@ -3235,7 +3971,7 @@ var expandExtendColumns = /* @__PURE__ */ __name((columns, dirPath) => {
3235
3971
  result.push(col);
3236
3972
  continue;
3237
3973
  }
3238
- const refColumns = normalizeColumns(resolved.json.columns) ?? [];
3974
+ const refColumns = resolved.json.columns;
3239
3975
  const parentColumnKey = col.column ?? col.id;
3240
3976
  for (const refCol of refColumns) {
3241
3977
  if (refCol.idField) continue;
@@ -3296,37 +4032,8 @@ var applyRelationFormatDefault = /* @__PURE__ */ __name((cols) => cols?.map((col
3296
4032
  }
3297
4033
  return col;
3298
4034
  }), "applyRelationFormatDefault");
3299
- var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
3300
- const base = col.column ?? col.id;
3301
- const fkNames = /* @__PURE__ */ new Set([
3302
- `${base}_id`,
3303
- `${col.id}_id`
3304
- ]);
3305
- return cols.some((c) => fkNames.has(c.id)) ? "manyToOne" : "oneToMany";
3306
- }, "deriveRelationTypeFromColumns");
3307
- var enrichNestedRelationColumns = /* @__PURE__ */ __name((cols, dir, baseUrl) => {
3308
- if (!cols || !dir) return cols;
3309
- const base = baseUrl ?? "";
3310
- return cols.map((col) => {
3311
- if (col.fieldInput?.format !== "relation" || !col.fieldInput.resource) return col;
3312
- const resolved = resolveChildResource(col.fieldInput.resource, dir);
3313
- const targetRoute = resolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\/resource\.json$/, "").replace(/\.resource$/, "");
3314
- const relationType = col.fieldInput.relationType ?? deriveRelationTypeFromColumns(col, cols);
3315
- return {
3316
- ...col,
3317
- fieldInput: {
3318
- ...col.fieldInput,
3319
- relationType,
3320
- options: {
3321
- ...col.fieldInput.options,
3322
- uri: `${base}/${targetRoute}`,
3323
- resourceUri: `${base}/${targetRoute}`,
3324
- resource: `${base}/${targetRoute}/schemas`
3325
- }
3326
- }
3327
- };
3328
- });
3329
- }, "enrichNestedRelationColumns");
4035
+
4036
+ // src/lib/crud/adapter/sub-resource.builder.ts
3330
4037
  var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentModel, parentDir, enums = {}, baseUrl) => {
3331
4038
  if (!columns || !parentDir) return [];
3332
4039
  return columns.filter((c) => c.fieldInput?.format === "relation" && c.fieldInput?.resource).map((c) => {
@@ -3334,7 +4041,7 @@ var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentMode
3334
4041
  const childJson = childResolved?.json;
3335
4042
  const childDir = childResolved?.dir;
3336
4043
  const childRoute = childJson?.route ?? c.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3337
- const rawChildColumns = childJson ? normalizeColumns(childJson.columns) : void 0;
4044
+ const rawChildColumns = childJson?.columns;
3338
4045
  const expandedChildColumns = rawChildColumns ? expandExtendColumns(rawChildColumns, childDir) : void 0;
3339
4046
  const childColumns = applyRelationFormatDefault(expandedChildColumns) ?? expandedChildColumns;
3340
4047
  injectEnumValues(childColumns, enums);
@@ -3396,71 +4103,10 @@ var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentMode
3396
4103
  };
3397
4104
  });
3398
4105
  }, "buildSubResources");
3399
- var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResources, baseUrl) => {
3400
- if (!columns) return columns;
3401
- const base = baseUrl ?? "";
3402
- return columns.map((col) => {
3403
- if (col.fieldInput?.format !== "relation") return col;
3404
- const sub = subResources.find((s) => s.column === col.id);
3405
- if (!sub) return col;
3406
- return {
3407
- ...col,
3408
- fieldInput: {
3409
- ...col.fieldInput,
3410
- options: {
3411
- ...col.fieldInput.options,
3412
- uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
3413
- resourceUri: `${base}/${sub.childRoute}`,
3414
- ...sub.views && {
3415
- resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
3416
- }
3417
- }
3418
- }
3419
- };
3420
- });
3421
- }, "enrichActionColumns");
3422
- var enrichResourceRefColumns = /* @__PURE__ */ __name((columns, parentDir, baseUrl) => {
3423
- if (!columns || !parentDir) return columns;
3424
- const base = baseUrl ?? "";
3425
- return columns.map((col) => {
3426
- if (!col.fieldInput?.resource || col.fieldInput.format === "relation") return col;
3427
- const childResolved = resolveChildResource(col.fieldInput.resource, parentDir);
3428
- const childRoute = childResolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
3429
- return {
3430
- ...col,
3431
- fieldInput: {
3432
- ...col.fieldInput,
3433
- options: {
3434
- ...col.fieldInput.options,
3435
- resourceUri: `${base}/${childRoute}`,
3436
- schemasUri: `${base}/${childRoute}/schemas`
3437
- }
3438
- }
3439
- };
3440
- });
3441
- }, "enrichResourceRefColumns");
3442
- var enrichIncludeWithSort = /* @__PURE__ */ __name((include, columns) => {
3443
- if (!include?.length) return include;
3444
- return include.map((entry) => {
3445
- const relationName = typeof entry === "string" ? entry : entry.relation;
3446
- const col = columns.find((c) => {
3447
- const opts2 = c.fieldInput?.options;
3448
- return (c.column ?? c.id) === relationName && opts2?.sort;
3449
- });
3450
- if (!col) return entry;
3451
- const opts = col.fieldInput.options;
3452
- const orderBy = buildChildSortClause(opts.sort, opts.sortDir ?? "asc");
3453
- return typeof entry === "string" ? {
3454
- relation: entry,
3455
- orderBy
3456
- } : {
3457
- ...entry,
3458
- orderBy
3459
- };
3460
- });
3461
- }, "enrichIncludeWithSort");
4106
+
4107
+ // src/lib/crud/adapter/json-adapter.ts
3462
4108
  var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, actions, tableActions, enums = {}) => {
3463
- const rawColumns = expandExtendColumns(normalizeColumns(json.columns) ?? [], dirPath);
4109
+ const rawColumns = expandExtendColumns(json.columns, dirPath);
3464
4110
  const columns = enrichRelationTypes(applyRelationFormatDefault(rawColumns) ?? rawColumns, schema);
3465
4111
  injectEnumValues(columns, enums);
3466
4112
  const subResources = buildSubResources(columns, json.route, json.model, dirPath, enums, baseUrl);
@@ -3504,22 +4150,14 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
3504
4150
  delete: true
3505
4151
  }
3506
4152
  };
3507
- const display = {
3508
- mode: json.display?.mode === "page" ? "page" : "modal",
3509
- customComponent: json.display?.customComponent ?? null
3510
- };
3511
4153
  return {
4154
+ ...json,
3512
4155
  name: json.name,
3513
4156
  route: json.route,
3514
4157
  model: json.model,
3515
4158
  tag: json.tag,
3516
- display,
3517
- ...json.title && {
3518
- title: json.title
3519
- },
3520
- ...json.sidebar && {
3521
- sidebar: json.sidebar
3522
- },
4159
+ sidebar: json.sidebar,
4160
+ display: json.display,
3523
4161
  ...json.idType && {
3524
4162
  idType: json.idType
3525
4163
  },
@@ -3562,38 +4200,6 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
3562
4200
  }
3563
4201
  };
3564
4202
  }, "fromJson");
3565
- var unwrapZodType = /* @__PURE__ */ __name((type) => {
3566
- if (type instanceof ZodOptional || type instanceof ZodNullable) {
3567
- return unwrapZodType(type.unwrap());
3568
- }
3569
- return type;
3570
- }, "unwrapZodType");
3571
- var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
3572
- if (!schema) return void 0;
3573
- const field = schema.shape[columnId];
3574
- if (!field) return void 0;
3575
- const inner = unwrapZodType(field);
3576
- return inner instanceof ZodArray ? "oneToMany" : "manyToOne";
3577
- }, "deriveRelationType");
3578
- var enrichRelationTypes = /* @__PURE__ */ __name((columns, schema) => {
3579
- if (!schema) return columns;
3580
- return columns.map((col) => {
3581
- const fi = col.fieldInput;
3582
- if (!fi) return col;
3583
- const isRelationField = fi.type === "autocomplete" || fi.format === "relation";
3584
- if (!isRelationField) return col;
3585
- if (fi.relationType) return col;
3586
- const derived = deriveRelationType(schema, col.id) ?? deriveRelationTypeFromColumns(col, columns);
3587
- if (!derived) return col;
3588
- return {
3589
- ...col,
3590
- fieldInput: {
3591
- ...fi,
3592
- relationType: derived
3593
- }
3594
- };
3595
- });
3596
- }, "enrichRelationTypes");
3597
4203
  var buildLookup = /* @__PURE__ */ __name((columns) => {
3598
4204
  if (!columns) return void 0;
3599
4205
  const keyCol = columns.find((c) => c.idField);
@@ -3607,17 +4213,42 @@ var buildLookup = /* @__PURE__ */ __name((columns) => {
3607
4213
  };
3608
4214
  }, "buildLookup");
3609
4215
 
3610
- // src/lib/crud/loader/index.ts
3611
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
3612
- import { join as join5 } from "path";
4216
+ // src/lib/crud/hooks/hooks.types.ts
4217
+ import { z as z18 } from "zod";
4218
+ var WriteOpSchema = z18.enum([
4219
+ "create",
4220
+ "update",
4221
+ "upsert",
4222
+ "delete"
4223
+ ]);
4224
+ var ReadOpSchema = z18.enum([
4225
+ "findAll",
4226
+ "findOne"
4227
+ ]);
4228
+ var ResourceHooksSchema = z18.object({
4229
+ beforeWrite: z18.custom().optional(),
4230
+ afterWrite: z18.custom().optional(),
4231
+ afterRead: z18.custom().optional()
4232
+ });
4233
+
4234
+ // src/lib/crud/hooks/hooks.loader.ts
4235
+ import { join as join6 } from "path";
4236
+ var loadResourceHooks = /* @__PURE__ */ __name(async (basePath) => {
4237
+ const file = findModule(basePath, "hooks");
4238
+ return file ? importDefault(file) : void 0;
4239
+ }, "loadResourceHooks");
3613
4240
  var loadSubResourceHooks = /* @__PURE__ */ __name(async (subResources, basePath) => {
3614
4241
  for (const sub of subResources) {
3615
- const file = sub.name ? findModule2(join5(basePath, "hooks"), sub.name) : void 0;
4242
+ const file = sub.name ? findModule(join6(basePath, "hooks"), sub.name) : void 0;
3616
4243
  if (!file) continue;
3617
4244
  const hooks = await importDefault(file);
3618
4245
  if (hooks) sub.hooks = hooks;
3619
4246
  }
3620
4247
  }, "loadSubResourceHooks");
4248
+
4249
+ // src/lib/crud/loader/index.ts
4250
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
4251
+ import { join as join7 } from "path";
3621
4252
  var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl, enumsFile) => {
3622
4253
  const enums = loadEnumRegistry(dirPath, enumsFile);
3623
4254
  const entries = readdirSync2(dirPath, {
@@ -3626,22 +4257,21 @@ var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl,
3626
4257
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
3627
4258
  const configs = [];
3628
4259
  for (const dir of dirs) {
3629
- const basePath = join5(dirPath, dir);
3630
- const schemaFile = findModule2(basePath, "schema");
4260
+ const basePath = join7(dirPath, dir);
4261
+ const schemaFile = findModule(basePath, "schema");
3631
4262
  const schema = schemaFile ? await importDefault(schemaFile) : void 0;
3632
- const hooksFile = findModule2(basePath, "hooks");
3633
- const hooks = hooksFile ? await importDefault(hooksFile) : void 0;
3634
- const jsonFile = join5(basePath, "resource.json");
3635
- if (existsSync5(jsonFile)) {
3636
- const json = JSON.parse(readFileSync4(jsonFile, "utf-8"));
3637
- const actions = await loadActions(json.actions ?? [], basePath);
3638
- const tableActions = await loadTableActions(json.tableActions ?? [], basePath);
4263
+ const hooks = await loadResourceHooks(basePath);
4264
+ const jsonFile = join7(basePath, "resource.json");
4265
+ if (existsSync6(jsonFile)) {
4266
+ const json = readResourceJson(jsonFile).json;
4267
+ const actions = await loadActions(json.actions ?? [], basePath, "row");
4268
+ const tableActions = await loadActions(json.tableActions ?? [], basePath, "table");
3639
4269
  const config = fromJson(json, schema, hooks, basePath, baseUrl, actions, tableActions, enums);
3640
4270
  await loadSubResourceHooks(config.subResources ?? [], basePath);
3641
4271
  configs.push(config);
3642
4272
  continue;
3643
4273
  }
3644
- const tsFile = findModule2(basePath, "resource");
4274
+ const tsFile = findModule(basePath, "resource");
3645
4275
  if (tsFile) {
3646
4276
  const config = await importDefault(tsFile);
3647
4277
  if (config) configs.push(hooks ? {
@@ -3692,11 +4322,18 @@ var CroutonApiModule = class _CroutonApiModule {
3692
4322
  static {
3693
4323
  __name(this, "CroutonApiModule");
3694
4324
  }
3695
- static forResources(configs, dataSources, loader, config) {
4325
+ onModuleInit() {
4326
+ if (!BigInt.prototype.toJSON) {
4327
+ BigInt.prototype.toJSON = function() {
4328
+ return Number(this);
4329
+ };
4330
+ }
4331
+ }
4332
+ static forResources(configs, dataSources, loader, { baseUrl }, config) {
3696
4333
  const dataSourceRegistry = new DataSourceRegistry(dataSources);
3697
4334
  const configRegistry = new ResourceConfigRegistry(loader, configs);
3698
4335
  const controllers = [
3699
- ...configs.map((c) => createCrudController(c, config.baseUrl)),
4336
+ ...configs.map((c) => createCrudController(c, baseUrl)),
3700
4337
  createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true)
3701
4338
  ];
3702
4339
  return {
@@ -3718,14 +4355,18 @@ var CroutonApiModule = class _CroutonApiModule {
3718
4355
  ]
3719
4356
  };
3720
4357
  }
3721
- static async forResourceDir(dirPath, dataSourcesPath, config) {
3722
- const loader = new FileSystemResourceConfigLoader(dirPath, config.baseUrl, config.enumsFile);
3723
- const configs = await loadResourceConfigsFromDir(dirPath, config.baseUrl, config.enumsFile);
4358
+ static async forResourceDir(dirPath, dataSourcesPath, { baseUrl }) {
4359
+ const config = await loadConfig();
4360
+ const loader = new FileSystemResourceConfigLoader(dirPath, baseUrl, config.enumsFile);
4361
+ const configs = await loadResourceConfigsFromDir(dirPath, baseUrl, config.enumsFile);
3724
4362
  const dataSources = await loadDataSourcesFromDir(dataSourcesPath);
3725
- return _CroutonApiModule.forResources(configs, dataSources, loader, config);
4363
+ return _CroutonApiModule.forResources(configs, dataSources, loader, {
4364
+ baseUrl
4365
+ }, config);
3726
4366
  }
3727
- static forLoader(loader, configs, dataSources, config) {
3728
- return _CroutonApiModule.forResources(configs, dataSources, loader, config);
4367
+ static async forLoader(loader, configs, dataSources, appConfig) {
4368
+ const config = await loadConfig();
4369
+ return _CroutonApiModule.forResources(configs, dataSources, loader, appConfig, config);
3729
4370
  }
3730
4371
  };
3731
4372
  CroutonApiModule = _ts_decorate5([
@@ -3742,6 +4383,8 @@ export {
3742
4383
  ResourceConfigLoader2 as ResourceConfigLoader,
3743
4384
  ResourceConfigRegistry,
3744
4385
  isOperationEnabled,
4386
+ isRowProcedureAction,
4387
+ isTableProcedureAction,
3745
4388
  loadDataSourcesFromDir,
3746
4389
  loadResourceConfigsFromDir,
3747
4390
  resolveDefinition,