@postxl/generator 0.33.3 → 0.34.0
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/generators/models/businesslogic.generator.js +101 -58
- package/dist/generators/models/react.generator/modals.generator.js +20 -4
- package/dist/generators/models/repository.generator.js +100 -41
- package/dist/generators/models/route.generator.js +2 -1
- package/dist/lib/attributes.d.ts +27 -2
- package/dist/lib/schema/fields.d.ts +7 -4
- package/dist/lib/schema/fields.js +11 -4
- package/dist/lib/schema/schema.d.ts +8 -0
- package/dist/lib/utils/ast.d.ts +29 -0
- package/dist/lib/utils/ast.js +23 -0
- package/dist/lib/utils/jsdoc.d.ts +1 -1
- package/dist/lib/utils/jsdoc.js +5 -4
- package/dist/prisma/attributes.js +39 -24
- package/dist/prisma/parse.js +19 -6
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ const imports_1 = require("../../lib/imports");
|
|
|
28
28
|
const meta_1 = require("../../lib/meta");
|
|
29
29
|
const fields_1 = require("../../lib/schema/fields");
|
|
30
30
|
const Types = __importStar(require("../../lib/schema/types"));
|
|
31
|
+
const ast_1 = require("../../lib/utils/ast");
|
|
31
32
|
const repository_generator_1 = require("./repository.generator");
|
|
32
33
|
/**
|
|
33
34
|
* Generates business logic for a given model.
|
|
@@ -188,72 +189,114 @@ export class ${meta.businessLogic.serviceClassName} {
|
|
|
188
189
|
* Deletes the ${meta.userFriendlyName} with the given id.
|
|
189
190
|
*
|
|
190
191
|
* Note: This does NOT delete any linked items.
|
|
191
|
-
* If the
|
|
192
|
+
* If the item is a dependency of another item, the deletion will fail!
|
|
192
193
|
*/
|
|
193
194
|
public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
|
|
194
195
|
return this.${modelRepositoryVariableName}.delete(id)
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.map((f) => `case '${f.name}':`)
|
|
205
|
-
.join('\n')}
|
|
206
|
-
return (a[field] || '').localeCompare(b[field] || '')`}
|
|
207
|
-
default:
|
|
208
|
-
return 0
|
|
209
|
-
}
|
|
199
|
+
// Utility Functions
|
|
200
|
+
|
|
201
|
+
${_createModelCompareFn({ model })}
|
|
202
|
+
|
|
203
|
+
${_createModelFilterFn({ model })}
|
|
204
|
+
`;
|
|
210
205
|
}
|
|
206
|
+
exports.generateModelBusinessLogic = generateModelBusinessLogic;
|
|
207
|
+
/**
|
|
208
|
+
* Generates a utility filter function for the given model that can be used to filter out instances
|
|
209
|
+
* of a model using a given operator on a given field.
|
|
210
|
+
*/
|
|
211
|
+
function _createModelFilterFn({ model }) {
|
|
212
|
+
const stringFieldFilters = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'string' }).map((f) => {
|
|
213
|
+
return {
|
|
214
|
+
match: `"${f.name}"`,
|
|
215
|
+
block: `
|
|
216
|
+
if (typeof value !== 'string') {
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
220
|
+
switch (operator) {
|
|
221
|
+
case 'contains': {
|
|
222
|
+
return (item[field] || '').toLowerCase().includes(value.toLowerCase())
|
|
223
|
+
}
|
|
224
|
+
default:
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
`,
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
const numberFieldsFilters = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'number' }).map((f) => {
|
|
231
|
+
return {
|
|
232
|
+
match: `"${f.name}"`,
|
|
233
|
+
block: `
|
|
234
|
+
const toCompare = item[field]
|
|
235
|
+
if (typeof value !== 'number' || toCompare === null) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
switch (operator) {
|
|
240
|
+
case 'eq': {
|
|
241
|
+
return item[field] === value
|
|
242
|
+
}
|
|
243
|
+
case 'gt': {
|
|
244
|
+
return toCompare > value
|
|
245
|
+
}
|
|
246
|
+
case 'lt': {
|
|
247
|
+
return toCompare < value
|
|
248
|
+
}
|
|
249
|
+
default: {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
const fnBlock = (0, ast_1.createSwitchStatement)({
|
|
257
|
+
field: 'field',
|
|
258
|
+
cases: [...stringFieldFilters, ...numberFieldsFilters],
|
|
259
|
+
defaultBlock: 'return false',
|
|
260
|
+
});
|
|
261
|
+
return `
|
|
262
|
+
/**
|
|
263
|
+
* Filters the given ${model.typeName} by the given field, using provided operator and value.
|
|
264
|
+
*/
|
|
265
|
+
function filterFn(
|
|
266
|
+
item: ${model.typeName},
|
|
267
|
+
field: keyof ${model.typeName},
|
|
268
|
+
operator: FilterOperator,
|
|
269
|
+
value: string | number
|
|
270
|
+
): boolean {
|
|
271
|
+
${fnBlock}
|
|
256
272
|
}
|
|
257
273
|
`;
|
|
258
274
|
}
|
|
259
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Returns a utility compare function that lets you compare two instances of a given model
|
|
277
|
+
* by a given field.
|
|
278
|
+
*/
|
|
279
|
+
function _createModelCompareFn({ model }) {
|
|
280
|
+
const stringFieldComparators = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'string' }).map((f) => {
|
|
281
|
+
return {
|
|
282
|
+
match: `"${f.name}"`,
|
|
283
|
+
block: `
|
|
284
|
+
return (a[field] || '').localeCompare(b[field] || '')
|
|
285
|
+
`,
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
const fnBlock = (0, ast_1.createSwitchStatement)({
|
|
289
|
+
field: 'field',
|
|
290
|
+
cases: [...stringFieldComparators],
|
|
291
|
+
defaultBlock: 'return 0',
|
|
292
|
+
});
|
|
293
|
+
return `
|
|
294
|
+
/**
|
|
295
|
+
* Compares two ${model.typeName} instances by the given field.
|
|
296
|
+
*/
|
|
297
|
+
function compare(a: ${model.typeName}, b: ${model.typeName}, field: keyof ${model.typeName}) {
|
|
298
|
+
${fnBlock}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
@@ -52,6 +52,9 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
|
|
|
52
52
|
() => createTypedForm<CreateInputData>().with({ ${(() => {
|
|
53
53
|
const components = new Set();
|
|
54
54
|
for (const field of fields.values()) {
|
|
55
|
+
if (field.attributes.isReadonly) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
55
58
|
switch (field.kind) {
|
|
56
59
|
case 'enum': {
|
|
57
60
|
const enumMeta = (0, meta_1.getEnumMetadata)({ enumerator: field.enumerator });
|
|
@@ -79,7 +82,7 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
|
|
|
79
82
|
<Typed.Formik
|
|
80
83
|
initialValues={{
|
|
81
84
|
${fields
|
|
82
|
-
.filter((f) => f.kind !== 'id')
|
|
85
|
+
.filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
|
|
83
86
|
.map((field) => `${getFormikFieldName(field.name)}: null,`)
|
|
84
87
|
.join('\n')}
|
|
85
88
|
}}
|
|
@@ -195,6 +198,7 @@ export const ${components.modals.editComponentName} = ({
|
|
|
195
198
|
<Typed.Formik
|
|
196
199
|
initialValues={{
|
|
197
200
|
${fields
|
|
201
|
+
.filter((f) => !f.attributes.isReadonly)
|
|
198
202
|
.map((field) => {
|
|
199
203
|
switch (field.kind) {
|
|
200
204
|
case 'enum':
|
|
@@ -387,6 +391,9 @@ function getFormImports({ model, meta }) {
|
|
|
387
391
|
function getCreateFormInputFields({ model }) {
|
|
388
392
|
const form = new serializer_1.Serializer();
|
|
389
393
|
for (const field of model.fields.values()) {
|
|
394
|
+
if (field.attributes.isReadonly) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
390
397
|
switch (field.kind) {
|
|
391
398
|
case 'id':
|
|
392
399
|
continue;
|
|
@@ -407,12 +414,13 @@ function getCreateFormInputFields({ model }) {
|
|
|
407
414
|
}
|
|
408
415
|
function getFormikCreateMutationData({ model: { fields } }) {
|
|
409
416
|
return fields
|
|
417
|
+
.filter((f) => !f.attributes.isReadonly)
|
|
410
418
|
.map((field) => {
|
|
411
419
|
const formikFieldName = getFormikFieldName(field.name);
|
|
412
420
|
switch (field.kind) {
|
|
413
421
|
case 'id':
|
|
414
422
|
case 'scalar':
|
|
415
|
-
// NOTE: Create methods generate the ID field upon
|
|
423
|
+
// NOTE: Create methods generate the ID field upon submission.
|
|
416
424
|
if (field.kind === 'id') {
|
|
417
425
|
return '';
|
|
418
426
|
}
|
|
@@ -454,6 +462,9 @@ function getFormikCreateMutationData({ model: { fields } }) {
|
|
|
454
462
|
function getEditFormInputFields({ model }) {
|
|
455
463
|
const form = new serializer_1.Serializer();
|
|
456
464
|
for (const field of model.fields.values()) {
|
|
465
|
+
if (field.attributes.isReadonly) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
457
468
|
switch (field.kind) {
|
|
458
469
|
case 'id':
|
|
459
470
|
continue;
|
|
@@ -489,6 +500,7 @@ function getEditFormInputFields({ model }) {
|
|
|
489
500
|
}
|
|
490
501
|
function getEditFormikMutationData({ model: { fields } }) {
|
|
491
502
|
return fields
|
|
503
|
+
.filter((f) => !f.attributes.isReadonly)
|
|
492
504
|
.map((field) => {
|
|
493
505
|
const formikFieldName = getFormikFieldName(field.name);
|
|
494
506
|
switch (field.kind) {
|
|
@@ -520,11 +532,15 @@ function getFormFieldComponents({ model }) {
|
|
|
520
532
|
var _a;
|
|
521
533
|
const form = new serializer_1.Serializer();
|
|
522
534
|
for (const field of model.fields.values()) {
|
|
535
|
+
// By default, we hide generated fields like createdAt, updatedAt, deletedAt.
|
|
536
|
+
if (field.attributes.isReadonly) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
523
539
|
const formikFieldName = getFormikFieldName(field.name);
|
|
524
540
|
const label = StringUtils.toPascalCase(field.name);
|
|
525
541
|
switch (field.kind) {
|
|
526
542
|
case 'id': {
|
|
527
|
-
// NOTE: We never show the ID field in the form even if it's in the type
|
|
543
|
+
// NOTE: We never show the ID field in the form even if it's in the type signature of the form input.
|
|
528
544
|
break;
|
|
529
545
|
}
|
|
530
546
|
case 'scalar': {
|
|
@@ -604,7 +620,7 @@ function getFormFieldComponents({ model }) {
|
|
|
604
620
|
function getFormikValidationCases({ model }) {
|
|
605
621
|
const form = new serializer_1.Serializer();
|
|
606
622
|
for (const field of model.fields.values()) {
|
|
607
|
-
if (field.kind === 'id') {
|
|
623
|
+
if (field.kind === 'id' || field.attributes.isReadonly) {
|
|
608
624
|
continue;
|
|
609
625
|
}
|
|
610
626
|
const formikFieldName = getFormikFieldName(field.name);
|
|
@@ -27,7 +27,7 @@ function generateRepository({ model, meta }) {
|
|
|
27
27
|
items: [meta.types.toBrandedIdTypeFnName],
|
|
28
28
|
from: meta.types.importPath,
|
|
29
29
|
});
|
|
30
|
-
// NOTE: We first generate different parts of the code
|
|
30
|
+
// NOTE: We first generate different parts of the code responsible for a particular task
|
|
31
31
|
// and then we combine them into the final code block.
|
|
32
32
|
//
|
|
33
33
|
// Based on the model, the repository is generated slightly differently:
|
|
@@ -132,36 +132,6 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
|
|
|
132
132
|
public count(): number {
|
|
133
133
|
return this.data.size
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
/**${(0, jsdoc_1.convertToJsDocComments)([
|
|
137
|
-
`Checks that item has the ${idField.name} field.`,
|
|
138
|
-
`In case none exists, ${idBlocks.verifyFunctionComment}`,
|
|
139
|
-
uniqueStringFieldsBlocks.verifyFunctionComment,
|
|
140
|
-
maxLengthBlocks.verifyFunctionComment,
|
|
141
|
-
])}
|
|
142
|
-
*/
|
|
143
|
-
|
|
144
|
-
private verifyItem(item: ${idBlocks.verifyFunctionParameterType}): ${model.typeName} {
|
|
145
|
-
${idBlocks.verifyCode}
|
|
146
|
-
|
|
147
|
-
${maxLengthBlocks.verifyCode.join('\n')}
|
|
148
|
-
|
|
149
|
-
${uniqueStringFieldsBlocks.verifyCode.join('\n')}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
${idField.name},
|
|
153
|
-
${[...model.fields.values()]
|
|
154
|
-
.filter((f) => f.kind !== 'id')
|
|
155
|
-
.map((f) => `${f.name}: item.${f.name}`)
|
|
156
|
-
.join(',\n')}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private toCreateItem(item: ${model.typeName}) {
|
|
161
|
-
return {
|
|
162
|
-
${[...model.fields.values()].map((f) => `${f.sourceName}: item.${f.name}`).join(',\n')}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
135
|
|
|
166
136
|
${mainBlocks.createCode}
|
|
167
137
|
|
|
@@ -247,9 +217,35 @@ function _generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
|
|
|
247
217
|
return this.init()
|
|
248
218
|
}`,
|
|
249
219
|
createCode: `
|
|
220
|
+
${(0, jsdoc_1.jsDocComment)([
|
|
221
|
+
`Checks that item has the ${model.idField.name} field.`,
|
|
222
|
+
`In case none exists, ${blocks.idBlocks.verifyFunctionComment}`,
|
|
223
|
+
blocks.uniqueStringFieldsBlocks.verifyFunctionComment,
|
|
224
|
+
blocks.maxLengthBlocks.verifyFunctionComment,
|
|
225
|
+
])}
|
|
226
|
+
private verifyItem(item: ${blocks.idBlocks.verifyFunctionParameterType}): ${model.name} {
|
|
227
|
+
${blocks.idBlocks.verifyCode}
|
|
228
|
+
|
|
229
|
+
${blocks.maxLengthBlocks.verifyCode.join('\n')}
|
|
230
|
+
|
|
231
|
+
${blocks.uniqueStringFieldsBlocks.verifyCode.join('\n')}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
${model.idField.name},
|
|
235
|
+
${model.fields
|
|
236
|
+
.filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
|
|
237
|
+
.map((f) => `${f.name}: item.${f.name}`)
|
|
238
|
+
.join(',\n')},
|
|
239
|
+
${model.createdAtField ? `${model.createdAtField.sourceName}: new Date(),` : ''}
|
|
240
|
+
${model.updatedAtField ? `${model.updatedAtField.sourceName}: new Date(),` : ''}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
250
244
|
// Non-mocked version is async - so we keep type-compatible signatures for create() and createWithId()
|
|
251
245
|
// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
|
|
252
|
-
public async create(
|
|
246
|
+
public async create(
|
|
247
|
+
item: ${methodTypeSignatures.create.parameters[0]}
|
|
248
|
+
): ${methodTypeSignatures.create.returnType} {
|
|
253
249
|
const newItem = await Promise.resolve(this.verifyItem(item))
|
|
254
250
|
|
|
255
251
|
this.set(newItem)
|
|
@@ -281,7 +277,8 @@ function _generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
|
|
|
281
277
|
${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
|
|
282
278
|
|
|
283
279
|
const newItem = await Promise.resolve({ ...existingItem, ...item })
|
|
284
|
-
|
|
280
|
+
${model.updatedAtField ? `newItem.${model.updatedAtField.name} = new Date()` : ''}
|
|
281
|
+
|
|
285
282
|
this.remove(existingItem)
|
|
286
283
|
this.set(newItem)
|
|
287
284
|
|
|
@@ -371,7 +368,42 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
|
|
|
371
368
|
}
|
|
372
369
|
`,
|
|
373
370
|
createCode: `
|
|
374
|
-
|
|
371
|
+
${(0, jsdoc_1.jsDocComment)([
|
|
372
|
+
`Checks that item has the ${idField.name} field.`,
|
|
373
|
+
`In case none exists, ${blocks.idBlocks.verifyFunctionComment}`,
|
|
374
|
+
blocks.uniqueStringFieldsBlocks.verifyFunctionComment,
|
|
375
|
+
blocks.maxLengthBlocks.verifyFunctionComment,
|
|
376
|
+
])}
|
|
377
|
+
private verifyItem(
|
|
378
|
+
item: ${blocks.idBlocks.verifyFunctionParameterType}
|
|
379
|
+
): ${blocks.idBlocks.createFunctionParameterType} {
|
|
380
|
+
${blocks.idBlocks.verifyCode}
|
|
381
|
+
|
|
382
|
+
${blocks.maxLengthBlocks.verifyCode.join('\n')}
|
|
383
|
+
|
|
384
|
+
${blocks.uniqueStringFieldsBlocks.verifyCode.join('\n')}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
${idField.name},
|
|
388
|
+
${model.fields
|
|
389
|
+
.filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
|
|
390
|
+
.map((f) => `${f.name}: item.${f.name}`)
|
|
391
|
+
.join(',\n')}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private toCreateItem(item: ${blocks.idBlocks.createFunctionParameterType}) {
|
|
396
|
+
return {
|
|
397
|
+
${model.fields
|
|
398
|
+
.filter((f) => !f.attributes.isReadonly)
|
|
399
|
+
.map((f) => `${f.sourceName}: item.${f.name}`)
|
|
400
|
+
.join(',\n')},
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
public async create(
|
|
405
|
+
item: ${methodTypeSignatures.create.parameters[0]}
|
|
406
|
+
): ${methodTypeSignatures.create.returnType} {
|
|
375
407
|
const newItem = this.${decoderFunctionName}(
|
|
376
408
|
await this.db.${meta.data.repository.getMethodFnName}.create({
|
|
377
409
|
data: this.toCreateItem(this.verifyItem(item)),
|
|
@@ -387,11 +419,19 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
|
|
|
387
419
|
|
|
388
420
|
await this.db.${meta.data.repository.getMethodFnName}.createMany({ data: newItems.map(i => this.toCreateItem(i)) })
|
|
389
421
|
|
|
390
|
-
|
|
422
|
+
const dbItems = await this.db.${meta.data.repository.getMethodFnName}.findMany({
|
|
423
|
+
where: {
|
|
424
|
+
${model.idField.sourceName}: { in: newItems.map(i => i.${model.idField.name}) }
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
const result = dbItems.map((item) => this.${decoderFunctionName}(item))
|
|
429
|
+
|
|
430
|
+
for (const item of result) {
|
|
391
431
|
this.set(item)
|
|
392
432
|
}
|
|
393
433
|
|
|
394
|
-
return
|
|
434
|
+
return result
|
|
395
435
|
}`,
|
|
396
436
|
// prettier-ignore
|
|
397
437
|
updateCode: `
|
|
@@ -411,8 +451,8 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
|
|
|
411
451
|
${idField.sourceName}: item.${idField.name},
|
|
412
452
|
},
|
|
413
453
|
data: {
|
|
414
|
-
${
|
|
415
|
-
.filter((f) => f.kind !== 'id')
|
|
454
|
+
${Array.from(model.fields.values())
|
|
455
|
+
.filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
|
|
416
456
|
.map((f) => f.isRequired
|
|
417
457
|
? `${f.sourceName}: item.${f.name} ?? existingItem.${f.name}`
|
|
418
458
|
: `${f.sourceName}: item.${f.name}`)
|
|
@@ -442,7 +482,9 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
|
|
|
442
482
|
/**
|
|
443
483
|
* Utility function that converts a given Database object to a TypeScript model instance
|
|
444
484
|
*/
|
|
445
|
-
private ${decoderFunctionName}(
|
|
485
|
+
private ${decoderFunctionName}(
|
|
486
|
+
item: Pick<DbType, ${Array.from(model.fields.values()).map((f) => `'${f.sourceName}'`).join(' | ')}>
|
|
487
|
+
): ${model.typeName} {
|
|
446
488
|
return ${meta.types.zodDecoderFnName}.parse({
|
|
447
489
|
${Array.from(model.fields.values()).map((f) => `${f.name}: item.${f.sourceName}`).join(',\n')}
|
|
448
490
|
})
|
|
@@ -507,6 +549,7 @@ function _generateIdBlocks_NoGeneration({ idField, model, meta, }) {
|
|
|
507
549
|
}
|
|
508
550
|
const ${idField.name} = ${meta.types.toBrandedIdTypeFnName}(item.${idField.name})`,
|
|
509
551
|
setCode: '',
|
|
552
|
+
createFunctionParameterType: model.typeName,
|
|
510
553
|
};
|
|
511
554
|
}
|
|
512
555
|
/**
|
|
@@ -514,6 +557,8 @@ function _generateIdBlocks_NoGeneration({ idField, model, meta, }) {
|
|
|
514
557
|
* Given chunks make sure that the id is unique if provided or generate a new one if not.
|
|
515
558
|
*/
|
|
516
559
|
function _generateIdBlock_Int({ idField, model, meta, }) {
|
|
560
|
+
const generatedFields = model.fields.filter((f) => f.kind === 'id' || f.attributes.isReadonly);
|
|
561
|
+
const readonlyFields = model.fields.filter((f) => f.attributes.isReadonly);
|
|
517
562
|
return {
|
|
518
563
|
libraryImports: '',
|
|
519
564
|
generateNextIdFunctionName: `
|
|
@@ -523,8 +568,14 @@ function _generateIdBlock_Int({ idField, model, meta, }) {
|
|
|
523
568
|
}`,
|
|
524
569
|
initCode: `this.currentMaxId = (await this.db.${meta.data.repository.getMethodFnName}.aggregate({ _max: { ${idField.sourceName}: true } }))._max.${idField.sourceName} ?? 0`,
|
|
525
570
|
verifyFunctionComment: 'the id is generated by increasing the highest former id and assigned to the item.',
|
|
526
|
-
|
|
571
|
+
// prettier-ignore
|
|
572
|
+
verifyFunctionParameterType: `(Omit<${model.typeName}, ${generatedFields.map((f) => `'${f.name}'`).join(' | ')}> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
|
|
527
573
|
verifyCode: `const ${idField.name} = (item.${idField.name} !== undefined) ? ${meta.types.toBrandedIdTypeFnName}(item.${idField.name}) : this.generateNextId()`,
|
|
574
|
+
createFunctionParameterType:
|
|
575
|
+
// NOTE: In case we have readonly fields, we need to omit them from the create function.
|
|
576
|
+
readonlyFields.length === 0
|
|
577
|
+
? model.typeName
|
|
578
|
+
: `Omit<${model.typeName}, ${readonlyFields.map((f) => `'${f.name}'`).join(' |')}>`,
|
|
528
579
|
setCode: `if (item.id > this.currentMaxId) { this.currentMaxId = item.id }`,
|
|
529
580
|
};
|
|
530
581
|
}
|
|
@@ -533,6 +584,8 @@ function _generateIdBlock_Int({ idField, model, meta, }) {
|
|
|
533
584
|
* It allows you to provide a custom id or generates a new one if not.
|
|
534
585
|
*/
|
|
535
586
|
function _generateIdBlock_UUID({ idField, model, meta, }) {
|
|
587
|
+
const dbGeneratedFields = model.fields.filter((f) => f.kind === 'id' || f.attributes.isReadonly);
|
|
588
|
+
const readonlyFields = model.fields.filter((f) => f.attributes.isReadonly);
|
|
536
589
|
return {
|
|
537
590
|
libraryImports: `import { randomUUID } from 'crypto'`,
|
|
538
591
|
generateNextIdFunctionName: `
|
|
@@ -541,8 +594,14 @@ function _generateIdBlock_UUID({ idField, model, meta, }) {
|
|
|
541
594
|
}`,
|
|
542
595
|
initCode: '',
|
|
543
596
|
verifyFunctionComment: 'a new UUID is generated and assigned to the item.',
|
|
544
|
-
|
|
597
|
+
// prettier-ignore
|
|
598
|
+
verifyFunctionParameterType: `(Omit<${model.typeName}, ${dbGeneratedFields.map((f) => `'${f.name}'`).join(' | ')}> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
|
|
545
599
|
verifyCode: `const ${idField.name} = (item.${idField.name} !== undefined) ? ${meta.types.toBrandedIdTypeFnName}(item.${idField.name}) : this.generateNextId()`,
|
|
600
|
+
createFunctionParameterType:
|
|
601
|
+
// NOTE: In case we have readonly fields, we need to omit them from the create function.
|
|
602
|
+
readonlyFields.length === 0
|
|
603
|
+
? model.typeName
|
|
604
|
+
: `Omit<${model.typeName}, ${readonlyFields.map((f) => `'${f.name}'`).join(' |')}>`,
|
|
546
605
|
setCode: '',
|
|
547
606
|
};
|
|
548
607
|
}
|
|
@@ -77,7 +77,7 @@ export const ${meta.trpc.routerName} = router({
|
|
|
77
77
|
exports.generateRoute = generateRoute;
|
|
78
78
|
function getCreateMethod({ model: { fields }, meta }) {
|
|
79
79
|
const parameters = fields
|
|
80
|
-
.filter((f) => f.kind !== 'id')
|
|
80
|
+
.filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
|
|
81
81
|
.map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field })}`)
|
|
82
82
|
.join(',');
|
|
83
83
|
return `
|
|
@@ -89,6 +89,7 @@ function getCreateMethod({ model: { fields }, meta }) {
|
|
|
89
89
|
}
|
|
90
90
|
function getUpdateMethod({ model: { fields }, meta }) {
|
|
91
91
|
const parameters = fields
|
|
92
|
+
.filter((f) => !f.attributes.isReadonly)
|
|
92
93
|
.map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field, allowAnyOptionalField: field.kind !== 'id' })}`)
|
|
93
94
|
.join(',');
|
|
94
95
|
return `update: procedure
|
package/dist/lib/attributes.d.ts
CHANGED
|
@@ -30,19 +30,44 @@ export type ModelAttributes = {
|
|
|
30
30
|
export type FieldAttributes = {
|
|
31
31
|
/**
|
|
32
32
|
* Schema tag: ´@@Ignore()`
|
|
33
|
+
*
|
|
34
|
+
* Field will be ignored by the generator and not be exposed from the database.
|
|
33
35
|
*/
|
|
34
36
|
ignore: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Schema tag: ´@@Readonly()`
|
|
39
|
+
*
|
|
40
|
+
* Field is generated by the database and shall not be edited by the user. It is readable though.
|
|
41
|
+
*/
|
|
42
|
+
isReadonly: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Derived from Prisma's @updatedAt attribute.
|
|
45
|
+
*
|
|
46
|
+
* Field represents the last time the record was updated.
|
|
47
|
+
*/
|
|
48
|
+
isUpdatedAt: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Automatically set is field name is "createdAt".
|
|
51
|
+
* Field represents the time the record was created.
|
|
52
|
+
*/
|
|
53
|
+
isCreatedAt: boolean;
|
|
35
54
|
/**
|
|
36
55
|
* Schema tag: ´@@Description("Description of the field")`
|
|
56
|
+
*
|
|
57
|
+
* The description that is generated as a JSDoc comment in the code.
|
|
37
58
|
*/
|
|
38
59
|
description?: string;
|
|
39
60
|
/**
|
|
40
|
-
* Schema tag: ´@@Examples("Example1", "Example2")`
|
|
61
|
+
* Schema tag: ´@@Examples("Example1", "Example2")` or `@@Example("Example1")`
|
|
62
|
+
*
|
|
63
|
+
* Examples that are generated as a JSDoc comment in the code and used as seed/test data.
|
|
41
64
|
*/
|
|
42
|
-
examples?: (string | number | boolean)[];
|
|
65
|
+
examples?: (string | number | boolean | null)[];
|
|
43
66
|
/**
|
|
44
67
|
* Schema tag: ´@@DefaultField()`
|
|
45
68
|
* The property of the model that identifies the default row.
|
|
69
|
+
*
|
|
70
|
+
* If this is set, the repository will verify that exactly one record has this field set to true - and use it as the default record.
|
|
46
71
|
*/
|
|
47
72
|
isDefaultField?: boolean;
|
|
48
73
|
/**
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { Field, FieldEnum, FieldRelation, FieldScalar } from './schema';
|
|
2
2
|
/**
|
|
3
3
|
* The default field of the model.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* NOTE: A model can only have one or no default field! We enforce this by schema validation.
|
|
6
6
|
*/
|
|
7
7
|
export declare const getDefaultField: ({ fields }: {
|
|
8
8
|
fields: Field[];
|
|
9
9
|
}) => FieldScalar | undefined;
|
|
10
10
|
/**
|
|
11
|
-
* List of all scalar fields of the model (excluding foreing keys and the id)
|
|
11
|
+
* List of all scalar fields of the model (excluding foreing keys and the id).
|
|
12
|
+
*
|
|
13
|
+
* If you provide an optional `tsTypeName` parameter, only scalar fields of that type are returned.
|
|
12
14
|
*/
|
|
13
|
-
export declare const getScalarFields: ({ fields }: {
|
|
15
|
+
export declare const getScalarFields: ({ fields, tsTypeName }: {
|
|
14
16
|
fields: Field[];
|
|
17
|
+
tsTypeName?: string | undefined;
|
|
15
18
|
}) => FieldScalar[];
|
|
16
19
|
/**
|
|
17
20
|
* List of all relation fields of the model.
|
|
@@ -3,15 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isMaxLengthStringField = exports.isUniqueStringField = exports.getDefaultValueForType = exports.getEnumFields = exports.getRelationFields = exports.getScalarFields = exports.getDefaultField = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* The default field of the model.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* NOTE: A model can only have one or no default field! We enforce this by schema validation.
|
|
8
8
|
*/
|
|
9
9
|
const getDefaultField = ({ fields }) => fields.find((f) => f.attributes.isDefaultField);
|
|
10
10
|
exports.getDefaultField = getDefaultField;
|
|
11
11
|
/**
|
|
12
|
-
* List of all scalar fields of the model (excluding foreing keys and the id)
|
|
12
|
+
* List of all scalar fields of the model (excluding foreing keys and the id).
|
|
13
|
+
*
|
|
14
|
+
* If you provide an optional `tsTypeName` parameter, only scalar fields of that type are returned.
|
|
13
15
|
*/
|
|
14
|
-
const getScalarFields = ({ fields }) =>
|
|
16
|
+
const getScalarFields = ({ fields, tsTypeName }) => {
|
|
17
|
+
if (tsTypeName) {
|
|
18
|
+
return fields.filter((f) => f.kind === 'scalar' && f.tsTypeName === tsTypeName);
|
|
19
|
+
}
|
|
20
|
+
return fields.filter((f) => f.kind === 'scalar');
|
|
21
|
+
};
|
|
15
22
|
exports.getScalarFields = getScalarFields;
|
|
16
23
|
/**
|
|
17
24
|
* List of all relation fields of the model.
|
|
@@ -148,6 +148,14 @@ export type ModelFields = {
|
|
|
148
148
|
* The id field of the model
|
|
149
149
|
*/
|
|
150
150
|
idField: FieldId;
|
|
151
|
+
/**
|
|
152
|
+
* The field of the model that identifies when the entry was created.
|
|
153
|
+
*/
|
|
154
|
+
createdAtField?: FieldScalar;
|
|
155
|
+
/**
|
|
156
|
+
* The field of the model that identifies when the entry was last updated.
|
|
157
|
+
*/
|
|
158
|
+
updatedAtField?: FieldScalar;
|
|
151
159
|
/**
|
|
152
160
|
* The property of the model that identifies the default row.
|
|
153
161
|
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A collection of utility functions that let you more easily write TypeScript code.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Utility function that lets you easily create a TypeScript switch statement.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSwitchStatement({ field, cases, defaultBlock, }: {
|
|
8
|
+
/**
|
|
9
|
+
* The variable name to switch on.
|
|
10
|
+
*/
|
|
11
|
+
field: string;
|
|
12
|
+
/**
|
|
13
|
+
* The cases to switch on.
|
|
14
|
+
*/
|
|
15
|
+
cases: {
|
|
16
|
+
/**
|
|
17
|
+
* The value to match.
|
|
18
|
+
*/
|
|
19
|
+
match: string;
|
|
20
|
+
/**
|
|
21
|
+
* The body of the switch case.
|
|
22
|
+
*/
|
|
23
|
+
block: string;
|
|
24
|
+
}[];
|
|
25
|
+
/**
|
|
26
|
+
* The execution block of the default case.
|
|
27
|
+
*/
|
|
28
|
+
defaultBlock?: string;
|
|
29
|
+
}): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A collection of utility functions that let you more easily write TypeScript code.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createSwitchStatement = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Utility function that lets you easily create a TypeScript switch statement.
|
|
9
|
+
*/
|
|
10
|
+
function createSwitchStatement({ field, cases, defaultBlock, }) {
|
|
11
|
+
const _cases = cases.map(_createSwitchCase).join('\n');
|
|
12
|
+
const _default = defaultBlock ? `default: {\n${defaultBlock}\n}` : '';
|
|
13
|
+
return `
|
|
14
|
+
switch (${field}) {
|
|
15
|
+
${_cases}
|
|
16
|
+
${_default}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
exports.createSwitchStatement = createSwitchStatement;
|
|
21
|
+
function _createSwitchCase({ match, block, }) {
|
|
22
|
+
return `case ${match}: {\n${block}\n}`;
|
|
23
|
+
}
|
package/dist/lib/utils/jsdoc.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.jsDocComment = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Returns a string of JSDoc comments from an array of lines.
|
|
6
6
|
*/
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function jsDocComment(lines) {
|
|
8
|
+
const _lines = lines
|
|
9
9
|
.filter((c) => c !== '')
|
|
10
10
|
.map((c) => `\n * ${c}`)
|
|
11
11
|
.join('');
|
|
12
|
+
return `/**${_lines}\n */`;
|
|
12
13
|
}
|
|
13
|
-
exports.
|
|
14
|
+
exports.jsDocComment = jsDocComment;
|
|
@@ -4,9 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getFieldAttributes = exports.getEnumAttributes = exports.getModelAttributes = exports.parseArgumentToStringOrStringArray = exports.parseAttributesFromDocumentation = void 0;
|
|
7
|
-
const remeda_1 = require("remeda");
|
|
8
|
-
const string_1 = require("../lib/utils/string");
|
|
9
7
|
const zod_1 = __importDefault(require("zod"));
|
|
8
|
+
const string_1 = require("../lib/utils/string");
|
|
10
9
|
/**
|
|
11
10
|
* Parses attributes from a given string using provided prefix.
|
|
12
11
|
*/
|
|
@@ -113,31 +112,47 @@ exports.getEnumAttributes = getEnumAttributes;
|
|
|
113
112
|
*/
|
|
114
113
|
function getFieldAttributes(field) {
|
|
115
114
|
const attributes = parseAttributesFromDocumentation(field);
|
|
116
|
-
if (attributes.examples === undefined && attributes.example !== undefined) {
|
|
117
|
-
attributes.examples = attributes.example;
|
|
118
|
-
}
|
|
119
115
|
// Prisma also has an "@ignore" attribute - see https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#ignore
|
|
120
116
|
// we handle this the same way as our custom "ignore" attribute
|
|
121
117
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
122
118
|
const isPrismaIgnored = field.isIgnored === true;
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
119
|
+
const exampleDecoder = zod_1.default.union([zod_1.default.string(), zod_1.default.number(), zod_1.default.boolean(), zod_1.default.null()]);
|
|
120
|
+
const examplesDecoder = exampleDecoder
|
|
121
|
+
.transform((obj) => [obj])
|
|
122
|
+
.or(zod_1.default.array(exampleDecoder))
|
|
123
|
+
.optional();
|
|
124
|
+
const decoder = zod_1.default
|
|
125
|
+
.object({
|
|
126
|
+
ignore: blankStringBooleanDecoder,
|
|
127
|
+
description: zod_1.default.string().optional(),
|
|
128
|
+
isDefault: blankStringBooleanDecoder,
|
|
129
|
+
label: blankStringBooleanDecoder,
|
|
130
|
+
example: examplesDecoder,
|
|
131
|
+
examples: examplesDecoder,
|
|
132
|
+
maxLength: zod_1.default
|
|
133
|
+
.number()
|
|
134
|
+
.or(zod_1.default.string().transform((s) => parseInt(s, 10)))
|
|
135
|
+
.optional(),
|
|
136
|
+
readonly: blankStringBooleanDecoder,
|
|
137
|
+
})
|
|
138
|
+
.transform((obj) => {
|
|
139
|
+
var _a;
|
|
140
|
+
return ({
|
|
141
|
+
ignore: obj.ignore || isPrismaIgnored,
|
|
142
|
+
description: obj.description,
|
|
143
|
+
isDefaultField: obj.isDefault,
|
|
144
|
+
isLabel: obj.label,
|
|
145
|
+
examples: obj.examples || obj.example,
|
|
146
|
+
maxLength: obj.maxLength,
|
|
147
|
+
isReadonly: obj.readonly || field.isGenerated || field.isUpdatedAt || field.name === 'createdAt',
|
|
148
|
+
isUpdatedAt: (_a = field.isUpdatedAt) !== null && _a !== void 0 ? _a : false,
|
|
149
|
+
isCreatedAt: field.name === 'createdAt',
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
const result = decoder.safeParse(attributes);
|
|
153
|
+
if (!result.success) {
|
|
154
|
+
throw new Error(`Field ${field.name} has invalid field attributes: ${result.error.toString()}`);
|
|
155
|
+
}
|
|
156
|
+
return result.data;
|
|
142
157
|
}
|
|
143
158
|
exports.getFieldAttributes = getFieldAttributes;
|
package/dist/prisma/parse.js
CHANGED
|
@@ -146,17 +146,21 @@ function parseModel({ dmmfModel, enums, models, config, }) {
|
|
|
146
146
|
(0, error_1.throwError)(`Investigate: Field ${shared.sourceName}.${shared.sourceName} is not scalar, enum nor relation.`);
|
|
147
147
|
})
|
|
148
148
|
.filter((field) => !isFieldIgnored({ field }));
|
|
149
|
-
const { idField, defaultField, nameField } = validateFields({ fields, model: core });
|
|
149
|
+
const { idField, defaultField, nameField, createdAtField, updatedAtField } = validateFields({ fields, model: core });
|
|
150
150
|
return Object.assign(Object.assign({}, core), { idField,
|
|
151
151
|
defaultField,
|
|
152
152
|
nameField,
|
|
153
|
-
fields
|
|
153
|
+
fields,
|
|
154
|
+
createdAtField,
|
|
155
|
+
updatedAtField });
|
|
154
156
|
}
|
|
155
157
|
/**
|
|
156
158
|
* Checks that there is exactly one id field and that there is at most one default field.
|
|
157
159
|
*/
|
|
158
160
|
function validateFields({ fields, model: { name } }) {
|
|
159
161
|
let idField = undefined;
|
|
162
|
+
let createdAtField = undefined;
|
|
163
|
+
let updatedAtField = undefined;
|
|
160
164
|
let nameField = undefined;
|
|
161
165
|
let nameFieldFallback = undefined;
|
|
162
166
|
let defaultField = undefined;
|
|
@@ -166,6 +170,18 @@ function validateFields({ fields, model: { name } }) {
|
|
|
166
170
|
if (field.name === 'name') {
|
|
167
171
|
nameFieldFallback = field;
|
|
168
172
|
}
|
|
173
|
+
if (field.attributes.isCreatedAt) {
|
|
174
|
+
if (createdAtField) {
|
|
175
|
+
throw new Error(`❌❌❌ Model ${name} has multiple createdAt fields`);
|
|
176
|
+
}
|
|
177
|
+
createdAtField = field;
|
|
178
|
+
}
|
|
179
|
+
if (field.attributes.isUpdatedAt) {
|
|
180
|
+
if (updatedAtField) {
|
|
181
|
+
throw new Error(`❌❌❌ Model ${name} has multiple updatedAt fields`);
|
|
182
|
+
}
|
|
183
|
+
updatedAtField = field;
|
|
184
|
+
}
|
|
169
185
|
break;
|
|
170
186
|
case 'id':
|
|
171
187
|
if (idField) {
|
|
@@ -199,7 +215,7 @@ function validateFields({ fields, model: { name } }) {
|
|
|
199
215
|
if (!nameField && nameFieldFallback) {
|
|
200
216
|
nameField = nameFieldFallback;
|
|
201
217
|
}
|
|
202
|
-
return { idField, defaultField, nameField };
|
|
218
|
+
return { idField, defaultField, nameField, createdAtField, updatedAtField };
|
|
203
219
|
}
|
|
204
220
|
function isAutoIncrementField(fieldDmmf) {
|
|
205
221
|
if (fieldDmmf.default === undefined) {
|
|
@@ -232,9 +248,6 @@ function isUniqueField(fieldRaw) {
|
|
|
232
248
|
* Tells whether the parsed schema should skip a given field.
|
|
233
249
|
*/
|
|
234
250
|
function isFieldIgnored({ field }) {
|
|
235
|
-
if (field.name === 'createdAt' || field.name === 'updatedAt' || field.name === 'deletedAt') {
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
251
|
if (Object.hasOwn(field.attributes, 'ignore') && field.attributes.ignore) {
|
|
239
252
|
return true;
|
|
240
253
|
}
|