@ghentcdh/crouton-api 0.0.1-alpha.50 → 0.0.1-alpha.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1387,6 +1387,49 @@ var init_TableAction_schema = __esm({
1387
1387
  }
1388
1388
  });
1389
1389
 
1390
+ // ../crouton-core/src/lib/resource/extensions.ts
1391
+ var CORE_RESOURCE_KEYS, registry, registerResourceExtension, registerResourceExtensions, getResourceExtensions;
1392
+ var init_extensions = __esm({
1393
+ "../crouton-core/src/lib/resource/extensions.ts"() {
1394
+ "use strict";
1395
+ init_cjs_shims();
1396
+ CORE_RESOURCE_KEYS = /* @__PURE__ */ new Set([
1397
+ "$schema",
1398
+ "schemaVersion",
1399
+ "draft",
1400
+ "kind",
1401
+ "id",
1402
+ "name",
1403
+ "route",
1404
+ "model",
1405
+ "tag",
1406
+ "title",
1407
+ "table",
1408
+ "idType",
1409
+ "database",
1410
+ "parent",
1411
+ "sidebar",
1412
+ "display",
1413
+ "security",
1414
+ "operations",
1415
+ "columns",
1416
+ "calculatedColumns",
1417
+ "actions",
1418
+ "tableActions",
1419
+ "modalSize",
1420
+ "include",
1421
+ "extensions"
1422
+ ]);
1423
+ registry = /* @__PURE__ */ new Map();
1424
+ registerResourceExtension = /* @__PURE__ */ __name((name, schema) => {
1425
+ if (CORE_RESOURCE_KEYS.has(name)) throw new Error(`Extension "${name}" shadows a core resource key.`);
1426
+ registry.set(name, schema);
1427
+ }, "registerResourceExtension");
1428
+ registerResourceExtensions = /* @__PURE__ */ __name((map = {}) => Object.entries(map).forEach(([k, s]) => registerResourceExtension(k, s)), "registerResourceExtensions");
1429
+ getResourceExtensions = /* @__PURE__ */ __name(() => registry, "getResourceExtensions");
1430
+ }
1431
+ });
1432
+
1390
1433
  // ../crouton-core/src/lib/resource/include.schema.ts
1391
1434
  var import_zod18, JsonIncludeEntrySchema;
1392
1435
  var init_include_schema = __esm({
@@ -1418,7 +1461,7 @@ var init_version = __esm({
1418
1461
  });
1419
1462
 
1420
1463
  // ../crouton-core/src/lib/resource/ResourceJson.schema.ts
1421
- var import_zod19, JsonColumnsMapSchema, ColumnsSchema, JsonDisplaySchema, normalizeColumns, ResourceJsonShape, refineByKind, ResourceJsonSchema;
1464
+ var import_zod19, JsonColumnsMapSchema, ColumnsSchema, JsonDisplaySchema, normalizeColumns, ResourceJsonShape, refineByKind, buildResourceJsonSchema, ResourceJsonSchema;
1422
1465
  var init_ResourceJson_schema = __esm({
1423
1466
  "../crouton-core/src/lib/resource/ResourceJson.schema.ts"() {
1424
1467
  "use strict";
@@ -1430,6 +1473,7 @@ var init_ResourceJson_schema = __esm({
1430
1473
  init_ResourceKind();
1431
1474
  init_Sidebar_schema();
1432
1475
  init_TableAction_schema();
1476
+ init_extensions();
1433
1477
  init_include_schema();
1434
1478
  init_version();
1435
1479
  init_Operations_schema();
@@ -1478,14 +1522,15 @@ var init_ResourceJson_schema = __esm({
1478
1522
  */
1479
1523
  kind: ResourceKindSchema,
1480
1524
  name: import_zod19.z.string(),
1481
- route: import_zod19.z.string(),
1525
+ route: import_zod19.z.string().optional(),
1526
+ id: import_zod19.z.string().optional(),
1482
1527
  /**
1483
1528
  * Prisma model name. Required when `kind` is `prisma` (enforced by the
1484
1529
  * refinement on `ResourceJsonSchema`), and must be absent when `kind` is
1485
1530
  * `custom` — there is no Prisma delegate to address.
1486
1531
  */
1487
1532
  model: import_zod19.z.string().optional(),
1488
- tag: import_zod19.z.string(),
1533
+ tag: import_zod19.z.string().optional().default("Crouton"),
1489
1534
  title: import_zod19.z.string().optional(),
1490
1535
  table: import_zod19.z.string().optional(),
1491
1536
  /**
@@ -1506,8 +1551,8 @@ var init_ResourceJson_schema = __esm({
1506
1551
  display: JsonDisplaySchema.default(JsonDisplaySchema.parse({})),
1507
1552
  /** Global security block — applies to every operation unless overridden per-operation. */
1508
1553
  security: SecuritySchema.optional(),
1509
- operations: JsonOperationsSchema,
1510
- columns: ColumnsSchema.default(ColumnsSchema.parse({})),
1554
+ operations: JsonOperationsSchema.optional(),
1555
+ columns: ColumnsSchema.optional().default(ColumnsSchema.parse({})),
1511
1556
  calculatedColumns: import_zod19.z.array(CalculatedColumnSchema).default([]),
1512
1557
  actions: import_zod19.z.array(JsonActionSchema).default([]),
1513
1558
  /** Global table-level actions (no record id). Shown as toolbar buttons. */
@@ -1526,7 +1571,12 @@ var init_ResourceJson_schema = __esm({
1526
1571
  * `{ "relation": "text_author", "include": ["author"] }` →
1527
1572
  * `include: { text_author: { include: { author: true } } }`
1528
1573
  */
1529
- include: import_zod19.z.array(JsonIncludeEntrySchema).default([])
1574
+ include: import_zod19.z.array(JsonIncludeEntrySchema).default([]),
1575
+ /**
1576
+ * Normalized extension blocks lifted from registered top-level keys.
1577
+ * Authors never write this key directly — the transform fills it.
1578
+ */
1579
+ extensions: import_zod19.z.record(import_zod19.z.string(), import_zod19.z.unknown()).optional()
1530
1580
  });
1531
1581
  refineByKind = /* @__PURE__ */ __name((obj, ctx) => {
1532
1582
  if (obj.kind === "custom") {
@@ -1592,16 +1642,59 @@ var init_ResourceJson_schema = __esm({
1592
1642
  });
1593
1643
  }
1594
1644
  }, "refineByKind");
1595
- ResourceJsonSchema = ResourceJsonShape.superRefine(refineByKind).transform((obj) => {
1596
- const title = obj.title ?? labelFromId(obj.name);
1597
- const schemaVersion = obj.schemaVersion ?? BASELINE_RESOURCE_VERSION;
1598
- return {
1599
- title,
1600
- ...obj,
1601
- schemaVersion,
1602
- columns: normalizeColumns(obj.columns)
1603
- };
1604
- });
1645
+ buildResourceJsonSchema = /* @__PURE__ */ __name(() => {
1646
+ const ext = getResourceExtensions();
1647
+ const extShape = Object.fromEntries([
1648
+ ...ext
1649
+ ].map(([name, schema]) => [
1650
+ name,
1651
+ schema.optional()
1652
+ ]));
1653
+ const shape = ext.size ? ResourceJsonShape.extend(extShape) : ResourceJsonShape;
1654
+ return import_zod19.z.preprocess((raw) => {
1655
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
1656
+ const obj = raw;
1657
+ if (obj["kind"] === void 0) {
1658
+ return {
1659
+ ...obj,
1660
+ kind: obj["model"] !== void 0 ? "prisma" : "custom"
1661
+ };
1662
+ }
1663
+ }
1664
+ return raw;
1665
+ }, shape.superRefine(refineByKind).transform((obj) => {
1666
+ const title = obj.title ?? labelFromId(obj.name);
1667
+ const schemaVersion = obj.schemaVersion ?? BASELINE_RESOURCE_VERSION;
1668
+ const defaultOps = obj.kind === "custom" ? JsonOperationsSchema.parse({
1669
+ findAll: false,
1670
+ findOne: false,
1671
+ create: false,
1672
+ update: false,
1673
+ patch: false,
1674
+ delete: false
1675
+ }) : JsonOperationsSchema.parse({});
1676
+ const raw = obj;
1677
+ const extensions = {};
1678
+ for (const name of ext.keys()) {
1679
+ if (raw[name] !== void 0) {
1680
+ extensions[name] = raw[name];
1681
+ delete raw[name];
1682
+ }
1683
+ }
1684
+ return {
1685
+ title,
1686
+ ...obj,
1687
+ route: obj.route ?? obj.id ?? obj.name ?? "",
1688
+ schemaVersion,
1689
+ columns: normalizeColumns(obj.columns),
1690
+ operations: obj.operations ?? defaultOps,
1691
+ ...Object.keys(extensions).length && {
1692
+ extensions
1693
+ }
1694
+ };
1695
+ }));
1696
+ }, "buildResourceJsonSchema");
1697
+ ResourceJsonSchema = buildResourceJsonSchema();
1605
1698
  }
1606
1699
  });
1607
1700
 
@@ -1703,6 +1796,7 @@ var init_resource = __esm({
1703
1796
  "use strict";
1704
1797
  init_cjs_shims();
1705
1798
  init_ResourceJson_schema();
1799
+ init_extensions();
1706
1800
  init_version();
1707
1801
  init_migrations();
1708
1802
  init_Sidebar_schema();
@@ -3078,6 +3172,7 @@ init_src();
3078
3172
  init_cjs_shims();
3079
3173
  var import_common24 = require("@nestjs/common");
3080
3174
  var import_core2 = require("@nestjs/core");
3175
+ init_src();
3081
3176
 
3082
3177
  // src/lib/crud/app-layout/index.ts
3083
3178
  init_cjs_shims();
@@ -3205,7 +3300,7 @@ var readResourceJson = /* @__PURE__ */ __name((jsonPath) => {
3205
3300
  error: `Invalid JSON in ${jsonPath}: ${err.message}`
3206
3301
  };
3207
3302
  }
3208
- const resource = ResourceJsonSchema.safeParse(fileContent);
3303
+ const resource = buildResourceJsonSchema().safeParse(fileContent);
3209
3304
  if (resource.error) {
3210
3305
  return {
3211
3306
  success: false,
@@ -3519,8 +3614,8 @@ var ResourceConfigRegistry = class {
3519
3614
  this.loader = loader;
3520
3615
  this.configs = initialConfigs;
3521
3616
  }
3522
- setTranslationRegistry(registry) {
3523
- this.translationRegistry = registry;
3617
+ setTranslationRegistry(registry2) {
3618
+ this.translationRegistry = registry2;
3524
3619
  }
3525
3620
  async getAll(language) {
3526
3621
  if (IS_DEV) {
@@ -3614,7 +3709,7 @@ function _ts_metadata2(metadataKey, metadataValue) {
3614
3709
  }
3615
3710
  }
3616
3711
  __name(_ts_metadata2, "_ts_metadata");
3617
- var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true, translationRegistry, i18nConfig) => {
3712
+ var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true, translationRegistry, i18nConfig, prefix) => {
3618
3713
  const layoutPayload = buildLayoutPayload(configs, sidebarGroups, title, autoSave, IS_DEV);
3619
3714
  let AppLayoutController = class AppLayoutController {
3620
3715
  static {
@@ -3692,7 +3787,7 @@ var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups =
3692
3787
  _ts_metadata2("design:returntype", void 0)
3693
3788
  ], AppLayoutController.prototype, "getTranslations", null);
3694
3789
  AppLayoutController = _ts_decorate2([
3695
- (0, import_common2.Controller)("_app"),
3790
+ (0, import_common2.Controller)(prefix ? `${prefix}/_app` : "_app"),
3696
3791
  (0, import_swagger.ApiTags)("App"),
3697
3792
  _ts_metadata2("design:type", Function),
3698
3793
  _ts_metadata2("design:paramtypes", [
@@ -3704,9 +3799,9 @@ var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups =
3704
3799
  ], AppLayoutController);
3705
3800
  return AppLayoutController;
3706
3801
  }, "createAppLayoutController");
3707
- var translateSidebarGroups = /* @__PURE__ */ __name((groups, language, registry) => {
3708
- if (!registry?.active) return groups;
3709
- const t = registry.translatorFor(language);
3802
+ var translateSidebarGroups = /* @__PURE__ */ __name((groups, language, registry2) => {
3803
+ if (!registry2?.active) return groups;
3804
+ const t = registry2.translatorFor(language);
3710
3805
  const translated = {};
3711
3806
  for (const [slug, cfg] of Object.entries(groups)) {
3712
3807
  const label = t(sidebarGroupKey(slug), cfg.label);
@@ -3717,9 +3812,9 @@ var translateSidebarGroups = /* @__PURE__ */ __name((groups, language, registry)
3717
3812
  }
3718
3813
  return translated;
3719
3814
  }, "translateSidebarGroups");
3720
- var translateTitle = /* @__PURE__ */ __name((title, language, registry) => {
3721
- if (!title || !registry?.active) return title;
3722
- const t = registry.translatorFor(language);
3815
+ var translateTitle = /* @__PURE__ */ __name((title, language, registry2) => {
3816
+ if (!title || !registry2?.active) return title;
3817
+ const t = registry2.translatorFor(language);
3723
3818
  return t("app.title", title);
3724
3819
  }, "translateTitle");
3725
3820
 
@@ -4203,8 +4298,8 @@ var LanguageInterceptor = class {
4203
4298
  __name(this, "LanguageInterceptor");
4204
4299
  }
4205
4300
  registry;
4206
- constructor(registry) {
4207
- this.registry = registry;
4301
+ constructor(registry2) {
4302
+ this.registry = registry2;
4208
4303
  }
4209
4304
  intercept(context, next) {
4210
4305
  const http = context.switchToHttp();
@@ -4407,7 +4502,8 @@ var validateCustomRepository = /* @__PURE__ */ __name((config, repository) => {
4407
4502
  const nested = !!config.parent;
4408
4503
  const methodFor = /* @__PURE__ */ __name((op) => nested ? PARENT_METHOD[op] : op, "methodFor");
4409
4504
  if (!repository) {
4410
- return `No repository.ts found. A custom resource implements its own data access; create ${config.name}/repository.ts with a default export implementing: ${enabled.map(methodFor).join(", ") || "no operations"}.`;
4505
+ if (enabled.length === 0) return void 0;
4506
+ return `No repository.ts found. A custom resource implements its own data access; create ${config.name}/repository.ts with a default export implementing: ${enabled.map(methodFor).join(", ")}.`;
4411
4507
  }
4412
4508
  const implemented = /* @__PURE__ */ __name((name) => typeof repository[name] === "function", "implemented");
4413
4509
  const missing = enabled.filter((op) => op === "patch" ? !implemented(methodFor("patch")) && !implemented(methodFor("update")) : !implemented(methodFor(op)));
@@ -5819,9 +5915,9 @@ var CroutonSecurityGuard = class {
5819
5915
  reflector;
5820
5916
  registry;
5821
5917
  moduleRef;
5822
- constructor(reflector, registry, moduleRef) {
5918
+ constructor(reflector, registry2, moduleRef) {
5823
5919
  this.reflector = reflector;
5824
- this.registry = registry;
5920
+ this.registry = registry2;
5825
5921
  this.moduleRef = moduleRef;
5826
5922
  }
5827
5923
  async canActivate(context) {
@@ -6049,14 +6145,14 @@ var import_swagger5 = require("@nestjs/swagger");
6049
6145
 
6050
6146
  // src/lib/crud/request.dto.ts
6051
6147
  init_cjs_shims();
6052
- var import_zod_nestjs = require("@anatine/zod-nestjs");
6148
+ var import_nestjs_zod = require("nestjs-zod");
6053
6149
  init_src();
6054
- var RequestDtoNoOffset = class extends (0, import_zod_nestjs.createZodDto)(RequestSchema) {
6150
+ var RequestDtoNoOffset = class extends (0, import_nestjs_zod.createZodDto)(RequestSchema) {
6055
6151
  static {
6056
6152
  __name(this, "RequestDtoNoOffset");
6057
6153
  }
6058
6154
  };
6059
- var RequestDto = class extends (0, import_zod_nestjs.createZodDto)(RequestSchemaWithOffset) {
6155
+ var RequestDto = class extends (0, import_nestjs_zod.createZodDto)(RequestSchemaWithOffset) {
6060
6156
  static {
6061
6157
  __name(this, "RequestDto");
6062
6158
  }
@@ -6224,7 +6320,7 @@ var registerFindAll = /* @__PURE__ */ __name((ctx, sub) => {
6224
6320
  def(cls, methodName, properties.findAll);
6225
6321
  const d = desc(cls, methodName);
6226
6322
  (0, import_common15.Get)(route)(cls.prototype, methodName, d);
6227
- (0, import_common15.Query)(new ZodValidationPipe(RequestDtoNoOffset.zodSchema))(cls.prototype, methodName, 0);
6323
+ (0, import_common15.Query)(new ZodValidationPipe(RequestDtoNoOffset.schema))(cls.prototype, methodName, 0);
6228
6324
  (0, import_common15.Query)("q")(cls.prototype, methodName, 1);
6229
6325
  if (!sub) (0, import_common15.Req)()(cls.prototype, methodName, 2);
6230
6326
  (0, import_swagger5.ApiOperation)({
@@ -6468,7 +6564,7 @@ var applyColumnPatch = /* @__PURE__ */ __name((raw, patch) => {
6468
6564
  }, "applyColumnPatch");
6469
6565
  var serializeResourceJson = /* @__PURE__ */ __name((config) => `${JSON.stringify(config, null, 2)}
6470
6566
  `, "serializeResourceJson");
6471
- var validateResourceJson = /* @__PURE__ */ __name((raw) => ResourceJsonSchema.safeParse(raw), "validateResourceJson");
6567
+ var validateResourceJson = /* @__PURE__ */ __name((raw) => buildResourceJsonSchema().safeParse(raw), "validateResourceJson");
6472
6568
  var writeRawResourceJson = /* @__PURE__ */ __name((jsonPath, raw) => {
6473
6569
  (0, import_node_fs6.writeFileSync)(jsonPath, serializeResourceJson(raw), "utf-8");
6474
6570
  }, "writeRawResourceJson");
@@ -6553,6 +6649,9 @@ var buildDefinitionPayload = /* @__PURE__ */ __name((config) => {
6553
6649
  ...isOperationEnabled(definition, "upsert") && upsertSchema ? {
6554
6650
  upsert: toJsonSchema(upsertSchema)
6555
6651
  } : {}
6652
+ },
6653
+ ...config.extensions && {
6654
+ extensions: config.extensions
6556
6655
  }
6557
6656
  };
6558
6657
  }, "buildDefinitionPayload");
@@ -6574,7 +6673,10 @@ var buildResourceJsonPayload = /* @__PURE__ */ __name((config, baseUrl) => {
6574
6673
  id: name,
6575
6674
  uri,
6576
6675
  operations,
6577
- schema
6676
+ schema,
6677
+ ...config.extensions && {
6678
+ extensions: config.extensions
6679
+ }
6578
6680
  };
6579
6681
  }, "buildResourceJsonPayload");
6580
6682
  var _resolveActions = /* @__PURE__ */ __name((baseUrl, actions) => {
@@ -6631,7 +6733,10 @@ var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
6631
6733
  display: config.display,
6632
6734
  schemas,
6633
6735
  actions: resolveActions(baseAction, config.actions),
6634
- tableActions: resolveActions(baseAction, config.tableActions)
6736
+ tableActions: resolveActions(baseAction, config.tableActions),
6737
+ ...config.extensions && {
6738
+ extensions: config.extensions
6739
+ }
6635
6740
  };
6636
6741
  }, "buildViewsPayload");
6637
6742
  var buildSubResourceViewsPayload = /* @__PURE__ */ __name((config, sub, baseUrl) => {
@@ -6691,16 +6796,21 @@ var buildEditableColumnsPayload = /* @__PURE__ */ __name((config) => {
6691
6796
  // src/lib/crud/operations/register-schema-endpoints.ts
6692
6797
  var import_node_path9 = require("path");
6693
6798
  var registerDefinitionEndpoint = /* @__PURE__ */ __name((ctx) => {
6694
- const { cls, config } = ctx;
6799
+ const { cls, config, schemaEnricher } = ctx;
6695
6800
  const { route, name } = config;
6696
6801
  const definitionPayload = buildDefinitionPayload(config);
6697
6802
  def(cls, "getDefinition", async function() {
6698
6803
  const language = getRequestLanguage();
6804
+ let payload = definitionPayload;
6699
6805
  if (IS_DEV || language) {
6700
6806
  const fresh = await this.configRegistry.getByRoute(route, language);
6701
- if (fresh) return buildDefinitionPayload(fresh);
6807
+ if (fresh) payload = buildDefinitionPayload(fresh);
6702
6808
  }
6703
- return definitionPayload;
6809
+ if (!schemaEnricher) return payload;
6810
+ return {
6811
+ ...payload,
6812
+ ...schemaEnricher(payload)
6813
+ };
6704
6814
  });
6705
6815
  const d = desc(cls, "getDefinition");
6706
6816
  (0, import_common18.Get)("definition")(cls.prototype, "getDefinition", d);
@@ -6717,16 +6827,21 @@ var registerDefinitionEndpoint = /* @__PURE__ */ __name((ctx) => {
6717
6827
  }
6718
6828
  }, "registerDefinitionEndpoint");
6719
6829
  var registerResourceJsonEndpoint = /* @__PURE__ */ __name((ctx) => {
6720
- const { cls, config, baseUrl } = ctx;
6830
+ const { cls, config, baseUrl, schemaEnricher } = ctx;
6721
6831
  const { route, name } = config;
6722
6832
  const resourceJsonPayload = buildResourceJsonPayload(config, baseUrl);
6723
6833
  def(cls, "getResourceJson", async function() {
6724
6834
  const language = getRequestLanguage();
6835
+ let payload = resourceJsonPayload;
6725
6836
  if (IS_DEV || language) {
6726
6837
  const fresh = await this.configRegistry.getByRoute(route, language);
6727
- if (fresh) return buildResourceJsonPayload(fresh, baseUrl);
6838
+ if (fresh) payload = buildResourceJsonPayload(fresh, baseUrl);
6728
6839
  }
6729
- return resourceJsonPayload;
6840
+ if (!schemaEnricher) return payload;
6841
+ return {
6842
+ ...payload,
6843
+ ...schemaEnricher(payload)
6844
+ };
6730
6845
  });
6731
6846
  const d = desc(cls, "getResourceJson");
6732
6847
  (0, import_common18.Get)("resource.json")(cls.prototype, "getResourceJson", d);
@@ -6866,7 +6981,7 @@ var import_common19 = require("@nestjs/common");
6866
6981
  var import_swagger9 = require("@nestjs/swagger");
6867
6982
  init_dev_mode();
6868
6983
  var defaultSchemas = /* @__PURE__ */ __name((ctx) => {
6869
- const { config, baseUrl } = ctx;
6984
+ const { config, baseUrl, schemaEnricher } = ctx;
6870
6985
  const { route, name } = config;
6871
6986
  const viewsPayload = buildViewsPayload(config, baseUrl);
6872
6987
  return {
@@ -6875,11 +6990,16 @@ var defaultSchemas = /* @__PURE__ */ __name((ctx) => {
6875
6990
  name,
6876
6991
  schemasFn: /* @__PURE__ */ __name(async function() {
6877
6992
  const language = getRequestLanguage();
6993
+ let payload = viewsPayload;
6878
6994
  if (IS_DEV || language) {
6879
6995
  const fresh = await this.configRegistry.getByRoute(route, language);
6880
- if (fresh) return buildViewsPayload(fresh, baseUrl) ?? viewsPayload;
6996
+ if (fresh) payload = buildViewsPayload(fresh, baseUrl) ?? viewsPayload;
6881
6997
  }
6882
- return viewsPayload;
6998
+ if (!payload || !schemaEnricher) return payload;
6999
+ return {
7000
+ ...payload,
7001
+ ...schemaEnricher(payload)
7002
+ };
6883
7003
  }, "schemasFn"),
6884
7004
  decorators: /* @__PURE__ */ __name(() => {
6885
7005
  }, "decorators")
@@ -7019,7 +7139,7 @@ var registerEndpoint = /* @__PURE__ */ __name((ctx, sub) => {
7019
7139
  }, "registerEndpoint");
7020
7140
 
7021
7141
  // src/lib/crud/crud-controller.factory.ts
7022
- function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEnabled = false) {
7142
+ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEnabled = false, prefix, schemaEnricher) {
7023
7143
  const { route, name, tag, idType = "string" } = config;
7024
7144
  const definition = resolveDefinition(config);
7025
7145
  const listSchema = schemaFor(definition, "findAll");
@@ -7037,9 +7157,9 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7037
7157
  if (isZodSchema(schema)) return (0, import_common21.Body)(new ZodValidationPipe(schema, options));
7038
7158
  return (0, import_common21.Body)();
7039
7159
  }, "bodyDecorator");
7040
- const resolveClient = /* @__PURE__ */ __name((registry, resource) => {
7160
+ const resolveClient = /* @__PURE__ */ __name((registry2, resource) => {
7041
7161
  try {
7042
- return registry.resolve(resource.database);
7162
+ return registry2.resolve(resource.database);
7043
7163
  } catch (e) {
7044
7164
  if (resource.kind === "custom") return void 0;
7045
7165
  throw e;
@@ -7051,9 +7171,9 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7051
7171
  }
7052
7172
  repo;
7053
7173
  configRegistry;
7054
- constructor(registry, configRegistry) {
7055
- const prisma = resolveClient(registry, config);
7056
- this.repo = createCrudRepository(prisma, config, registry, configRegistry);
7174
+ constructor(registry2, configRegistry) {
7175
+ const prisma = resolveClient(registry2, config);
7176
+ this.repo = createCrudRepository(prisma, config, registry2, configRegistry);
7057
7177
  this.configRegistry = configRegistry;
7058
7178
  }
7059
7179
  };
@@ -7080,10 +7200,12 @@ function createCrudController(config, baseUrl, moduleDefaultSecurity, securityEn
7080
7200
  bodyDecorator,
7081
7201
  baseUrl,
7082
7202
  moduleDefaultSecurity,
7083
- secure
7203
+ secure,
7204
+ schemaEnricher
7084
7205
  };
7085
7206
  registerEndpoints(ctx);
7086
- (0, import_common21.Controller)(resourceControllerPath(route, config.parent))(CrudControllerBase);
7207
+ const controllerPath = resourceControllerPath(route, config.parent);
7208
+ (0, import_common21.Controller)(prefix ? `${prefix}/${controllerPath}` : controllerPath)(CrudControllerBase);
7087
7209
  if (securityEnabled) {
7088
7210
  (0, import_common21.UseGuards)(CroutonSecurityGuard)(CrudControllerBase);
7089
7211
  }
@@ -9187,6 +9309,9 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
9187
9309
  },
9188
9310
  ...buildValueLabelColumns(enrichedColumns).length && {
9189
9311
  valueLabelColumns: buildValueLabelColumns(enrichedColumns)
9312
+ },
9313
+ ...json.extensions && {
9314
+ extensions: json.extensions
9190
9315
  }
9191
9316
  };
9192
9317
  }, "fromJson");
@@ -9438,8 +9563,8 @@ var getCroutonVersion = /* @__PURE__ */ __name(() => {
9438
9563
  }, "getCroutonVersion");
9439
9564
  var getVersion = /* @__PURE__ */ __name(() => process.env["APP_VERSION"] ?? "unknown", "getVersion");
9440
9565
  var getEnvironment = /* @__PURE__ */ __name(() => process.env["ENVIRONMENT"] ?? process.env["NODE_ENV"] ?? "unknown", "getEnvironment");
9441
- var checkDatabases = /* @__PURE__ */ __name(async (registry) => {
9442
- const entries = registry.entries();
9566
+ var checkDatabases = /* @__PURE__ */ __name(async (registry2) => {
9567
+ const entries = registry2.entries();
9443
9568
  const results = [];
9444
9569
  for (const { name, client } of entries) {
9445
9570
  const prismaClient = client;
@@ -9655,8 +9780,8 @@ var getI18nStatus = /* @__PURE__ */ __name((translationRegistry) => {
9655
9780
  bundles
9656
9781
  };
9657
9782
  }, "getI18nStatus");
9658
- var buildStatus = /* @__PURE__ */ __name(async (registry, loadedConfigs, enumRegistry, translationRegistry) => {
9659
- const databases = await checkDatabases(registry);
9783
+ var buildStatus = /* @__PURE__ */ __name(async (registry2, loadedConfigs, enumRegistry, translationRegistry) => {
9784
+ const databases = await checkDatabases(registry2);
9660
9785
  const resources = getResourceStatus(loadedConfigs);
9661
9786
  const summary = buildSummary(databases, resources);
9662
9787
  return {
@@ -9697,7 +9822,7 @@ function _ts_metadata9(metadataKey, metadataValue) {
9697
9822
  }
9698
9823
  }
9699
9824
  __name(_ts_metadata9, "_ts_metadata");
9700
- var createStatusController = /* @__PURE__ */ __name((enumRegistry, translationRegistry) => {
9825
+ var createStatusController = /* @__PURE__ */ __name((enumRegistry, translationRegistry, prefix) => {
9701
9826
  let StatusController = class StatusController {
9702
9827
  static {
9703
9828
  __name(this, "StatusController");
@@ -9727,7 +9852,7 @@ var createStatusController = /* @__PURE__ */ __name((enumRegistry, translationRe
9727
9852
  _ts_metadata9("design:returntype", Promise)
9728
9853
  ], StatusController.prototype, "getStatus", null);
9729
9854
  StatusController = _ts_decorate9([
9730
- (0, import_common23.Controller)("crouton"),
9855
+ (0, import_common23.Controller)(prefix ? `${prefix}/crouton` : "crouton"),
9731
9856
  (0, import_swagger13.ApiTags)("Status"),
9732
9857
  _ts_metadata9("design:type", Function),
9733
9858
  _ts_metadata9("design:paramtypes", [
@@ -9771,7 +9896,7 @@ var CroutonApiModule = class _CroutonApiModule {
9771
9896
  }
9772
9897
  }
9773
9898
  static forResources(configs, dataSources, loader, appConfig, config) {
9774
- const { baseUrl, security } = appConfig;
9899
+ const { baseUrl, prefix, security } = appConfig;
9775
9900
  const dataSourceRegistry = new DataSourceRegistry(dataSources);
9776
9901
  const guardRegistry = new SecurityGuardRegistry(security?.guards ?? {});
9777
9902
  const moduleDefaultSecurity = security?.default;
@@ -9840,14 +9965,14 @@ var CroutonApiModule = class _CroutonApiModule {
9840
9965
  configRegistry.setTranslationRegistry(translationRegistry);
9841
9966
  }
9842
9967
  const controllers = [
9843
- ...validConfigs.map((c) => createCrudController(c, baseUrl, moduleDefaultSecurity, !!security)),
9844
- createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true, translationRegistry, config.i18n),
9968
+ ...validConfigs.map((c) => createCrudController(c, baseUrl, moduleDefaultSecurity, !!security, prefix, appConfig.schemaEnricher)),
9969
+ createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true, translationRegistry, config.i18n, prefix),
9845
9970
  // Only registered (and thus only visible in Swagger/routing) when the
9846
9971
  // visual resource builder is enabled — see dev-resources.controller.ts.
9847
9972
  ...IS_DEV ? [
9848
9973
  DevResourcesController
9849
9974
  ] : [],
9850
- createStatusController(enumRegistry, translationRegistry)
9975
+ createStatusController(enumRegistry, translationRegistry, prefix)
9851
9976
  ];
9852
9977
  return {
9853
9978
  module: _CroutonApiModule,
@@ -9889,6 +10014,7 @@ var CroutonApiModule = class _CroutonApiModule {
9889
10014
  };
9890
10015
  }
9891
10016
  static async forResourceDir(dirPath, dataSourcesPath, appConfig) {
10017
+ if (appConfig.extensions) registerResourceExtensions(appConfig.extensions);
9892
10018
  const config = await loadConfig();
9893
10019
  const loader = new FileSystemResourceConfigLoader(dirPath, appConfig.baseUrl, config.enumsFile);
9894
10020
  const configs = await loadResourceConfigsFromDir(dirPath, appConfig.baseUrl, config.enumsFile);