@ghentcdh/crouton-api 0.0.1-alpha.1
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 +3787 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +344 -0
- package/dist/index.d.ts +344 -0
- package/dist/index.js +3751 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3787 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc2) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
CroutonApiModule: () => CroutonApiModule,
|
|
25
|
+
DataSourceRegistry: () => DataSourceRegistry,
|
|
26
|
+
FileSystemResourceConfigLoader: () => FileSystemResourceConfigLoader,
|
|
27
|
+
ResourceConfigLoader: () => ResourceConfigLoader2,
|
|
28
|
+
ResourceConfigRegistry: () => ResourceConfigRegistry,
|
|
29
|
+
isOperationEnabled: () => isOperationEnabled,
|
|
30
|
+
loadDataSourcesFromDir: () => loadDataSourcesFromDir,
|
|
31
|
+
loadResourceConfigsFromDir: () => loadResourceConfigsFromDir,
|
|
32
|
+
resolveDefinition: () => resolveDefinition,
|
|
33
|
+
schemaFor: () => schemaFor,
|
|
34
|
+
upsertOnFor: () => upsertOnFor
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/lib/crouton-api.module.ts
|
|
39
|
+
var import_common13 = require("@nestjs/common");
|
|
40
|
+
var import_core = require("@nestjs/core");
|
|
41
|
+
|
|
42
|
+
// src/lib/crud/app-layout.controller.ts
|
|
43
|
+
var import_common2 = require("@nestjs/common");
|
|
44
|
+
var import_swagger = require("@nestjs/swagger");
|
|
45
|
+
|
|
46
|
+
// ../crouton-core/src/lib/request.model.ts
|
|
47
|
+
var import_zod2 = require("zod");
|
|
48
|
+
|
|
49
|
+
// ../crouton-core/src/lib/zod.types.ts
|
|
50
|
+
var import_zod = require("zod");
|
|
51
|
+
var PositiveRequestNumber = /* @__PURE__ */ __name(() => import_zod.z.coerce.number().int().positive().nonnegative(), "PositiveRequestNumber");
|
|
52
|
+
var StringOrArray = /* @__PURE__ */ __name(() => import_zod.z.string().or(import_zod.z.array(import_zod.z.string())).transform((val) => {
|
|
53
|
+
if (Array.isArray(val)) return val;
|
|
54
|
+
return [
|
|
55
|
+
val
|
|
56
|
+
];
|
|
57
|
+
}), "StringOrArray");
|
|
58
|
+
|
|
59
|
+
// ../crouton-core/src/lib/request.model.ts
|
|
60
|
+
var SortDirEnum = import_zod2.z.enum([
|
|
61
|
+
"asc",
|
|
62
|
+
"desc"
|
|
63
|
+
]);
|
|
64
|
+
var RequestSchema = import_zod2.z.object({
|
|
65
|
+
page: PositiveRequestNumber().optional().default(1),
|
|
66
|
+
pageSize: PositiveRequestNumber().optional().default(20),
|
|
67
|
+
sort: import_zod2.z.string().optional().default("id"),
|
|
68
|
+
sortDir: SortDirEnum.optional().default("asc"),
|
|
69
|
+
// Filter is of the format key:value:operator (e.g. name:john:eq) operator is optional
|
|
70
|
+
filter: StringOrArray().optional().default([])
|
|
71
|
+
});
|
|
72
|
+
var RequestSchemaWithOffset = RequestSchema.transform((schema) => {
|
|
73
|
+
const { page, pageSize, sort } = schema;
|
|
74
|
+
return {
|
|
75
|
+
...schema,
|
|
76
|
+
sort: sort || "id",
|
|
77
|
+
offset: (page - 1) * pageSize
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ../crouton-core/src/lib/filter.ts
|
|
82
|
+
var Operator = [
|
|
83
|
+
"contains",
|
|
84
|
+
"not_contains",
|
|
85
|
+
"equals",
|
|
86
|
+
"not_equals",
|
|
87
|
+
"gt",
|
|
88
|
+
"lt",
|
|
89
|
+
"isnull",
|
|
90
|
+
"isnotnull"
|
|
91
|
+
];
|
|
92
|
+
var OperatorLabel = {
|
|
93
|
+
contains: "contains",
|
|
94
|
+
not_contains: "not contains",
|
|
95
|
+
equals: "equals",
|
|
96
|
+
not_equals: "not equals",
|
|
97
|
+
gt: ">",
|
|
98
|
+
lt: "<",
|
|
99
|
+
isnull: "is empty",
|
|
100
|
+
isnotnull: "is not empty"
|
|
101
|
+
};
|
|
102
|
+
var OperatorOptions = Operator.map((k) => ({
|
|
103
|
+
value: k,
|
|
104
|
+
label: OperatorLabel[k]
|
|
105
|
+
}));
|
|
106
|
+
var buildSort = /* @__PURE__ */ __name((key, sortDir) => buildSortKey(key.split("."), SortDirEnum.safeParse(sortDir).data ?? "asc"), "buildSort");
|
|
107
|
+
var buildSortKey = /* @__PURE__ */ __name((keys, sortDir) => {
|
|
108
|
+
if (keys.length === 1) return {
|
|
109
|
+
[keys[0]]: sortDir
|
|
110
|
+
};
|
|
111
|
+
const buildKey = keys.pop();
|
|
112
|
+
return buildSortKey(keys, {
|
|
113
|
+
[buildKey]: sortDir
|
|
114
|
+
});
|
|
115
|
+
}, "buildSortKey");
|
|
116
|
+
|
|
117
|
+
// ../crouton-core/src/lib/response.model.ts
|
|
118
|
+
var import_zod4 = require("zod");
|
|
119
|
+
var ResponseRequestSchema = RequestSchema.extend({
|
|
120
|
+
count: PositiveRequestNumber(),
|
|
121
|
+
totalPages: PositiveRequestNumber()
|
|
122
|
+
});
|
|
123
|
+
var ResponseSchema = import_zod4.z.object({
|
|
124
|
+
data: import_zod4.z.array(import_zod4.z.unknown()),
|
|
125
|
+
request: ResponseRequestSchema
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ../crouton-core/src/lib/create-schema.ts
|
|
129
|
+
var import_zod6 = require("zod");
|
|
130
|
+
|
|
131
|
+
// ../crouton-core/src/lib/layout/base.builder.ts
|
|
132
|
+
var Builder = class {
|
|
133
|
+
static {
|
|
134
|
+
__name(this, "Builder");
|
|
135
|
+
}
|
|
136
|
+
type;
|
|
137
|
+
constructor(type) {
|
|
138
|
+
this.type = type;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var BuilderWithElements = class extends Builder {
|
|
142
|
+
static {
|
|
143
|
+
__name(this, "BuilderWithElements");
|
|
144
|
+
}
|
|
145
|
+
elements = [];
|
|
146
|
+
addControl(control) {
|
|
147
|
+
this.elements.push(control);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
addControls(...controls) {
|
|
151
|
+
this.elements.push(...controls);
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
buildElements() {
|
|
155
|
+
return this.elements.map((e) => e.build());
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ../crouton-core/src/lib/layout/control.builder.ts
|
|
160
|
+
var ControlType = {
|
|
161
|
+
number: "number",
|
|
162
|
+
string: "string",
|
|
163
|
+
integer: "Integer",
|
|
164
|
+
autocomplete: "autocomplete",
|
|
165
|
+
textArea: "textarea",
|
|
166
|
+
markdown: "markdown",
|
|
167
|
+
array: "array",
|
|
168
|
+
custom: "custom",
|
|
169
|
+
select: "select",
|
|
170
|
+
mutliSelect: "mutliSelect",
|
|
171
|
+
boolean: "boolean",
|
|
172
|
+
link: "link",
|
|
173
|
+
relation: "relation",
|
|
174
|
+
date: "date",
|
|
175
|
+
dateTime: "dateTime",
|
|
176
|
+
dateRange: "date-range"
|
|
177
|
+
};
|
|
178
|
+
var ControlBuilder = class _ControlBuilder extends Builder {
|
|
179
|
+
static {
|
|
180
|
+
__name(this, "ControlBuilder");
|
|
181
|
+
}
|
|
182
|
+
scope;
|
|
183
|
+
options = {
|
|
184
|
+
format: "Control",
|
|
185
|
+
styles: {}
|
|
186
|
+
};
|
|
187
|
+
_detail;
|
|
188
|
+
constructor(scope, type = "Control") {
|
|
189
|
+
super(type), this.scope = scope;
|
|
190
|
+
}
|
|
191
|
+
static asObject(property) {
|
|
192
|
+
return new _ControlBuilder(`#/properties/${property}`, "Object");
|
|
193
|
+
}
|
|
194
|
+
static properties(property) {
|
|
195
|
+
return new _ControlBuilder(`#/properties/${property}`);
|
|
196
|
+
}
|
|
197
|
+
static asCustom(property, type) {
|
|
198
|
+
const builder = new _ControlBuilder(`#/properties/${property}`);
|
|
199
|
+
builder.addOptions({
|
|
200
|
+
format: ControlType.custom,
|
|
201
|
+
type
|
|
202
|
+
});
|
|
203
|
+
return builder;
|
|
204
|
+
}
|
|
205
|
+
setCustomRender(customRender) {
|
|
206
|
+
this.addOptions({
|
|
207
|
+
customRender
|
|
208
|
+
});
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
detail(layoutBuilder, label) {
|
|
212
|
+
this._detail = layoutBuilder;
|
|
213
|
+
this.addOptions({
|
|
214
|
+
format: ControlType.array,
|
|
215
|
+
elementLabelProp: label
|
|
216
|
+
});
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
addAction(action) {
|
|
220
|
+
const actions = this.options?.actions ?? [];
|
|
221
|
+
actions.push(action);
|
|
222
|
+
return this.addOptions({
|
|
223
|
+
actions
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
detailFixed(layoutBuilder, options = {}) {
|
|
227
|
+
this._detail = layoutBuilder;
|
|
228
|
+
return this.addOptions({
|
|
229
|
+
hideActions: true,
|
|
230
|
+
format: ControlType.array,
|
|
231
|
+
layout: options.layout ?? "column",
|
|
232
|
+
elementLabelProp: options.label
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
labelKey(labelKey) {
|
|
236
|
+
return this.addOptions({
|
|
237
|
+
labelKey
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
readonly() {
|
|
241
|
+
return this.addOptions({
|
|
242
|
+
format: ControlType.string,
|
|
243
|
+
readonly: true
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
link() {
|
|
247
|
+
return this.addOptions({
|
|
248
|
+
format: ControlType.link
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
markdown(options) {
|
|
252
|
+
return this.addOptions({
|
|
253
|
+
format: ControlType.markdown,
|
|
254
|
+
...options ?? {}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
textArea(options) {
|
|
258
|
+
return this.addOptions({
|
|
259
|
+
format: ControlType.textArea,
|
|
260
|
+
...options ?? {}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
autocomplete(options) {
|
|
264
|
+
return this.addOptions({
|
|
265
|
+
format: ControlType.autocomplete,
|
|
266
|
+
dataField: "data",
|
|
267
|
+
...options ?? {}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
control(format, options) {
|
|
271
|
+
return this.addOptions({
|
|
272
|
+
format,
|
|
273
|
+
...options
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
select(options) {
|
|
277
|
+
return this.addOptions({
|
|
278
|
+
format: ControlType.select,
|
|
279
|
+
...options
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
mutliSelect(options) {
|
|
283
|
+
return this.addOptions({
|
|
284
|
+
format: ControlType.mutliSelect,
|
|
285
|
+
...options
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
width(width) {
|
|
289
|
+
return this.addOptions({
|
|
290
|
+
styles: {
|
|
291
|
+
...this.options?.styles,
|
|
292
|
+
width,
|
|
293
|
+
control: {
|
|
294
|
+
wrapper: `input-${width}`
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
customLabel(label) {
|
|
300
|
+
return this.addOptions({
|
|
301
|
+
label
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
placeHolder(placeholder) {
|
|
305
|
+
return this.addOptions({
|
|
306
|
+
placeholder
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
hideLabel() {
|
|
310
|
+
return this.addOptions({
|
|
311
|
+
hideLabel: true
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
addOptions(options) {
|
|
315
|
+
this.options = {
|
|
316
|
+
...this.options,
|
|
317
|
+
...options
|
|
318
|
+
};
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
build() {
|
|
322
|
+
return {
|
|
323
|
+
type: this.type,
|
|
324
|
+
scope: this.scope,
|
|
325
|
+
options: {
|
|
326
|
+
...this.options,
|
|
327
|
+
detail: this._detail ? this._detail?.build() : void 0
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// ../crouton-core/src/lib/layout/layout.builder.ts
|
|
334
|
+
var LayoutTypes = {
|
|
335
|
+
HorizontalLayout: "HorizontalLayout",
|
|
336
|
+
VerticalLayout: "VerticalLayout",
|
|
337
|
+
CollapseLayout: "CollapseLayout",
|
|
338
|
+
GridLayout: "GridLayout"
|
|
339
|
+
};
|
|
340
|
+
var LayoutBuilder = class _LayoutBuilder extends BuilderWithElements {
|
|
341
|
+
static {
|
|
342
|
+
__name(this, "LayoutBuilder");
|
|
343
|
+
}
|
|
344
|
+
options;
|
|
345
|
+
constructor(type, options = {}) {
|
|
346
|
+
super(type);
|
|
347
|
+
this.options = options;
|
|
348
|
+
}
|
|
349
|
+
static horizontal() {
|
|
350
|
+
return new _LayoutBuilder(LayoutTypes.HorizontalLayout);
|
|
351
|
+
}
|
|
352
|
+
static collapse() {
|
|
353
|
+
return new _LayoutBuilder(LayoutTypes.CollapseLayout);
|
|
354
|
+
}
|
|
355
|
+
static vertical() {
|
|
356
|
+
return new _LayoutBuilder(LayoutTypes.VerticalLayout);
|
|
357
|
+
}
|
|
358
|
+
static grid() {
|
|
359
|
+
return new _LayoutBuilder(LayoutTypes.GridLayout);
|
|
360
|
+
}
|
|
361
|
+
titleKey(titleKey) {
|
|
362
|
+
return this.addOptions({
|
|
363
|
+
titleKey
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
title(title) {
|
|
367
|
+
return this.addOptions({
|
|
368
|
+
title
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
addOptions(options) {
|
|
372
|
+
this.options = {
|
|
373
|
+
...this.options,
|
|
374
|
+
...options
|
|
375
|
+
};
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
build() {
|
|
379
|
+
return {
|
|
380
|
+
type: this.type,
|
|
381
|
+
elements: this.buildElements(),
|
|
382
|
+
options: this.options
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// ../crouton-core/src/lib/json-config.types.ts
|
|
388
|
+
var labelFromId = /* @__PURE__ */ __name((id) => {
|
|
389
|
+
const words = id.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
390
|
+
return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
|
|
391
|
+
}, "labelFromId");
|
|
392
|
+
var normalizeColumns = /* @__PURE__ */ __name((columns) => {
|
|
393
|
+
if (!columns) return void 0;
|
|
394
|
+
const raw = Array.isArray(columns) ? columns : Object.entries(columns).map(([id, col]) => ({
|
|
395
|
+
id,
|
|
396
|
+
...col
|
|
397
|
+
}));
|
|
398
|
+
return raw.map((col) => ({
|
|
399
|
+
...col,
|
|
400
|
+
label: col.label ?? labelFromId(col.id)
|
|
401
|
+
}));
|
|
402
|
+
}, "normalizeColumns");
|
|
403
|
+
|
|
404
|
+
// ../crouton-core/src/lib/value-label.ts
|
|
405
|
+
var wrapOne = /* @__PURE__ */ __name((value, values) => {
|
|
406
|
+
const match = values.find((o) => o.value === value);
|
|
407
|
+
return {
|
|
408
|
+
value,
|
|
409
|
+
label: match ? match.label : String(value)
|
|
410
|
+
};
|
|
411
|
+
}, "wrapOne");
|
|
412
|
+
var toValueLabel = /* @__PURE__ */ __name((value, values) => {
|
|
413
|
+
if (value === null || value === void 0) return value;
|
|
414
|
+
if (Array.isArray(value)) return value.map((v) => wrapOne(v, values));
|
|
415
|
+
return wrapOne(value, values);
|
|
416
|
+
}, "toValueLabel");
|
|
417
|
+
var fromValueLabel = /* @__PURE__ */ __name((input) => {
|
|
418
|
+
if (Array.isArray(input)) return input.map(fromValueLabel);
|
|
419
|
+
if (input && typeof input === "object" && "value" in input) {
|
|
420
|
+
return input.value;
|
|
421
|
+
}
|
|
422
|
+
return input;
|
|
423
|
+
}, "fromValueLabel");
|
|
424
|
+
|
|
425
|
+
// ../crouton-core/src/lib/table/table.builder.ts
|
|
426
|
+
var TextCellBuilder = class _TextCellBuilder extends Builder {
|
|
427
|
+
static {
|
|
428
|
+
__name(this, "TextCellBuilder");
|
|
429
|
+
}
|
|
430
|
+
scope;
|
|
431
|
+
options;
|
|
432
|
+
constructor(scope, type = "TextCell") {
|
|
433
|
+
super(type), this.scope = scope;
|
|
434
|
+
}
|
|
435
|
+
static properties(property) {
|
|
436
|
+
return new _TextCellBuilder(`#/properties/${property}`);
|
|
437
|
+
}
|
|
438
|
+
key(key) {
|
|
439
|
+
this.options = {
|
|
440
|
+
format: "keyValue",
|
|
441
|
+
key
|
|
442
|
+
};
|
|
443
|
+
return this;
|
|
444
|
+
}
|
|
445
|
+
setSortId(sortId) {
|
|
446
|
+
this.options = {
|
|
447
|
+
...this.options ?? {
|
|
448
|
+
format: this.type
|
|
449
|
+
},
|
|
450
|
+
sortId
|
|
451
|
+
};
|
|
452
|
+
return this;
|
|
453
|
+
}
|
|
454
|
+
build() {
|
|
455
|
+
return {
|
|
456
|
+
type: this.type,
|
|
457
|
+
scope: this.scope,
|
|
458
|
+
options: this.options
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var BooleanCellBuilder = class _BooleanCellBuilder extends TextCellBuilder {
|
|
463
|
+
static {
|
|
464
|
+
__name(this, "BooleanCellBuilder");
|
|
465
|
+
}
|
|
466
|
+
constructor(scope) {
|
|
467
|
+
super(scope, "BooleanCell");
|
|
468
|
+
}
|
|
469
|
+
static properties(property) {
|
|
470
|
+
return new _BooleanCellBuilder(`#/properties/${property}`);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var TableBuilder = class _TableBuilder {
|
|
474
|
+
static {
|
|
475
|
+
__name(this, "TableBuilder");
|
|
476
|
+
}
|
|
477
|
+
builder;
|
|
478
|
+
constructor() {
|
|
479
|
+
this.builder = LayoutBuilder.horizontal();
|
|
480
|
+
}
|
|
481
|
+
static init() {
|
|
482
|
+
return new _TableBuilder();
|
|
483
|
+
}
|
|
484
|
+
addControl(control) {
|
|
485
|
+
this.builder.addControls(control);
|
|
486
|
+
return this;
|
|
487
|
+
}
|
|
488
|
+
addControls(...controls) {
|
|
489
|
+
this.builder.addControls(...controls);
|
|
490
|
+
return this;
|
|
491
|
+
}
|
|
492
|
+
build() {
|
|
493
|
+
return this.builder.build();
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/lib/crud/dev-mode.ts
|
|
498
|
+
var IS_DEV = process.env["NODE_ENV"] !== "production";
|
|
499
|
+
|
|
500
|
+
// src/lib/crud/resource-config.registry.ts
|
|
501
|
+
var import_common = require("@nestjs/common");
|
|
502
|
+
function _ts_decorate(decorators, target, key, desc2) {
|
|
503
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
504
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
505
|
+
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;
|
|
506
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
507
|
+
}
|
|
508
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
509
|
+
function _ts_metadata(k, v) {
|
|
510
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
511
|
+
}
|
|
512
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
513
|
+
var ResourceConfigRegistry = class {
|
|
514
|
+
static {
|
|
515
|
+
__name(this, "ResourceConfigRegistry");
|
|
516
|
+
}
|
|
517
|
+
loader;
|
|
518
|
+
configs;
|
|
519
|
+
constructor(loader, initialConfigs) {
|
|
520
|
+
this.loader = loader;
|
|
521
|
+
this.configs = initialConfigs;
|
|
522
|
+
}
|
|
523
|
+
async getAll() {
|
|
524
|
+
if (IS_DEV) {
|
|
525
|
+
this.configs = await this.loader.loadAll();
|
|
526
|
+
}
|
|
527
|
+
return this.configs;
|
|
528
|
+
}
|
|
529
|
+
async getByRoute(route) {
|
|
530
|
+
if (IS_DEV) {
|
|
531
|
+
return this.loader.loadByRoute(route);
|
|
532
|
+
}
|
|
533
|
+
return this.configs.find((c) => c.route === route);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
ResourceConfigRegistry = _ts_decorate([
|
|
537
|
+
(0, import_common.Injectable)(),
|
|
538
|
+
_ts_metadata("design:type", Function),
|
|
539
|
+
_ts_metadata("design:paramtypes", [
|
|
540
|
+
typeof ResourceConfigLoader === "undefined" ? Object : ResourceConfigLoader,
|
|
541
|
+
Array
|
|
542
|
+
])
|
|
543
|
+
], ResourceConfigRegistry);
|
|
544
|
+
|
|
545
|
+
// src/lib/crud/app-layout.controller.ts
|
|
546
|
+
function _ts_decorate2(decorators, target, key, desc2) {
|
|
547
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
548
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
549
|
+
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;
|
|
550
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
551
|
+
}
|
|
552
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
553
|
+
function _ts_metadata2(k, v) {
|
|
554
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
555
|
+
}
|
|
556
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
557
|
+
var byPosition = /* @__PURE__ */ __name((a, b) => {
|
|
558
|
+
if (a.position != null && b.position != null) return a.position - b.position;
|
|
559
|
+
if (a.position != null) return -1;
|
|
560
|
+
if (b.position != null) return 1;
|
|
561
|
+
return a.label.localeCompare(b.label);
|
|
562
|
+
}, "byPosition");
|
|
563
|
+
var buildLayoutPayload = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
|
|
564
|
+
const visible = configs.filter((c) => c.sidebar?.hide !== true && c.views?.["table"]);
|
|
565
|
+
const topLevel = [];
|
|
566
|
+
const groupMap = new Map(Object.entries(sidebarGroups).map(([slug, cfg]) => [
|
|
567
|
+
slug,
|
|
568
|
+
{
|
|
569
|
+
label: cfg.label ?? labelFromId(slug),
|
|
570
|
+
position: cfg.position,
|
|
571
|
+
children: []
|
|
572
|
+
}
|
|
573
|
+
]));
|
|
574
|
+
for (const c of visible) {
|
|
575
|
+
const leaf = {
|
|
576
|
+
kind: "item",
|
|
577
|
+
id: c.name,
|
|
578
|
+
label: c.sidebar?.label ?? c.title ?? c.tag,
|
|
579
|
+
position: c.sidebar?.position
|
|
580
|
+
};
|
|
581
|
+
const groupSlug = c.sidebar?.group;
|
|
582
|
+
if (groupSlug) {
|
|
583
|
+
if (!groupMap.has(groupSlug)) {
|
|
584
|
+
groupMap.set(groupSlug, {
|
|
585
|
+
label: labelFromId(groupSlug),
|
|
586
|
+
children: []
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
groupMap.get(groupSlug).children.push(leaf);
|
|
590
|
+
} else {
|
|
591
|
+
topLevel.push(leaf);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const groups = [
|
|
595
|
+
...groupMap.entries()
|
|
596
|
+
].filter(([, g]) => g.children.length > 0).map(([id, g]) => ({
|
|
597
|
+
kind: "group",
|
|
598
|
+
id,
|
|
599
|
+
label: g.label,
|
|
600
|
+
position: g.position,
|
|
601
|
+
children: g.children.sort(byPosition)
|
|
602
|
+
}));
|
|
603
|
+
const sidebar = [
|
|
604
|
+
...topLevel,
|
|
605
|
+
...groups
|
|
606
|
+
].sort(byPosition);
|
|
607
|
+
return {
|
|
608
|
+
sidebar,
|
|
609
|
+
title,
|
|
610
|
+
autoSave
|
|
611
|
+
};
|
|
612
|
+
}, "buildLayoutPayload");
|
|
613
|
+
var createAppLayoutController = /* @__PURE__ */ __name((configs, sidebarGroups = {}, title, autoSave = true) => {
|
|
614
|
+
const layoutPayload = buildLayoutPayload(configs, sidebarGroups, title, autoSave);
|
|
615
|
+
let AppLayoutController = class AppLayoutController {
|
|
616
|
+
static {
|
|
617
|
+
__name(this, "AppLayoutController");
|
|
618
|
+
}
|
|
619
|
+
configRegistry;
|
|
620
|
+
constructor(configRegistry) {
|
|
621
|
+
this.configRegistry = configRegistry;
|
|
622
|
+
}
|
|
623
|
+
async getLayout() {
|
|
624
|
+
if (IS_DEV) {
|
|
625
|
+
const fresh = await this.configRegistry.getAll();
|
|
626
|
+
return buildLayoutPayload(fresh, sidebarGroups, title, autoSave);
|
|
627
|
+
}
|
|
628
|
+
return layoutPayload;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
_ts_decorate2([
|
|
632
|
+
(0, import_common2.Get)("layout"),
|
|
633
|
+
(0, import_swagger.ApiOperation)({
|
|
634
|
+
summary: "Get the application layout (sidebar, \u2026)"
|
|
635
|
+
}),
|
|
636
|
+
(0, import_swagger.ApiResponse)({
|
|
637
|
+
status: 200,
|
|
638
|
+
description: "Application layout metadata"
|
|
639
|
+
}),
|
|
640
|
+
_ts_metadata2("design:type", Function),
|
|
641
|
+
_ts_metadata2("design:paramtypes", []),
|
|
642
|
+
_ts_metadata2("design:returntype", Promise)
|
|
643
|
+
], AppLayoutController.prototype, "getLayout", null);
|
|
644
|
+
AppLayoutController = _ts_decorate2([
|
|
645
|
+
(0, import_common2.Controller)("_app"),
|
|
646
|
+
(0, import_swagger.ApiTags)("App"),
|
|
647
|
+
_ts_metadata2("design:type", Function),
|
|
648
|
+
_ts_metadata2("design:paramtypes", [
|
|
649
|
+
typeof ResourceConfigRegistry === "undefined" ? Object : ResourceConfigRegistry
|
|
650
|
+
])
|
|
651
|
+
], AppLayoutController);
|
|
652
|
+
Reflect.defineMetadata("design:paramtypes", [
|
|
653
|
+
ResourceConfigRegistry
|
|
654
|
+
], AppLayoutController);
|
|
655
|
+
return AppLayoutController;
|
|
656
|
+
}, "createAppLayoutController");
|
|
657
|
+
|
|
658
|
+
// src/lib/crud/crouton-validation.filter.ts
|
|
659
|
+
var import_common3 = require("@nestjs/common");
|
|
660
|
+
|
|
661
|
+
// src/lib/crud/crouton-validation.error.ts
|
|
662
|
+
var CroutonValidationError = class extends Error {
|
|
663
|
+
static {
|
|
664
|
+
__name(this, "CroutonValidationError");
|
|
665
|
+
}
|
|
666
|
+
errors;
|
|
667
|
+
constructor(errors) {
|
|
668
|
+
super("Validation failed"), this.errors = errors;
|
|
669
|
+
this.name = "CroutonValidationError";
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// src/lib/crud/crouton-validation.filter.ts
|
|
674
|
+
function _ts_decorate3(decorators, target, key, desc2) {
|
|
675
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
676
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
677
|
+
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;
|
|
678
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
679
|
+
}
|
|
680
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
681
|
+
var CroutonValidationExceptionFilter = class {
|
|
682
|
+
static {
|
|
683
|
+
__name(this, "CroutonValidationExceptionFilter");
|
|
684
|
+
}
|
|
685
|
+
catch(exception, host) {
|
|
686
|
+
const res = host.switchToHttp().getResponse();
|
|
687
|
+
res.status(400).json({
|
|
688
|
+
statusCode: 400,
|
|
689
|
+
message: exception.errors,
|
|
690
|
+
error: "Bad Request"
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
CroutonValidationExceptionFilter = _ts_decorate3([
|
|
695
|
+
(0, import_common3.Catch)(CroutonValidationError)
|
|
696
|
+
], CroutonValidationExceptionFilter);
|
|
697
|
+
|
|
698
|
+
// src/lib/crud/crud-controller.factory.ts
|
|
699
|
+
var import_common11 = require("@nestjs/common");
|
|
700
|
+
var import_common12 = require("@nestjs/common");
|
|
701
|
+
var import_swagger6 = require("@nestjs/swagger");
|
|
702
|
+
|
|
703
|
+
// src/lib/crud/crud.config.ts
|
|
704
|
+
var resolveDefinition = /* @__PURE__ */ __name((config) => {
|
|
705
|
+
const def2 = config.definition;
|
|
706
|
+
return typeof def2 === "function" ? def2() : def2;
|
|
707
|
+
}, "resolveDefinition");
|
|
708
|
+
var isOperationEnabled = /* @__PURE__ */ __name((def2, op) => def2[op] != null, "isOperationEnabled");
|
|
709
|
+
var schemaFor = /* @__PURE__ */ __name((def2, op) => {
|
|
710
|
+
const entry = def2[op];
|
|
711
|
+
if (!entry || entry === true) return void 0;
|
|
712
|
+
return entry.schema;
|
|
713
|
+
}, "schemaFor");
|
|
714
|
+
var upsertOnFor = /* @__PURE__ */ __name((def2) => def2.upsert?.upsertOn, "upsertOnFor");
|
|
715
|
+
|
|
716
|
+
// src/lib/crud/read.repository.ts
|
|
717
|
+
var import_common4 = require("@nestjs/common");
|
|
718
|
+
|
|
719
|
+
// src/lib/crud/sql.helpers.ts
|
|
720
|
+
var castExpression = /* @__PURE__ */ __name((col) => {
|
|
721
|
+
if (col.type === "string") return `(${col.sqlExpression})`;
|
|
722
|
+
if (col.type === "boolean") return `CAST((${col.sqlExpression}) AS BOOLEAN)`;
|
|
723
|
+
return `CAST((${col.sqlExpression}) AS INTEGER)`;
|
|
724
|
+
}, "castExpression");
|
|
725
|
+
var defaultValueForType = /* @__PURE__ */ __name((col) => {
|
|
726
|
+
if (col.type === "boolean") return false;
|
|
727
|
+
if (col.type === "string") return null;
|
|
728
|
+
return 0;
|
|
729
|
+
}, "defaultValueForType");
|
|
730
|
+
var coerceColumnValue = /* @__PURE__ */ __name((col) => {
|
|
731
|
+
if (col.type === "boolean") return (v) => Boolean(v);
|
|
732
|
+
if (col.type === "string") return (v) => v === void 0 || v === null ? null : String(v);
|
|
733
|
+
return (v) => Number(v ?? 0);
|
|
734
|
+
}, "coerceColumnValue");
|
|
735
|
+
var buildCalculatedColumnSql = /* @__PURE__ */ __name((col, tableName, ids) => {
|
|
736
|
+
const alias = col.alias ?? col.id;
|
|
737
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ");
|
|
738
|
+
return `SELECT main.id, ${castExpression(col)} AS "${alias}" FROM "${tableName}" main WHERE main.id IN (${placeholders})`;
|
|
739
|
+
}, "buildCalculatedColumnSql");
|
|
740
|
+
var mergeCalculatedColumnsForRows = /* @__PURE__ */ __name(async (rows, calcCols, tableName, prisma) => {
|
|
741
|
+
if (!calcCols.length || !rows.length) return rows;
|
|
742
|
+
const ids = rows.map((r) => r.id);
|
|
743
|
+
const results = await Promise.all(calcCols.map(async (col) => {
|
|
744
|
+
const alias = col.alias ?? col.id;
|
|
745
|
+
const sql = buildCalculatedColumnSql(col, tableName, ids);
|
|
746
|
+
const defaultValue = defaultValueForType(col);
|
|
747
|
+
const coerce = coerceColumnValue(col);
|
|
748
|
+
try {
|
|
749
|
+
const calcRows = await prisma.$queryRawUnsafe(sql, ...ids);
|
|
750
|
+
return {
|
|
751
|
+
id: col.id,
|
|
752
|
+
defaultValue,
|
|
753
|
+
map: Object.fromEntries(calcRows.map((r) => [
|
|
754
|
+
String(r.id),
|
|
755
|
+
coerce(r[alias])
|
|
756
|
+
]))
|
|
757
|
+
};
|
|
758
|
+
} catch (e) {
|
|
759
|
+
console.error(`[calculatedColumns] Failed for "${tableName}.${col.id}":`, e);
|
|
760
|
+
return {
|
|
761
|
+
id: col.id,
|
|
762
|
+
defaultValue,
|
|
763
|
+
map: {}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}));
|
|
767
|
+
return rows.map((row) => {
|
|
768
|
+
const extra = {};
|
|
769
|
+
for (const { id, defaultValue, map } of results) {
|
|
770
|
+
extra[id] = map[String(row.id)] ?? defaultValue;
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
...row,
|
|
774
|
+
...extra
|
|
775
|
+
};
|
|
776
|
+
});
|
|
777
|
+
}, "mergeCalculatedColumnsForRows");
|
|
778
|
+
var buildIncludeClause = /* @__PURE__ */ __name((include) => {
|
|
779
|
+
if (!include?.length) return void 0;
|
|
780
|
+
const map = /* @__PURE__ */ new Map();
|
|
781
|
+
const orderByMap = /* @__PURE__ */ new Map();
|
|
782
|
+
for (const entry of include) {
|
|
783
|
+
if (typeof entry === "string") {
|
|
784
|
+
const dotIdx = entry.indexOf(".");
|
|
785
|
+
if (dotIdx === -1) {
|
|
786
|
+
if (!map.has(entry)) map.set(entry, []);
|
|
787
|
+
} else {
|
|
788
|
+
const relation = entry.slice(0, dotIdx);
|
|
789
|
+
const rest = entry.slice(dotIdx + 1);
|
|
790
|
+
map.set(relation, [
|
|
791
|
+
...map.get(relation) ?? [],
|
|
792
|
+
rest
|
|
793
|
+
]);
|
|
794
|
+
}
|
|
795
|
+
} else {
|
|
796
|
+
map.set(entry.relation, [
|
|
797
|
+
...map.get(entry.relation) ?? [],
|
|
798
|
+
...entry.include ?? []
|
|
799
|
+
]);
|
|
800
|
+
if (entry.orderBy) orderByMap.set(entry.relation, entry.orderBy);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return Object.fromEntries(Array.from(map.entries()).map(([relation, nestedIncludes]) => {
|
|
804
|
+
const orderBy = orderByMap.get(relation);
|
|
805
|
+
if (!nestedIncludes.length && !orderBy) return [
|
|
806
|
+
relation,
|
|
807
|
+
true
|
|
808
|
+
];
|
|
809
|
+
const nested = buildIncludeClause(nestedIncludes);
|
|
810
|
+
const clause = {};
|
|
811
|
+
if (nested) clause.include = nested;
|
|
812
|
+
if (orderBy) clause.orderBy = orderBy;
|
|
813
|
+
return [
|
|
814
|
+
relation,
|
|
815
|
+
clause
|
|
816
|
+
];
|
|
817
|
+
}));
|
|
818
|
+
}, "buildIncludeClause");
|
|
819
|
+
var buildChildSortClause = /* @__PURE__ */ __name((sort, sortDir) => {
|
|
820
|
+
const parts = sort.split(".");
|
|
821
|
+
if (parts.length === 1) return buildSort(sort, sortDir);
|
|
822
|
+
const dir = sortDir ?? "asc";
|
|
823
|
+
return parts.reduceRight((acc, part, i) => i === parts.length - 1 ? {
|
|
824
|
+
[part]: dir
|
|
825
|
+
} : {
|
|
826
|
+
[part]: acc
|
|
827
|
+
}, {});
|
|
828
|
+
}, "buildChildSortClause");
|
|
829
|
+
|
|
830
|
+
// src/lib/crud/read.repository.ts
|
|
831
|
+
var parseFilterString = /* @__PURE__ */ __name((raw) => {
|
|
832
|
+
const parts = raw.split(":");
|
|
833
|
+
if (parts.length < 2 || !parts[0]) return null;
|
|
834
|
+
const field = parts[0];
|
|
835
|
+
const lastPart = parts[parts.length - 1];
|
|
836
|
+
const hasOperator = Operator.includes(lastPart);
|
|
837
|
+
const value = hasOperator ? parts.slice(1, -1).join(":") : parts.slice(1).join(":");
|
|
838
|
+
const operator = hasOperator ? lastPart : "contains";
|
|
839
|
+
return {
|
|
840
|
+
field,
|
|
841
|
+
value,
|
|
842
|
+
operator
|
|
843
|
+
};
|
|
844
|
+
}, "parseFilterString");
|
|
845
|
+
var buildNestedPath = /* @__PURE__ */ __name((path, condition) => {
|
|
846
|
+
if (path.length === 1) return {
|
|
847
|
+
[path[0]]: condition
|
|
848
|
+
};
|
|
849
|
+
const [head, ...rest] = path;
|
|
850
|
+
return {
|
|
851
|
+
[head]: buildNestedPath(rest, condition)
|
|
852
|
+
};
|
|
853
|
+
}, "buildNestedPath");
|
|
854
|
+
var JSON_PATH_SEP = "->";
|
|
855
|
+
var isJsonPath = /* @__PURE__ */ __name((field) => field.includes(JSON_PATH_SEP), "isJsonPath");
|
|
856
|
+
var buildJsonPathCondition = /* @__PURE__ */ __name((field, value, operator) => {
|
|
857
|
+
const [column, ...path] = field.split(JSON_PATH_SEP);
|
|
858
|
+
if (!column || path.length === 0) return null;
|
|
859
|
+
const frag = /* @__PURE__ */ __name((extra) => ({
|
|
860
|
+
[column]: {
|
|
861
|
+
path,
|
|
862
|
+
...extra
|
|
863
|
+
}
|
|
864
|
+
}), "frag");
|
|
865
|
+
switch (operator) {
|
|
866
|
+
case "equals":
|
|
867
|
+
return frag({
|
|
868
|
+
equals: value
|
|
869
|
+
});
|
|
870
|
+
case "not_equals":
|
|
871
|
+
return frag({
|
|
872
|
+
not: value
|
|
873
|
+
});
|
|
874
|
+
case "gt":
|
|
875
|
+
return frag({
|
|
876
|
+
gt: value
|
|
877
|
+
});
|
|
878
|
+
case "lt":
|
|
879
|
+
return frag({
|
|
880
|
+
lt: value
|
|
881
|
+
});
|
|
882
|
+
case "contains":
|
|
883
|
+
return frag({
|
|
884
|
+
string_contains: value
|
|
885
|
+
});
|
|
886
|
+
case "not_contains":
|
|
887
|
+
return {
|
|
888
|
+
NOT: frag({
|
|
889
|
+
string_contains: value
|
|
890
|
+
})
|
|
891
|
+
};
|
|
892
|
+
// isnull/isnotnull on a json sub-path are not supported — filter on the
|
|
893
|
+
// whole column instead if you need an emptiness check.
|
|
894
|
+
default:
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}, "buildJsonPathCondition");
|
|
898
|
+
var operatorToCondition = /* @__PURE__ */ __name((value, operator) => {
|
|
899
|
+
const num = Number(value);
|
|
900
|
+
const numVal = Number.isNaN(num) ? value : num;
|
|
901
|
+
switch (operator) {
|
|
902
|
+
case "contains":
|
|
903
|
+
return {
|
|
904
|
+
contains: value
|
|
905
|
+
};
|
|
906
|
+
case "not_contains":
|
|
907
|
+
return {
|
|
908
|
+
not: {
|
|
909
|
+
contains: value
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
case "equals":
|
|
913
|
+
return {
|
|
914
|
+
equals: value
|
|
915
|
+
};
|
|
916
|
+
case "not_equals":
|
|
917
|
+
return {
|
|
918
|
+
not: value
|
|
919
|
+
};
|
|
920
|
+
case "gt":
|
|
921
|
+
return {
|
|
922
|
+
gt: numVal
|
|
923
|
+
};
|
|
924
|
+
case "lt":
|
|
925
|
+
return {
|
|
926
|
+
lt: numVal
|
|
927
|
+
};
|
|
928
|
+
case "isnull":
|
|
929
|
+
return null;
|
|
930
|
+
case "isnotnull":
|
|
931
|
+
return {
|
|
932
|
+
not: null
|
|
933
|
+
};
|
|
934
|
+
default:
|
|
935
|
+
return void 0;
|
|
936
|
+
}
|
|
937
|
+
}, "operatorToCondition");
|
|
938
|
+
var buildFilterWhere = /* @__PURE__ */ __name((filter) => {
|
|
939
|
+
if (!filter?.length) return void 0;
|
|
940
|
+
const conditions = filter.map(parseFilterString).filter((f) => f !== null).map(({ field, value, operator }) => isJsonPath(field) ? buildJsonPathCondition(field, value, operator) : buildNestedPath(field.split("."), operatorToCondition(value, operator))).filter((c) => c !== null);
|
|
941
|
+
return conditions.length ? {
|
|
942
|
+
AND: conditions
|
|
943
|
+
} : void 0;
|
|
944
|
+
}, "buildFilterWhere");
|
|
945
|
+
var sanitizeValueLabelSort = /* @__PURE__ */ __name((sort, cols) => {
|
|
946
|
+
if (!sort || !cols?.length || !sort.endsWith(".label")) return sort;
|
|
947
|
+
const base = sort.slice(0, -".label".length);
|
|
948
|
+
const leaf = base.split(".").pop();
|
|
949
|
+
return cols.some((c) => c.field === base || c.field === leaf) ? base : sort;
|
|
950
|
+
}, "sanitizeValueLabelSort");
|
|
951
|
+
var orderableChildSort = /* @__PURE__ */ __name((sort, childModel, _sub) => {
|
|
952
|
+
if (!sort) return void 0;
|
|
953
|
+
const scalarFields = new Set(Object.keys(childModel?.fields ?? {}));
|
|
954
|
+
if (scalarFields.size === 0) return sort;
|
|
955
|
+
if (sort.includes(".")) return sort;
|
|
956
|
+
return scalarFields.has(sort) ? sort : void 0;
|
|
957
|
+
}, "orderableChildSort");
|
|
958
|
+
var applyValueLabelColumns = /* @__PURE__ */ __name((row, cols) => {
|
|
959
|
+
if (!row || !cols?.length) return row;
|
|
960
|
+
const out = {
|
|
961
|
+
...row
|
|
962
|
+
};
|
|
963
|
+
for (const { field, values } of cols) {
|
|
964
|
+
if (field in out) out[field] = toValueLabel(out[field], values);
|
|
965
|
+
}
|
|
966
|
+
return out;
|
|
967
|
+
}, "applyValueLabelColumns");
|
|
968
|
+
var ReadRepository = class {
|
|
969
|
+
static {
|
|
970
|
+
__name(this, "ReadRepository");
|
|
971
|
+
}
|
|
972
|
+
prismaModel;
|
|
973
|
+
prisma;
|
|
974
|
+
config;
|
|
975
|
+
listSelect;
|
|
976
|
+
oneSelect;
|
|
977
|
+
constructor(prismaModel, prisma, config, listSelect, oneSelect) {
|
|
978
|
+
this.prismaModel = prismaModel;
|
|
979
|
+
this.prisma = prisma;
|
|
980
|
+
this.config = config;
|
|
981
|
+
this.listSelect = listSelect;
|
|
982
|
+
this.oneSelect = oneSelect;
|
|
983
|
+
}
|
|
984
|
+
toId(id) {
|
|
985
|
+
return (this.config.idType ?? "string") === "number" ? +id : String(id);
|
|
986
|
+
}
|
|
987
|
+
buildWhere(filter) {
|
|
988
|
+
return buildFilterWhere(filter);
|
|
989
|
+
}
|
|
990
|
+
projection(op) {
|
|
991
|
+
const select = op === "findAll" ? this.listSelect : this.oneSelect;
|
|
992
|
+
return select ? {
|
|
993
|
+
select
|
|
994
|
+
} : {};
|
|
995
|
+
}
|
|
996
|
+
safeSort(sort, sortDir) {
|
|
997
|
+
if (!sort) return void 0;
|
|
998
|
+
if (this.listSelect && !(sort in this.listSelect)) return void 0;
|
|
999
|
+
return buildSort(sort, sortDir);
|
|
1000
|
+
}
|
|
1001
|
+
async decorate(rows, op) {
|
|
1002
|
+
const hook = this.config.hooks?.afterRead;
|
|
1003
|
+
const hooked = hook ? await Promise.all(rows.map((row) => hook(row, {
|
|
1004
|
+
prisma: this.prisma,
|
|
1005
|
+
op
|
|
1006
|
+
}))) : rows;
|
|
1007
|
+
const cols = this.config.valueLabelColumns;
|
|
1008
|
+
return cols?.length ? hooked.map((r) => applyValueLabelColumns(r, cols)) : hooked;
|
|
1009
|
+
}
|
|
1010
|
+
async decorateOne(row, op) {
|
|
1011
|
+
const hook = this.config.hooks?.afterRead;
|
|
1012
|
+
return hook ? hook(row, {
|
|
1013
|
+
prisma: this.prisma,
|
|
1014
|
+
op
|
|
1015
|
+
}) : row;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Fetch a paginated, sorted, and filtered list of records.
|
|
1019
|
+
* Sub-resource counts are merged onto each row; calculated columns are resolved via raw SQL.
|
|
1020
|
+
*/
|
|
1021
|
+
async findAll(params) {
|
|
1022
|
+
const subResources = this.config.subResources ?? [];
|
|
1023
|
+
const projection = this.projection("findAll");
|
|
1024
|
+
let query = {
|
|
1025
|
+
where: this.buildWhere(params.filter),
|
|
1026
|
+
take: params.pageSize,
|
|
1027
|
+
skip: params.offset ?? (params.page - 1) * params.pageSize,
|
|
1028
|
+
orderBy: this.safeSort(sanitizeValueLabelSort(params.sort, this.config.valueLabelColumns), params.sortDir)
|
|
1029
|
+
};
|
|
1030
|
+
if (subResources.length) {
|
|
1031
|
+
const countClause = {
|
|
1032
|
+
select: Object.fromEntries(subResources.map((s) => [
|
|
1033
|
+
s.relation,
|
|
1034
|
+
true
|
|
1035
|
+
]))
|
|
1036
|
+
};
|
|
1037
|
+
if (projection.select) {
|
|
1038
|
+
query.select = {
|
|
1039
|
+
...projection.select,
|
|
1040
|
+
_count: countClause
|
|
1041
|
+
};
|
|
1042
|
+
} else {
|
|
1043
|
+
query = {
|
|
1044
|
+
...query,
|
|
1045
|
+
_count: countClause
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
} else {
|
|
1049
|
+
Object.assign(query, projection);
|
|
1050
|
+
}
|
|
1051
|
+
const rows = await this.prismaModel.findMany(query);
|
|
1052
|
+
const mapped = subResources.length ? rows.map((row) => {
|
|
1053
|
+
const { _count, ...rest } = row;
|
|
1054
|
+
if (!_count) return rest;
|
|
1055
|
+
const counts = Object.fromEntries(subResources.map((s) => [
|
|
1056
|
+
s.column,
|
|
1057
|
+
_count[s.relation] ?? 0
|
|
1058
|
+
]));
|
|
1059
|
+
return {
|
|
1060
|
+
...rest,
|
|
1061
|
+
...counts
|
|
1062
|
+
};
|
|
1063
|
+
}) : rows;
|
|
1064
|
+
const withCalc = await mergeCalculatedColumnsForRows(mapped, this.config.calculatedColumns ?? [], this.config.model, this.prisma);
|
|
1065
|
+
return this.decorate(withCalc, "findAll");
|
|
1066
|
+
}
|
|
1067
|
+
/** Count records matching the given filter strings. */
|
|
1068
|
+
count(filter) {
|
|
1069
|
+
return this.prismaModel.count({
|
|
1070
|
+
where: this.buildWhere(filter)
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Fetch a single record by id.
|
|
1075
|
+
* Sub-resources with `includeInFindOne: true` are eagerly loaded (flat).
|
|
1076
|
+
* `config.include` entries are loaded with full nesting via `buildIncludeClause`.
|
|
1077
|
+
* @throws {NotFoundException} When no record exists for the given id.
|
|
1078
|
+
*/
|
|
1079
|
+
async findOne(id) {
|
|
1080
|
+
const formIncludes = (this.config.subResources ?? []).filter((s) => s.includeInFindOne).map((s) => s.relation);
|
|
1081
|
+
const projection = this.projection("findOne");
|
|
1082
|
+
const idField = this.config.idField ?? "id";
|
|
1083
|
+
const query = {
|
|
1084
|
+
where: {
|
|
1085
|
+
[idField]: this.toId(id)
|
|
1086
|
+
},
|
|
1087
|
+
...projection
|
|
1088
|
+
};
|
|
1089
|
+
const flatIncludes = formIncludes.length ? Object.fromEntries(formIncludes.map((r) => [
|
|
1090
|
+
r,
|
|
1091
|
+
true
|
|
1092
|
+
])) : void 0;
|
|
1093
|
+
const configInclude = buildIncludeClause(this.config.include);
|
|
1094
|
+
const mergedInclude = flatIncludes || configInclude ? {
|
|
1095
|
+
...flatIncludes,
|
|
1096
|
+
...configInclude
|
|
1097
|
+
} : void 0;
|
|
1098
|
+
if (mergedInclude) {
|
|
1099
|
+
if (projection.select) {
|
|
1100
|
+
query.select = {
|
|
1101
|
+
...projection.select,
|
|
1102
|
+
...mergedInclude
|
|
1103
|
+
};
|
|
1104
|
+
} else {
|
|
1105
|
+
query.include = mergedInclude;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const record = await this.prismaModel.findUnique(query);
|
|
1109
|
+
if (!record) throw new import_common4.NotFoundException(`${this.config.name} with id ${id} not found`);
|
|
1110
|
+
const [withCalc] = await mergeCalculatedColumnsForRows([
|
|
1111
|
+
record
|
|
1112
|
+
], this.config.calculatedColumns ?? [], this.config.model, this.prisma);
|
|
1113
|
+
let enriched = withCalc ?? record;
|
|
1114
|
+
for (const sub of this.config.subResources ?? []) {
|
|
1115
|
+
if (!sub.calculatedColumns?.length) continue;
|
|
1116
|
+
const nested = enriched[sub.relation];
|
|
1117
|
+
if (!Array.isArray(nested) || !nested.length) continue;
|
|
1118
|
+
const enrichedNested = await mergeCalculatedColumnsForRows(nested, sub.calculatedColumns, sub.childModel, this.prisma);
|
|
1119
|
+
enriched = {
|
|
1120
|
+
...enriched,
|
|
1121
|
+
[sub.relation]: enrichedNested
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
return this.decorateOne(enriched, "findOne");
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Fetch a paginated list of child records belonging to the given parent.
|
|
1128
|
+
* @param childRoute - Matches the `childRoute` key on a `SubResourceConfig`.
|
|
1129
|
+
* @throws {Error} When no matching sub-resource config or Prisma model is found.
|
|
1130
|
+
*/
|
|
1131
|
+
async findAllByParent(parentId, childRoute, params) {
|
|
1132
|
+
const sub = (this.config.subResources ?? []).find((s) => s.childRoute === childRoute);
|
|
1133
|
+
if (!sub) throw new Error(`No sub-resource "${childRoute}" on "${this.config.name}"`);
|
|
1134
|
+
const childModel = this.prisma[sub.childModel];
|
|
1135
|
+
if (!childModel) throw new Error(`Prisma model "${sub.childModel}" not found`);
|
|
1136
|
+
const where = {
|
|
1137
|
+
...this.buildWhere(params.filter),
|
|
1138
|
+
[sub.foreignKey]: this.toId(parentId)
|
|
1139
|
+
};
|
|
1140
|
+
const includeClause = buildIncludeClause(sub.include);
|
|
1141
|
+
const childSort = orderableChildSort(sanitizeValueLabelSort(params.sort, sub.valueLabelColumns), childModel, sub);
|
|
1142
|
+
const [data, count] = await Promise.all([
|
|
1143
|
+
childModel.findMany({
|
|
1144
|
+
where,
|
|
1145
|
+
take: params.pageSize,
|
|
1146
|
+
skip: params.offset ?? (params.page - 1) * params.pageSize,
|
|
1147
|
+
orderBy: childSort ? buildChildSortClause(childSort, params.sortDir) : void 0,
|
|
1148
|
+
...includeClause && {
|
|
1149
|
+
include: includeClause
|
|
1150
|
+
}
|
|
1151
|
+
}),
|
|
1152
|
+
childModel.count({
|
|
1153
|
+
where
|
|
1154
|
+
})
|
|
1155
|
+
]);
|
|
1156
|
+
const withCalc = sub.calculatedColumns?.length ? await mergeCalculatedColumnsForRows(data, sub.calculatedColumns, sub.childModel, this.prisma) : data;
|
|
1157
|
+
const decorated = sub.hooks?.afterRead ? await Promise.all(withCalc.map((row) => sub.hooks.afterRead(row, {
|
|
1158
|
+
prisma: this.prisma,
|
|
1159
|
+
op: "findAll"
|
|
1160
|
+
}))) : withCalc;
|
|
1161
|
+
const labeled = sub.valueLabelColumns?.length ? decorated.map((r) => applyValueLabelColumns(r, sub.valueLabelColumns)) : decorated;
|
|
1162
|
+
return {
|
|
1163
|
+
data: labeled,
|
|
1164
|
+
count
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Fetch a single child record. When `parentId` is supplied the query also filters by the foreign key.
|
|
1169
|
+
* @throws {NotFoundException} When no matching record is found.
|
|
1170
|
+
*/
|
|
1171
|
+
async findOneChild(sub, childId, parentId) {
|
|
1172
|
+
const childModel = this.prisma[sub.childModel];
|
|
1173
|
+
if (!childModel) throw new Error(`Prisma model "${sub.childModel}" not found`);
|
|
1174
|
+
const id = (sub.idType ?? "string") === "number" ? +childId : String(childId);
|
|
1175
|
+
const idField = sub.idField ?? "id";
|
|
1176
|
+
const where = {
|
|
1177
|
+
[idField]: id
|
|
1178
|
+
};
|
|
1179
|
+
if (parentId !== void 0) where[sub.foreignKey] = this.toId(parentId);
|
|
1180
|
+
const includeClause = buildIncludeClause(sub.include);
|
|
1181
|
+
const record = await childModel.findFirst({
|
|
1182
|
+
where,
|
|
1183
|
+
...includeClause && {
|
|
1184
|
+
include: includeClause
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
if (!record) throw new import_common4.NotFoundException(`${sub.childRoute} with id ${childId} not found`);
|
|
1188
|
+
const [withCalc] = sub.calculatedColumns?.length ? await mergeCalculatedColumnsForRows([
|
|
1189
|
+
record
|
|
1190
|
+
], sub.calculatedColumns, sub.childModel, this.prisma) : [
|
|
1191
|
+
record
|
|
1192
|
+
];
|
|
1193
|
+
if (sub.hooks?.afterRead) return sub.hooks.afterRead(withCalc, {
|
|
1194
|
+
prisma: this.prisma,
|
|
1195
|
+
op: "findOne"
|
|
1196
|
+
});
|
|
1197
|
+
return withCalc;
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// src/lib/crud/schema.utils.ts
|
|
1202
|
+
var import_zod7 = require("zod");
|
|
1203
|
+
var dateOverride = /* @__PURE__ */ __name(({ zodSchema, jsonSchema }) => {
|
|
1204
|
+
if (zodSchema instanceof import_zod7.z.ZodDate) {
|
|
1205
|
+
jsonSchema.type = "string";
|
|
1206
|
+
jsonSchema.format = "date-time";
|
|
1207
|
+
}
|
|
1208
|
+
}, "dateOverride");
|
|
1209
|
+
var jsonSchemaOpts = {
|
|
1210
|
+
unrepresentable: "any",
|
|
1211
|
+
override: dateOverride
|
|
1212
|
+
};
|
|
1213
|
+
function isZodSchema(schema) {
|
|
1214
|
+
return schema instanceof import_zod7.ZodObject;
|
|
1215
|
+
}
|
|
1216
|
+
__name(isZodSchema, "isZodSchema");
|
|
1217
|
+
var isNullableProperty = /* @__PURE__ */ __name((property) => {
|
|
1218
|
+
const anyOf = property?.["anyOf"];
|
|
1219
|
+
return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
|
|
1220
|
+
}, "isNullableProperty");
|
|
1221
|
+
var dropNullableFromRequired = /* @__PURE__ */ __name((jsonSchema) => {
|
|
1222
|
+
const { properties, required } = jsonSchema;
|
|
1223
|
+
if (!properties || !Array.isArray(required)) return;
|
|
1224
|
+
jsonSchema.required = required.filter((key) => !isNullableProperty(properties[key]));
|
|
1225
|
+
}, "dropNullableFromRequired");
|
|
1226
|
+
function toJsonSchema(schema) {
|
|
1227
|
+
if (isZodSchema(schema)) {
|
|
1228
|
+
const jsonSchema = (0, import_zod7.toJSONSchema)(schema, {
|
|
1229
|
+
target: "openApi3",
|
|
1230
|
+
...jsonSchemaOpts
|
|
1231
|
+
});
|
|
1232
|
+
dropNullableFromRequired(jsonSchema);
|
|
1233
|
+
return jsonSchema;
|
|
1234
|
+
}
|
|
1235
|
+
return schema;
|
|
1236
|
+
}
|
|
1237
|
+
__name(toJsonSchema, "toJsonSchema");
|
|
1238
|
+
var unwrap = /* @__PURE__ */ __name((schema) => {
|
|
1239
|
+
let s = schema;
|
|
1240
|
+
let t = s?._zod?.def?.type;
|
|
1241
|
+
while (t === "optional" || t === "nullable" || t === "default" || t === "readonly") {
|
|
1242
|
+
s = s._zod.def.innerType;
|
|
1243
|
+
t = s?._zod?.def?.type;
|
|
1244
|
+
}
|
|
1245
|
+
return s;
|
|
1246
|
+
}, "unwrap");
|
|
1247
|
+
var typeOf = /* @__PURE__ */ __name((schema) => schema?._zod?.def?.type, "typeOf");
|
|
1248
|
+
function toSelectFields(schema) {
|
|
1249
|
+
if (!isZodSchema(schema)) {
|
|
1250
|
+
return Object.keys(schema.properties).reduce((acc, key) => {
|
|
1251
|
+
acc[key] = true;
|
|
1252
|
+
return acc;
|
|
1253
|
+
}, {});
|
|
1254
|
+
}
|
|
1255
|
+
const result = {};
|
|
1256
|
+
for (const [key, value] of Object.entries(schema.shape)) {
|
|
1257
|
+
const inner = unwrap(value);
|
|
1258
|
+
const t = typeOf(inner);
|
|
1259
|
+
if (t === "object") {
|
|
1260
|
+
result[key] = {
|
|
1261
|
+
select: toSelectFields(inner)
|
|
1262
|
+
};
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
if (t === "array") {
|
|
1266
|
+
const element = unwrap(inner._zod.def.element);
|
|
1267
|
+
if (typeOf(element) === "object") {
|
|
1268
|
+
result[key] = {
|
|
1269
|
+
select: toSelectFields(element)
|
|
1270
|
+
};
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
result[key] = true;
|
|
1275
|
+
}
|
|
1276
|
+
return result;
|
|
1277
|
+
}
|
|
1278
|
+
__name(toSelectFields, "toSelectFields");
|
|
1279
|
+
|
|
1280
|
+
// src/lib/crud/write.repository.ts
|
|
1281
|
+
var import_common5 = require("@nestjs/common");
|
|
1282
|
+
|
|
1283
|
+
// src/lib/crud/constants.ts
|
|
1284
|
+
var PRISMA_NOT_FOUND_CODE = "P2025";
|
|
1285
|
+
var DEFAULT_ID_FIELD = "id";
|
|
1286
|
+
|
|
1287
|
+
// src/lib/crud/write.repository.ts
|
|
1288
|
+
var normalizeValueLabels = /* @__PURE__ */ __name((data, cols) => {
|
|
1289
|
+
if (!data || typeof data !== "object" || Array.isArray(data) || !cols?.length) return data;
|
|
1290
|
+
const out = {
|
|
1291
|
+
...data
|
|
1292
|
+
};
|
|
1293
|
+
for (const { field } of cols) {
|
|
1294
|
+
if (field in out) out[field] = fromValueLabel(out[field]);
|
|
1295
|
+
}
|
|
1296
|
+
return out;
|
|
1297
|
+
}, "normalizeValueLabels");
|
|
1298
|
+
var includeRelationNames = /* @__PURE__ */ __name((include) => new Set((include ?? []).map((e) => typeof e === "string" ? e : e.relation)), "includeRelationNames");
|
|
1299
|
+
var WriteRepository = class {
|
|
1300
|
+
static {
|
|
1301
|
+
__name(this, "WriteRepository");
|
|
1302
|
+
}
|
|
1303
|
+
prismaModel;
|
|
1304
|
+
prisma;
|
|
1305
|
+
config;
|
|
1306
|
+
constructor(prismaModel, prisma, config) {
|
|
1307
|
+
this.prismaModel = prismaModel;
|
|
1308
|
+
this.prisma = prisma;
|
|
1309
|
+
this.config = config;
|
|
1310
|
+
}
|
|
1311
|
+
toId(id) {
|
|
1312
|
+
return (this.config.idType ?? "string") === "number" ? +id : String(id);
|
|
1313
|
+
}
|
|
1314
|
+
notFound(id) {
|
|
1315
|
+
return new import_common5.NotFoundException(`${this.config.name} with id ${id} not found`);
|
|
1316
|
+
}
|
|
1317
|
+
stripSubResourceKeys(data) {
|
|
1318
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return data;
|
|
1319
|
+
const subKeys = new Set((this.config.subResources ?? []).map((s) => s.column));
|
|
1320
|
+
if (!subKeys.size) return data;
|
|
1321
|
+
return Object.fromEntries(Object.entries(data).filter(([k]) => !subKeys.has(k)));
|
|
1322
|
+
}
|
|
1323
|
+
stripNonCreateableChildFields(data, sub) {
|
|
1324
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return data;
|
|
1325
|
+
const nonCreateable = new Set((sub.views?.form?.columns ?? []).filter((c) => c.createable === false).map((c) => c.id));
|
|
1326
|
+
if (!nonCreateable.size) return data;
|
|
1327
|
+
return Object.fromEntries(Object.entries(data).filter(([k]) => !nonCreateable.has(k)));
|
|
1328
|
+
}
|
|
1329
|
+
async prepare(data, op, id) {
|
|
1330
|
+
const normalized = normalizeValueLabels(data, this.config.valueLabelColumns);
|
|
1331
|
+
const hook = this.config.hooks?.beforeWrite;
|
|
1332
|
+
return hook ? hook(normalized, {
|
|
1333
|
+
prisma: this.prisma,
|
|
1334
|
+
op,
|
|
1335
|
+
id
|
|
1336
|
+
}) : normalized;
|
|
1337
|
+
}
|
|
1338
|
+
async postWrite(result, op, id) {
|
|
1339
|
+
const hook = this.config.hooks?.afterWrite;
|
|
1340
|
+
return hook ? hook(result, {
|
|
1341
|
+
prisma: this.prisma,
|
|
1342
|
+
op,
|
|
1343
|
+
id
|
|
1344
|
+
}) : result;
|
|
1345
|
+
}
|
|
1346
|
+
upsertWhere(data) {
|
|
1347
|
+
const keys = upsertOnFor(resolveDefinition(this.config));
|
|
1348
|
+
if (!keys) throw new import_common5.BadRequestException(`${this.config.name} has no upsertOn configured`);
|
|
1349
|
+
if (typeof keys === "string") return {
|
|
1350
|
+
[keys]: data[keys]
|
|
1351
|
+
};
|
|
1352
|
+
const composite = keys.join("_");
|
|
1353
|
+
return {
|
|
1354
|
+
[composite]: Object.fromEntries(keys.map((k) => [
|
|
1355
|
+
k,
|
|
1356
|
+
data[k]
|
|
1357
|
+
]))
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
async create(data) {
|
|
1361
|
+
const result = await this.prismaModel.create({
|
|
1362
|
+
data: await this.prepare(this.stripSubResourceKeys(data), "create")
|
|
1363
|
+
});
|
|
1364
|
+
return this.postWrite(result, "create");
|
|
1365
|
+
}
|
|
1366
|
+
async update(id, data) {
|
|
1367
|
+
const idField = this.config.idField ?? "id";
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await this.prismaModel.update({
|
|
1370
|
+
where: {
|
|
1371
|
+
[idField]: this.toId(id)
|
|
1372
|
+
},
|
|
1373
|
+
data: await this.prepare(this.stripSubResourceKeys(data), "update", this.toId(id))
|
|
1374
|
+
});
|
|
1375
|
+
return this.postWrite(result, "update", this.toId(id));
|
|
1376
|
+
} catch (e) {
|
|
1377
|
+
if (e?.code === PRISMA_NOT_FOUND_CODE) throw this.notFound(id);
|
|
1378
|
+
throw e;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
async upsert(data) {
|
|
1382
|
+
const where = this.upsertWhere(data);
|
|
1383
|
+
const existing = await this.prismaModel.findFirst({
|
|
1384
|
+
where
|
|
1385
|
+
});
|
|
1386
|
+
const op = existing ? "update" : "create";
|
|
1387
|
+
const prepared = await this.prepare(this.stripSubResourceKeys(data), op, existing ? existing[this.config.idField ?? DEFAULT_ID_FIELD] : void 0);
|
|
1388
|
+
const result = await this.prismaModel.upsert({
|
|
1389
|
+
where,
|
|
1390
|
+
create: prepared,
|
|
1391
|
+
update: prepared
|
|
1392
|
+
});
|
|
1393
|
+
return this.postWrite(result, op, existing ? existing[this.config.idField ?? DEFAULT_ID_FIELD] : void 0);
|
|
1394
|
+
}
|
|
1395
|
+
/** Upsert multiple rows in parallel. */
|
|
1396
|
+
upsertMany(rows) {
|
|
1397
|
+
return Promise.all(rows.map((r) => this.upsert(r)));
|
|
1398
|
+
}
|
|
1399
|
+
async delete(id) {
|
|
1400
|
+
const idField = this.config.idField ?? "id";
|
|
1401
|
+
try {
|
|
1402
|
+
const result = await this.prismaModel.delete({
|
|
1403
|
+
where: {
|
|
1404
|
+
[idField]: this.toId(id)
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
return this.postWrite(result, "delete", this.toId(id));
|
|
1408
|
+
} catch (e) {
|
|
1409
|
+
if (e?.code === PRISMA_NOT_FOUND_CODE) throw this.notFound(id);
|
|
1410
|
+
throw e;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Create a child record and attach it to the parent via the configured foreign key.
|
|
1415
|
+
* Fields marked `createable: false` in the form view are stripped before writing.
|
|
1416
|
+
*/
|
|
1417
|
+
async createChild(parentId, sub, data) {
|
|
1418
|
+
const childModel = this.prisma[sub.childModel];
|
|
1419
|
+
if (!childModel) throw new Error(`Prisma model "${sub.childModel}" not found`);
|
|
1420
|
+
const stripped = this.stripNonCreateableChildFields(data, sub);
|
|
1421
|
+
const normalized = normalizeValueLabels(stripped, sub.valueLabelColumns);
|
|
1422
|
+
const payload = {
|
|
1423
|
+
...normalized,
|
|
1424
|
+
[sub.foreignKey]: this.toId(parentId)
|
|
1425
|
+
};
|
|
1426
|
+
const prepared = sub.hooks?.beforeWrite ? await sub.hooks.beforeWrite(payload, {
|
|
1427
|
+
prisma: this.prisma,
|
|
1428
|
+
op: "create"
|
|
1429
|
+
}) : payload;
|
|
1430
|
+
const includeKeys = includeRelationNames(sub.include);
|
|
1431
|
+
const prismaData = {
|
|
1432
|
+
...Object.fromEntries(Object.entries(prepared).filter(([k]) => !includeKeys.has(k))),
|
|
1433
|
+
[sub.foreignKey]: this.toId(parentId)
|
|
1434
|
+
};
|
|
1435
|
+
const result = await childModel.create({
|
|
1436
|
+
data: prismaData
|
|
1437
|
+
});
|
|
1438
|
+
return sub.hooks?.afterWrite ? sub.hooks.afterWrite(result, {
|
|
1439
|
+
prisma: this.prisma,
|
|
1440
|
+
op: "create"
|
|
1441
|
+
}) : result;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Update a child record. Relation include-keys are stripped from the payload so Prisma doesn't
|
|
1445
|
+
* receive non-scalar fields.
|
|
1446
|
+
* @throws {NotFoundException} When the child record does not exist (Prisma P2025).
|
|
1447
|
+
*/
|
|
1448
|
+
async updateChild(sub, childId, data) {
|
|
1449
|
+
const childModel = this.prisma[sub.childModel];
|
|
1450
|
+
if (!childModel) throw new Error(`Prisma model "${sub.childModel}" not found`);
|
|
1451
|
+
const id = (sub.idType ?? "string") === "number" ? +childId : String(childId);
|
|
1452
|
+
const normalized = normalizeValueLabels(data, sub.valueLabelColumns);
|
|
1453
|
+
const afterHook = sub.hooks?.beforeWrite ? await sub.hooks.beforeWrite(normalized, {
|
|
1454
|
+
prisma: this.prisma,
|
|
1455
|
+
op: "update",
|
|
1456
|
+
id
|
|
1457
|
+
}) : normalized;
|
|
1458
|
+
const includeKeys = includeRelationNames(sub.include);
|
|
1459
|
+
const prepared = Object.fromEntries(Object.entries(afterHook).filter(([k]) => !includeKeys.has(k)));
|
|
1460
|
+
try {
|
|
1461
|
+
const result = await childModel.update({
|
|
1462
|
+
where: {
|
|
1463
|
+
[sub.idField ?? DEFAULT_ID_FIELD]: id
|
|
1464
|
+
},
|
|
1465
|
+
data: prepared
|
|
1466
|
+
});
|
|
1467
|
+
return sub.hooks?.afterWrite ? sub.hooks.afterWrite(result, {
|
|
1468
|
+
prisma: this.prisma,
|
|
1469
|
+
op: "update",
|
|
1470
|
+
id
|
|
1471
|
+
}) : result;
|
|
1472
|
+
} catch (e) {
|
|
1473
|
+
if (e?.code === PRISMA_NOT_FOUND_CODE) throw new import_common5.NotFoundException(`${sub.childRoute} with id ${childId} not found`);
|
|
1474
|
+
throw e;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Delete a child record. When `parentId` is supplied the foreign key is included in the `where`
|
|
1479
|
+
* clause to prevent cross-parent deletions.
|
|
1480
|
+
* @throws {NotFoundException} When no matching record is found.
|
|
1481
|
+
*/
|
|
1482
|
+
async deleteChild(sub, childId, parentId) {
|
|
1483
|
+
const childModel = this.prisma[sub.childModel];
|
|
1484
|
+
if (!childModel) throw new Error(`Prisma model "${sub.childModel}" not found`);
|
|
1485
|
+
const id = (sub.idType ?? "string") === "number" ? +childId : String(childId);
|
|
1486
|
+
const idField = sub.idField ?? "id";
|
|
1487
|
+
const where = {
|
|
1488
|
+
[idField]: id
|
|
1489
|
+
};
|
|
1490
|
+
if (parentId !== void 0) where[sub.foreignKey] = this.toId(parentId);
|
|
1491
|
+
try {
|
|
1492
|
+
const result = await childModel.deleteMany({
|
|
1493
|
+
where
|
|
1494
|
+
});
|
|
1495
|
+
if (result.count === 0) throw new import_common5.NotFoundException(`${sub.childRoute} with id ${childId} not found`);
|
|
1496
|
+
return sub.hooks?.afterWrite ? sub.hooks.afterWrite(result, {
|
|
1497
|
+
prisma: this.prisma,
|
|
1498
|
+
op: "delete",
|
|
1499
|
+
id
|
|
1500
|
+
}) : result;
|
|
1501
|
+
} catch (e) {
|
|
1502
|
+
if (e?.code === PRISMA_NOT_FOUND_CODE) throw new import_common5.NotFoundException(`${sub.childRoute} with id ${childId} not found`);
|
|
1503
|
+
throw e;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1508
|
+
// src/lib/crud/crud-repository.factory.ts
|
|
1509
|
+
function createCrudRepository(prisma, config) {
|
|
1510
|
+
const model = prisma[config.model];
|
|
1511
|
+
if (!model) {
|
|
1512
|
+
throw new Error(`Model "${config.model}" not found on the provided PrismaClient. Check the resource config for "${config.name}".`);
|
|
1513
|
+
}
|
|
1514
|
+
const definition = resolveDefinition(config);
|
|
1515
|
+
const listSchema = schemaFor(definition, "findAll");
|
|
1516
|
+
const oneSchema = schemaFor(definition, "findOne");
|
|
1517
|
+
const listSelect = listSchema ? toSelectFields(listSchema) : void 0;
|
|
1518
|
+
const oneSelect = oneSchema ? toSelectFields(oneSchema) : listSelect;
|
|
1519
|
+
const reader = new ReadRepository(model, prisma, config, listSelect, oneSelect);
|
|
1520
|
+
const writer = new WriteRepository(model, prisma, config);
|
|
1521
|
+
return {
|
|
1522
|
+
prisma,
|
|
1523
|
+
findAll: reader.findAll.bind(reader),
|
|
1524
|
+
count: reader.count.bind(reader),
|
|
1525
|
+
findOne: reader.findOne.bind(reader),
|
|
1526
|
+
findAllByParent: reader.findAllByParent.bind(reader),
|
|
1527
|
+
findOneChild: reader.findOneChild.bind(reader),
|
|
1528
|
+
create: writer.create.bind(writer),
|
|
1529
|
+
update: writer.update.bind(writer),
|
|
1530
|
+
upsert: writer.upsert.bind(writer),
|
|
1531
|
+
upsertMany: writer.upsertMany.bind(writer),
|
|
1532
|
+
delete: writer.delete.bind(writer),
|
|
1533
|
+
createChild: writer.createChild.bind(writer),
|
|
1534
|
+
updateChild: writer.updateChild.bind(writer),
|
|
1535
|
+
deleteChild: writer.deleteChild.bind(writer)
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
__name(createCrudRepository, "createCrudRepository");
|
|
1539
|
+
|
|
1540
|
+
// src/lib/crud/data-source/data-source.registry.ts
|
|
1541
|
+
var import_common6 = require("@nestjs/common");
|
|
1542
|
+
function _ts_decorate4(decorators, target, key, desc2) {
|
|
1543
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
1544
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
1545
|
+
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;
|
|
1546
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1547
|
+
}
|
|
1548
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
1549
|
+
function _ts_metadata3(k, v) {
|
|
1550
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1551
|
+
}
|
|
1552
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
1553
|
+
var DataSourceRegistry = class {
|
|
1554
|
+
static {
|
|
1555
|
+
__name(this, "DataSourceRegistry");
|
|
1556
|
+
}
|
|
1557
|
+
clients = /* @__PURE__ */ new Map();
|
|
1558
|
+
defaultName;
|
|
1559
|
+
constructor(entries) {
|
|
1560
|
+
for (const entry of entries) {
|
|
1561
|
+
this.clients.set(entry.config.name, entry.client);
|
|
1562
|
+
if (entry.config.default) {
|
|
1563
|
+
this.defaultName = entry.config.name;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (!this.defaultName && entries.length > 0) {
|
|
1567
|
+
this.defaultName = entries[0].config.name;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
get(name) {
|
|
1571
|
+
const client = this.clients.get(name);
|
|
1572
|
+
if (!client) {
|
|
1573
|
+
throw new Error(`Data source "${name}" not found in registry`);
|
|
1574
|
+
}
|
|
1575
|
+
return client;
|
|
1576
|
+
}
|
|
1577
|
+
getDefault() {
|
|
1578
|
+
if (!this.defaultName) {
|
|
1579
|
+
throw new Error("No default data source configured");
|
|
1580
|
+
}
|
|
1581
|
+
return this.get(this.defaultName);
|
|
1582
|
+
}
|
|
1583
|
+
resolve(database) {
|
|
1584
|
+
return database ? this.get(database) : this.getDefault();
|
|
1585
|
+
}
|
|
1586
|
+
async onModuleDestroy() {
|
|
1587
|
+
for (const client of this.clients.values()) {
|
|
1588
|
+
if (typeof client.$disconnect === "function") {
|
|
1589
|
+
await client.$disconnect();
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
DataSourceRegistry = _ts_decorate4([
|
|
1595
|
+
(0, import_common6.Injectable)(),
|
|
1596
|
+
_ts_metadata3("design:type", Function),
|
|
1597
|
+
_ts_metadata3("design:paramtypes", [
|
|
1598
|
+
Array
|
|
1599
|
+
])
|
|
1600
|
+
], DataSourceRegistry);
|
|
1601
|
+
|
|
1602
|
+
// src/lib/crud/data-source/data-source.loader.ts
|
|
1603
|
+
var import_node_fs = require("fs");
|
|
1604
|
+
var import_node_path = require("path");
|
|
1605
|
+
var loadDataSourcesFromDir = /* @__PURE__ */ __name(async (dirPath) => {
|
|
1606
|
+
if (!(0, import_node_fs.existsSync)(dirPath)) return [];
|
|
1607
|
+
const entries = (0, import_node_fs.readdirSync)(dirPath, {
|
|
1608
|
+
withFileTypes: true
|
|
1609
|
+
});
|
|
1610
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1611
|
+
const results = [];
|
|
1612
|
+
for (const dir of dirs) {
|
|
1613
|
+
const basePath = (0, import_node_path.join)(dirPath, dir);
|
|
1614
|
+
const jsonFile = (0, import_node_path.join)(basePath, "data-source.json");
|
|
1615
|
+
if (!(0, import_node_fs.existsSync)(jsonFile)) continue;
|
|
1616
|
+
const config = JSON.parse((0, import_node_fs.readFileSync)(jsonFile, "utf-8"));
|
|
1617
|
+
const indexFile = findModule(basePath, "index");
|
|
1618
|
+
if (!indexFile) continue;
|
|
1619
|
+
const mod = await import(indexFile);
|
|
1620
|
+
const client = mod.default;
|
|
1621
|
+
if (!client) continue;
|
|
1622
|
+
results.push({
|
|
1623
|
+
config,
|
|
1624
|
+
client
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
return results;
|
|
1628
|
+
}, "loadDataSourcesFromDir");
|
|
1629
|
+
var findModule = /* @__PURE__ */ __name((dir, name) => {
|
|
1630
|
+
for (const ext of [
|
|
1631
|
+
".ts",
|
|
1632
|
+
".js"
|
|
1633
|
+
]) {
|
|
1634
|
+
const p = (0, import_node_path.join)(dir, `${name}${ext}`);
|
|
1635
|
+
if ((0, import_node_fs.existsSync)(p)) return p;
|
|
1636
|
+
}
|
|
1637
|
+
return void 0;
|
|
1638
|
+
}, "findModule");
|
|
1639
|
+
|
|
1640
|
+
// src/lib/crud/operations/register-actions.ts
|
|
1641
|
+
var import_common7 = require("@nestjs/common");
|
|
1642
|
+
var import_swagger2 = require("@nestjs/swagger");
|
|
1643
|
+
|
|
1644
|
+
// src/lib/crud/operations/decorator.utils.ts
|
|
1645
|
+
var def = /* @__PURE__ */ __name((cls, method, fn) => {
|
|
1646
|
+
Object.defineProperty(cls.prototype, method, {
|
|
1647
|
+
value: fn,
|
|
1648
|
+
writable: true,
|
|
1649
|
+
configurable: true
|
|
1650
|
+
});
|
|
1651
|
+
}, "def");
|
|
1652
|
+
var desc = /* @__PURE__ */ __name((cls, method) => Object.getOwnPropertyDescriptor(cls.prototype, method), "desc");
|
|
1653
|
+
|
|
1654
|
+
// src/lib/crud/operations/register-actions.ts
|
|
1655
|
+
var registerActionRoutes = /* @__PURE__ */ __name((ctx) => {
|
|
1656
|
+
const { cls, config } = ctx;
|
|
1657
|
+
const { name } = config;
|
|
1658
|
+
const procedureActions = (config.actions ?? []).filter((a) => a.type !== "link");
|
|
1659
|
+
for (const action of procedureActions) {
|
|
1660
|
+
const methodName = `procedure_${action.id}`;
|
|
1661
|
+
def(cls, methodName, async function(recordId) {
|
|
1662
|
+
return action.procedure(this.repo.prisma, recordId);
|
|
1663
|
+
});
|
|
1664
|
+
const d = desc(cls, methodName);
|
|
1665
|
+
(0, import_common7.Post)(`procedure/${action.id}/:recordId`)(cls.prototype, methodName, d);
|
|
1666
|
+
(0, import_common7.Param)("recordId")(cls.prototype, methodName, 0);
|
|
1667
|
+
(0, import_swagger2.ApiOperation)({
|
|
1668
|
+
summary: `Execute action "${action.label}" on a ${name}`
|
|
1669
|
+
})(cls.prototype, methodName, d);
|
|
1670
|
+
(0, import_swagger2.ApiParam)({
|
|
1671
|
+
name: "recordId",
|
|
1672
|
+
type: "string"
|
|
1673
|
+
})(cls.prototype, methodName, d);
|
|
1674
|
+
(0, import_swagger2.ApiResponse)({
|
|
1675
|
+
status: 200,
|
|
1676
|
+
description: `Action "${action.id}" result`
|
|
1677
|
+
})(cls.prototype, methodName, d);
|
|
1678
|
+
}
|
|
1679
|
+
}, "registerActionRoutes");
|
|
1680
|
+
var registerTableActionRoutes = /* @__PURE__ */ __name((ctx) => {
|
|
1681
|
+
const { cls, config } = ctx;
|
|
1682
|
+
const { name } = config;
|
|
1683
|
+
const procedureActions = (config.tableActions ?? []).filter((a) => a.type !== "link");
|
|
1684
|
+
for (const action of procedureActions) {
|
|
1685
|
+
const methodName = `tableAction_${action.id}`;
|
|
1686
|
+
def(cls, methodName, async function() {
|
|
1687
|
+
return action.procedure(this.repo.prisma);
|
|
1688
|
+
});
|
|
1689
|
+
const d = desc(cls, methodName);
|
|
1690
|
+
(0, import_common7.Post)(`table-action/${action.id}`)(cls.prototype, methodName, d);
|
|
1691
|
+
(0, import_swagger2.ApiOperation)({
|
|
1692
|
+
summary: `Execute table action "${action.label ?? action.id}" on ${name}`
|
|
1693
|
+
})(cls.prototype, methodName, d);
|
|
1694
|
+
(0, import_swagger2.ApiResponse)({
|
|
1695
|
+
status: 200,
|
|
1696
|
+
description: `Table action "${action.id}" result`
|
|
1697
|
+
})(cls.prototype, methodName, d);
|
|
1698
|
+
}
|
|
1699
|
+
}, "registerTableActionRoutes");
|
|
1700
|
+
|
|
1701
|
+
// src/lib/crud/operations/register-crud.ts
|
|
1702
|
+
var import_common8 = require("@nestjs/common");
|
|
1703
|
+
var import_swagger3 = require("@nestjs/swagger");
|
|
1704
|
+
|
|
1705
|
+
// src/lib/crud/request.dto.ts
|
|
1706
|
+
var import_zod_nestjs = require("@anatine/zod-nestjs");
|
|
1707
|
+
var RequestDtoNoOffset = class extends (0, import_zod_nestjs.createZodDto)(RequestSchema) {
|
|
1708
|
+
static {
|
|
1709
|
+
__name(this, "RequestDtoNoOffset");
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
var RequestDto = class extends (0, import_zod_nestjs.createZodDto)(RequestSchemaWithOffset) {
|
|
1713
|
+
static {
|
|
1714
|
+
__name(this, "RequestDto");
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// src/lib/crud/zod-validation.pipe.ts
|
|
1719
|
+
var ZodValidationPipe = class {
|
|
1720
|
+
static {
|
|
1721
|
+
__name(this, "ZodValidationPipe");
|
|
1722
|
+
}
|
|
1723
|
+
schema;
|
|
1724
|
+
/** Top-level field names that accept `null` (i.e. are nullable). */
|
|
1725
|
+
nullableKeys;
|
|
1726
|
+
/** Whether to coerce `undefined` → `null` on nullable fields (create/upsert only). */
|
|
1727
|
+
coerceUndefinedToNull;
|
|
1728
|
+
constructor(schema, options = {}) {
|
|
1729
|
+
this.schema = schema;
|
|
1730
|
+
this.nullableKeys = Object.entries(schema.shape).filter(([, field]) => field.safeParse(null).success).map(([key]) => key);
|
|
1731
|
+
this.coerceUndefinedToNull = options.coerceNullableUndefinedToNull ?? false;
|
|
1732
|
+
}
|
|
1733
|
+
transform(value) {
|
|
1734
|
+
const stripped = this.stripEmptyStrings(value);
|
|
1735
|
+
const input = this.coerceNullable(stripped);
|
|
1736
|
+
const result = this.schema.safeParse(input);
|
|
1737
|
+
if (!result.success) {
|
|
1738
|
+
throw new CroutonValidationError(this.formatErrors(result.error));
|
|
1739
|
+
}
|
|
1740
|
+
return result.data;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Convert empty strings (`''`) on all top-level fields:
|
|
1744
|
+
* - Nullable fields (`z.string().nullable()`): `""` → `null`
|
|
1745
|
+
* (clearing a nullable field should store null, not fail validation)
|
|
1746
|
+
* - Required/optional fields (`z.string()` / `z.string().optional()`): `""` → `undefined`
|
|
1747
|
+
* (required → fails with invalid_type; optional → passes as absent)
|
|
1748
|
+
*/
|
|
1749
|
+
stripEmptyStrings(value) {
|
|
1750
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
1751
|
+
return value;
|
|
1752
|
+
}
|
|
1753
|
+
const out = {
|
|
1754
|
+
...value
|
|
1755
|
+
};
|
|
1756
|
+
for (const key of Object.keys(out)) {
|
|
1757
|
+
if (out[key] === "") {
|
|
1758
|
+
out[key] = this.nullableKeys.includes(key) ? null : void 0;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return out;
|
|
1762
|
+
}
|
|
1763
|
+
/** Replace `undefined` with `null` on nullable fields (create/upsert only). */
|
|
1764
|
+
coerceNullable(value) {
|
|
1765
|
+
if (!this.coerceUndefinedToNull || value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
1766
|
+
return value;
|
|
1767
|
+
}
|
|
1768
|
+
const out = {
|
|
1769
|
+
...value
|
|
1770
|
+
};
|
|
1771
|
+
for (const key of this.nullableKeys) {
|
|
1772
|
+
if (out[key] === void 0) out[key] = null;
|
|
1773
|
+
}
|
|
1774
|
+
return out;
|
|
1775
|
+
}
|
|
1776
|
+
formatErrors(error) {
|
|
1777
|
+
return error.issues.map((e) => ({
|
|
1778
|
+
field: e.path.join("."),
|
|
1779
|
+
message: e.message
|
|
1780
|
+
}));
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
// src/lib/crud/operations/register-crud.ts
|
|
1785
|
+
var registerFindAll = /* @__PURE__ */ __name((ctx) => {
|
|
1786
|
+
if (!isOperationEnabled(ctx.definition, "findAll")) return;
|
|
1787
|
+
const { cls, config, listSchema } = ctx;
|
|
1788
|
+
const { name } = config;
|
|
1789
|
+
const lookupLabel = config.lookup?.label;
|
|
1790
|
+
def(cls, "findAll", async function(params, q) {
|
|
1791
|
+
const effectiveParams = {
|
|
1792
|
+
...params
|
|
1793
|
+
};
|
|
1794
|
+
if (q && lookupLabel) {
|
|
1795
|
+
effectiveParams.filter = [
|
|
1796
|
+
...params.filter ?? [],
|
|
1797
|
+
`${lookupLabel}:${q}`
|
|
1798
|
+
];
|
|
1799
|
+
}
|
|
1800
|
+
const [data, count] = await Promise.all([
|
|
1801
|
+
this.repo.findAll(effectiveParams),
|
|
1802
|
+
this.repo.count(effectiveParams.filter)
|
|
1803
|
+
]);
|
|
1804
|
+
const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
|
|
1805
|
+
return {
|
|
1806
|
+
data,
|
|
1807
|
+
request: {
|
|
1808
|
+
count,
|
|
1809
|
+
page: params.page,
|
|
1810
|
+
pageSize: params.pageSize,
|
|
1811
|
+
totalPages,
|
|
1812
|
+
sort: params.sort,
|
|
1813
|
+
sortDir: params.sortDir,
|
|
1814
|
+
filter: params.filter
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
});
|
|
1818
|
+
const d = desc(cls, "findAll");
|
|
1819
|
+
(0, import_common8.Get)()(cls.prototype, "findAll", d);
|
|
1820
|
+
(0, import_common8.Query)(new ZodValidationPipe(RequestDtoNoOffset.zodSchema))(cls.prototype, "findAll", 0);
|
|
1821
|
+
(0, import_common8.Query)("q")(cls.prototype, "findAll", 1);
|
|
1822
|
+
(0, import_swagger3.ApiOperation)({
|
|
1823
|
+
summary: `List all ${name}s`
|
|
1824
|
+
})(cls.prototype, "findAll", d);
|
|
1825
|
+
(0, import_swagger3.ApiResponse)({
|
|
1826
|
+
status: 200,
|
|
1827
|
+
description: `Array of ${name}`,
|
|
1828
|
+
...listSchema && {
|
|
1829
|
+
schema: {
|
|
1830
|
+
type: "array",
|
|
1831
|
+
items: toJsonSchema(listSchema)
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
})(cls.prototype, "findAll", d);
|
|
1835
|
+
}, "registerFindAll");
|
|
1836
|
+
var registerFindOne = /* @__PURE__ */ __name((ctx) => {
|
|
1837
|
+
if (!isOperationEnabled(ctx.definition, "findOne")) return;
|
|
1838
|
+
const { cls, config, oneSchema, idParamMeta } = ctx;
|
|
1839
|
+
const { name } = config;
|
|
1840
|
+
def(cls, "findOne", function(id) {
|
|
1841
|
+
return this.repo.findOne(id);
|
|
1842
|
+
});
|
|
1843
|
+
const d = desc(cls, "findOne");
|
|
1844
|
+
(0, import_common8.Get)(":id")(cls.prototype, "findOne", d);
|
|
1845
|
+
(0, import_common8.Param)("id")(cls.prototype, "findOne", 0);
|
|
1846
|
+
(0, import_swagger3.ApiOperation)({
|
|
1847
|
+
summary: `Get one ${name} by id`
|
|
1848
|
+
})(cls.prototype, "findOne", d);
|
|
1849
|
+
(0, import_swagger3.ApiParam)(idParamMeta)(cls.prototype, "findOne", d);
|
|
1850
|
+
(0, import_swagger3.ApiResponse)({
|
|
1851
|
+
status: 200,
|
|
1852
|
+
description: `The ${name}`,
|
|
1853
|
+
...oneSchema && {
|
|
1854
|
+
schema: toJsonSchema(oneSchema)
|
|
1855
|
+
}
|
|
1856
|
+
})(cls.prototype, "findOne", d);
|
|
1857
|
+
(0, import_swagger3.ApiNotFoundResponse)({
|
|
1858
|
+
description: "Not found"
|
|
1859
|
+
})(cls.prototype, "findOne", d);
|
|
1860
|
+
}, "registerFindOne");
|
|
1861
|
+
var registerCreate = /* @__PURE__ */ __name((ctx) => {
|
|
1862
|
+
if (!isOperationEnabled(ctx.definition, "create")) return;
|
|
1863
|
+
const { cls, config, createSchema, bodyDecorator } = ctx;
|
|
1864
|
+
const { name } = config;
|
|
1865
|
+
def(cls, "create", function(body) {
|
|
1866
|
+
return this.repo.create(body);
|
|
1867
|
+
});
|
|
1868
|
+
const d = desc(cls, "create");
|
|
1869
|
+
(0, import_common8.Post)()(cls.prototype, "create", d);
|
|
1870
|
+
bodyDecorator(createSchema, {
|
|
1871
|
+
coerceNullableUndefinedToNull: true
|
|
1872
|
+
})(cls.prototype, "create", 0);
|
|
1873
|
+
(0, import_swagger3.ApiOperation)({
|
|
1874
|
+
summary: `Create a ${name}`
|
|
1875
|
+
})(cls.prototype, "create", d);
|
|
1876
|
+
if (createSchema) (0, import_swagger3.ApiBody)({
|
|
1877
|
+
schema: toJsonSchema(createSchema)
|
|
1878
|
+
})(cls.prototype, "create", d);
|
|
1879
|
+
(0, import_swagger3.ApiResponse)({
|
|
1880
|
+
status: 201,
|
|
1881
|
+
description: `${name} created`
|
|
1882
|
+
})(cls.prototype, "create", d);
|
|
1883
|
+
}, "registerCreate");
|
|
1884
|
+
var registerUpdate = /* @__PURE__ */ __name((ctx) => {
|
|
1885
|
+
if (!isOperationEnabled(ctx.definition, "update")) return;
|
|
1886
|
+
const { cls, config, updateSchema, idParamMeta, bodyDecorator } = ctx;
|
|
1887
|
+
const { name } = config;
|
|
1888
|
+
def(cls, "update", function(id, body) {
|
|
1889
|
+
return this.repo.update(id, body);
|
|
1890
|
+
});
|
|
1891
|
+
const d = desc(cls, "update");
|
|
1892
|
+
(0, import_common8.Patch)(":id")(cls.prototype, "update", d);
|
|
1893
|
+
(0, import_common8.Param)("id")(cls.prototype, "update", 0);
|
|
1894
|
+
bodyDecorator(updateSchema)(cls.prototype, "update", 1);
|
|
1895
|
+
(0, import_swagger3.ApiOperation)({
|
|
1896
|
+
summary: `Update a ${name}`
|
|
1897
|
+
})(cls.prototype, "update", d);
|
|
1898
|
+
(0, import_swagger3.ApiParam)(idParamMeta)(cls.prototype, "update", d);
|
|
1899
|
+
if (updateSchema) (0, import_swagger3.ApiBody)({
|
|
1900
|
+
schema: toJsonSchema(updateSchema)
|
|
1901
|
+
})(cls.prototype, "update", d);
|
|
1902
|
+
(0, import_swagger3.ApiResponse)({
|
|
1903
|
+
status: 200,
|
|
1904
|
+
description: `${name} updated`
|
|
1905
|
+
})(cls.prototype, "update", d);
|
|
1906
|
+
(0, import_swagger3.ApiNotFoundResponse)({
|
|
1907
|
+
description: "Not found"
|
|
1908
|
+
})(cls.prototype, "update", d);
|
|
1909
|
+
}, "registerUpdate");
|
|
1910
|
+
var registerUpsert = /* @__PURE__ */ __name((ctx) => {
|
|
1911
|
+
if (!isOperationEnabled(ctx.definition, "upsert")) return;
|
|
1912
|
+
const { cls, config, upsertSchema, bodyDecorator } = ctx;
|
|
1913
|
+
const { name } = config;
|
|
1914
|
+
def(cls, "upsert", function(body) {
|
|
1915
|
+
return this.repo.upsert(body);
|
|
1916
|
+
});
|
|
1917
|
+
const d = desc(cls, "upsert");
|
|
1918
|
+
(0, import_common8.Put)()(cls.prototype, "upsert", d);
|
|
1919
|
+
bodyDecorator(upsertSchema, {
|
|
1920
|
+
coerceNullableUndefinedToNull: true
|
|
1921
|
+
})(cls.prototype, "upsert", 0);
|
|
1922
|
+
(0, import_swagger3.ApiOperation)({
|
|
1923
|
+
summary: `Upsert a ${name}`
|
|
1924
|
+
})(cls.prototype, "upsert", d);
|
|
1925
|
+
if (upsertSchema) (0, import_swagger3.ApiBody)({
|
|
1926
|
+
schema: toJsonSchema(upsertSchema)
|
|
1927
|
+
})(cls.prototype, "upsert", d);
|
|
1928
|
+
(0, import_swagger3.ApiResponse)({
|
|
1929
|
+
status: 200,
|
|
1930
|
+
description: `${name} upserted`
|
|
1931
|
+
})(cls.prototype, "upsert", d);
|
|
1932
|
+
}, "registerUpsert");
|
|
1933
|
+
var registerDelete = /* @__PURE__ */ __name((ctx) => {
|
|
1934
|
+
if (!isOperationEnabled(ctx.definition, "delete")) return;
|
|
1935
|
+
const { cls, config, idParamMeta } = ctx;
|
|
1936
|
+
const { name } = config;
|
|
1937
|
+
def(cls, "delete", function(id) {
|
|
1938
|
+
return this.repo.delete(id);
|
|
1939
|
+
});
|
|
1940
|
+
const d = desc(cls, "delete");
|
|
1941
|
+
(0, import_common8.Delete)(":id")(cls.prototype, "delete", d);
|
|
1942
|
+
(0, import_common8.Param)("id")(cls.prototype, "delete", 0);
|
|
1943
|
+
(0, import_swagger3.ApiOperation)({
|
|
1944
|
+
summary: `Delete a ${name}`
|
|
1945
|
+
})(cls.prototype, "delete", d);
|
|
1946
|
+
(0, import_swagger3.ApiParam)(idParamMeta)(cls.prototype, "delete", d);
|
|
1947
|
+
(0, import_swagger3.ApiResponse)({
|
|
1948
|
+
status: 200,
|
|
1949
|
+
description: `${name} deleted`
|
|
1950
|
+
})(cls.prototype, "delete", d);
|
|
1951
|
+
(0, import_swagger3.ApiNotFoundResponse)({
|
|
1952
|
+
description: "Not found"
|
|
1953
|
+
})(cls.prototype, "delete", d);
|
|
1954
|
+
}, "registerDelete");
|
|
1955
|
+
|
|
1956
|
+
// src/lib/crud/operations/register-schema-endpoints.ts
|
|
1957
|
+
var import_common9 = require("@nestjs/common");
|
|
1958
|
+
var import_swagger4 = require("@nestjs/swagger");
|
|
1959
|
+
|
|
1960
|
+
// src/lib/crud/operations/payload-builders.ts
|
|
1961
|
+
var resolveEnvPlaceholders = /* @__PURE__ */ __name((value) => value.replace(/\{env\.([^}]+)\}/g, (match, varName) => process.env[varName] ?? match), "resolveEnvPlaceholders");
|
|
1962
|
+
var buildSubResourceOperations = /* @__PURE__ */ __name((ops, baseUri, idField = "id") => {
|
|
1963
|
+
if (!ops) return {};
|
|
1964
|
+
const idPlaceholder = `{${idField}}`;
|
|
1965
|
+
return {
|
|
1966
|
+
...ops.findAll && {
|
|
1967
|
+
findAll: {
|
|
1968
|
+
uri: baseUri,
|
|
1969
|
+
method: "get"
|
|
1970
|
+
}
|
|
1971
|
+
},
|
|
1972
|
+
...ops.findOne && {
|
|
1973
|
+
findOne: {
|
|
1974
|
+
uri: `${baseUri}/${idPlaceholder}`,
|
|
1975
|
+
method: "get"
|
|
1976
|
+
}
|
|
1977
|
+
},
|
|
1978
|
+
...ops.create && {
|
|
1979
|
+
create: {
|
|
1980
|
+
uri: baseUri,
|
|
1981
|
+
method: "post"
|
|
1982
|
+
}
|
|
1983
|
+
},
|
|
1984
|
+
...ops.update && {
|
|
1985
|
+
update: {
|
|
1986
|
+
uri: `${baseUri}/${idPlaceholder}`,
|
|
1987
|
+
method: "patch"
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
...ops.delete && {
|
|
1991
|
+
delete: {
|
|
1992
|
+
uri: `${baseUri}/${idPlaceholder}`,
|
|
1993
|
+
method: "delete"
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
}, "buildSubResourceOperations");
|
|
1998
|
+
var RESOURCE_OPS = [
|
|
1999
|
+
"findAll",
|
|
2000
|
+
"findOne",
|
|
2001
|
+
"create",
|
|
2002
|
+
"update",
|
|
2003
|
+
"delete"
|
|
2004
|
+
];
|
|
2005
|
+
var OP_METHOD = {
|
|
2006
|
+
findAll: "get",
|
|
2007
|
+
findOne: "get",
|
|
2008
|
+
create: "post",
|
|
2009
|
+
update: "patch",
|
|
2010
|
+
delete: "delete"
|
|
2011
|
+
};
|
|
2012
|
+
var OP_SUFFIX = {
|
|
2013
|
+
findAll: "",
|
|
2014
|
+
findOne: "/{id}",
|
|
2015
|
+
create: "",
|
|
2016
|
+
update: "/{id}",
|
|
2017
|
+
delete: "/{id}"
|
|
2018
|
+
};
|
|
2019
|
+
var buildResourceOperations = /* @__PURE__ */ __name((definition, baseUri) => Object.fromEntries(RESOURCE_OPS.filter((op) => isOperationEnabled(definition, op)).map((op) => [
|
|
2020
|
+
op,
|
|
2021
|
+
{
|
|
2022
|
+
uri: `${baseUri}${OP_SUFFIX[op]}`,
|
|
2023
|
+
method: OP_METHOD[op]
|
|
2024
|
+
}
|
|
2025
|
+
])), "buildResourceOperations");
|
|
2026
|
+
var buildDefinitionPayload = /* @__PURE__ */ __name((config) => {
|
|
2027
|
+
const { route, name, tag, idType = "string" } = config;
|
|
2028
|
+
const definition = resolveDefinition(config);
|
|
2029
|
+
const listSchema = schemaFor(definition, "findAll");
|
|
2030
|
+
const oneSchema = schemaFor(definition, "findOne") ?? listSchema;
|
|
2031
|
+
const createSchema = schemaFor(definition, "create");
|
|
2032
|
+
const updateSchema = schemaFor(definition, "update");
|
|
2033
|
+
const upsertSchema = schemaFor(definition, "upsert") ?? createSchema;
|
|
2034
|
+
const operations = [
|
|
2035
|
+
"findAll",
|
|
2036
|
+
"findOne",
|
|
2037
|
+
"create",
|
|
2038
|
+
"update",
|
|
2039
|
+
"upsert",
|
|
2040
|
+
"delete"
|
|
2041
|
+
].filter((op) => isOperationEnabled(definition, op));
|
|
2042
|
+
return {
|
|
2043
|
+
name,
|
|
2044
|
+
route,
|
|
2045
|
+
idType,
|
|
2046
|
+
tag,
|
|
2047
|
+
operations,
|
|
2048
|
+
upsertOn: upsertOnFor(definition),
|
|
2049
|
+
schemas: {
|
|
2050
|
+
...listSchema && {
|
|
2051
|
+
findAll: toJsonSchema(listSchema)
|
|
2052
|
+
},
|
|
2053
|
+
...oneSchema && {
|
|
2054
|
+
findOne: toJsonSchema(oneSchema)
|
|
2055
|
+
},
|
|
2056
|
+
...createSchema && {
|
|
2057
|
+
create: toJsonSchema(createSchema)
|
|
2058
|
+
},
|
|
2059
|
+
...updateSchema && {
|
|
2060
|
+
update: toJsonSchema(updateSchema)
|
|
2061
|
+
},
|
|
2062
|
+
...isOperationEnabled(definition, "upsert") && upsertSchema ? {
|
|
2063
|
+
upsert: toJsonSchema(upsertSchema)
|
|
2064
|
+
} : {}
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
}, "buildDefinitionPayload");
|
|
2068
|
+
var buildResourceJsonPayload = /* @__PURE__ */ __name((config, baseUrl) => {
|
|
2069
|
+
const { name, route } = config;
|
|
2070
|
+
const definition = resolveDefinition(config);
|
|
2071
|
+
const uri = `${baseUrl}/${route}`;
|
|
2072
|
+
const operations = Object.fromEntries(RESOURCE_OPS.map((op) => [
|
|
2073
|
+
op,
|
|
2074
|
+
isOperationEnabled(definition, op)
|
|
2075
|
+
]));
|
|
2076
|
+
operations.lookup = `${uri}?q={text}`;
|
|
2077
|
+
const form = config.views?.["form"];
|
|
2078
|
+
const schema = form?.json_schema ? {
|
|
2079
|
+
data: form.json_schema,
|
|
2080
|
+
ui: form.ui_schema
|
|
2081
|
+
} : null;
|
|
2082
|
+
return {
|
|
2083
|
+
id: name,
|
|
2084
|
+
uri,
|
|
2085
|
+
operations,
|
|
2086
|
+
schema
|
|
2087
|
+
};
|
|
2088
|
+
}, "buildResourceJsonPayload");
|
|
2089
|
+
var buildViewsPayload = /* @__PURE__ */ __name((config, baseUrl) => {
|
|
2090
|
+
if (!config.views || !Object.keys(config.views).length) return void 0;
|
|
2091
|
+
const definition = resolveDefinition(config);
|
|
2092
|
+
const baseUri = `${baseUrl}/${config.route}`;
|
|
2093
|
+
const operations = buildResourceOperations(definition, baseUri);
|
|
2094
|
+
if (isOperationEnabled(definition, "findAll")) {
|
|
2095
|
+
operations["lookup"] = `${baseUri}?q={text}`;
|
|
2096
|
+
}
|
|
2097
|
+
const schemas = Object.fromEntries(Object.entries(config.views).map(([key, v]) => [
|
|
2098
|
+
key,
|
|
2099
|
+
{
|
|
2100
|
+
data: v.json_schema,
|
|
2101
|
+
ui: v.ui_schema,
|
|
2102
|
+
...v.defaultSort !== void 0 && {
|
|
2103
|
+
defaultSort: v.defaultSort
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
]));
|
|
2107
|
+
return {
|
|
2108
|
+
id: config.name,
|
|
2109
|
+
name: config.name,
|
|
2110
|
+
route: config.route,
|
|
2111
|
+
uri: `${baseUrl}/${config.route}`,
|
|
2112
|
+
title: config.title ?? config.tag,
|
|
2113
|
+
idField: config.lookup?.key ?? "id",
|
|
2114
|
+
idType: config.idType ?? "string",
|
|
2115
|
+
...config.modalSize && {
|
|
2116
|
+
modalSize: config.modalSize
|
|
2117
|
+
},
|
|
2118
|
+
operations,
|
|
2119
|
+
display: config.display,
|
|
2120
|
+
schemas,
|
|
2121
|
+
...config.actions?.length && {
|
|
2122
|
+
actions: config.actions.map((a) => a.type === "link" ? {
|
|
2123
|
+
type: "link",
|
|
2124
|
+
id: a.id,
|
|
2125
|
+
label: a.label,
|
|
2126
|
+
href: resolveEnvPlaceholders(a.href),
|
|
2127
|
+
...a.condition && {
|
|
2128
|
+
condition: a.condition
|
|
2129
|
+
}
|
|
2130
|
+
} : {
|
|
2131
|
+
id: a.id,
|
|
2132
|
+
label: a.label,
|
|
2133
|
+
uri: `${baseUrl}/${config.route}/procedure/${a.id}/{id}`,
|
|
2134
|
+
method: a.method ?? "post",
|
|
2135
|
+
...a.data && {
|
|
2136
|
+
data: a.data
|
|
2137
|
+
},
|
|
2138
|
+
...a.condition && {
|
|
2139
|
+
condition: a.condition
|
|
2140
|
+
}
|
|
2141
|
+
})
|
|
2142
|
+
},
|
|
2143
|
+
...config.tableActions?.length && {
|
|
2144
|
+
tableActions: config.tableActions.map((a) => a.type === "link" ? {
|
|
2145
|
+
type: "link",
|
|
2146
|
+
id: a.id,
|
|
2147
|
+
label: a.label,
|
|
2148
|
+
icon: a.icon,
|
|
2149
|
+
tooltip: a.tooltip,
|
|
2150
|
+
href: resolveEnvPlaceholders(a.href)
|
|
2151
|
+
} : {
|
|
2152
|
+
id: a.id,
|
|
2153
|
+
label: a.label,
|
|
2154
|
+
icon: a.icon,
|
|
2155
|
+
tooltip: a.tooltip,
|
|
2156
|
+
uri: `${baseUrl}/${config.route}/table-action/${a.id}`,
|
|
2157
|
+
method: a.method ?? "post",
|
|
2158
|
+
...a.data && {
|
|
2159
|
+
data: a.data
|
|
2160
|
+
}
|
|
2161
|
+
})
|
|
2162
|
+
}
|
|
2163
|
+
};
|
|
2164
|
+
}, "buildViewsPayload");
|
|
2165
|
+
|
|
2166
|
+
// src/lib/crud/operations/register-schema-endpoints.ts
|
|
2167
|
+
var registerDefinitionEndpoint = /* @__PURE__ */ __name((ctx) => {
|
|
2168
|
+
const { cls, config } = ctx;
|
|
2169
|
+
const { route, name } = config;
|
|
2170
|
+
const definitionPayload = buildDefinitionPayload(config);
|
|
2171
|
+
def(cls, "getDefinition", async function() {
|
|
2172
|
+
if (IS_DEV) {
|
|
2173
|
+
const fresh = await this.configRegistry.getByRoute(route);
|
|
2174
|
+
if (fresh) return buildDefinitionPayload(fresh);
|
|
2175
|
+
}
|
|
2176
|
+
return definitionPayload;
|
|
2177
|
+
});
|
|
2178
|
+
const d = desc(cls, "getDefinition");
|
|
2179
|
+
(0, import_common9.Get)("definition")(cls.prototype, "getDefinition", d);
|
|
2180
|
+
(0, import_swagger4.ApiOperation)({
|
|
2181
|
+
summary: `Get the resource definition for ${name}`
|
|
2182
|
+
})(cls.prototype, "getDefinition", d);
|
|
2183
|
+
(0, import_swagger4.ApiResponse)({
|
|
2184
|
+
status: 200,
|
|
2185
|
+
description: `Definition (operations + schemas) for ${name}`
|
|
2186
|
+
})(cls.prototype, "getDefinition", d);
|
|
2187
|
+
}, "registerDefinitionEndpoint");
|
|
2188
|
+
var registerSchemasEndpoint = /* @__PURE__ */ __name((ctx) => {
|
|
2189
|
+
const { cls, config, baseUrl } = ctx;
|
|
2190
|
+
const { route, name } = config;
|
|
2191
|
+
const viewsPayload = buildViewsPayload(config, baseUrl);
|
|
2192
|
+
def(cls, "getSchemas", async function() {
|
|
2193
|
+
if (IS_DEV) {
|
|
2194
|
+
const fresh = await this.configRegistry.getByRoute(route);
|
|
2195
|
+
if (fresh) return buildViewsPayload(fresh, baseUrl) ?? viewsPayload;
|
|
2196
|
+
}
|
|
2197
|
+
return viewsPayload;
|
|
2198
|
+
});
|
|
2199
|
+
const d = desc(cls, "getSchemas");
|
|
2200
|
+
(0, import_common9.Get)("schemas")(cls.prototype, "getSchemas", d);
|
|
2201
|
+
(0, import_swagger4.ApiOperation)({
|
|
2202
|
+
summary: `Get view schemas (table/form) for ${name}`
|
|
2203
|
+
})(cls.prototype, "getSchemas", d);
|
|
2204
|
+
(0, import_swagger4.ApiResponse)({
|
|
2205
|
+
status: 200,
|
|
2206
|
+
description: `View schemas for ${name}`
|
|
2207
|
+
})(cls.prototype, "getSchemas", d);
|
|
2208
|
+
}, "registerSchemasEndpoint");
|
|
2209
|
+
var registerResourceJsonEndpoint = /* @__PURE__ */ __name((ctx) => {
|
|
2210
|
+
const { cls, config, baseUrl } = ctx;
|
|
2211
|
+
const { route, name } = config;
|
|
2212
|
+
const resourceJsonPayload = buildResourceJsonPayload(config, baseUrl);
|
|
2213
|
+
def(cls, "getResourceJson", async function() {
|
|
2214
|
+
if (IS_DEV) {
|
|
2215
|
+
const fresh = await this.configRegistry.getByRoute(route);
|
|
2216
|
+
if (fresh) return buildResourceJsonPayload(fresh, baseUrl);
|
|
2217
|
+
}
|
|
2218
|
+
return resourceJsonPayload;
|
|
2219
|
+
});
|
|
2220
|
+
const d = desc(cls, "getResourceJson");
|
|
2221
|
+
(0, import_common9.Get)("resource.json")(cls.prototype, "getResourceJson", d);
|
|
2222
|
+
(0, import_swagger4.ApiOperation)({
|
|
2223
|
+
summary: `Get resource descriptor for ${name}`
|
|
2224
|
+
})(cls.prototype, "getResourceJson", d);
|
|
2225
|
+
(0, import_swagger4.ApiResponse)({
|
|
2226
|
+
status: 200,
|
|
2227
|
+
description: `Resource descriptor (operations + JSON Schema) for ${name}`
|
|
2228
|
+
})(cls.prototype, "getResourceJson", d);
|
|
2229
|
+
}, "registerResourceJsonEndpoint");
|
|
2230
|
+
|
|
2231
|
+
// src/lib/crud/operations/register-sub-resources.ts
|
|
2232
|
+
var import_common10 = require("@nestjs/common");
|
|
2233
|
+
var import_swagger5 = require("@nestjs/swagger");
|
|
2234
|
+
var registerSubResourceSchemas = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2235
|
+
if (!sub.views) return;
|
|
2236
|
+
const { cls, config, baseUrl } = ctx;
|
|
2237
|
+
const { route } = config;
|
|
2238
|
+
const methodName = `getSchemas_${sub.childRoute}`;
|
|
2239
|
+
const childUri = `${baseUrl}/${route}/{parent.id}/${sub.childRoute}`;
|
|
2240
|
+
const schemasPayload = {
|
|
2241
|
+
id: sub.name ?? sub.childRoute,
|
|
2242
|
+
name: sub.name ?? sub.childRoute,
|
|
2243
|
+
route: sub.childRoute,
|
|
2244
|
+
uri: childUri,
|
|
2245
|
+
title: sub.title ?? sub.childRoute,
|
|
2246
|
+
idField: sub.idField ?? "id",
|
|
2247
|
+
idType: sub.idType ?? "string",
|
|
2248
|
+
...sub.modalSize && {
|
|
2249
|
+
modalSize: sub.modalSize
|
|
2250
|
+
},
|
|
2251
|
+
operations: buildSubResourceOperations(sub.operations, childUri, sub.idField ?? "id"),
|
|
2252
|
+
schemas: Object.fromEntries(Object.entries(sub.views).map(([key, v]) => [
|
|
2253
|
+
key,
|
|
2254
|
+
{
|
|
2255
|
+
data: v.json_schema,
|
|
2256
|
+
ui: v.ui_schema,
|
|
2257
|
+
...v.defaultSort !== void 0 && {
|
|
2258
|
+
defaultSort: v.defaultSort
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
])),
|
|
2262
|
+
...sub.actions?.length && {
|
|
2263
|
+
actions: sub.actions.map((a) => a.type === "link" ? {
|
|
2264
|
+
type: "link",
|
|
2265
|
+
id: a.id,
|
|
2266
|
+
label: a.label,
|
|
2267
|
+
href: resolveEnvPlaceholders(a.href),
|
|
2268
|
+
...a.condition && {
|
|
2269
|
+
condition: a.condition
|
|
2270
|
+
}
|
|
2271
|
+
} : {
|
|
2272
|
+
id: a.id,
|
|
2273
|
+
label: a.label,
|
|
2274
|
+
uri: `${baseUrl}/${sub.childRoute}/procedure/${a.id}/{id}`,
|
|
2275
|
+
method: a.method ?? "post",
|
|
2276
|
+
...a.data && {
|
|
2277
|
+
data: a.data
|
|
2278
|
+
},
|
|
2279
|
+
...a.condition && {
|
|
2280
|
+
condition: a.condition
|
|
2281
|
+
}
|
|
2282
|
+
})
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
def(cls, methodName, async function() {
|
|
2286
|
+
return schemasPayload;
|
|
2287
|
+
});
|
|
2288
|
+
const ds = desc(cls, methodName);
|
|
2289
|
+
(0, import_common10.Get)(`${sub.childRoute}/schemas`)(cls.prototype, methodName, ds);
|
|
2290
|
+
(0, import_swagger5.ApiOperation)({
|
|
2291
|
+
summary: `Get schemas for ${sub.childRoute}`
|
|
2292
|
+
})(cls.prototype, methodName, ds);
|
|
2293
|
+
(0, import_swagger5.ApiResponse)({
|
|
2294
|
+
status: 200,
|
|
2295
|
+
description: `View schemas for ${sub.childRoute}`
|
|
2296
|
+
})(cls.prototype, methodName, ds);
|
|
2297
|
+
}, "registerSubResourceSchemas");
|
|
2298
|
+
var registerSubResourceFindAll = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2299
|
+
if (sub.operations?.findAll === false) return;
|
|
2300
|
+
const { cls, config } = ctx;
|
|
2301
|
+
const { name } = config;
|
|
2302
|
+
const idParamMeta = ctx.idParamMeta;
|
|
2303
|
+
const methodName = `findAllBy_${sub.childRoute}`;
|
|
2304
|
+
def(cls, methodName, async function(id, params) {
|
|
2305
|
+
const { data, count } = await this.repo.findAllByParent(id, sub.childRoute, params);
|
|
2306
|
+
const totalPages = Math.max(1, Math.ceil(count / params.pageSize));
|
|
2307
|
+
return {
|
|
2308
|
+
data,
|
|
2309
|
+
request: {
|
|
2310
|
+
count,
|
|
2311
|
+
page: params.page,
|
|
2312
|
+
pageSize: params.pageSize,
|
|
2313
|
+
totalPages,
|
|
2314
|
+
sort: params.sort,
|
|
2315
|
+
sortDir: params.sortDir,
|
|
2316
|
+
filter: params.filter
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
});
|
|
2320
|
+
const d = desc(cls, methodName);
|
|
2321
|
+
(0, import_common10.Get)(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
|
|
2322
|
+
(0, import_common10.Param)("id")(cls.prototype, methodName, 0);
|
|
2323
|
+
(0, import_common10.Query)(new ZodValidationPipe(RequestDtoNoOffset.zodSchema))(cls.prototype, methodName, 1);
|
|
2324
|
+
(0, import_swagger5.ApiOperation)({
|
|
2325
|
+
summary: `List ${sub.childRoute} for a ${name}`
|
|
2326
|
+
})(cls.prototype, methodName, d);
|
|
2327
|
+
(0, import_swagger5.ApiParam)(idParamMeta)(cls.prototype, methodName, d);
|
|
2328
|
+
(0, import_swagger5.ApiResponse)({
|
|
2329
|
+
status: 200,
|
|
2330
|
+
description: `${sub.childRoute} list`
|
|
2331
|
+
})(cls.prototype, methodName, d);
|
|
2332
|
+
}, "registerSubResourceFindAll");
|
|
2333
|
+
var registerSubResourceCreate = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2334
|
+
if (!sub.operations?.create) return;
|
|
2335
|
+
const { cls, config } = ctx;
|
|
2336
|
+
const { name } = config;
|
|
2337
|
+
const methodName = `createChild_${sub.childRoute}`;
|
|
2338
|
+
def(cls, methodName, async function(id, body) {
|
|
2339
|
+
return this.repo.createChild(id, sub, body);
|
|
2340
|
+
});
|
|
2341
|
+
const d = desc(cls, methodName);
|
|
2342
|
+
(0, import_common10.Post)(`:id/${sub.childRoute}`)(cls.prototype, methodName, d);
|
|
2343
|
+
(0, import_common10.Param)("id")(cls.prototype, methodName, 0);
|
|
2344
|
+
(0, import_common10.Body)()(cls.prototype, methodName, 1);
|
|
2345
|
+
(0, import_swagger5.ApiOperation)({
|
|
2346
|
+
summary: `Create ${sub.childRoute} for a ${name}`
|
|
2347
|
+
})(cls.prototype, methodName, d);
|
|
2348
|
+
(0, import_swagger5.ApiParam)(ctx.idParamMeta)(cls.prototype, methodName, d);
|
|
2349
|
+
(0, import_swagger5.ApiResponse)({
|
|
2350
|
+
status: 201,
|
|
2351
|
+
description: `${sub.childRoute} created`
|
|
2352
|
+
})(cls.prototype, methodName, d);
|
|
2353
|
+
}, "registerSubResourceCreate");
|
|
2354
|
+
var registerSubResourceFindOne = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2355
|
+
if (!sub.operations?.findOne) return;
|
|
2356
|
+
const { cls } = ctx;
|
|
2357
|
+
const methodName = `findOneChild_${sub.childRoute}`;
|
|
2358
|
+
def(cls, methodName, async function(parentId, childId) {
|
|
2359
|
+
return this.repo.findOneChild(sub, childId, parentId);
|
|
2360
|
+
});
|
|
2361
|
+
const d = desc(cls, methodName);
|
|
2362
|
+
(0, import_common10.Get)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
|
|
2363
|
+
(0, import_common10.Param)("id")(cls.prototype, methodName, 0);
|
|
2364
|
+
(0, import_common10.Param)("childId")(cls.prototype, methodName, 1);
|
|
2365
|
+
(0, import_swagger5.ApiOperation)({
|
|
2366
|
+
summary: `Get a ${sub.childRoute} record`
|
|
2367
|
+
})(cls.prototype, methodName, d);
|
|
2368
|
+
(0, import_swagger5.ApiParam)(ctx.idParamMeta)(cls.prototype, methodName, d);
|
|
2369
|
+
(0, import_swagger5.ApiResponse)({
|
|
2370
|
+
status: 200,
|
|
2371
|
+
description: `${sub.childRoute} record`
|
|
2372
|
+
})(cls.prototype, methodName, d);
|
|
2373
|
+
}, "registerSubResourceFindOne");
|
|
2374
|
+
var registerSubResourceUpdate = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2375
|
+
if (!sub.operations?.update) return;
|
|
2376
|
+
const { cls } = ctx;
|
|
2377
|
+
const methodName = `updateChild_${sub.childRoute}`;
|
|
2378
|
+
def(cls, methodName, async function(_id, childId, body) {
|
|
2379
|
+
return this.repo.updateChild(sub, childId, body);
|
|
2380
|
+
});
|
|
2381
|
+
const d = desc(cls, methodName);
|
|
2382
|
+
(0, import_common10.Patch)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
|
|
2383
|
+
(0, import_common10.Param)("id")(cls.prototype, methodName, 0);
|
|
2384
|
+
(0, import_common10.Param)("childId")(cls.prototype, methodName, 1);
|
|
2385
|
+
(0, import_common10.Body)()(cls.prototype, methodName, 2);
|
|
2386
|
+
(0, import_swagger5.ApiOperation)({
|
|
2387
|
+
summary: `Update a ${sub.childRoute} record`
|
|
2388
|
+
})(cls.prototype, methodName, d);
|
|
2389
|
+
(0, import_swagger5.ApiParam)(ctx.idParamMeta)(cls.prototype, methodName, d);
|
|
2390
|
+
(0, import_swagger5.ApiResponse)({
|
|
2391
|
+
status: 200,
|
|
2392
|
+
description: `${sub.childRoute} updated`
|
|
2393
|
+
})(cls.prototype, methodName, d);
|
|
2394
|
+
}, "registerSubResourceUpdate");
|
|
2395
|
+
var registerSubResourceDelete = /* @__PURE__ */ __name((ctx, sub) => {
|
|
2396
|
+
if (!sub.operations?.delete) return;
|
|
2397
|
+
const { cls } = ctx;
|
|
2398
|
+
const methodName = `deleteChild_${sub.childRoute}`;
|
|
2399
|
+
def(cls, methodName, async function(parentId, childId) {
|
|
2400
|
+
return this.repo.deleteChild(sub, childId, parentId);
|
|
2401
|
+
});
|
|
2402
|
+
const d = desc(cls, methodName);
|
|
2403
|
+
(0, import_common10.Delete)(`:id/${sub.childRoute}/:childId`)(cls.prototype, methodName, d);
|
|
2404
|
+
(0, import_common10.Param)("id")(cls.prototype, methodName, 0);
|
|
2405
|
+
(0, import_common10.Param)("childId")(cls.prototype, methodName, 1);
|
|
2406
|
+
(0, import_swagger5.ApiOperation)({
|
|
2407
|
+
summary: `Delete ${sub.childRoute} record`
|
|
2408
|
+
})(cls.prototype, methodName, d);
|
|
2409
|
+
(0, import_swagger5.ApiParam)(ctx.idParamMeta)(cls.prototype, methodName, d);
|
|
2410
|
+
(0, import_swagger5.ApiResponse)({
|
|
2411
|
+
status: 200,
|
|
2412
|
+
description: `${sub.childRoute} deleted`
|
|
2413
|
+
})(cls.prototype, methodName, d);
|
|
2414
|
+
}, "registerSubResourceDelete");
|
|
2415
|
+
var registerSubResourceRoutes = /* @__PURE__ */ __name((ctx) => {
|
|
2416
|
+
for (const sub of ctx.config.subResources ?? []) {
|
|
2417
|
+
registerSubResourceSchemas(ctx, sub);
|
|
2418
|
+
registerSubResourceFindAll(ctx, sub);
|
|
2419
|
+
registerSubResourceCreate(ctx, sub);
|
|
2420
|
+
registerSubResourceFindOne(ctx, sub);
|
|
2421
|
+
registerSubResourceUpdate(ctx, sub);
|
|
2422
|
+
registerSubResourceDelete(ctx, sub);
|
|
2423
|
+
}
|
|
2424
|
+
}, "registerSubResourceRoutes");
|
|
2425
|
+
|
|
2426
|
+
// src/lib/crud/crud-controller.factory.ts
|
|
2427
|
+
function createCrudController(config, baseUrl) {
|
|
2428
|
+
const { route, name, tag, idType = "string" } = config;
|
|
2429
|
+
const definition = resolveDefinition(config);
|
|
2430
|
+
const listSchema = schemaFor(definition, "findAll");
|
|
2431
|
+
const oneSchema = schemaFor(definition, "findOne") ?? listSchema;
|
|
2432
|
+
const createSchema = schemaFor(definition, "create");
|
|
2433
|
+
const updateSchema = schemaFor(definition, "update");
|
|
2434
|
+
const upsertSchema = schemaFor(definition, "upsert") ?? createSchema;
|
|
2435
|
+
if (isOperationEnabled(definition, "upsert") && !upsertOnFor(definition)) {
|
|
2436
|
+
throw new Error(`Resource "${name}" declares 'upsert' but no upsertOn`);
|
|
2437
|
+
}
|
|
2438
|
+
const bodyDecorator = /* @__PURE__ */ __name((schema, options) => {
|
|
2439
|
+
if (!schema) return (0, import_common12.Body)();
|
|
2440
|
+
if (isZodSchema(schema)) return (0, import_common12.Body)(new ZodValidationPipe(schema, options));
|
|
2441
|
+
return (0, import_common12.Body)();
|
|
2442
|
+
}, "bodyDecorator");
|
|
2443
|
+
let CrudControllerBase = class CrudControllerBase {
|
|
2444
|
+
static {
|
|
2445
|
+
__name(this, "CrudControllerBase");
|
|
2446
|
+
}
|
|
2447
|
+
repo;
|
|
2448
|
+
configRegistry;
|
|
2449
|
+
constructor(registry, configRegistry) {
|
|
2450
|
+
const prisma = registry.resolve(config.database);
|
|
2451
|
+
this.repo = createCrudRepository(prisma, config);
|
|
2452
|
+
this.configRegistry = configRegistry;
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
const ctx = {
|
|
2456
|
+
cls: CrudControllerBase,
|
|
2457
|
+
config,
|
|
2458
|
+
definition,
|
|
2459
|
+
listSchema,
|
|
2460
|
+
oneSchema,
|
|
2461
|
+
createSchema,
|
|
2462
|
+
updateSchema,
|
|
2463
|
+
upsertSchema,
|
|
2464
|
+
idParamMeta: {
|
|
2465
|
+
name: "id",
|
|
2466
|
+
type: idType === "number" ? "number" : "string"
|
|
2467
|
+
},
|
|
2468
|
+
bodyDecorator,
|
|
2469
|
+
baseUrl
|
|
2470
|
+
};
|
|
2471
|
+
registerFindAll(ctx);
|
|
2472
|
+
registerDefinitionEndpoint(ctx);
|
|
2473
|
+
registerSchemasEndpoint(ctx);
|
|
2474
|
+
registerResourceJsonEndpoint(ctx);
|
|
2475
|
+
registerActionRoutes(ctx);
|
|
2476
|
+
registerTableActionRoutes(ctx);
|
|
2477
|
+
registerSubResourceRoutes(ctx);
|
|
2478
|
+
registerFindOne(ctx);
|
|
2479
|
+
registerCreate(ctx);
|
|
2480
|
+
registerUpdate(ctx);
|
|
2481
|
+
registerUpsert(ctx);
|
|
2482
|
+
registerDelete(ctx);
|
|
2483
|
+
(0, import_common11.Controller)(route)(CrudControllerBase);
|
|
2484
|
+
(0, import_swagger6.ApiTags)(tag)(CrudControllerBase);
|
|
2485
|
+
Object.defineProperty(CrudControllerBase, "name", {
|
|
2486
|
+
value: `${name.charAt(0).toUpperCase() + name.slice(1)}Controller`
|
|
2487
|
+
});
|
|
2488
|
+
Reflect.defineMetadata("design:paramtypes", [
|
|
2489
|
+
DataSourceRegistry,
|
|
2490
|
+
ResourceConfigRegistry
|
|
2491
|
+
], CrudControllerBase);
|
|
2492
|
+
return CrudControllerBase;
|
|
2493
|
+
}
|
|
2494
|
+
__name(createCrudController, "createCrudController");
|
|
2495
|
+
|
|
2496
|
+
// src/lib/crud/loader/module.loader.ts
|
|
2497
|
+
var import_node_fs2 = require("fs");
|
|
2498
|
+
var import_node_module = require("module");
|
|
2499
|
+
var import_node_path2 = require("path");
|
|
2500
|
+
var import_meta = {};
|
|
2501
|
+
var _require = (0, import_node_module.createRequire)(import_meta.url);
|
|
2502
|
+
var findModule2 = /* @__PURE__ */ __name((dir, name) => {
|
|
2503
|
+
for (const ext of [
|
|
2504
|
+
".ts",
|
|
2505
|
+
".js"
|
|
2506
|
+
]) {
|
|
2507
|
+
const p = (0, import_node_path2.join)(dir, `${name}${ext}`);
|
|
2508
|
+
if ((0, import_node_fs2.existsSync)(p)) return p;
|
|
2509
|
+
}
|
|
2510
|
+
return void 0;
|
|
2511
|
+
}, "findModule");
|
|
2512
|
+
var IS_VITE = typeof globalThis.__vite_ssr_import__ === "function";
|
|
2513
|
+
var importDefault = /* @__PURE__ */ __name(async (filePath) => {
|
|
2514
|
+
try {
|
|
2515
|
+
if (IS_VITE) {
|
|
2516
|
+
const importPath = IS_DEV ? `${filePath}?t=${Date.now()}` : filePath;
|
|
2517
|
+
const mod = await import(importPath);
|
|
2518
|
+
return mod.default ?? mod;
|
|
2519
|
+
} else {
|
|
2520
|
+
const mod = _require(filePath);
|
|
2521
|
+
return mod.default;
|
|
2522
|
+
}
|
|
2523
|
+
} catch {
|
|
2524
|
+
return void 0;
|
|
2525
|
+
}
|
|
2526
|
+
}, "importDefault");
|
|
2527
|
+
|
|
2528
|
+
// src/lib/crud/loader/action.loader.ts
|
|
2529
|
+
var import_node_path3 = require("path");
|
|
2530
|
+
var loadActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
|
|
2531
|
+
const results = [];
|
|
2532
|
+
for (const action of jsonActions) {
|
|
2533
|
+
if (action.type === "link") {
|
|
2534
|
+
results.push({
|
|
2535
|
+
type: "link",
|
|
2536
|
+
id: action.id,
|
|
2537
|
+
label: action.label,
|
|
2538
|
+
href: action.href,
|
|
2539
|
+
...action.condition && {
|
|
2540
|
+
condition: action.condition
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const file = findModule2((0, import_node_path3.join)(basePath, "actions"), action.procedure);
|
|
2546
|
+
if (!file) {
|
|
2547
|
+
console.warn(`[actions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
|
|
2548
|
+
continue;
|
|
2549
|
+
}
|
|
2550
|
+
const mod = await importDefault(file);
|
|
2551
|
+
if (!mod) {
|
|
2552
|
+
console.warn(`[actions] No default export in ${file}`);
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
results.push({
|
|
2556
|
+
id: action.id,
|
|
2557
|
+
label: action.label,
|
|
2558
|
+
method: action.method,
|
|
2559
|
+
data: action.data,
|
|
2560
|
+
...action.condition && {
|
|
2561
|
+
condition: action.condition
|
|
2562
|
+
},
|
|
2563
|
+
procedure: mod
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
return results;
|
|
2567
|
+
}, "loadActions");
|
|
2568
|
+
var loadTableActions = /* @__PURE__ */ __name(async (jsonActions, basePath) => {
|
|
2569
|
+
const results = [];
|
|
2570
|
+
for (const action of jsonActions) {
|
|
2571
|
+
if (action.type === "link") {
|
|
2572
|
+
results.push({
|
|
2573
|
+
type: "link",
|
|
2574
|
+
id: action.id,
|
|
2575
|
+
label: action.label,
|
|
2576
|
+
icon: action.icon,
|
|
2577
|
+
tooltip: action.tooltip,
|
|
2578
|
+
href: action.href
|
|
2579
|
+
});
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
const file = findModule2((0, import_node_path3.join)(basePath, "actions"), action.procedure);
|
|
2583
|
+
if (!file) {
|
|
2584
|
+
console.warn(`[tableActions] Procedure file not found for "${action.id}" in ${basePath}/actions/`);
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
const mod = await importDefault(file);
|
|
2588
|
+
if (!mod) {
|
|
2589
|
+
console.warn(`[tableActions] No default export in ${file}`);
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
results.push({
|
|
2593
|
+
id: action.id,
|
|
2594
|
+
label: action.label,
|
|
2595
|
+
icon: action.icon,
|
|
2596
|
+
tooltip: action.tooltip,
|
|
2597
|
+
method: action.method,
|
|
2598
|
+
data: action.data,
|
|
2599
|
+
procedure: mod
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
return results;
|
|
2603
|
+
}, "loadTableActions");
|
|
2604
|
+
|
|
2605
|
+
// src/lib/crud/loader/enum-registry.ts
|
|
2606
|
+
var import_node_fs3 = require("fs");
|
|
2607
|
+
var import_node_path4 = require("path");
|
|
2608
|
+
var ENUMS_FILE = "crouton.enums.json";
|
|
2609
|
+
var loadEnumRegistry = /* @__PURE__ */ __name((startDir, enumsFile) => {
|
|
2610
|
+
let file = enumsFile;
|
|
2611
|
+
if (!file) {
|
|
2612
|
+
let dir = startDir;
|
|
2613
|
+
while (true) {
|
|
2614
|
+
const candidate = (0, import_node_path4.join)(dir, ENUMS_FILE);
|
|
2615
|
+
if ((0, import_node_fs3.existsSync)(candidate)) {
|
|
2616
|
+
file = candidate;
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
const parent = (0, import_node_path4.dirname)(dir);
|
|
2620
|
+
if (parent === dir) break;
|
|
2621
|
+
dir = parent;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
if (!file || !(0, import_node_fs3.existsSync)(file)) return {};
|
|
2625
|
+
try {
|
|
2626
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(file, "utf-8"));
|
|
2627
|
+
} catch {
|
|
2628
|
+
return {};
|
|
2629
|
+
}
|
|
2630
|
+
}, "loadEnumRegistry");
|
|
2631
|
+
var injectEnumValues = /* @__PURE__ */ __name((columns, enums) => {
|
|
2632
|
+
if (!columns) return;
|
|
2633
|
+
for (const col of columns) {
|
|
2634
|
+
if (!col.enum) continue;
|
|
2635
|
+
const values = enums[col.enum];
|
|
2636
|
+
if (!values) continue;
|
|
2637
|
+
col.fieldInput = col.fieldInput ?? {
|
|
2638
|
+
type: "select"
|
|
2639
|
+
};
|
|
2640
|
+
const options = col.fieldInput.options ?? {};
|
|
2641
|
+
if (!("values" in options)) options.values = values;
|
|
2642
|
+
col.fieldInput.options = options;
|
|
2643
|
+
}
|
|
2644
|
+
}, "injectEnumValues");
|
|
2645
|
+
|
|
2646
|
+
// src/lib/crud/loader/json-adapter.ts
|
|
2647
|
+
var import_zod9 = require("zod");
|
|
2648
|
+
|
|
2649
|
+
// src/lib/crud/loader/schema.helpers.ts
|
|
2650
|
+
var pickByColumns = /* @__PURE__ */ __name((schema, columns, filter) => {
|
|
2651
|
+
if (!schema) return void 0;
|
|
2652
|
+
if (!columns?.length) return schema;
|
|
2653
|
+
const isRelation2 = /* @__PURE__ */ __name((c) => c.fieldInput?.format === "relation", "isRelation");
|
|
2654
|
+
const baseFilter = /* @__PURE__ */ __name((c) => !isRelation2(c) && (filter ? filter(c) : true), "baseFilter");
|
|
2655
|
+
const filtered = columns.filter(baseFilter);
|
|
2656
|
+
if (!filtered.length) return void 0;
|
|
2657
|
+
const mask = Object.fromEntries(filtered.map((c) => [
|
|
2658
|
+
c.id,
|
|
2659
|
+
true
|
|
2660
|
+
]));
|
|
2661
|
+
return schema.pick(mask);
|
|
2662
|
+
}, "pickByColumns");
|
|
2663
|
+
var opWithSchema = /* @__PURE__ */ __name((enabled, schema) => {
|
|
2664
|
+
if (enabled === false) return void 0;
|
|
2665
|
+
return schema ? {
|
|
2666
|
+
schema
|
|
2667
|
+
} : true;
|
|
2668
|
+
}, "opWithSchema");
|
|
2669
|
+
var upsertOp = /* @__PURE__ */ __name((entry, schema) => {
|
|
2670
|
+
if (!entry) return void 0;
|
|
2671
|
+
if (entry === true) {
|
|
2672
|
+
throw new Error("`operations.upsert` must be an object with `upsertOn`, not `true`.");
|
|
2673
|
+
}
|
|
2674
|
+
if (typeof entry === "object") {
|
|
2675
|
+
return {
|
|
2676
|
+
upsertOn: entry.upsertOn,
|
|
2677
|
+
...schema && {
|
|
2678
|
+
schema
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
return void 0;
|
|
2683
|
+
}, "upsertOp");
|
|
2684
|
+
|
|
2685
|
+
// src/lib/crud/loader/view.builders.ts
|
|
2686
|
+
var import_zod8 = require("zod");
|
|
2687
|
+
|
|
2688
|
+
// src/lib/crud/loader/form-schema.builder.ts
|
|
2689
|
+
var buildConditionSchema = /* @__PURE__ */ __name((when) => {
|
|
2690
|
+
if (when.notExists) return {
|
|
2691
|
+
not: {
|
|
2692
|
+
minLength: 1
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
if (when.exists) return {
|
|
2696
|
+
minLength: 1
|
|
2697
|
+
};
|
|
2698
|
+
if (when.neq !== void 0) return {
|
|
2699
|
+
not: {
|
|
2700
|
+
const: when.neq
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
return {
|
|
2704
|
+
const: when.eq
|
|
2705
|
+
};
|
|
2706
|
+
}, "buildConditionSchema");
|
|
2707
|
+
var buildRule = /* @__PURE__ */ __name((col) => {
|
|
2708
|
+
if (col.disabledWhen) {
|
|
2709
|
+
return {
|
|
2710
|
+
effect: "DISABLE",
|
|
2711
|
+
condition: {
|
|
2712
|
+
scope: `#/properties/${col.disabledWhen.field}`,
|
|
2713
|
+
schema: buildConditionSchema(col.disabledWhen)
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
const when = col.showWhen ?? col.hideWhen;
|
|
2718
|
+
if (!when) return void 0;
|
|
2719
|
+
const effect = col.showWhen ? "SHOW" : "HIDE";
|
|
2720
|
+
return {
|
|
2721
|
+
effect,
|
|
2722
|
+
condition: {
|
|
2723
|
+
scope: `#/properties/${when.field}`,
|
|
2724
|
+
schema: buildConditionSchema(when)
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
}, "buildRule");
|
|
2728
|
+
var buildDetailLayout = /* @__PURE__ */ __name((detail) => {
|
|
2729
|
+
const inner = detail.layout === "collapse" ? LayoutBuilder.collapse() : LayoutBuilder.horizontal();
|
|
2730
|
+
inner.addControls(...detail.controls.map((dc) => {
|
|
2731
|
+
const ctrl = ControlBuilder.properties(dc.property);
|
|
2732
|
+
if (dc.type === "markdown") {
|
|
2733
|
+
ctrl.markdown(dc.options);
|
|
2734
|
+
} else if (dc.type) {
|
|
2735
|
+
ctrl.control(dc.type, dc.options ?? {});
|
|
2736
|
+
}
|
|
2737
|
+
if (dc.hideLabel) ctrl.hideLabel();
|
|
2738
|
+
if (dc.width) ctrl.width(dc.width);
|
|
2739
|
+
return ctrl;
|
|
2740
|
+
}));
|
|
2741
|
+
if (detail.titleKey) inner.titleKey(detail.titleKey);
|
|
2742
|
+
return inner;
|
|
2743
|
+
}, "buildDetailLayout");
|
|
2744
|
+
var buildFormControl = /* @__PURE__ */ __name((col) => {
|
|
2745
|
+
const control = ControlBuilder.properties(col.id);
|
|
2746
|
+
const fieldInput = col.fieldInput;
|
|
2747
|
+
if (fieldInput?.format === "relation") {
|
|
2748
|
+
const options = {
|
|
2749
|
+
...fieldInput.options
|
|
2750
|
+
};
|
|
2751
|
+
if (!options.colspan) options.colspan = 12;
|
|
2752
|
+
if (fieldInput.relationType) options.relationType = fieldInput.relationType;
|
|
2753
|
+
control.control("relation", options).width("full");
|
|
2754
|
+
} else if (fieldInput?.type === "autocomplete") {
|
|
2755
|
+
const options = {
|
|
2756
|
+
...fieldInput.options
|
|
2757
|
+
};
|
|
2758
|
+
if (!options.colspan) options.colspan = 12;
|
|
2759
|
+
if (fieldInput.relationType) options.relationType = fieldInput.relationType;
|
|
2760
|
+
const format = fieldInput.format ?? fieldInput.type;
|
|
2761
|
+
control.control(format, options).width("full");
|
|
2762
|
+
} else if (fieldInput?.detail) {
|
|
2763
|
+
const detailLayout = buildDetailLayout(fieldInput.detail);
|
|
2764
|
+
control.detailFixed(detailLayout, {
|
|
2765
|
+
layout: fieldInput.detail.layout === "collapse" ? "row" : void 0
|
|
2766
|
+
});
|
|
2767
|
+
} else if (fieldInput?.format === "date-range") {
|
|
2768
|
+
const options = {
|
|
2769
|
+
...fieldInput.options
|
|
2770
|
+
};
|
|
2771
|
+
if (!options.colspan) options.colspan = 12;
|
|
2772
|
+
control.control("date-range", options).width("full");
|
|
2773
|
+
} else {
|
|
2774
|
+
const options = fieldInput?.options ?? {};
|
|
2775
|
+
if (!options.colspan) options.colspan = 12;
|
|
2776
|
+
const type = fieldInput?.type ?? "text";
|
|
2777
|
+
control.control(type, options).width("full");
|
|
2778
|
+
}
|
|
2779
|
+
if (fieldInput?.customRender) control.setCustomRender(fieldInput?.customRender);
|
|
2780
|
+
if (col.hideLabel) control.hideLabel();
|
|
2781
|
+
return control;
|
|
2782
|
+
}, "buildFormControl");
|
|
2783
|
+
var buildFormUiSchema = /* @__PURE__ */ __name((cols) => {
|
|
2784
|
+
const layout = LayoutBuilder.grid().addControls(...cols.map(buildFormControl)).build();
|
|
2785
|
+
const colMap = Object.fromEntries(cols.map((c) => [
|
|
2786
|
+
c.id,
|
|
2787
|
+
c
|
|
2788
|
+
]));
|
|
2789
|
+
layout.elements = layout.elements.map((el) => {
|
|
2790
|
+
const id = el.scope?.replace("#/properties/", "");
|
|
2791
|
+
const col = id ? colMap[id] : void 0;
|
|
2792
|
+
if (!col) return el;
|
|
2793
|
+
const rule = buildRule(col);
|
|
2794
|
+
return {
|
|
2795
|
+
...el,
|
|
2796
|
+
options: {
|
|
2797
|
+
...el.options ?? {},
|
|
2798
|
+
label: col.label
|
|
2799
|
+
},
|
|
2800
|
+
...rule && {
|
|
2801
|
+
rule
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
});
|
|
2805
|
+
return layout;
|
|
2806
|
+
}, "buildFormUiSchema");
|
|
2807
|
+
|
|
2808
|
+
// src/lib/crud/loader/schema-transforms.ts
|
|
2809
|
+
var allowAdditionalProperties = /* @__PURE__ */ __name((schema) => {
|
|
2810
|
+
if (schema["type"] === "object") {
|
|
2811
|
+
schema["additionalProperties"] = true;
|
|
2812
|
+
const props = schema["properties"];
|
|
2813
|
+
if (props) {
|
|
2814
|
+
for (const value of Object.values(props)) {
|
|
2815
|
+
allowAdditionalProperties(value);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
if (schema["type"] === "array" && schema["items"]) {
|
|
2820
|
+
allowAdditionalProperties(schema["items"]);
|
|
2821
|
+
}
|
|
2822
|
+
}, "allowAdditionalProperties");
|
|
2823
|
+
var isNullableProperty2 = /* @__PURE__ */ __name((prop) => {
|
|
2824
|
+
const anyOf = prop?.["anyOf"];
|
|
2825
|
+
return Array.isArray(anyOf) && anyOf.some((s) => s?.["type"] === "null");
|
|
2826
|
+
}, "isNullableProperty");
|
|
2827
|
+
var dropNullableFromRequired2 = /* @__PURE__ */ __name((schema) => {
|
|
2828
|
+
if (schema["type"] !== "object") return;
|
|
2829
|
+
const props = schema["properties"];
|
|
2830
|
+
if (!props) return;
|
|
2831
|
+
const required = schema["required"];
|
|
2832
|
+
if (Array.isArray(required)) {
|
|
2833
|
+
schema["required"] = required.filter((key) => !isNullableProperty2(props[key]));
|
|
2834
|
+
}
|
|
2835
|
+
for (const value of Object.values(props)) {
|
|
2836
|
+
dropNullableFromRequired2(value);
|
|
2837
|
+
}
|
|
2838
|
+
}, "dropNullableFromRequired");
|
|
2839
|
+
var enforceRequiredMinLength = /* @__PURE__ */ __name((schema) => {
|
|
2840
|
+
if (schema["type"] !== "object") return;
|
|
2841
|
+
const props = schema["properties"];
|
|
2842
|
+
if (!props) return;
|
|
2843
|
+
for (const prop of Object.values(props)) {
|
|
2844
|
+
if (prop?.["type"] === "string" && !("minLength" in prop)) {
|
|
2845
|
+
prop["minLength"] = 1;
|
|
2846
|
+
} else if (prop?.["type"] === "object") {
|
|
2847
|
+
enforceRequiredMinLength(prop);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}, "enforceRequiredMinLength");
|
|
2851
|
+
|
|
2852
|
+
// src/lib/crud/loader/table-schema.builder.ts
|
|
2853
|
+
var isBoolean = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "boolean" || col.columnType === "boolean", "isBoolean");
|
|
2854
|
+
var isRelation = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "relation", "isRelation");
|
|
2855
|
+
var isAutocomplete = /* @__PURE__ */ __name((col) => col.fieldInput?.type === "autocomplete", "isAutocomplete");
|
|
2856
|
+
var isRecordCell = /* @__PURE__ */ __name((col) => isRelation(col) || isAutocomplete(col), "isRecordCell");
|
|
2857
|
+
var isDateRange = /* @__PURE__ */ __name((col) => col.fieldInput?.format === "date-range", "isDateRange");
|
|
2858
|
+
var deriveSortId = /* @__PURE__ */ __name((col) => {
|
|
2859
|
+
if (col.sortable === false) return null;
|
|
2860
|
+
if (col.sortId) return col.sortId;
|
|
2861
|
+
const base = col.column ?? col.id;
|
|
2862
|
+
const fi = col.fieldInput;
|
|
2863
|
+
const opts = fi?.options ?? {};
|
|
2864
|
+
const isRelation2 = fi?.format === "relation" || !!fi?.relationType || fi?.type === "autocomplete" || typeof opts.resource === "string";
|
|
2865
|
+
if (isRelation2) {
|
|
2866
|
+
const key = (typeof col.displayKey === "string" ? col.displayKey : void 0) ?? (typeof opts.displayKey === "string" ? opts.displayKey : void 0) ?? (typeof opts.labelKey === "string" ? opts.labelKey : void 0);
|
|
2867
|
+
if (!key) return null;
|
|
2868
|
+
const path2 = key.includes(".") ? key : `${base}.${key}`;
|
|
2869
|
+
return path2.endsWith(".label") ? path2.slice(0, -".label".length) : path2;
|
|
2870
|
+
}
|
|
2871
|
+
if (!col.displayKey) return base;
|
|
2872
|
+
const path = `${base}.${col.displayKey}`;
|
|
2873
|
+
const isValueLabel = !!col.enum || opts.emitObject === true;
|
|
2874
|
+
if (isValueLabel && path.endsWith(".label")) return path.slice(0, -".label".length);
|
|
2875
|
+
return path;
|
|
2876
|
+
}, "deriveSortId");
|
|
2877
|
+
var resolveDefaultSort = /* @__PURE__ */ __name((tableCols, allColumns) => {
|
|
2878
|
+
const isSortable = /* @__PURE__ */ __name((c) => c.sortable !== false, "isSortable");
|
|
2879
|
+
const sortCol = tableCols.find((c) => c.defaultSort && isSortable(c)) ?? tableCols.find(isSortable) ?? allColumns?.find((c) => c.idField);
|
|
2880
|
+
return sortCol ? deriveSortId(sortCol) ?? sortCol.id : void 0;
|
|
2881
|
+
}, "resolveDefaultSort");
|
|
2882
|
+
var SHARED_CELL_OPTION_KEYS = [
|
|
2883
|
+
"values",
|
|
2884
|
+
"storeValue",
|
|
2885
|
+
"uri",
|
|
2886
|
+
"resourceUri",
|
|
2887
|
+
"schemasUri"
|
|
2888
|
+
];
|
|
2889
|
+
var pickSharedCellOptions = /* @__PURE__ */ __name((col) => {
|
|
2890
|
+
const options = col.fieldInput?.options ?? {};
|
|
2891
|
+
return Object.fromEntries(SHARED_CELL_OPTION_KEYS.filter((key) => options[key] !== void 0).map((key) => [
|
|
2892
|
+
key,
|
|
2893
|
+
options[key]
|
|
2894
|
+
]));
|
|
2895
|
+
}, "pickSharedCellOptions");
|
|
2896
|
+
var buildTableUiSchema = /* @__PURE__ */ __name((cols) => {
|
|
2897
|
+
const layout = TableBuilder.init().addControls(...cols.map((col) => {
|
|
2898
|
+
const cellBuilder = isBoolean(col) ? BooleanCellBuilder : TextCellBuilder;
|
|
2899
|
+
let builder = cellBuilder.properties(col.id);
|
|
2900
|
+
if (col.displayKey) builder = builder.key(col.displayKey);
|
|
2901
|
+
if (col.sortId) builder = builder.setSortId(col.sortId);
|
|
2902
|
+
return builder;
|
|
2903
|
+
})).build();
|
|
2904
|
+
const colMap = Object.fromEntries(cols.map((c) => [
|
|
2905
|
+
c.id,
|
|
2906
|
+
c
|
|
2907
|
+
]));
|
|
2908
|
+
layout.elements = layout.elements.map((el) => {
|
|
2909
|
+
const id = el.scope?.replace("#/properties/", "");
|
|
2910
|
+
const col = id ? colMap[id] : void 0;
|
|
2911
|
+
if (!col) return el;
|
|
2912
|
+
const fieldInputOptions = isRecordCell(col) || isDateRange(col) ? col.fieldInput?.options ?? {} : pickSharedCellOptions(col);
|
|
2913
|
+
const dataPathOption = col.column ? {
|
|
2914
|
+
dataPath: col.column
|
|
2915
|
+
} : {};
|
|
2916
|
+
const derivedSortId = isRecordCell(col) || isDateRange(col) ? null : deriveSortId(col);
|
|
2917
|
+
const sortOptions = derivedSortId ? {
|
|
2918
|
+
sortId: derivedSortId
|
|
2919
|
+
} : {
|
|
2920
|
+
sortable: false
|
|
2921
|
+
};
|
|
2922
|
+
const relationTypeOption = col.fieldInput?.relationType ? {
|
|
2923
|
+
relationType: col.fieldInput.relationType
|
|
2924
|
+
} : {};
|
|
2925
|
+
return {
|
|
2926
|
+
...el,
|
|
2927
|
+
options: {
|
|
2928
|
+
...el.options ?? {},
|
|
2929
|
+
...fieldInputOptions,
|
|
2930
|
+
...dataPathOption,
|
|
2931
|
+
...sortOptions,
|
|
2932
|
+
...relationTypeOption,
|
|
2933
|
+
...isDateRange(col) && {
|
|
2934
|
+
format: "date-range"
|
|
2935
|
+
},
|
|
2936
|
+
label: col.label
|
|
2937
|
+
},
|
|
2938
|
+
...isRecordCell(col) && {
|
|
2939
|
+
type: "RecordCell"
|
|
2940
|
+
},
|
|
2941
|
+
...isDateRange(col) && {
|
|
2942
|
+
type: "Control"
|
|
2943
|
+
}
|
|
2944
|
+
};
|
|
2945
|
+
});
|
|
2946
|
+
return layout;
|
|
2947
|
+
}, "buildTableUiSchema");
|
|
2948
|
+
|
|
2949
|
+
// src/lib/crud/loader/view.builders.ts
|
|
2950
|
+
var patchFilterProperties = /* @__PURE__ */ __name((jsonSchema, columns) => {
|
|
2951
|
+
if (!columns?.length) return;
|
|
2952
|
+
const properties = jsonSchema.properties;
|
|
2953
|
+
if (!properties) return;
|
|
2954
|
+
for (const col of columns) {
|
|
2955
|
+
if (col.fieldInput?.format === "date-range") {
|
|
2956
|
+
delete properties[col.id];
|
|
2957
|
+
const opts = col.fieldInput?.options ?? {};
|
|
2958
|
+
const base = col.label ?? col.id;
|
|
2959
|
+
const fromField = opts["fromField"] ?? "from";
|
|
2960
|
+
const toField = opts["toField"] ?? "to";
|
|
2961
|
+
const dateProp = /* @__PURE__ */ __name((title) => ({
|
|
2962
|
+
type: "string",
|
|
2963
|
+
format: "date",
|
|
2964
|
+
title,
|
|
2965
|
+
"x-field-type": "date"
|
|
2966
|
+
}), "dateProp");
|
|
2967
|
+
properties[`${col.id}->${fromField}`] = dateProp(opts["fromLabel"] ?? `${base} (from)`);
|
|
2968
|
+
properties[`${col.id}->${toField}`] = dateProp(opts["toLabel"] ?? `${base} (to)`);
|
|
2969
|
+
continue;
|
|
2970
|
+
}
|
|
2971
|
+
const prop = properties[col.id];
|
|
2972
|
+
if (!prop) continue;
|
|
2973
|
+
prop.title = col.label ?? col.id;
|
|
2974
|
+
const values = col.fieldInput?.options?.["values"];
|
|
2975
|
+
if (values) {
|
|
2976
|
+
prop["x-values"] = values;
|
|
2977
|
+
}
|
|
2978
|
+
const ft = col.fieldInput?.type;
|
|
2979
|
+
if (ft === "select" || col.enum) {
|
|
2980
|
+
prop["x-field-type"] = "enum";
|
|
2981
|
+
} else if (ft === "number" || prop.type === "number" || prop.type === "integer") {
|
|
2982
|
+
prop["x-field-type"] = "number";
|
|
2983
|
+
} else if (prop.format === "date-time" || prop.format === "date") {
|
|
2984
|
+
prop["x-field-type"] = "date";
|
|
2985
|
+
} else if (prop.type === "boolean") {
|
|
2986
|
+
prop["x-field-type"] = "boolean";
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}, "patchFilterProperties");
|
|
2990
|
+
var colPosition = /* @__PURE__ */ __name((col, i) => col.fieldInput?.position ?? i, "colPosition");
|
|
2991
|
+
var sortByPosition = /* @__PURE__ */ __name((cols) => cols.map((col, i) => ({
|
|
2992
|
+
col,
|
|
2993
|
+
i
|
|
2994
|
+
})).sort((a, b) => colPosition(a.col, a.i) - colPosition(b.col, b.i)).map(({ col }) => col), "sortByPosition");
|
|
2995
|
+
var toViewColumn = /* @__PURE__ */ __name((col) => ({
|
|
2996
|
+
id: col.id,
|
|
2997
|
+
...col.label && {
|
|
2998
|
+
label: col.label
|
|
2999
|
+
},
|
|
3000
|
+
...col.sortable != null && {
|
|
3001
|
+
sortable: col.sortable
|
|
3002
|
+
},
|
|
3003
|
+
...col.searchable != null && {
|
|
3004
|
+
searchable: col.searchable
|
|
3005
|
+
},
|
|
3006
|
+
...col.fieldInput && {
|
|
3007
|
+
fieldInput: col.fieldInput
|
|
3008
|
+
}
|
|
3009
|
+
}), "toViewColumn");
|
|
3010
|
+
var buildView = /* @__PURE__ */ __name((schema, columns, visible, buildUiSchema, sort = false, schemaVisible) => {
|
|
3011
|
+
if (!schema || !columns?.length) return void 0;
|
|
3012
|
+
const visibleCols = sort ? sortByPosition(columns.filter(visible)) : columns.filter(visible);
|
|
3013
|
+
if (!visibleCols.length) return void 0;
|
|
3014
|
+
const schemaCols = schemaVisible ? columns.filter((c) => visible(c) || schemaVisible(c)) : visibleCols;
|
|
3015
|
+
const schemaIds = schemaCols.map((c) => c.id);
|
|
3016
|
+
const mask = Object.fromEntries(schemaIds.map((id) => [
|
|
3017
|
+
id,
|
|
3018
|
+
true
|
|
3019
|
+
]));
|
|
3020
|
+
const picked = schema.pick(mask);
|
|
3021
|
+
const jsonSchema = (0, import_zod8.toJSONSchema)(picked, {
|
|
3022
|
+
target: "draft-07",
|
|
3023
|
+
...jsonSchemaOpts
|
|
3024
|
+
});
|
|
3025
|
+
allowAdditionalProperties(jsonSchema);
|
|
3026
|
+
dropNullableFromRequired2(jsonSchema);
|
|
3027
|
+
enforceRequiredMinLength(jsonSchema);
|
|
3028
|
+
return {
|
|
3029
|
+
json_schema: jsonSchema,
|
|
3030
|
+
ui_schema: buildUiSchema(visibleCols),
|
|
3031
|
+
columns: visibleCols.map(toViewColumn)
|
|
3032
|
+
};
|
|
3033
|
+
}, "buildView");
|
|
3034
|
+
var isVisibleInMode = /* @__PURE__ */ __name((c, mode) => mode === "table" ? !c.hiddenInTable : !c.hiddenInView, "isVisibleInMode");
|
|
3035
|
+
var buildCalculatedElement = /* @__PURE__ */ __name((c, mode) => mode === "table" ? {
|
|
3036
|
+
type: c.type === "boolean" ? "BooleanCell" : "TextCell",
|
|
3037
|
+
scope: `#/properties/${c.id}`,
|
|
3038
|
+
options: {
|
|
3039
|
+
label: c.label ?? c.id
|
|
3040
|
+
}
|
|
3041
|
+
} : {
|
|
3042
|
+
type: "Control",
|
|
3043
|
+
scope: `#/properties/${c.id}`,
|
|
3044
|
+
options: {
|
|
3045
|
+
// Spread fieldInput.options first (e.g. colspan), then layer type-derived options on top.
|
|
3046
|
+
...c.fieldInput?.options ?? {},
|
|
3047
|
+
label: c.label ?? c.id,
|
|
3048
|
+
...c.type === "boolean" && {
|
|
3049
|
+
format: "boolean"
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}, "buildCalculatedElement");
|
|
3053
|
+
var calcPosition = /* @__PURE__ */ __name((c) => c.fieldInput?.position ?? c.position, "calcPosition");
|
|
3054
|
+
var injectCalculatedColumnsIntoView = /* @__PURE__ */ __name((viewConfig, calculated, mode) => {
|
|
3055
|
+
if (!calculated.length) return viewConfig;
|
|
3056
|
+
const visible = calculated.filter((c) => isVisibleInMode(c, mode));
|
|
3057
|
+
if (!visible.length) return viewConfig;
|
|
3058
|
+
const jsonSchema = {
|
|
3059
|
+
...viewConfig.json_schema
|
|
3060
|
+
};
|
|
3061
|
+
jsonSchema.properties = {
|
|
3062
|
+
...jsonSchema.properties
|
|
3063
|
+
};
|
|
3064
|
+
for (const c of visible) {
|
|
3065
|
+
jsonSchema.properties[c.id] = {
|
|
3066
|
+
type: c.type === "boolean" ? "boolean" : "string",
|
|
3067
|
+
title: c.label ?? c.id
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
const uiSchema = {
|
|
3071
|
+
...viewConfig.ui_schema
|
|
3072
|
+
};
|
|
3073
|
+
const existing = [
|
|
3074
|
+
...uiSchema.elements ?? []
|
|
3075
|
+
];
|
|
3076
|
+
const tagged = [
|
|
3077
|
+
...existing.map((el, i) => ({
|
|
3078
|
+
el,
|
|
3079
|
+
pos: i + 0.5
|
|
3080
|
+
})),
|
|
3081
|
+
...visible.map((c) => ({
|
|
3082
|
+
el: buildCalculatedElement(c, mode),
|
|
3083
|
+
pos: calcPosition(c) ?? Infinity
|
|
3084
|
+
}))
|
|
3085
|
+
];
|
|
3086
|
+
tagged.sort((a, b) => a.pos - b.pos);
|
|
3087
|
+
uiSchema.elements = tagged.map((t) => t.el);
|
|
3088
|
+
const columns = [
|
|
3089
|
+
...viewConfig.columns
|
|
3090
|
+
];
|
|
3091
|
+
for (const c of visible) {
|
|
3092
|
+
const pos = calcPosition(c);
|
|
3093
|
+
const entry = {
|
|
3094
|
+
id: c.id,
|
|
3095
|
+
label: c.label
|
|
3096
|
+
};
|
|
3097
|
+
if (pos !== void 0) {
|
|
3098
|
+
columns.splice(pos, 0, entry);
|
|
3099
|
+
} else {
|
|
3100
|
+
columns.push(entry);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
return {
|
|
3104
|
+
...viewConfig,
|
|
3105
|
+
json_schema: jsonSchema,
|
|
3106
|
+
ui_schema: uiSchema,
|
|
3107
|
+
columns
|
|
3108
|
+
};
|
|
3109
|
+
}, "injectCalculatedColumnsIntoView");
|
|
3110
|
+
var injectCalculatedColumns = /* @__PURE__ */ __name((tableView, calculated) => injectCalculatedColumnsIntoView(tableView, calculated, "table"), "injectCalculatedColumns");
|
|
3111
|
+
var injectCalculatedColumnsToView = /* @__PURE__ */ __name((viewConfig, calculated) => injectCalculatedColumnsIntoView(viewConfig, calculated, "view"), "injectCalculatedColumnsToView");
|
|
3112
|
+
var emptyTableView = /* @__PURE__ */ __name(() => ({
|
|
3113
|
+
json_schema: {
|
|
3114
|
+
type: "object",
|
|
3115
|
+
additionalProperties: true,
|
|
3116
|
+
properties: {}
|
|
3117
|
+
},
|
|
3118
|
+
ui_schema: buildTableUiSchema([]),
|
|
3119
|
+
columns: []
|
|
3120
|
+
}), "emptyTableView");
|
|
3121
|
+
var buildViews = /* @__PURE__ */ __name((schema, columns) => {
|
|
3122
|
+
const views = {};
|
|
3123
|
+
const table = buildView(schema, columns, (c) => !c.hiddenInTable, buildTableUiSchema);
|
|
3124
|
+
if (table) {
|
|
3125
|
+
table.defaultSort = resolveDefaultSort(table.columns, columns);
|
|
3126
|
+
views.table = table;
|
|
3127
|
+
} else if (columns?.length) {
|
|
3128
|
+
views.table = emptyTableView();
|
|
3129
|
+
}
|
|
3130
|
+
const form = buildView(schema, columns, (c) => !c.hiddenInForm, buildFormUiSchema, true, (c) => c.createable === true || c.updateable === true);
|
|
3131
|
+
if (form) views.form = form;
|
|
3132
|
+
const filter = buildView(schema, columns, (c) => !!c.filterable, buildFormUiSchema, true);
|
|
3133
|
+
if (filter) {
|
|
3134
|
+
patchFilterProperties(filter.json_schema, columns?.filter((c) => !!c.filterable));
|
|
3135
|
+
views.filter = filter;
|
|
3136
|
+
}
|
|
3137
|
+
const view = buildView(schema, columns, (c) => !c.hiddenInView, buildFormUiSchema, true);
|
|
3138
|
+
if (view) views.view = view;
|
|
3139
|
+
return Object.keys(views).length ? views : void 0;
|
|
3140
|
+
}, "buildViews");
|
|
3141
|
+
var buildViewsFromColumns = /* @__PURE__ */ __name((columns) => {
|
|
3142
|
+
if (!columns?.length) return void 0;
|
|
3143
|
+
const buildJsonSchema = /* @__PURE__ */ __name((cols) => {
|
|
3144
|
+
const properties = {};
|
|
3145
|
+
for (const c of cols) {
|
|
3146
|
+
if (c.column) {
|
|
3147
|
+
if (!properties[c.column]) {
|
|
3148
|
+
properties[c.column] = {
|
|
3149
|
+
type: "object",
|
|
3150
|
+
additionalProperties: true,
|
|
3151
|
+
properties: {}
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
const keyPath = (c.displayKey ?? c.id).split(".");
|
|
3155
|
+
let target = properties[c.column].properties;
|
|
3156
|
+
for (let i = 0; i < keyPath.length - 1; i++) {
|
|
3157
|
+
const segment = keyPath[i];
|
|
3158
|
+
if (!target[segment]) {
|
|
3159
|
+
target[segment] = {
|
|
3160
|
+
type: "object",
|
|
3161
|
+
additionalProperties: true,
|
|
3162
|
+
properties: {}
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
target = target[segment].properties;
|
|
3166
|
+
}
|
|
3167
|
+
target[keyPath[keyPath.length - 1]] = {
|
|
3168
|
+
type: "string",
|
|
3169
|
+
title: c.label ?? c.id
|
|
3170
|
+
};
|
|
3171
|
+
} else {
|
|
3172
|
+
properties[c.id] = {
|
|
3173
|
+
type: c.columnType ?? "string",
|
|
3174
|
+
title: c.label ?? c.id
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return {
|
|
3179
|
+
type: "object",
|
|
3180
|
+
additionalProperties: true,
|
|
3181
|
+
properties
|
|
3182
|
+
};
|
|
3183
|
+
}, "buildJsonSchema");
|
|
3184
|
+
const fixNestedScopes = /* @__PURE__ */ __name((uiSchema, cols) => {
|
|
3185
|
+
const colMap = Object.fromEntries(cols.map((c) => [
|
|
3186
|
+
c.id,
|
|
3187
|
+
c
|
|
3188
|
+
]));
|
|
3189
|
+
const elements = uiSchema.elements;
|
|
3190
|
+
if (!elements) return uiSchema;
|
|
3191
|
+
return {
|
|
3192
|
+
...uiSchema,
|
|
3193
|
+
elements: elements.map((el) => {
|
|
3194
|
+
const id = el.scope?.replace("#/properties/", "");
|
|
3195
|
+
const col = id ? colMap[id] : void 0;
|
|
3196
|
+
if (!col?.column) return el;
|
|
3197
|
+
const keyPath = (col.displayKey ?? col.id).split(".");
|
|
3198
|
+
const propPath = keyPath.map((k) => `properties/${k}`).join("/");
|
|
3199
|
+
return {
|
|
3200
|
+
...el,
|
|
3201
|
+
scope: `#/properties/${col.column}/${propPath}`
|
|
3202
|
+
};
|
|
3203
|
+
})
|
|
3204
|
+
};
|
|
3205
|
+
}, "fixNestedScopes");
|
|
3206
|
+
const makeView = /* @__PURE__ */ __name((visible, buildUiSchema) => {
|
|
3207
|
+
if (!visible.length) return void 0;
|
|
3208
|
+
return {
|
|
3209
|
+
json_schema: buildJsonSchema(visible),
|
|
3210
|
+
ui_schema: fixNestedScopes(buildUiSchema(visible), visible),
|
|
3211
|
+
columns: visible.map(toViewColumn)
|
|
3212
|
+
};
|
|
3213
|
+
}, "makeView");
|
|
3214
|
+
const views = {};
|
|
3215
|
+
const tableCols = sortByPosition(columns.filter((c) => !c.hiddenInTable));
|
|
3216
|
+
const table = makeView(tableCols, buildTableUiSchema);
|
|
3217
|
+
if (table) {
|
|
3218
|
+
table.defaultSort = resolveDefaultSort(tableCols, columns);
|
|
3219
|
+
views.table = table;
|
|
3220
|
+
} else {
|
|
3221
|
+
views.table = emptyTableView();
|
|
3222
|
+
}
|
|
3223
|
+
const formCols = sortByPosition(columns.filter((c) => !c.hiddenInForm));
|
|
3224
|
+
const form = makeView(formCols, buildFormUiSchema);
|
|
3225
|
+
if (form) views.form = form;
|
|
3226
|
+
const viewCols = sortByPosition(columns.filter((c) => !c.hiddenInView));
|
|
3227
|
+
const viewView = makeView(viewCols, buildFormUiSchema);
|
|
3228
|
+
if (viewView) views.view = viewView;
|
|
3229
|
+
return Object.keys(views).length ? views : void 0;
|
|
3230
|
+
}, "buildViewsFromColumns");
|
|
3231
|
+
|
|
3232
|
+
// src/lib/crud/loader/json-adapter.ts
|
|
3233
|
+
var import_node_fs4 = require("fs");
|
|
3234
|
+
var import_node_path5 = require("path");
|
|
3235
|
+
var resolveChildResource = /* @__PURE__ */ __name((resourcePath, parentDir) => {
|
|
3236
|
+
const directPath = (0, import_node_path5.resolve)(parentDir, resourcePath);
|
|
3237
|
+
if (resourcePath.endsWith(".json") && (0, import_node_fs4.existsSync)(directPath)) {
|
|
3238
|
+
try {
|
|
3239
|
+
return {
|
|
3240
|
+
json: JSON.parse((0, import_node_fs4.readFileSync)(directPath, "utf-8")),
|
|
3241
|
+
dir: (0, import_node_path5.dirname)(directPath)
|
|
3242
|
+
};
|
|
3243
|
+
} catch {
|
|
3244
|
+
return void 0;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
const childName = resourcePath.replace(/^\.\//, "").replace(/\.resource$/, "");
|
|
3248
|
+
const childJsonPath = (0, import_node_path5.resolve)((0, import_node_path5.dirname)(parentDir), childName, "resource.json");
|
|
3249
|
+
if (!(0, import_node_fs4.existsSync)(childJsonPath)) return void 0;
|
|
3250
|
+
try {
|
|
3251
|
+
return {
|
|
3252
|
+
json: JSON.parse((0, import_node_fs4.readFileSync)(childJsonPath, "utf-8")),
|
|
3253
|
+
dir: (0, import_node_path5.dirname)(childJsonPath)
|
|
3254
|
+
};
|
|
3255
|
+
} catch {
|
|
3256
|
+
return void 0;
|
|
3257
|
+
}
|
|
3258
|
+
}, "resolveChildResource");
|
|
3259
|
+
var expandExtendColumns = /* @__PURE__ */ __name((columns, dirPath) => {
|
|
3260
|
+
if (!dirPath) return columns;
|
|
3261
|
+
const result = [];
|
|
3262
|
+
for (const col of columns) {
|
|
3263
|
+
if (!col.extend) {
|
|
3264
|
+
result.push(col);
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3267
|
+
const resolved = resolveChildResource(col.extend, dirPath);
|
|
3268
|
+
if (!resolved) {
|
|
3269
|
+
console.warn(`[extend] Could not resolve "${col.extend}" for column "${col.id}" \u2014 keeping as-is`);
|
|
3270
|
+
result.push(col);
|
|
3271
|
+
continue;
|
|
3272
|
+
}
|
|
3273
|
+
const refColumns = normalizeColumns(resolved.json.columns) ?? [];
|
|
3274
|
+
const parentColumnKey = col.column ?? col.id;
|
|
3275
|
+
for (const refCol of refColumns) {
|
|
3276
|
+
if (refCol.idField) continue;
|
|
3277
|
+
const virtualId = `${col.id}_${refCol.id}`;
|
|
3278
|
+
const displayKey = refCol.displayKey ? `${refCol.id}.${refCol.displayKey}` : refCol.id;
|
|
3279
|
+
const hiddenInTable = col.hiddenInTable === true || refCol.hiddenInTable === true ? true : col.hiddenInTable ?? refCol.hiddenInTable;
|
|
3280
|
+
const hiddenInForm = col.hiddenInForm === true || refCol.hiddenInForm === true ? true : col.hiddenInForm ?? refCol.hiddenInForm;
|
|
3281
|
+
const hiddenInView = col.hiddenInView === true || refCol.hiddenInView === true ? true : col.hiddenInView ?? refCol.hiddenInView;
|
|
3282
|
+
const override = col.columns?.[virtualId] ?? col.columns?.[refCol.id] ?? {};
|
|
3283
|
+
const virtualCol = {
|
|
3284
|
+
id: virtualId,
|
|
3285
|
+
column: parentColumnKey,
|
|
3286
|
+
displayKey,
|
|
3287
|
+
label: refCol.label ?? refCol.id,
|
|
3288
|
+
columnType: "object",
|
|
3289
|
+
...hiddenInTable !== void 0 && {
|
|
3290
|
+
hiddenInTable
|
|
3291
|
+
},
|
|
3292
|
+
...hiddenInForm !== void 0 && {
|
|
3293
|
+
hiddenInForm
|
|
3294
|
+
},
|
|
3295
|
+
...hiddenInView !== void 0 && {
|
|
3296
|
+
hiddenInView
|
|
3297
|
+
},
|
|
3298
|
+
...refCol.sortable != null && {
|
|
3299
|
+
sortable: refCol.sortable
|
|
3300
|
+
},
|
|
3301
|
+
...refCol.fieldInput && {
|
|
3302
|
+
fieldInput: refCol.fieldInput
|
|
3303
|
+
},
|
|
3304
|
+
...override
|
|
3305
|
+
};
|
|
3306
|
+
result.push(virtualCol);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return result;
|
|
3310
|
+
}, "expandExtendColumns");
|
|
3311
|
+
var buildValueLabelColumns = /* @__PURE__ */ __name((columns) => (columns ?? []).flatMap((c) => {
|
|
3312
|
+
const opts = c.fieldInput?.options;
|
|
3313
|
+
if (!opts?.emitObject || !Array.isArray(opts.values)) return [];
|
|
3314
|
+
return [
|
|
3315
|
+
{
|
|
3316
|
+
field: c.column ?? c.id,
|
|
3317
|
+
values: opts.values
|
|
3318
|
+
}
|
|
3319
|
+
];
|
|
3320
|
+
}), "buildValueLabelColumns");
|
|
3321
|
+
var applyRelationFormatDefault = /* @__PURE__ */ __name((cols) => cols?.map((col) => {
|
|
3322
|
+
const fi = col.fieldInput;
|
|
3323
|
+
if (fi && fi.resource && !fi.format && !fi.type) {
|
|
3324
|
+
return {
|
|
3325
|
+
...col,
|
|
3326
|
+
fieldInput: {
|
|
3327
|
+
...fi,
|
|
3328
|
+
format: "relation"
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
return col;
|
|
3333
|
+
}), "applyRelationFormatDefault");
|
|
3334
|
+
var deriveRelationTypeFromColumns = /* @__PURE__ */ __name((col, cols) => {
|
|
3335
|
+
const base = col.column ?? col.id;
|
|
3336
|
+
const fkNames = /* @__PURE__ */ new Set([
|
|
3337
|
+
`${base}_id`,
|
|
3338
|
+
`${col.id}_id`
|
|
3339
|
+
]);
|
|
3340
|
+
return cols.some((c) => fkNames.has(c.id)) ? "manyToOne" : "oneToMany";
|
|
3341
|
+
}, "deriveRelationTypeFromColumns");
|
|
3342
|
+
var enrichNestedRelationColumns = /* @__PURE__ */ __name((cols, dir, baseUrl) => {
|
|
3343
|
+
if (!cols || !dir) return cols;
|
|
3344
|
+
const base = baseUrl ?? "";
|
|
3345
|
+
return cols.map((col) => {
|
|
3346
|
+
if (col.fieldInput?.format !== "relation" || !col.fieldInput.resource) return col;
|
|
3347
|
+
const resolved = resolveChildResource(col.fieldInput.resource, dir);
|
|
3348
|
+
const targetRoute = resolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\/resource\.json$/, "").replace(/\.resource$/, "");
|
|
3349
|
+
const relationType = col.fieldInput.relationType ?? deriveRelationTypeFromColumns(col, cols);
|
|
3350
|
+
return {
|
|
3351
|
+
...col,
|
|
3352
|
+
fieldInput: {
|
|
3353
|
+
...col.fieldInput,
|
|
3354
|
+
relationType,
|
|
3355
|
+
options: {
|
|
3356
|
+
...col.fieldInput.options,
|
|
3357
|
+
uri: `${base}/${targetRoute}`,
|
|
3358
|
+
resourceUri: `${base}/${targetRoute}`,
|
|
3359
|
+
resource: `${base}/${targetRoute}/schemas`
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3363
|
+
});
|
|
3364
|
+
}, "enrichNestedRelationColumns");
|
|
3365
|
+
var buildSubResources = /* @__PURE__ */ __name((columns, parentRoute, parentModel, parentDir, enums = {}, baseUrl) => {
|
|
3366
|
+
if (!columns || !parentDir) return [];
|
|
3367
|
+
return columns.filter((c) => c.fieldInput?.format === "relation" && c.fieldInput?.resource).map((c) => {
|
|
3368
|
+
const childResolved = resolveChildResource(c.fieldInput.resource, parentDir);
|
|
3369
|
+
const childJson = childResolved?.json;
|
|
3370
|
+
const childDir = childResolved?.dir;
|
|
3371
|
+
const childRoute = childJson?.route ?? c.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
|
|
3372
|
+
const rawChildColumns = childJson ? normalizeColumns(childJson.columns) : void 0;
|
|
3373
|
+
const expandedChildColumns = rawChildColumns ? expandExtendColumns(rawChildColumns, childDir) : void 0;
|
|
3374
|
+
const childColumns = applyRelationFormatDefault(expandedChildColumns) ?? expandedChildColumns;
|
|
3375
|
+
injectEnumValues(childColumns, enums);
|
|
3376
|
+
const enrichedChildColumns = enrichNestedRelationColumns(childColumns, childDir, baseUrl);
|
|
3377
|
+
const childLookupKey = childColumns?.find((col) => col.idField)?.id ?? "id";
|
|
3378
|
+
const childCalculatedColumns = childJson?.calculatedColumns ?? [];
|
|
3379
|
+
let childViews = childJson ? buildViewsFromColumns(enrichedChildColumns) : void 0;
|
|
3380
|
+
if (childViews && childCalculatedColumns.length) {
|
|
3381
|
+
childViews = {
|
|
3382
|
+
...childViews,
|
|
3383
|
+
table: injectCalculatedColumns(childViews.table, childCalculatedColumns)
|
|
3384
|
+
};
|
|
3385
|
+
if (childViews.view) {
|
|
3386
|
+
childViews = {
|
|
3387
|
+
...childViews,
|
|
3388
|
+
view: injectCalculatedColumnsToView(childViews.view, childCalculatedColumns)
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
const childOps = childJson?.operations ?? {};
|
|
3393
|
+
return {
|
|
3394
|
+
column: c.id,
|
|
3395
|
+
relation: c.id,
|
|
3396
|
+
childRoute,
|
|
3397
|
+
childModel: c.id,
|
|
3398
|
+
foreignKey: `${parentModel}_id`,
|
|
3399
|
+
name: childJson?.name ?? childRoute,
|
|
3400
|
+
title: childJson?.title ?? childJson?.tag ?? childRoute,
|
|
3401
|
+
idField: childLookupKey,
|
|
3402
|
+
idType: childJson?.idType ?? "string",
|
|
3403
|
+
...childViews && {
|
|
3404
|
+
views: childViews
|
|
3405
|
+
},
|
|
3406
|
+
operations: {
|
|
3407
|
+
findAll: childOps.findAll !== false,
|
|
3408
|
+
findOne: childOps.findOne !== false,
|
|
3409
|
+
create: childOps.create !== false,
|
|
3410
|
+
update: childOps.update !== false,
|
|
3411
|
+
delete: childOps.delete !== false
|
|
3412
|
+
},
|
|
3413
|
+
...childJson?.actions?.length && {
|
|
3414
|
+
actions: childJson.actions
|
|
3415
|
+
},
|
|
3416
|
+
...childJson?.modalSize && {
|
|
3417
|
+
modalSize: childJson.modalSize
|
|
3418
|
+
},
|
|
3419
|
+
...childJson?.include?.length && {
|
|
3420
|
+
include: childJson.include
|
|
3421
|
+
},
|
|
3422
|
+
...childJson?.calculatedColumns?.length && {
|
|
3423
|
+
calculatedColumns: childJson.calculatedColumns
|
|
3424
|
+
},
|
|
3425
|
+
...(c.hiddenInForm === false || c.hiddenInView === false) && {
|
|
3426
|
+
includeInFindOne: true
|
|
3427
|
+
},
|
|
3428
|
+
...buildValueLabelColumns(childColumns).length && {
|
|
3429
|
+
valueLabelColumns: buildValueLabelColumns(childColumns)
|
|
3430
|
+
}
|
|
3431
|
+
};
|
|
3432
|
+
});
|
|
3433
|
+
}, "buildSubResources");
|
|
3434
|
+
var enrichActionColumns = /* @__PURE__ */ __name((columns, parentRoute, subResources, baseUrl) => {
|
|
3435
|
+
if (!columns) return columns;
|
|
3436
|
+
const base = baseUrl ?? "";
|
|
3437
|
+
return columns.map((col) => {
|
|
3438
|
+
if (col.fieldInput?.format !== "relation") return col;
|
|
3439
|
+
const sub = subResources.find((s) => s.column === col.id);
|
|
3440
|
+
if (!sub) return col;
|
|
3441
|
+
return {
|
|
3442
|
+
...col,
|
|
3443
|
+
fieldInput: {
|
|
3444
|
+
...col.fieldInput,
|
|
3445
|
+
options: {
|
|
3446
|
+
...col.fieldInput.options,
|
|
3447
|
+
uri: `${base}/${parentRoute}/{id}/${sub.childRoute}`,
|
|
3448
|
+
resourceUri: `${base}/${sub.childRoute}`,
|
|
3449
|
+
...sub.views && {
|
|
3450
|
+
resource: `${base}/${parentRoute}/${sub.childRoute}/schemas`
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
});
|
|
3456
|
+
}, "enrichActionColumns");
|
|
3457
|
+
var enrichResourceRefColumns = /* @__PURE__ */ __name((columns, parentDir, baseUrl) => {
|
|
3458
|
+
if (!columns || !parentDir) return columns;
|
|
3459
|
+
const base = baseUrl ?? "";
|
|
3460
|
+
return columns.map((col) => {
|
|
3461
|
+
if (!col.fieldInput?.resource || col.fieldInput.format === "relation") return col;
|
|
3462
|
+
const childResolved = resolveChildResource(col.fieldInput.resource, parentDir);
|
|
3463
|
+
const childRoute = childResolved?.json?.route ?? col.fieldInput.resource.replace(/^\.\//, "").replace(/\.resource$/, "");
|
|
3464
|
+
return {
|
|
3465
|
+
...col,
|
|
3466
|
+
fieldInput: {
|
|
3467
|
+
...col.fieldInput,
|
|
3468
|
+
options: {
|
|
3469
|
+
...col.fieldInput.options,
|
|
3470
|
+
resourceUri: `${base}/${childRoute}`,
|
|
3471
|
+
schemasUri: `${base}/${childRoute}/schemas`
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
});
|
|
3476
|
+
}, "enrichResourceRefColumns");
|
|
3477
|
+
var enrichIncludeWithSort = /* @__PURE__ */ __name((include, columns) => {
|
|
3478
|
+
if (!include?.length) return include;
|
|
3479
|
+
return include.map((entry) => {
|
|
3480
|
+
const relationName = typeof entry === "string" ? entry : entry.relation;
|
|
3481
|
+
const col = columns.find((c) => {
|
|
3482
|
+
const opts2 = c.fieldInput?.options;
|
|
3483
|
+
return (c.column ?? c.id) === relationName && opts2?.sort;
|
|
3484
|
+
});
|
|
3485
|
+
if (!col) return entry;
|
|
3486
|
+
const opts = col.fieldInput.options;
|
|
3487
|
+
const orderBy = buildChildSortClause(opts.sort, opts.sortDir ?? "asc");
|
|
3488
|
+
return typeof entry === "string" ? {
|
|
3489
|
+
relation: entry,
|
|
3490
|
+
orderBy
|
|
3491
|
+
} : {
|
|
3492
|
+
...entry,
|
|
3493
|
+
orderBy
|
|
3494
|
+
};
|
|
3495
|
+
});
|
|
3496
|
+
}, "enrichIncludeWithSort");
|
|
3497
|
+
var fromJson = /* @__PURE__ */ __name((json, schema, hooks, dirPath, baseUrl, actions, tableActions, enums = {}) => {
|
|
3498
|
+
const rawColumns = expandExtendColumns(normalizeColumns(json.columns) ?? [], dirPath);
|
|
3499
|
+
const columns = enrichRelationTypes(applyRelationFormatDefault(rawColumns) ?? rawColumns, schema);
|
|
3500
|
+
injectEnumValues(columns, enums);
|
|
3501
|
+
const subResources = buildSubResources(columns, json.route, json.model, dirPath, enums, baseUrl);
|
|
3502
|
+
const enrichedColumns = enrichResourceRefColumns(enrichActionColumns(columns, json.route, subResources, baseUrl), dirPath, baseUrl) ?? columns;
|
|
3503
|
+
const calculatedColumns = json.calculatedColumns ?? [];
|
|
3504
|
+
const picked = pickByColumns(schema, enrichedColumns);
|
|
3505
|
+
const createSchema = pickByColumns(schema, enrichedColumns, (c) => !c.idField && c.createable !== false);
|
|
3506
|
+
const updateSchema = pickByColumns(schema, enrichedColumns, (c) => !c.idField && c.updateable !== false);
|
|
3507
|
+
let views = buildViews(schema, enrichedColumns);
|
|
3508
|
+
if (views && calculatedColumns.length) {
|
|
3509
|
+
views = {
|
|
3510
|
+
...views,
|
|
3511
|
+
table: injectCalculatedColumns(views.table, calculatedColumns)
|
|
3512
|
+
};
|
|
3513
|
+
if (views.view) {
|
|
3514
|
+
views = {
|
|
3515
|
+
...views,
|
|
3516
|
+
view: injectCalculatedColumnsToView(views.view, calculatedColumns)
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
const lookup = buildLookup(enrichedColumns);
|
|
3521
|
+
const enrichedInclude = enrichIncludeWithSort(json.include, enrichedColumns);
|
|
3522
|
+
const definition = {
|
|
3523
|
+
...opWithSchema(json.operations.findAll, picked) && {
|
|
3524
|
+
findAll: opWithSchema(json.operations.findAll, picked)
|
|
3525
|
+
},
|
|
3526
|
+
...opWithSchema(json.operations.findOne, picked) && {
|
|
3527
|
+
findOne: opWithSchema(json.operations.findOne, picked)
|
|
3528
|
+
},
|
|
3529
|
+
...opWithSchema(json.operations.create, createSchema) && {
|
|
3530
|
+
create: opWithSchema(json.operations.create, createSchema)
|
|
3531
|
+
},
|
|
3532
|
+
...opWithSchema(json.operations.update, updateSchema) && {
|
|
3533
|
+
update: opWithSchema(json.operations.update, updateSchema)
|
|
3534
|
+
},
|
|
3535
|
+
...upsertOp(json.operations.upsert, createSchema) && {
|
|
3536
|
+
upsert: upsertOp(json.operations.upsert, createSchema)
|
|
3537
|
+
},
|
|
3538
|
+
...json.operations.delete !== false && {
|
|
3539
|
+
delete: true
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
const display = {
|
|
3543
|
+
mode: json.display?.mode === "page" ? "page" : "modal",
|
|
3544
|
+
customComponent: json.display?.customComponent ?? null
|
|
3545
|
+
};
|
|
3546
|
+
return {
|
|
3547
|
+
name: json.name,
|
|
3548
|
+
route: json.route,
|
|
3549
|
+
model: json.model,
|
|
3550
|
+
tag: json.tag,
|
|
3551
|
+
display,
|
|
3552
|
+
...json.title && {
|
|
3553
|
+
title: json.title
|
|
3554
|
+
},
|
|
3555
|
+
...json.sidebar && {
|
|
3556
|
+
sidebar: json.sidebar
|
|
3557
|
+
},
|
|
3558
|
+
...json.idType && {
|
|
3559
|
+
idType: json.idType
|
|
3560
|
+
},
|
|
3561
|
+
...lookup?.key && lookup.key !== "id" && {
|
|
3562
|
+
idField: lookup.key
|
|
3563
|
+
},
|
|
3564
|
+
...json.database && {
|
|
3565
|
+
database: json.database
|
|
3566
|
+
},
|
|
3567
|
+
...hooks && {
|
|
3568
|
+
hooks
|
|
3569
|
+
},
|
|
3570
|
+
definition,
|
|
3571
|
+
...views && {
|
|
3572
|
+
views
|
|
3573
|
+
},
|
|
3574
|
+
...lookup && {
|
|
3575
|
+
lookup
|
|
3576
|
+
},
|
|
3577
|
+
...subResources.length && {
|
|
3578
|
+
subResources
|
|
3579
|
+
},
|
|
3580
|
+
...calculatedColumns.length && {
|
|
3581
|
+
calculatedColumns
|
|
3582
|
+
},
|
|
3583
|
+
...actions?.length && {
|
|
3584
|
+
actions
|
|
3585
|
+
},
|
|
3586
|
+
...tableActions?.length && {
|
|
3587
|
+
tableActions
|
|
3588
|
+
},
|
|
3589
|
+
...enrichedInclude?.length && {
|
|
3590
|
+
include: enrichedInclude
|
|
3591
|
+
},
|
|
3592
|
+
...json.modalSize && {
|
|
3593
|
+
modalSize: json.modalSize
|
|
3594
|
+
},
|
|
3595
|
+
...buildValueLabelColumns(enrichedColumns).length && {
|
|
3596
|
+
valueLabelColumns: buildValueLabelColumns(enrichedColumns)
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
}, "fromJson");
|
|
3600
|
+
var unwrapZodType = /* @__PURE__ */ __name((type) => {
|
|
3601
|
+
if (type instanceof import_zod9.ZodOptional || type instanceof import_zod9.ZodNullable) {
|
|
3602
|
+
return unwrapZodType(type.unwrap());
|
|
3603
|
+
}
|
|
3604
|
+
return type;
|
|
3605
|
+
}, "unwrapZodType");
|
|
3606
|
+
var deriveRelationType = /* @__PURE__ */ __name((schema, columnId) => {
|
|
3607
|
+
if (!schema) return void 0;
|
|
3608
|
+
const field = schema.shape[columnId];
|
|
3609
|
+
if (!field) return void 0;
|
|
3610
|
+
const inner = unwrapZodType(field);
|
|
3611
|
+
return inner instanceof import_zod9.ZodArray ? "oneToMany" : "manyToOne";
|
|
3612
|
+
}, "deriveRelationType");
|
|
3613
|
+
var enrichRelationTypes = /* @__PURE__ */ __name((columns, schema) => {
|
|
3614
|
+
if (!schema) return columns;
|
|
3615
|
+
return columns.map((col) => {
|
|
3616
|
+
const fi = col.fieldInput;
|
|
3617
|
+
if (!fi) return col;
|
|
3618
|
+
const isRelationField = fi.type === "autocomplete" || fi.format === "relation";
|
|
3619
|
+
if (!isRelationField) return col;
|
|
3620
|
+
if (fi.relationType) return col;
|
|
3621
|
+
const derived = deriveRelationType(schema, col.id) ?? deriveRelationTypeFromColumns(col, columns);
|
|
3622
|
+
if (!derived) return col;
|
|
3623
|
+
return {
|
|
3624
|
+
...col,
|
|
3625
|
+
fieldInput: {
|
|
3626
|
+
...fi,
|
|
3627
|
+
relationType: derived
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
});
|
|
3631
|
+
}, "enrichRelationTypes");
|
|
3632
|
+
var buildLookup = /* @__PURE__ */ __name((columns) => {
|
|
3633
|
+
if (!columns) return void 0;
|
|
3634
|
+
const keyCol = columns.find((c) => c.idField);
|
|
3635
|
+
const labelCol = columns.find((c) => c.showInLookup) ?? columns.find((c) => c.searchable);
|
|
3636
|
+
if (!keyCol && !labelCol) return void 0;
|
|
3637
|
+
return {
|
|
3638
|
+
key: keyCol?.id ?? "id",
|
|
3639
|
+
...labelCol && {
|
|
3640
|
+
label: labelCol.id
|
|
3641
|
+
}
|
|
3642
|
+
};
|
|
3643
|
+
}, "buildLookup");
|
|
3644
|
+
|
|
3645
|
+
// src/lib/crud/loader/index.ts
|
|
3646
|
+
var import_node_fs5 = require("fs");
|
|
3647
|
+
var import_node_path6 = require("path");
|
|
3648
|
+
var loadSubResourceHooks = /* @__PURE__ */ __name(async (subResources, basePath) => {
|
|
3649
|
+
for (const sub of subResources) {
|
|
3650
|
+
const file = sub.name ? findModule2((0, import_node_path6.join)(basePath, "hooks"), sub.name) : void 0;
|
|
3651
|
+
if (!file) continue;
|
|
3652
|
+
const hooks = await importDefault(file);
|
|
3653
|
+
if (hooks) sub.hooks = hooks;
|
|
3654
|
+
}
|
|
3655
|
+
}, "loadSubResourceHooks");
|
|
3656
|
+
var loadResourceConfigsFromDir = /* @__PURE__ */ __name(async (dirPath, baseUrl, enumsFile) => {
|
|
3657
|
+
const enums = loadEnumRegistry(dirPath, enumsFile);
|
|
3658
|
+
const entries = (0, import_node_fs5.readdirSync)(dirPath, {
|
|
3659
|
+
withFileTypes: true
|
|
3660
|
+
});
|
|
3661
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3662
|
+
const configs = [];
|
|
3663
|
+
for (const dir of dirs) {
|
|
3664
|
+
const basePath = (0, import_node_path6.join)(dirPath, dir);
|
|
3665
|
+
const schemaFile = findModule2(basePath, "schema");
|
|
3666
|
+
const schema = schemaFile ? await importDefault(schemaFile) : void 0;
|
|
3667
|
+
const hooksFile = findModule2(basePath, "hooks");
|
|
3668
|
+
const hooks = hooksFile ? await importDefault(hooksFile) : void 0;
|
|
3669
|
+
const jsonFile = (0, import_node_path6.join)(basePath, "resource.json");
|
|
3670
|
+
if ((0, import_node_fs5.existsSync)(jsonFile)) {
|
|
3671
|
+
const json = JSON.parse((0, import_node_fs5.readFileSync)(jsonFile, "utf-8"));
|
|
3672
|
+
const actions = await loadActions(json.actions ?? [], basePath);
|
|
3673
|
+
const tableActions = await loadTableActions(json.tableActions ?? [], basePath);
|
|
3674
|
+
const config = fromJson(json, schema, hooks, basePath, baseUrl, actions, tableActions, enums);
|
|
3675
|
+
await loadSubResourceHooks(config.subResources ?? [], basePath);
|
|
3676
|
+
configs.push(config);
|
|
3677
|
+
continue;
|
|
3678
|
+
}
|
|
3679
|
+
const tsFile = findModule2(basePath, "resource");
|
|
3680
|
+
if (tsFile) {
|
|
3681
|
+
const config = await importDefault(tsFile);
|
|
3682
|
+
if (config) configs.push(hooks ? {
|
|
3683
|
+
...config,
|
|
3684
|
+
hooks
|
|
3685
|
+
} : config);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
return configs;
|
|
3689
|
+
}, "loadResourceConfigsFromDir");
|
|
3690
|
+
|
|
3691
|
+
// src/lib/crud/loader/resource-config.loader.ts
|
|
3692
|
+
var ResourceConfigLoader2 = class {
|
|
3693
|
+
static {
|
|
3694
|
+
__name(this, "ResourceConfigLoader");
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
|
|
3698
|
+
// src/lib/crud/loader/fs-resource-config.loader.ts
|
|
3699
|
+
var FileSystemResourceConfigLoader = class extends ResourceConfigLoader2 {
|
|
3700
|
+
static {
|
|
3701
|
+
__name(this, "FileSystemResourceConfigLoader");
|
|
3702
|
+
}
|
|
3703
|
+
dirPath;
|
|
3704
|
+
baseUrl;
|
|
3705
|
+
enumsFile;
|
|
3706
|
+
constructor(dirPath, baseUrl, enumsFile) {
|
|
3707
|
+
super(), this.dirPath = dirPath, this.baseUrl = baseUrl, this.enumsFile = enumsFile;
|
|
3708
|
+
}
|
|
3709
|
+
async loadAll() {
|
|
3710
|
+
return loadResourceConfigsFromDir(this.dirPath, this.baseUrl, this.enumsFile);
|
|
3711
|
+
}
|
|
3712
|
+
async loadByRoute(route) {
|
|
3713
|
+
const configs = await this.loadAll();
|
|
3714
|
+
return configs.find((c) => c.route === route);
|
|
3715
|
+
}
|
|
3716
|
+
};
|
|
3717
|
+
|
|
3718
|
+
// src/lib/crouton-api.module.ts
|
|
3719
|
+
function _ts_decorate5(decorators, target, key, desc2) {
|
|
3720
|
+
var c = arguments.length, r = c < 3 ? target : desc2 === null ? desc2 = Object.getOwnPropertyDescriptor(target, key) : desc2, d;
|
|
3721
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc2);
|
|
3722
|
+
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;
|
|
3723
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3724
|
+
}
|
|
3725
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
3726
|
+
var CroutonApiModule = class _CroutonApiModule {
|
|
3727
|
+
static {
|
|
3728
|
+
__name(this, "CroutonApiModule");
|
|
3729
|
+
}
|
|
3730
|
+
static forResources(configs, dataSources, loader, config) {
|
|
3731
|
+
const dataSourceRegistry = new DataSourceRegistry(dataSources);
|
|
3732
|
+
const configRegistry = new ResourceConfigRegistry(loader, configs);
|
|
3733
|
+
const controllers = [
|
|
3734
|
+
...configs.map((c) => createCrudController(c, config.baseUrl)),
|
|
3735
|
+
createAppLayoutController(configs, config.sidebarGroups, config.title, config.autoSave ?? true)
|
|
3736
|
+
];
|
|
3737
|
+
return {
|
|
3738
|
+
module: _CroutonApiModule,
|
|
3739
|
+
controllers,
|
|
3740
|
+
providers: [
|
|
3741
|
+
{
|
|
3742
|
+
provide: import_core.APP_FILTER,
|
|
3743
|
+
useClass: CroutonValidationExceptionFilter
|
|
3744
|
+
},
|
|
3745
|
+
{
|
|
3746
|
+
provide: DataSourceRegistry,
|
|
3747
|
+
useValue: dataSourceRegistry
|
|
3748
|
+
},
|
|
3749
|
+
{
|
|
3750
|
+
provide: ResourceConfigRegistry,
|
|
3751
|
+
useValue: configRegistry
|
|
3752
|
+
}
|
|
3753
|
+
]
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
static async forResourceDir(dirPath, dataSourcesPath, config) {
|
|
3757
|
+
const loader = new FileSystemResourceConfigLoader(dirPath, config.baseUrl, config.enumsFile);
|
|
3758
|
+
const configs = await loadResourceConfigsFromDir(dirPath, config.baseUrl, config.enumsFile);
|
|
3759
|
+
const dataSources = await loadDataSourcesFromDir(dataSourcesPath);
|
|
3760
|
+
return _CroutonApiModule.forResources(configs, dataSources, loader, config);
|
|
3761
|
+
}
|
|
3762
|
+
static forLoader(loader, configs, dataSources, config) {
|
|
3763
|
+
return _CroutonApiModule.forResources(configs, dataSources, loader, config);
|
|
3764
|
+
}
|
|
3765
|
+
};
|
|
3766
|
+
CroutonApiModule = _ts_decorate5([
|
|
3767
|
+
(0, import_common13.Module)({
|
|
3768
|
+
controllers: [],
|
|
3769
|
+
providers: [],
|
|
3770
|
+
exports: []
|
|
3771
|
+
})
|
|
3772
|
+
], CroutonApiModule);
|
|
3773
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3774
|
+
0 && (module.exports = {
|
|
3775
|
+
CroutonApiModule,
|
|
3776
|
+
DataSourceRegistry,
|
|
3777
|
+
FileSystemResourceConfigLoader,
|
|
3778
|
+
ResourceConfigLoader,
|
|
3779
|
+
ResourceConfigRegistry,
|
|
3780
|
+
isOperationEnabled,
|
|
3781
|
+
loadDataSourcesFromDir,
|
|
3782
|
+
loadResourceConfigsFromDir,
|
|
3783
|
+
resolveDefinition,
|
|
3784
|
+
schemaFor,
|
|
3785
|
+
upsertOnFor
|
|
3786
|
+
});
|
|
3787
|
+
//# sourceMappingURL=index.cjs.map
|