@ghentcdh/crouton-api 0.0.1-alpha.34 → 0.0.1-alpha.36
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 +354 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +354 -41
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ var getImportMetaUrl = /* @__PURE__ */ __name(() => typeof document === "undefin
|
|
|
52
52
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
53
53
|
|
|
54
54
|
// src/lib/crouton-api.module.ts
|
|
55
|
-
var
|
|
55
|
+
var import_common13 = require("@nestjs/common");
|
|
56
56
|
var import_core = require("@nestjs/core");
|
|
57
57
|
|
|
58
58
|
// src/lib/crud/app-layout/app-layout.types.ts
|
|
@@ -595,6 +595,7 @@ var DetailConfigSchema = import_zod10.z.object({
|
|
|
595
595
|
controls: import_zod10.z.array(DetailControlSchema)
|
|
596
596
|
});
|
|
597
597
|
var RelationFieldInputOptionsSchema = import_zod10.z.object({
|
|
598
|
+
colspan: import_zod10.z.number().optional().default(12),
|
|
598
599
|
/** Field to sort related records by, e.g. `"title"` or `"author.name"`. */
|
|
599
600
|
sort: import_zod10.z.string().optional(),
|
|
600
601
|
/** Sort direction. Defaults to `"asc"` when omitted. */
|
|
@@ -639,6 +640,28 @@ var FieldInputSchema = import_zod10.z.object({
|
|
|
639
640
|
/** Nested array detail layout (renders via `detailFixed`). */
|
|
640
641
|
detail: DetailConfigSchema.optional()
|
|
641
642
|
});
|
|
643
|
+
var FieldVariantSchema = FieldInputSchema;
|
|
644
|
+
var stripNull = /* @__PURE__ */ __name((obj) => {
|
|
645
|
+
const out = {};
|
|
646
|
+
for (const [k, v] of Object.entries(obj)) if (v !== null) out[k] = v;
|
|
647
|
+
return out;
|
|
648
|
+
}, "stripNull");
|
|
649
|
+
var mergeFieldVariant = /* @__PURE__ */ __name((base, override) => {
|
|
650
|
+
if (!base) return override;
|
|
651
|
+
if (!override) return base;
|
|
652
|
+
const merged = stripNull({
|
|
653
|
+
...base,
|
|
654
|
+
...override
|
|
655
|
+
});
|
|
656
|
+
if (base.options || override.options) {
|
|
657
|
+
const mergedOptions = stripNull({
|
|
658
|
+
...base.options,
|
|
659
|
+
...override.options
|
|
660
|
+
});
|
|
661
|
+
merged.options = mergedOptions;
|
|
662
|
+
}
|
|
663
|
+
return merged;
|
|
664
|
+
}, "mergeFieldVariant");
|
|
642
665
|
|
|
643
666
|
// ../crouton-core/src/lib/schema/label.helper.ts
|
|
644
667
|
var labelFromId = /* @__PURE__ */ __name((id) => {
|
|
@@ -723,6 +746,18 @@ var JsonColumnSchema = import_zod12.z.object({
|
|
|
723
746
|
columnType: import_zod12.z.string().default("string"),
|
|
724
747
|
fieldInput: FieldInputSchema.optional(),
|
|
725
748
|
/**
|
|
749
|
+
* Optional per-context override for the read-only VIEW ui. Same shape as
|
|
750
|
+
* `fieldInput`; every key optional. Falls back to `fieldInput` (deep-merged
|
|
751
|
+
* one level into `options`). Resolved at read time by the transformer.
|
|
752
|
+
*/
|
|
753
|
+
fieldView: FieldVariantSchema.optional(),
|
|
754
|
+
/**
|
|
755
|
+
* Optional per-context override for the TABLE ui. Same shape as `fieldInput`;
|
|
756
|
+
* every key optional. Falls back to `fieldView`, then `fieldInput`.
|
|
757
|
+
* Resolved at read time by the transformer.
|
|
758
|
+
*/
|
|
759
|
+
fieldTable: FieldVariantSchema.optional(),
|
|
760
|
+
/**
|
|
726
761
|
* Path to another `resource.json` whose columns are expanded as nested sub-columns
|
|
727
762
|
* under this column's object key.
|
|
728
763
|
*
|
|
@@ -904,6 +939,10 @@ var ResourceJsonSchema = ResourceJsonShape.transform((obj) => {
|
|
|
904
939
|
};
|
|
905
940
|
});
|
|
906
941
|
|
|
942
|
+
// ../crouton-core/src/lib/resource/fieldVariants.ts
|
|
943
|
+
var resolveViewField = /* @__PURE__ */ __name((c) => mergeFieldVariant(c.fieldInput, c.fieldView), "resolveViewField");
|
|
944
|
+
var resolveTableField = /* @__PURE__ */ __name((c) => mergeFieldVariant(resolveViewField(c), c.fieldTable), "resolveTableField");
|
|
945
|
+
|
|
907
946
|
// ../crouton-core/src/lib/config/CroutonConfig.schema.ts
|
|
908
947
|
var import_zod17 = require("zod");
|
|
909
948
|
var RulesetSchema = import_zod17.z.object({
|
|
@@ -1661,33 +1700,40 @@ var ReadRepository = class {
|
|
|
1661
1700
|
async findAll(params) {
|
|
1662
1701
|
const subResources = this.config.subResources ?? [];
|
|
1663
1702
|
const projection = this.projection("findAll");
|
|
1664
|
-
|
|
1703
|
+
const query = {
|
|
1665
1704
|
where: this.buildWhere(params.filter),
|
|
1666
1705
|
take: params.pageSize,
|
|
1667
1706
|
skip: params.offset ?? (params.page - 1) * params.pageSize,
|
|
1668
1707
|
orderBy: this.safeSort(sanitizeValueLabelSort(params.sort, this.config.valueLabelColumns), params.sortDir)
|
|
1669
1708
|
};
|
|
1670
1709
|
const countableSubResources = subResources.filter((s) => s.relationType !== "manyToOne");
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1710
|
+
const manyToOneIncludes = subResources.filter((s) => s.relationType === "manyToOne").map((s) => s.relation);
|
|
1711
|
+
const flatIncludes = manyToOneIncludes.length ? Object.fromEntries(manyToOneIncludes.map((r) => [
|
|
1712
|
+
r,
|
|
1713
|
+
true
|
|
1714
|
+
])) : void 0;
|
|
1715
|
+
const configInclude = buildIncludeClause(this.config.include);
|
|
1716
|
+
const mergedInclude = flatIncludes || configInclude ? {
|
|
1717
|
+
...flatIncludes,
|
|
1718
|
+
...configInclude
|
|
1719
|
+
} : void 0;
|
|
1720
|
+
const countClause = countableSubResources.length ? {
|
|
1721
|
+
select: Object.fromEntries(countableSubResources.map((s) => [
|
|
1722
|
+
s.relation,
|
|
1723
|
+
true
|
|
1724
|
+
]))
|
|
1725
|
+
} : void 0;
|
|
1726
|
+
if (projection.select) {
|
|
1727
|
+
query.select = {
|
|
1728
|
+
...projection.select,
|
|
1729
|
+
...countClause && {
|
|
1686
1730
|
_count: countClause
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1731
|
+
},
|
|
1732
|
+
...mergedInclude
|
|
1733
|
+
};
|
|
1689
1734
|
} else {
|
|
1690
|
-
|
|
1735
|
+
if (countClause) query._count = countClause;
|
|
1736
|
+
if (mergedInclude) query.include = mergedInclude;
|
|
1691
1737
|
}
|
|
1692
1738
|
const rows = await this.prismaModel.findMany(query);
|
|
1693
1739
|
const mapped = countableSubResources.length ? rows.map((row) => {
|
|
@@ -2240,6 +2286,14 @@ var DataSourceRegistry = class {
|
|
|
2240
2286
|
resolve(database) {
|
|
2241
2287
|
return database ? this.get(database) : this.getDefault();
|
|
2242
2288
|
}
|
|
2289
|
+
entries() {
|
|
2290
|
+
return [
|
|
2291
|
+
...this.clients.entries()
|
|
2292
|
+
].map(([name, client]) => ({
|
|
2293
|
+
name,
|
|
2294
|
+
client
|
|
2295
|
+
}));
|
|
2296
|
+
}
|
|
2243
2297
|
async onModuleDestroy() {
|
|
2244
2298
|
for (const client of this.clients.values()) {
|
|
2245
2299
|
if (typeof client.$disconnect === "function") {
|
|
@@ -2256,6 +2310,26 @@ DataSourceRegistry = _ts_decorate4([
|
|
|
2256
2310
|
])
|
|
2257
2311
|
], DataSourceRegistry);
|
|
2258
2312
|
|
|
2313
|
+
// src/lib/crud/resource/resource-load-errors.registry.ts
|
|
2314
|
+
var ResourceLoadErrorsRegistry = class ResourceLoadErrorsRegistry2 {
|
|
2315
|
+
static {
|
|
2316
|
+
__name(this, "ResourceLoadErrorsRegistry");
|
|
2317
|
+
}
|
|
2318
|
+
errors = [];
|
|
2319
|
+
record(e) {
|
|
2320
|
+
this.errors.push(e);
|
|
2321
|
+
}
|
|
2322
|
+
getAll() {
|
|
2323
|
+
return [
|
|
2324
|
+
...this.errors
|
|
2325
|
+
];
|
|
2326
|
+
}
|
|
2327
|
+
clear() {
|
|
2328
|
+
this.errors = [];
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
var resourceLoadErrorsRegistry = new ResourceLoadErrorsRegistry();
|
|
2332
|
+
|
|
2259
2333
|
// src/lib/crud/data-source/data-source.loader.ts
|
|
2260
2334
|
var import_node_fs2 = require("fs");
|
|
2261
2335
|
var import_node_path4 = require("path");
|
|
@@ -2270,11 +2344,25 @@ var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
|
|
|
2270
2344
|
const basePath = (0, import_node_path4.join)(dirPath, dir);
|
|
2271
2345
|
const jsonFile = (0, import_node_path4.join)(basePath, "data-source.json");
|
|
2272
2346
|
if (!(0, import_node_fs2.existsSync)(jsonFile)) continue;
|
|
2273
|
-
|
|
2347
|
+
let _config;
|
|
2348
|
+
try {
|
|
2349
|
+
_config = JSON.parse((0, import_node_fs2.readFileSync)(jsonFile, "utf-8"));
|
|
2350
|
+
} catch (err) {
|
|
2351
|
+
resourceLoadErrorsRegistry.record({
|
|
2352
|
+
name: dir,
|
|
2353
|
+
path: jsonFile,
|
|
2354
|
+
error: `Invalid JSON in ${jsonFile}: ${err.message}`
|
|
2355
|
+
});
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2274
2358
|
const datasource = DataSourceSchema.safeParse(_config);
|
|
2275
2359
|
if (!datasource.success) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2360
|
+
resourceLoadErrorsRegistry.record({
|
|
2361
|
+
name: dir,
|
|
2362
|
+
path: jsonFile,
|
|
2363
|
+
error: `Invalid datasource schema: ${jsonFile}: ${datasource.error.message}`
|
|
2364
|
+
});
|
|
2365
|
+
continue;
|
|
2278
2366
|
}
|
|
2279
2367
|
const config = datasource.data;
|
|
2280
2368
|
const indexFile = findModule2(basePath, "index");
|
|
@@ -3290,6 +3378,17 @@ var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
|
|
|
3290
3378
|
col,
|
|
3291
3379
|
i
|
|
3292
3380
|
})).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
|
|
3381
|
+
var columnForContext = /* @__PURE__ */ __name((col, ctx) => {
|
|
3382
|
+
if (ctx === "view") return {
|
|
3383
|
+
...col,
|
|
3384
|
+
fieldInput: col.fieldView ?? col.fieldInput
|
|
3385
|
+
};
|
|
3386
|
+
if (ctx === "table") return {
|
|
3387
|
+
...col,
|
|
3388
|
+
fieldInput: col.fieldTable ?? col.fieldInput
|
|
3389
|
+
};
|
|
3390
|
+
return col;
|
|
3391
|
+
}, "columnForContext");
|
|
3293
3392
|
var toViewColumn = /* @__PURE__ */ __name((col) => ({
|
|
3294
3393
|
id: col.id,
|
|
3295
3394
|
...col.label && {
|
|
@@ -3386,7 +3485,8 @@ var pickByColumns = /* @__PURE__ */ __name((schema, columns, filter) => {
|
|
|
3386
3485
|
if (!schema) return void 0;
|
|
3387
3486
|
if (!columns?.length) return schema;
|
|
3388
3487
|
const baseFilter = /* @__PURE__ */ __name((c) => !isRelation(c) && (filter ? filter(c) : true), "baseFilter");
|
|
3389
|
-
const
|
|
3488
|
+
const schemaKeys = new Set(Object.keys(schema.shape));
|
|
3489
|
+
const filtered = columns.filter((c) => baseFilter(c) && schemaKeys.has(c.id));
|
|
3390
3490
|
if (!filtered.length) return void 0;
|
|
3391
3491
|
const mask = Object.fromEntries(filtered.map((c) => [
|
|
3392
3492
|
c.id,
|
|
@@ -3731,7 +3831,9 @@ var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema,
|
|
|
3731
3831
|
const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
|
|
3732
3832
|
if (!visibleCols.length) return void 0;
|
|
3733
3833
|
const schemaCols = (schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols).filter((c) => !isRelation(c));
|
|
3734
|
-
const
|
|
3834
|
+
const schemaKeys = new Set(Object.keys(schema.shape));
|
|
3835
|
+
const schemaIds = schemaCols.map((c) => c.id).filter((id) => schemaKeys.has(id));
|
|
3836
|
+
if (!schemaIds.length) return void 0;
|
|
3735
3837
|
const mask = Object.fromEntries(schemaIds.map((id) => [
|
|
3736
3838
|
id,
|
|
3737
3839
|
true
|
|
@@ -3766,7 +3868,7 @@ var emptyTableView = /* @__PURE__ */ __name(() => ({
|
|
|
3766
3868
|
}), "emptyTableView");
|
|
3767
3869
|
var buildViews = /* @__PURE__ */ __name((schema, columns) => {
|
|
3768
3870
|
const views = {};
|
|
3769
|
-
const table = buildView(schema, columns, (c) => !c.hiddenInTable, buildTableUiSchema);
|
|
3871
|
+
const table = buildView(schema, columns?.map((c) => columnForContext(c, "table")), (c) => !c.hiddenInTable, buildTableUiSchema);
|
|
3770
3872
|
if (table) {
|
|
3771
3873
|
table.defaultSort = resolveDefaultSort(table.columns, columns);
|
|
3772
3874
|
views.table = table;
|
|
@@ -3780,7 +3882,7 @@ var buildViews = /* @__PURE__ */ __name((schema, columns) => {
|
|
|
3780
3882
|
patchFilterProperties(filter.json_schema, columns?.filter((c) => !!c.filterable));
|
|
3781
3883
|
views.filter = filter;
|
|
3782
3884
|
}
|
|
3783
|
-
const view = buildView(schema, columns, (c) => !c.hiddenInView, buildFormUiSchema, true);
|
|
3885
|
+
const view = buildView(schema, columns?.map((c) => columnForContext(c, "view")), (c) => !c.hiddenInView, buildFormUiSchema, true);
|
|
3784
3886
|
if (view) views.view = view;
|
|
3785
3887
|
return Object.keys(views).length ? views : void 0;
|
|
3786
3888
|
}, "buildViews");
|
|
@@ -3861,7 +3963,7 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
|
|
|
3861
3963
|
};
|
|
3862
3964
|
}, "makeView");
|
|
3863
3965
|
const views = {};
|
|
3864
|
-
const tableCols = sortByPosition(columns.filter((c) => !c.hiddenInTable));
|
|
3966
|
+
const tableCols = sortByPosition(columns.filter((c) => !c.hiddenInTable).map((c) => columnForContext(c, "table")));
|
|
3865
3967
|
const table = makeView(tableCols, buildTableUiSchema);
|
|
3866
3968
|
if (table) {
|
|
3867
3969
|
table.defaultSort = resolveDefaultSort(tableCols, columns);
|
|
@@ -3872,7 +3974,7 @@ var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
|
|
|
3872
3974
|
const formCols = sortByPosition(columns.filter((c) => !c.hiddenInForm));
|
|
3873
3975
|
const form = makeView(formCols, buildFormUiSchema);
|
|
3874
3976
|
if (form) views.form = form;
|
|
3875
|
-
const viewCols = sortByPosition(columns.filter((c) => !c.hiddenInView));
|
|
3977
|
+
const viewCols = sortByPosition(columns.filter((c) => !c.hiddenInView).map((c) => columnForContext(c, "view")));
|
|
3876
3978
|
const viewView = makeView(viewCols, buildFormUiSchema);
|
|
3877
3979
|
if (viewView) views.view = viewView;
|
|
3878
3980
|
return Object.keys(views).length ? views : void 0;
|
|
@@ -3977,15 +4079,28 @@ var import_node_fs4 = require("fs");
|
|
|
3977
4079
|
var import_node_path6 = require("path");
|
|
3978
4080
|
var readResourceJson = /* @__PURE__ */ __name((jsonPath) => {
|
|
3979
4081
|
if (!(0, import_node_fs4.existsSync)(jsonPath)) return void 0;
|
|
3980
|
-
|
|
4082
|
+
let fileContent;
|
|
4083
|
+
try {
|
|
4084
|
+
fileContent = JSON.parse((0, import_node_fs4.readFileSync)(jsonPath, "utf-8"));
|
|
4085
|
+
} catch (err) {
|
|
4086
|
+
return {
|
|
4087
|
+
success: false,
|
|
4088
|
+
error: `Invalid JSON in ${jsonPath}: ${err.message}`
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
3981
4091
|
const resource = ResourceJsonSchema.safeParse(fileContent);
|
|
3982
4092
|
if (resource.error) {
|
|
3983
|
-
|
|
3984
|
-
|
|
4093
|
+
return {
|
|
4094
|
+
success: false,
|
|
4095
|
+
error: `Resource cannot be parsed ${jsonPath}: ${resource.error.message}`
|
|
4096
|
+
};
|
|
3985
4097
|
}
|
|
3986
4098
|
return {
|
|
3987
|
-
|
|
3988
|
-
|
|
4099
|
+
success: true,
|
|
4100
|
+
data: {
|
|
4101
|
+
json: resource.data,
|
|
4102
|
+
dir: (0, import_node_path6.dirname)(jsonPath)
|
|
4103
|
+
}
|
|
3989
4104
|
};
|
|
3990
4105
|
}, "readResourceJson");
|
|
3991
4106
|
|
|
@@ -4023,7 +4138,7 @@ var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResou
|
|
|
4023
4138
|
uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
|
|
4024
4139
|
resourceUri: `${base}/${sub.childRoute}`,
|
|
4025
4140
|
...sub.views && {
|
|
4026
|
-
resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
|
|
4141
|
+
resource: sub.relationType === "manyToOne" ? `${base}/${sub.childRoute}/schemas` : `${base}/${parentRoute}/${sub.childRoute}/schemas`
|
|
4027
4142
|
}
|
|
4028
4143
|
}
|
|
4029
4144
|
}
|
|
@@ -4170,6 +4285,19 @@ var applyRelationFormatDefault = /* @__PURE__ */ __name((cols) => cols?.map((col
|
|
|
4170
4285
|
}
|
|
4171
4286
|
return col;
|
|
4172
4287
|
}), "applyRelationFormatDefault");
|
|
4288
|
+
var resolveColumnFieldVariants = /* @__PURE__ */ __name((cols) => cols?.map((col) => {
|
|
4289
|
+
const fieldView = resolveViewField(col);
|
|
4290
|
+
const fieldTable = resolveTableField(col);
|
|
4291
|
+
return {
|
|
4292
|
+
...col,
|
|
4293
|
+
...fieldView && {
|
|
4294
|
+
fieldView
|
|
4295
|
+
},
|
|
4296
|
+
...fieldTable && {
|
|
4297
|
+
fieldTable
|
|
4298
|
+
}
|
|
4299
|
+
};
|
|
4300
|
+
}), "resolveColumnFieldVariants");
|
|
4173
4301
|
|
|
4174
4302
|
// src/lib/crud/adapter/sub-resource.builder.ts
|
|
4175
4303
|
var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentModel, parentDir, enums = {}, baseUrl) => {
|
|
@@ -4251,7 +4379,7 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
|
|
|
4251
4379
|
const columns = enrichRelationTypes(applyRelationFormatDefault(rawColumns) ?? rawColumns, schema);
|
|
4252
4380
|
injectEnumValues(columns, enums);
|
|
4253
4381
|
const subResources = buildSubResources(columns, json.route, json.model, dirPath, enums, baseUrl);
|
|
4254
|
-
const enrichedColumns = enrichResourceRefColumns(enrichActionColumns(columns, json.route, subResources, baseUrl), dirPath, baseUrl) ?? columns;
|
|
4382
|
+
const enrichedColumns = resolveColumnFieldVariants(enrichResourceRefColumns(enrichActionColumns(columns, json.route, subResources, baseUrl), dirPath, baseUrl) ?? columns);
|
|
4255
4383
|
const calculatedColumns = json.calculatedColumns ?? [];
|
|
4256
4384
|
const picked = pickByColumns(schema, enrichedColumns);
|
|
4257
4385
|
const createSchema = pickByColumns(schema, enrichedColumns, (c) => !c.idField && c.createable !== false);
|
|
@@ -4287,6 +4415,9 @@ var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, ac
|
|
|
4287
4415
|
...upsertOp(json.operations.upsert, createSchema) && {
|
|
4288
4416
|
upsert: upsertOp(json.operations.upsert, createSchema)
|
|
4289
4417
|
},
|
|
4418
|
+
...opWithSchema(json.operations.patch, createSchema) && {
|
|
4419
|
+
patch: opWithSchema(json.operations.patch, createSchema)
|
|
4420
|
+
},
|
|
4290
4421
|
...json.operations.delete !== false && {
|
|
4291
4422
|
delete: true
|
|
4292
4423
|
}
|
|
@@ -4393,6 +4524,7 @@ var import_node_fs6 = require("fs");
|
|
|
4393
4524
|
var import_node_path9 = require("path");
|
|
4394
4525
|
var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl, enumsFile) => {
|
|
4395
4526
|
if (!(0, import_node_fs6.existsSync)(dirPath)) return [];
|
|
4527
|
+
resourceLoadErrorsRegistry.clear();
|
|
4396
4528
|
const enums = loadEnumRegistry(dirPath, enumsFile);
|
|
4397
4529
|
const entries = (0, import_node_fs6.readdirSync)(dirPath, {
|
|
4398
4530
|
withFileTypes: true
|
|
@@ -4406,7 +4538,16 @@ var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl,
|
|
|
4406
4538
|
const hooks = await loadResourceHooks(basePath);
|
|
4407
4539
|
const jsonFile = (0, import_node_path9.join)(basePath, "resource.json");
|
|
4408
4540
|
if ((0, import_node_fs6.existsSync)(jsonFile)) {
|
|
4409
|
-
const
|
|
4541
|
+
const result = readResourceJson(jsonFile);
|
|
4542
|
+
if (!result || !result.success) {
|
|
4543
|
+
resourceLoadErrorsRegistry.record({
|
|
4544
|
+
name: dir,
|
|
4545
|
+
path: jsonFile,
|
|
4546
|
+
error: result?.error ?? `Failed to read ${jsonFile}`
|
|
4547
|
+
});
|
|
4548
|
+
continue;
|
|
4549
|
+
}
|
|
4550
|
+
const json = result.data.json;
|
|
4410
4551
|
const actions = await loadActions(json.actions ?? [], basePath, "row");
|
|
4411
4552
|
const tableActions = await loadActions(json.tableActions ?? [], basePath, "table");
|
|
4412
4553
|
const config = fromJson(json, schema, hooks, basePath, baseUrl, actions, tableActions, enums);
|
|
@@ -4453,7 +4594,97 @@ var FileSystemResourceConfigLoader = class extends ResourceConfigLoader2 {
|
|
|
4453
4594
|
}
|
|
4454
4595
|
};
|
|
4455
4596
|
|
|
4456
|
-
// src/lib/
|
|
4597
|
+
// src/lib/crud/status/status.service.ts
|
|
4598
|
+
var import_node_fs7 = require("fs");
|
|
4599
|
+
var import_node_path10 = require("path");
|
|
4600
|
+
var import_node_url = require("url");
|
|
4601
|
+
var DB_CHECK_TIMEOUT_MS = 3e3;
|
|
4602
|
+
var CONNECTION_STRING_PATTERN = /(?:postgresql|postgres|mysql|mongodb|sqlserver|sqlite):\/\/[^\s"')]+/gi;
|
|
4603
|
+
var stripConnectionStrings = /* @__PURE__ */ __name((message) => message.replace(CONNECTION_STRING_PATTERN, "[REDACTED]"), "stripConnectionStrings");
|
|
4604
|
+
var getCroutonVersion = /* @__PURE__ */ __name(() => {
|
|
4605
|
+
try {
|
|
4606
|
+
const startDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path10.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
4607
|
+
let dir = startDir;
|
|
4608
|
+
while (dir !== (0, import_node_path10.dirname)(dir)) {
|
|
4609
|
+
const pkgPath = (0, import_node_path10.join)(dir, "package.json");
|
|
4610
|
+
if ((0, import_node_fs7.existsSync)(pkgPath)) {
|
|
4611
|
+
const pkg = JSON.parse((0, import_node_fs7.readFileSync)(pkgPath, "utf-8"));
|
|
4612
|
+
if (pkg.name === "@ghentcdh/crouton-api") return pkg.version;
|
|
4613
|
+
}
|
|
4614
|
+
dir = (0, import_node_path10.dirname)(dir);
|
|
4615
|
+
}
|
|
4616
|
+
} catch {
|
|
4617
|
+
}
|
|
4618
|
+
return "unknown";
|
|
4619
|
+
}, "getCroutonVersion");
|
|
4620
|
+
var getVersion = /* @__PURE__ */ __name(() => process.env["APP_VERSION"] ?? "unknown", "getVersion");
|
|
4621
|
+
var getEnvironment = /* @__PURE__ */ __name(() => process.env["ENVIRONMENT"] ?? process.env["NODE_ENV"] ?? "unknown", "getEnvironment");
|
|
4622
|
+
var checkDatabases = /* @__PURE__ */ __name(async (registry) => {
|
|
4623
|
+
const entries = registry.entries();
|
|
4624
|
+
const results = [];
|
|
4625
|
+
for (const { name, client } of entries) {
|
|
4626
|
+
try {
|
|
4627
|
+
await Promise.race([
|
|
4628
|
+
client.$queryRaw`SELECT 1`,
|
|
4629
|
+
new Promise((_resolve, reject) => setTimeout(() => reject(new Error("Database health check timed out")), DB_CHECK_TIMEOUT_MS))
|
|
4630
|
+
]);
|
|
4631
|
+
results.push({
|
|
4632
|
+
name,
|
|
4633
|
+
connected: true
|
|
4634
|
+
});
|
|
4635
|
+
} catch (err) {
|
|
4636
|
+
results.push({
|
|
4637
|
+
name,
|
|
4638
|
+
connected: false,
|
|
4639
|
+
error: stripConnectionStrings(err.message)
|
|
4640
|
+
});
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
return results;
|
|
4644
|
+
}, "checkDatabases");
|
|
4645
|
+
var getResourceStatus = /* @__PURE__ */ __name((loadedConfigs) => {
|
|
4646
|
+
const valid = loadedConfigs.map((c) => ({
|
|
4647
|
+
name: c.name,
|
|
4648
|
+
path: c.route,
|
|
4649
|
+
valid: true
|
|
4650
|
+
}));
|
|
4651
|
+
const failed = resourceLoadErrorsRegistry.getAll().map((e) => ({
|
|
4652
|
+
name: e.name,
|
|
4653
|
+
path: e.path,
|
|
4654
|
+
valid: false,
|
|
4655
|
+
error: e.error
|
|
4656
|
+
}));
|
|
4657
|
+
return [
|
|
4658
|
+
...valid,
|
|
4659
|
+
...failed
|
|
4660
|
+
];
|
|
4661
|
+
}, "getResourceStatus");
|
|
4662
|
+
var buildSummary = /* @__PURE__ */ __name((databases, resources) => {
|
|
4663
|
+
const databaseErrors = databases.filter((d) => !d.connected).length;
|
|
4664
|
+
const resourceErrors = resources.filter((r) => !r.valid).length;
|
|
4665
|
+
return {
|
|
4666
|
+
ok: databaseErrors === 0 && resourceErrors === 0,
|
|
4667
|
+
databaseErrors,
|
|
4668
|
+
resourceErrors
|
|
4669
|
+
};
|
|
4670
|
+
}, "buildSummary");
|
|
4671
|
+
var buildStatus = /* @__PURE__ */ __name(async (registry, loadedConfigs) => {
|
|
4672
|
+
const databases = await checkDatabases(registry);
|
|
4673
|
+
const resources = getResourceStatus(loadedConfigs);
|
|
4674
|
+
const summary = buildSummary(databases, resources);
|
|
4675
|
+
return {
|
|
4676
|
+
version: getVersion(),
|
|
4677
|
+
croutonVersion: getCroutonVersion(),
|
|
4678
|
+
environment: getEnvironment(),
|
|
4679
|
+
summary,
|
|
4680
|
+
databases,
|
|
4681
|
+
resources
|
|
4682
|
+
};
|
|
4683
|
+
}, "buildStatus");
|
|
4684
|
+
|
|
4685
|
+
// src/lib/crud/status/status.controller.ts
|
|
4686
|
+
var import_common12 = require("@nestjs/common");
|
|
4687
|
+
var import_swagger7 = require("@nestjs/swagger");
|
|
4457
4688
|
function _ts_decorate5(decorators, target, key, desc2) {
|
|
4458
4689
|
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
4459
4690
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
@@ -4461,6 +4692,63 @@ function _ts_decorate5(decorators, target, key, desc2) {
|
|
|
4461
4692
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
4462
4693
|
}
|
|
4463
4694
|
__name(_ts_decorate5, "_ts_decorate");
|
|
4695
|
+
function _ts_metadata4(k, v) {
|
|
4696
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
4697
|
+
}
|
|
4698
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
4699
|
+
var createStatusController = /* @__PURE__ */ __name(() => {
|
|
4700
|
+
let StatusController = class StatusController {
|
|
4701
|
+
static {
|
|
4702
|
+
__name(this, "StatusController");
|
|
4703
|
+
}
|
|
4704
|
+
dataSourceRegistry;
|
|
4705
|
+
configRegistry;
|
|
4706
|
+
constructor(dataSourceRegistry, configRegistry) {
|
|
4707
|
+
this.dataSourceRegistry = dataSourceRegistry;
|
|
4708
|
+
this.configRegistry = configRegistry;
|
|
4709
|
+
}
|
|
4710
|
+
async getStatus() {
|
|
4711
|
+
const configs = await this.configRegistry.getAll();
|
|
4712
|
+
return buildStatus(this.dataSourceRegistry, configs);
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
_ts_decorate5([
|
|
4716
|
+
(0, import_common12.Get)("status.json"),
|
|
4717
|
+
(0, import_swagger7.ApiOperation)({
|
|
4718
|
+
summary: "Crouton system status (db, resources, version)"
|
|
4719
|
+
}),
|
|
4720
|
+
(0, import_swagger7.ApiResponse)({
|
|
4721
|
+
status: 200,
|
|
4722
|
+
description: "System status"
|
|
4723
|
+
}),
|
|
4724
|
+
_ts_metadata4("design:type", Function),
|
|
4725
|
+
_ts_metadata4("design:paramtypes", []),
|
|
4726
|
+
_ts_metadata4("design:returntype", Promise)
|
|
4727
|
+
], StatusController.prototype, "getStatus", null);
|
|
4728
|
+
StatusController = _ts_decorate5([
|
|
4729
|
+
(0, import_common12.Controller)("crouton"),
|
|
4730
|
+
(0, import_swagger7.ApiTags)("Status"),
|
|
4731
|
+
_ts_metadata4("design:type", Function),
|
|
4732
|
+
_ts_metadata4("design:paramtypes", [
|
|
4733
|
+
typeof DataSourceRegistry === "undefined" ? Object : DataSourceRegistry,
|
|
4734
|
+
typeof ResourceConfigRegistry === "undefined" ? Object : ResourceConfigRegistry
|
|
4735
|
+
])
|
|
4736
|
+
], StatusController);
|
|
4737
|
+
Reflect.defineMetadata("design:paramtypes", [
|
|
4738
|
+
DataSourceRegistry,
|
|
4739
|
+
ResourceConfigRegistry
|
|
4740
|
+
], StatusController);
|
|
4741
|
+
return StatusController;
|
|
4742
|
+
}, "createStatusController");
|
|
4743
|
+
|
|
4744
|
+
// src/lib/crouton-api.module.ts
|
|
4745
|
+
function _ts_decorate6(decorators, target, key, desc2) {
|
|
4746
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
4747
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
4748
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
4749
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
4750
|
+
}
|
|
4751
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
4464
4752
|
var CroutonApiModule = class _CroutonApiModule {
|
|
4465
4753
|
static {
|
|
4466
4754
|
__name(this, "CroutonApiModule");
|
|
@@ -4474,10 +4762,33 @@ var CroutonApiModule = class _CroutonApiModule {
|
|
|
4474
4762
|
}
|
|
4475
4763
|
static forResources(configs, dataSources, loader, { baseUrl }, config) {
|
|
4476
4764
|
const dataSourceRegistry = new DataSourceRegistry(dataSources);
|
|
4477
|
-
const
|
|
4765
|
+
const validConfigs = [];
|
|
4766
|
+
for (const c of configs) {
|
|
4767
|
+
try {
|
|
4768
|
+
const prisma = dataSourceRegistry.resolve(c.database);
|
|
4769
|
+
if (!prisma[c.model]) {
|
|
4770
|
+
resourceLoadErrorsRegistry.record({
|
|
4771
|
+
name: c.name,
|
|
4772
|
+
path: c.route,
|
|
4773
|
+
error: `Model "${c.model}" not found on the provided PrismaClient. Check the resource config for "${c.name}".`
|
|
4774
|
+
});
|
|
4775
|
+
continue;
|
|
4776
|
+
}
|
|
4777
|
+
} catch (e) {
|
|
4778
|
+
resourceLoadErrorsRegistry.record({
|
|
4779
|
+
name: c.name,
|
|
4780
|
+
path: c.route,
|
|
4781
|
+
error: e.message ?? String(e)
|
|
4782
|
+
});
|
|
4783
|
+
continue;
|
|
4784
|
+
}
|
|
4785
|
+
validConfigs.push(c);
|
|
4786
|
+
}
|
|
4787
|
+
const configRegistry = new ResourceConfigRegistry(loader, validConfigs);
|
|
4478
4788
|
const controllers = [
|
|
4479
|
-
...
|
|
4480
|
-
createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true)
|
|
4789
|
+
...validConfigs.map((c) => createCrudController(c, baseUrl)),
|
|
4790
|
+
createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true),
|
|
4791
|
+
createStatusController()
|
|
4481
4792
|
];
|
|
4482
4793
|
return {
|
|
4483
4794
|
module: _CroutonApiModule,
|
|
@@ -4512,8 +4823,8 @@ var CroutonApiModule = class _CroutonApiModule {
|
|
|
4512
4823
|
return _CroutonApiModule.forResources(configs, dataSources, loader, appConfig, config);
|
|
4513
4824
|
}
|
|
4514
4825
|
};
|
|
4515
|
-
CroutonApiModule =
|
|
4516
|
-
(0,
|
|
4826
|
+
CroutonApiModule = _ts_decorate6([
|
|
4827
|
+
(0, import_common13.Module)({
|
|
4517
4828
|
controllers: [],
|
|
4518
4829
|
providers: [],
|
|
4519
4830
|
exports: []
|