@jskit-ai/crud-server-generator 0.1.63 → 0.1.64
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/package.descriptor.mjs +13 -30
- package/package.json +8 -8
- package/src/server/buildTemplateContext.js +449 -496
- package/src/server/subcommands/addField.js +8 -80
- package/src/server/subcommands/resourceAst.js +40 -393
- package/src/shared/crud/crudResource.js +85 -185
- package/templates/src/local-package/package.descriptor.mjs +3 -6
- package/templates/src/local-package/package.json +0 -1
- package/templates/src/local-package/server/CrudProvider.js +28 -21
- package/templates/src/local-package/server/actions.js +42 -54
- package/templates/src/local-package/server/registerRoutes.js +22 -50
- package/templates/src/local-package/server/repository.js +82 -38
- package/templates/src/local-package/server/service.js +45 -73
- package/templates/src/local-package/shared/crudResource.js +15 -140
- package/test/addFieldSubcommand.test.js +100 -77
- package/test/buildTemplateContext.test.js +139 -203
- package/test/crudResource.test.js +26 -31
- package/test/crudServerGuards.test.js +157 -42
- package/test/crudService.test.js +91 -173
- package/test/packageDescriptor.test.js +3 -11
- package/test/routeInputContracts.test.js +77 -8
- package/test/templateSymbolConsistency.test.js +19 -3
- package/test-support/templateServerFixture.js +155 -112
- package/templates/src/local-package/server/actionIds.js +0 -9
- package/templates/src/local-package/server/listConfig.js +0 -5
|
@@ -5,29 +5,14 @@ import { toCamelCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
|
5
5
|
import {
|
|
6
6
|
resolveGenerationSnapshot,
|
|
7
7
|
resolveScaffoldColumns,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
renderInputNormalizer,
|
|
11
|
-
renderOutputNormalizerExpression,
|
|
12
|
-
buildFieldMetaEntries
|
|
8
|
+
renderCanonicalResourceFieldSchema,
|
|
9
|
+
buildFieldContractEntries
|
|
13
10
|
} from "../buildTemplateContext.js";
|
|
14
11
|
import {
|
|
15
12
|
resolveCrudResourceDefaults,
|
|
16
13
|
applyCrudResourceFieldPatch
|
|
17
14
|
} from "./resourceAst.js";
|
|
18
15
|
|
|
19
|
-
const NORMALIZE_SUPPORT_IMPORTS = new Set([
|
|
20
|
-
"normalizeText",
|
|
21
|
-
"normalizeBoolean",
|
|
22
|
-
"normalizeFiniteNumber",
|
|
23
|
-
"normalizeFiniteInteger",
|
|
24
|
-
"normalizeIfInSource",
|
|
25
|
-
"normalizeIfPresent",
|
|
26
|
-
"normalizeOrNull"
|
|
27
|
-
]);
|
|
28
|
-
const DATABASE_RUNTIME_IMPORTS = new Set(["toIsoString", "toDatabaseDateTimeUtc"]);
|
|
29
|
-
const DATABASE_RUNTIME_REPOSITORY_IMPORTS = new Set(["parseJsonValue"]);
|
|
30
|
-
|
|
31
16
|
function toPosixPath(value = "") {
|
|
32
17
|
return String(value || "").replaceAll(path.sep, "/");
|
|
33
18
|
}
|
|
@@ -114,8 +99,8 @@ function resolveColumnForField(snapshot = {}, fieldKey = "", { idColumn = "id" }
|
|
|
114
99
|
);
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
function
|
|
118
|
-
const entries =
|
|
102
|
+
function buildFieldContractEntry(snapshot = {}, column = {}) {
|
|
103
|
+
const entries = buildFieldContractEntries({
|
|
119
104
|
outputColumns: [column],
|
|
120
105
|
writableColumns: [column],
|
|
121
106
|
snapshot
|
|
@@ -124,51 +109,6 @@ function buildFieldMetaEntry(snapshot = {}, column = {}) {
|
|
|
124
109
|
return entries.find((entry) => normalizeText(entry?.key) === key) || null;
|
|
125
110
|
}
|
|
126
111
|
|
|
127
|
-
function collectKnownIdentifiers(expression = "") {
|
|
128
|
-
return new Set(String(expression || "").match(/[A-Za-z_$][A-Za-z0-9_$]*/g) || []);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveImportsForField({ inputNormalizationExpression = "", outputNormalizationExpression = "" } = {}) {
|
|
132
|
-
const normalizeImports = new Set(["normalizeIfInSource"]);
|
|
133
|
-
const databaseRuntimeImports = new Set();
|
|
134
|
-
const databaseRuntimeRepositoryImports = new Set();
|
|
135
|
-
|
|
136
|
-
const identifiers = new Set([
|
|
137
|
-
...collectKnownIdentifiers(inputNormalizationExpression),
|
|
138
|
-
...collectKnownIdentifiers(outputNormalizationExpression)
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
for (const identifier of identifiers) {
|
|
142
|
-
if (NORMALIZE_SUPPORT_IMPORTS.has(identifier)) {
|
|
143
|
-
normalizeImports.add(identifier);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (DATABASE_RUNTIME_IMPORTS.has(identifier)) {
|
|
147
|
-
databaseRuntimeImports.add(identifier);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (DATABASE_RUNTIME_REPOSITORY_IMPORTS.has(identifier)) {
|
|
151
|
-
databaseRuntimeRepositoryImports.add(identifier);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
normalizeImports: [...normalizeImports],
|
|
157
|
-
databaseRuntimeImports: [...databaseRuntimeImports],
|
|
158
|
-
databaseRuntimeRepositoryImports: [...databaseRuntimeRepositoryImports]
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function resolveOutputNormalizationExpression(column = {}) {
|
|
163
|
-
const outputNormalizer = renderOutputNormalizerExpression(column);
|
|
164
|
-
const sourceAccess = renderPropertyAccess("source", column.key);
|
|
165
|
-
if (!outputNormalizer) {
|
|
166
|
-
return sourceAccess;
|
|
167
|
-
}
|
|
168
|
-
const wrapper = column?.nullable === true ? "normalizeOrNull" : "normalizeIfPresent";
|
|
169
|
-
return `${wrapper}(${sourceAccess}, ${outputNormalizer})`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
112
|
async function runGeneratorSubcommand({
|
|
173
113
|
appRoot,
|
|
174
114
|
subcommand = "",
|
|
@@ -201,26 +141,14 @@ async function runGeneratorSubcommand({
|
|
|
201
141
|
);
|
|
202
142
|
}
|
|
203
143
|
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
const outputNormalizationExpression = resolveOutputNormalizationExpression(column);
|
|
208
|
-
const fieldMetaEntry = buildFieldMetaEntry(snapshot, column);
|
|
209
|
-
const imports = resolveImportsForField({
|
|
210
|
-
inputNormalizationExpression,
|
|
211
|
-
outputNormalizationExpression
|
|
144
|
+
const fieldContractEntry = buildFieldContractEntry(snapshot, column);
|
|
145
|
+
const resourceSchemaExpression = renderCanonicalResourceFieldSchema(column, {
|
|
146
|
+
fieldContractEntry
|
|
212
147
|
});
|
|
213
148
|
|
|
214
149
|
const applied = applyCrudResourceFieldPatch(originalSource, {
|
|
215
150
|
fieldKey,
|
|
216
|
-
|
|
217
|
-
outputSchemaExpression,
|
|
218
|
-
inputNormalizationExpression,
|
|
219
|
-
outputNormalizationExpression,
|
|
220
|
-
fieldMetaEntry,
|
|
221
|
-
normalizeImportNames: imports.normalizeImports,
|
|
222
|
-
databaseRuntimeImportNames: imports.databaseRuntimeImports,
|
|
223
|
-
databaseRuntimeRepositoryOptionsImportNames: imports.databaseRuntimeRepositoryImports
|
|
151
|
+
resourceSchemaExpression
|
|
224
152
|
});
|
|
225
153
|
|
|
226
154
|
if (applied.changed && dryRun !== true) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as recast from "recast";
|
|
2
2
|
import { parse as parseBabel } from "@babel/parser";
|
|
3
3
|
import { normalizeText } from "@jskit-ai/database-runtime/shared";
|
|
4
|
-
import { normalizeCrudLookupNamespace } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
5
4
|
|
|
6
5
|
const { namedTypes: n, builders: b } = recast.types;
|
|
7
6
|
|
|
@@ -61,15 +60,6 @@ function parseExpression(source = "", context = "crud-server-generator scaffold-
|
|
|
61
60
|
return declaration.init;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
function parseStatement(source = "", context = "crud-server-generator scaffold-field") {
|
|
65
|
-
const ast = parseModule(String(source || ""), context);
|
|
66
|
-
const statement = ast?.program?.body?.[0];
|
|
67
|
-
if (!statement) {
|
|
68
|
-
throw new Error(`${context} could not parse statement.`);
|
|
69
|
-
}
|
|
70
|
-
return statement;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
63
|
function resolveNodeKeyName(keyNode, { computed = false } = {}) {
|
|
74
64
|
if (!keyNode) {
|
|
75
65
|
return "";
|
|
@@ -122,58 +112,40 @@ function requireVariableDeclarator(programNode, variableName = "", context = "cr
|
|
|
122
112
|
throw new Error(`${context} could not find const ${variableName}.`);
|
|
123
113
|
}
|
|
124
114
|
|
|
125
|
-
function
|
|
126
|
-
const declaration = requireVariableDeclarator(programNode,
|
|
115
|
+
function requireCrudResourceConfigObject(programNode, context = "crud-server-generator scaffold-field") {
|
|
116
|
+
const declaration = requireVariableDeclarator(programNode, "resource", context);
|
|
127
117
|
const initExpression = declaration.init;
|
|
128
118
|
if (!n.CallExpression.check(initExpression)) {
|
|
129
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`${context} requires resource files authored as const resource = defineCrudResource({ ... }).`
|
|
121
|
+
);
|
|
130
122
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
!callee.computed &&
|
|
136
|
-
n.Identifier.check(callee.object) &&
|
|
137
|
-
callee.object.name === "Type" &&
|
|
138
|
-
n.Identifier.check(callee.property) &&
|
|
139
|
-
callee.property.name === "Object";
|
|
140
|
-
if (!isTypeObjectCall) {
|
|
141
|
-
throw new Error(`${context} expected ${variableName} to call Type.Object(...).`);
|
|
123
|
+
if (!n.Identifier.check(initExpression.callee) || initExpression.callee.name !== "defineCrudResource") {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`${context} requires resource files authored as const resource = defineCrudResource({ ... }).`
|
|
126
|
+
);
|
|
142
127
|
}
|
|
143
128
|
|
|
144
129
|
const firstArgument = initExpression.arguments?.[0];
|
|
145
130
|
if (!n.ObjectExpression.check(firstArgument)) {
|
|
146
|
-
throw new Error(
|
|
131
|
+
throw new Error(
|
|
132
|
+
`${context} requires defineCrudResource(...) to receive an inline object literal.`
|
|
133
|
+
);
|
|
147
134
|
}
|
|
148
135
|
|
|
149
136
|
return firstArgument;
|
|
150
137
|
}
|
|
151
138
|
|
|
152
|
-
function
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
if (!n.
|
|
156
|
-
throw new Error(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const callee = initExpression.callee;
|
|
160
|
-
const isObjectFreezeCall =
|
|
161
|
-
n.MemberExpression.check(callee) &&
|
|
162
|
-
!callee.computed &&
|
|
163
|
-
n.Identifier.check(callee.object) &&
|
|
164
|
-
callee.object.name === "Object" &&
|
|
165
|
-
n.Identifier.check(callee.property) &&
|
|
166
|
-
callee.property.name === "freeze";
|
|
167
|
-
if (!isObjectFreezeCall) {
|
|
168
|
-
throw new Error(`${context} expected ${variableName} to call Object.freeze(...).`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const payload = initExpression.arguments?.[0];
|
|
172
|
-
if (!n.ObjectExpression.check(payload)) {
|
|
173
|
-
throw new Error(`${context} expected ${variableName} Object.freeze payload to be an object literal.`);
|
|
139
|
+
function requireResourceSchemaObject(programNode, context = "crud-server-generator scaffold-field") {
|
|
140
|
+
const resourceObject = requireCrudResourceConfigObject(programNode, context);
|
|
141
|
+
const schemaProperty = findObjectPropertyByName(resourceObject, "schema");
|
|
142
|
+
if (!schemaProperty || !n.ObjectExpression.check(schemaProperty.value)) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`${context} requires defineCrudResource({ ..., schema: { ... } }) with an inline schema object literal.`
|
|
145
|
+
);
|
|
174
146
|
}
|
|
175
147
|
|
|
176
|
-
return
|
|
148
|
+
return schemaProperty.value;
|
|
177
149
|
}
|
|
178
150
|
|
|
179
151
|
function findObjectPropertyByName(objectNode, propertyName = "") {
|
|
@@ -197,48 +169,6 @@ function findObjectPropertyByName(objectNode, propertyName = "") {
|
|
|
197
169
|
return null;
|
|
198
170
|
}
|
|
199
171
|
|
|
200
|
-
function requireNormalizeFunctionBody(programNode, variableName = "", context = "crud-server-generator scaffold-field") {
|
|
201
|
-
const validatorObject = requireObjectFreezePayloadObject(programNode, variableName, context);
|
|
202
|
-
const normalizeProperty = findObjectPropertyByName(validatorObject, "normalize");
|
|
203
|
-
if (!normalizeProperty) {
|
|
204
|
-
throw new Error(`${context} expected ${variableName}.normalize(...) to exist.`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (n.ObjectMethod.check(normalizeProperty)) {
|
|
208
|
-
return normalizeProperty.body;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const propertyValue = normalizeProperty.value;
|
|
212
|
-
if (n.FunctionExpression.check(propertyValue) || n.ArrowFunctionExpression.check(propertyValue)) {
|
|
213
|
-
if (n.BlockStatement.check(propertyValue.body)) {
|
|
214
|
-
return propertyValue.body;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
throw new Error(`${context} expected ${variableName}.normalize to be a function with a block body.`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function requireNormalizedObjectLiteral(functionBody, context = "crud-server-generator scaffold-field") {
|
|
222
|
-
for (const statement of functionBody.body || []) {
|
|
223
|
-
if (!n.VariableDeclaration.check(statement)) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
for (const declaration of statement.declarations || []) {
|
|
227
|
-
if (!n.VariableDeclarator.check(declaration) || !n.Identifier.check(declaration.id)) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (declaration.id.name !== "normalized") {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (!n.ObjectExpression.check(declaration.init)) {
|
|
234
|
-
throw new Error(`${context} expected normalized to be initialized as an object literal.`);
|
|
235
|
-
}
|
|
236
|
-
return declaration.init;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
throw new Error(`${context} could not find "const normalized = { ... }".`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
172
|
function hasObjectProperty(objectNode, propertyName = "") {
|
|
243
173
|
return Boolean(findObjectPropertyByName(objectNode, propertyName));
|
|
244
174
|
}
|
|
@@ -284,155 +214,6 @@ function insertObjectProperty(
|
|
|
284
214
|
return true;
|
|
285
215
|
}
|
|
286
216
|
|
|
287
|
-
function hasNormalizeIfInSourceCall(functionBody, fieldKey = "") {
|
|
288
|
-
const targetFieldKey = normalizeText(fieldKey);
|
|
289
|
-
if (!targetFieldKey) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for (const statement of functionBody.body || []) {
|
|
294
|
-
if (!n.ExpressionStatement.check(statement) || !n.CallExpression.check(statement.expression)) {
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
const callExpression = statement.expression;
|
|
298
|
-
if (!n.Identifier.check(callExpression.callee) || callExpression.callee.name !== "normalizeIfInSource") {
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
const keyArgument = callExpression.arguments?.[2];
|
|
302
|
-
if (n.StringLiteral.check(keyArgument) && normalizeText(keyArgument.value) === targetFieldKey) {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
if (n.Literal.check(keyArgument) && typeof keyArgument.value === "string" && normalizeText(keyArgument.value) === targetFieldKey) {
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function resolveReturnNormalizedIndex(functionBody) {
|
|
314
|
-
const statements = Array.isArray(functionBody?.body) ? functionBody.body : [];
|
|
315
|
-
for (const [index, statement] of statements.entries()) {
|
|
316
|
-
if (!n.ReturnStatement.check(statement)) {
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (n.Identifier.check(statement.argument) && statement.argument.name === "normalized") {
|
|
320
|
-
return index;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return statements.length;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function hasResourceFieldMetaEntry(programNode, fieldKey = "") {
|
|
327
|
-
const targetKey = normalizeText(fieldKey);
|
|
328
|
-
if (!targetKey || !programNode || !Array.isArray(programNode.body)) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
for (const statement of programNode.body) {
|
|
333
|
-
if (!n.ExpressionStatement.check(statement) || !n.CallExpression.check(statement.expression)) {
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
const callExpression = statement.expression;
|
|
337
|
-
if (!n.MemberExpression.check(callExpression.callee) || callExpression.callee.computed) {
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (!n.Identifier.check(callExpression.callee.object) || callExpression.callee.object.name !== "RESOURCE_FIELD_META") {
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
if (!n.Identifier.check(callExpression.callee.property) || callExpression.callee.property.name !== "push") {
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const firstArgument = callExpression.arguments?.[0];
|
|
348
|
-
if (!n.ObjectExpression.check(firstArgument)) {
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
const keyProperty = findObjectPropertyByName(firstArgument, "key");
|
|
352
|
-
if (!keyProperty) {
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
const keyValue = keyProperty.value;
|
|
356
|
-
if (n.StringLiteral.check(keyValue) && normalizeText(keyValue.value) === targetKey) {
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
if (n.Literal.check(keyValue) && typeof keyValue.value === "string" && normalizeText(keyValue.value) === targetKey) {
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function sortImportSpecifiers(importDeclaration) {
|
|
368
|
-
const sourceSpecifiers = Array.isArray(importDeclaration?.specifiers) ? importDeclaration.specifiers : [];
|
|
369
|
-
const named = sourceSpecifiers
|
|
370
|
-
.filter((specifier) => n.ImportSpecifier.check(specifier))
|
|
371
|
-
.sort((left, right) => {
|
|
372
|
-
const leftName = String(left?.imported?.name || left?.imported?.value || "");
|
|
373
|
-
const rightName = String(right?.imported?.name || right?.imported?.value || "");
|
|
374
|
-
return leftName.localeCompare(rightName);
|
|
375
|
-
});
|
|
376
|
-
const nonNamed = sourceSpecifiers.filter((specifier) => !n.ImportSpecifier.check(specifier));
|
|
377
|
-
importDeclaration.specifiers = [...nonNamed, ...named];
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function ensureNamedImport(programNode, modulePath = "", importName = "") {
|
|
381
|
-
const normalizedModulePath = normalizeText(modulePath);
|
|
382
|
-
const normalizedImportName = normalizeText(importName);
|
|
383
|
-
if (!normalizedModulePath || !normalizedImportName) {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const importDeclarations = (programNode.body || []).filter((statement) => n.ImportDeclaration.check(statement));
|
|
388
|
-
let declaration = importDeclarations.find((statement) => {
|
|
389
|
-
const source = statement.source;
|
|
390
|
-
if (n.StringLiteral.check(source)) {
|
|
391
|
-
return source.value === normalizedModulePath;
|
|
392
|
-
}
|
|
393
|
-
if (n.Literal.check(source)) {
|
|
394
|
-
return source.value === normalizedModulePath;
|
|
395
|
-
}
|
|
396
|
-
return false;
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (!declaration) {
|
|
400
|
-
declaration = b.importDeclaration(
|
|
401
|
-
[b.importSpecifier(b.identifier(normalizedImportName), b.identifier(normalizedImportName))],
|
|
402
|
-
b.stringLiteral(normalizedModulePath)
|
|
403
|
-
);
|
|
404
|
-
const insertionIndex = (() => {
|
|
405
|
-
const body = Array.isArray(programNode.body) ? programNode.body : [];
|
|
406
|
-
let index = 0;
|
|
407
|
-
while (index < body.length && n.ImportDeclaration.check(body[index])) {
|
|
408
|
-
index += 1;
|
|
409
|
-
}
|
|
410
|
-
return index;
|
|
411
|
-
})();
|
|
412
|
-
programNode.body.splice(insertionIndex, 0, declaration);
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const hasSpecifier = (declaration.specifiers || []).some((specifier) => {
|
|
417
|
-
if (!n.ImportSpecifier.check(specifier)) {
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
const importedName = String(specifier.imported?.name || specifier.imported?.value || "");
|
|
421
|
-
const localName = String(specifier.local?.name || "");
|
|
422
|
-
return importedName === normalizedImportName || localName === normalizedImportName;
|
|
423
|
-
});
|
|
424
|
-
if (hasSpecifier) {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
declaration.specifiers = [
|
|
429
|
-
...(declaration.specifiers || []),
|
|
430
|
-
b.importSpecifier(b.identifier(normalizedImportName), b.identifier(normalizedImportName))
|
|
431
|
-
];
|
|
432
|
-
sortImportSpecifiers(declaration);
|
|
433
|
-
return true;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
217
|
function resolveObjectPropertyStringValue(objectNode, propertyName = "") {
|
|
437
218
|
const propertyNode = findObjectPropertyByName(objectNode, propertyName);
|
|
438
219
|
if (!propertyNode) {
|
|
@@ -450,88 +231,23 @@ function resolveObjectPropertyStringValue(objectNode, propertyName = "") {
|
|
|
450
231
|
|
|
451
232
|
function resolveCrudResourceDefaults(source = "", context = "crud-server-generator scaffold-field") {
|
|
452
233
|
const ast = parseModule(source, context);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const tableName = resolveObjectPropertyStringValue(declaration.init, "tableName");
|
|
464
|
-
if (!tableName) {
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
const idColumn = resolveObjectPropertyStringValue(declaration.init, "idColumn");
|
|
468
|
-
return Object.freeze({
|
|
469
|
-
tableName,
|
|
470
|
-
idColumn: idColumn || "id"
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
throw new Error(`${context} could not resolve resource tableName/idColumn from resource object literal.`);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function renderResourceFieldMetaPushStatement(entry = {}) {
|
|
479
|
-
const key = normalizeText(entry?.key);
|
|
480
|
-
if (!key) {
|
|
481
|
-
throw new Error("crud-server-generator scaffold-field fieldMeta entry requires key.");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const lines = ["RESOURCE_FIELD_META.push({"];
|
|
485
|
-
lines.push(` key: ${JSON.stringify(key)},`);
|
|
486
|
-
|
|
487
|
-
const repositoryColumn = normalizeText(entry?.repository?.column);
|
|
488
|
-
if (repositoryColumn) {
|
|
489
|
-
lines.push(" repository: {");
|
|
490
|
-
lines.push(` column: ${JSON.stringify(repositoryColumn)}`);
|
|
491
|
-
lines.push(" },");
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const relation = entry?.relation && typeof entry.relation === "object" ? entry.relation : null;
|
|
495
|
-
if (relation) {
|
|
496
|
-
const relationNamespace =
|
|
497
|
-
normalizeCrudLookupNamespace(relation.namespace) ||
|
|
498
|
-
normalizeCrudLookupNamespace(relation.apiPath);
|
|
499
|
-
if (!relationNamespace) {
|
|
500
|
-
throw new Error("crud-server-generator scaffold-field fieldMeta relation requires namespace.");
|
|
501
|
-
}
|
|
502
|
-
lines.push(" relation: {");
|
|
503
|
-
lines.push(` kind: ${JSON.stringify(normalizeText(relation.kind) || "lookup")},`);
|
|
504
|
-
lines.push(` namespace: ${JSON.stringify(relationNamespace)},`);
|
|
505
|
-
lines.push(` valueKey: ${JSON.stringify(normalizeText(relation.valueKey) || "id")}`);
|
|
506
|
-
lines.push(" },");
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const formControl = normalizeText(entry?.ui?.formControl);
|
|
510
|
-
if (formControl) {
|
|
511
|
-
lines.push(" ui: {");
|
|
512
|
-
lines.push(` formControl: ${JSON.stringify(formControl)} // or "select"`);
|
|
513
|
-
lines.push(" }");
|
|
514
|
-
} else {
|
|
515
|
-
const lastIndex = lines.length - 1;
|
|
516
|
-
lines[lastIndex] = lines[lastIndex].replace(/,$/, "");
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
lines.push("});");
|
|
520
|
-
return lines.join("\n");
|
|
234
|
+
const resourceObject = requireCrudResourceConfigObject(ast.program, context);
|
|
235
|
+
const tableName = resolveObjectPropertyStringValue(resourceObject, "tableName");
|
|
236
|
+
if (!tableName) {
|
|
237
|
+
throw new Error(`${context} could not resolve resource tableName from resource object literal.`);
|
|
238
|
+
}
|
|
239
|
+
const idColumn = resolveObjectPropertyStringValue(resourceObject, "idColumn") || "id";
|
|
240
|
+
return Object.freeze({
|
|
241
|
+
tableName,
|
|
242
|
+
idColumn
|
|
243
|
+
});
|
|
521
244
|
}
|
|
522
245
|
|
|
523
246
|
function applyCrudResourceFieldPatch(
|
|
524
247
|
source = "",
|
|
525
248
|
{
|
|
526
249
|
fieldKey = "",
|
|
527
|
-
|
|
528
|
-
createSchemaExpression = "",
|
|
529
|
-
outputNormalizationExpression = "",
|
|
530
|
-
inputNormalizationExpression = "",
|
|
531
|
-
fieldMetaEntry = null,
|
|
532
|
-
normalizeImportNames = [],
|
|
533
|
-
databaseRuntimeImportNames = [],
|
|
534
|
-
databaseRuntimeRepositoryOptionsImportNames = [],
|
|
250
|
+
resourceSchemaExpression = "",
|
|
535
251
|
context = "crud-server-generator scaffold-field"
|
|
536
252
|
} = {}
|
|
537
253
|
) {
|
|
@@ -539,88 +255,19 @@ function applyCrudResourceFieldPatch(
|
|
|
539
255
|
if (!normalizedFieldKey) {
|
|
540
256
|
throw new Error(`${context} apply patch requires fieldKey.`);
|
|
541
257
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
if (!normalizeText(outputNormalizationExpression)) {
|
|
549
|
-
throw new Error(`${context} apply patch requires outputNormalizationExpression.`);
|
|
550
|
-
}
|
|
551
|
-
if (!normalizeText(inputNormalizationExpression)) {
|
|
552
|
-
throw new Error(`${context} apply patch requires inputNormalizationExpression.`);
|
|
258
|
+
const hasCanonicalFieldExpression = Boolean(normalizeText(resourceSchemaExpression));
|
|
259
|
+
if (!hasCanonicalFieldExpression) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`${context} apply patch requires resourceSchemaExpression.`
|
|
262
|
+
);
|
|
553
263
|
}
|
|
554
264
|
|
|
555
265
|
const ast = parseModule(source, context);
|
|
556
266
|
const programNode = ast.program;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
insertObjectProperty(recordOutputSchemaObject, normalizedFieldKey, outputSchemaExpression, {
|
|
562
|
-
context,
|
|
563
|
-
insertBeforeComputed: true
|
|
564
|
-
}) || changed;
|
|
565
|
-
|
|
566
|
-
const createBodySchemaObject = requireSchemaPropertiesObject(programNode, "createBodySchema", context);
|
|
567
|
-
changed =
|
|
568
|
-
insertObjectProperty(createBodySchemaObject, normalizedFieldKey, createSchemaExpression, {
|
|
569
|
-
context
|
|
570
|
-
}) || changed;
|
|
571
|
-
|
|
572
|
-
const recordNormalizeFunctionBody = requireNormalizeFunctionBody(programNode, "recordOutputValidator", context);
|
|
573
|
-
const recordNormalizedObject = requireNormalizedObjectLiteral(recordNormalizeFunctionBody, context);
|
|
574
|
-
changed =
|
|
575
|
-
insertObjectProperty(recordNormalizedObject, normalizedFieldKey, outputNormalizationExpression, {
|
|
576
|
-
context
|
|
577
|
-
}) || changed;
|
|
578
|
-
|
|
579
|
-
const createNormalizeFunctionBody = requireNormalizeFunctionBody(programNode, "createBodyValidator", context);
|
|
580
|
-
if (!hasNormalizeIfInSourceCall(createNormalizeFunctionBody, normalizedFieldKey)) {
|
|
581
|
-
const insertionStatement = parseStatement(
|
|
582
|
-
`normalizeIfInSource(source, normalized, ${JSON.stringify(normalizedFieldKey)}, ${inputNormalizationExpression});`,
|
|
583
|
-
context
|
|
584
|
-
);
|
|
585
|
-
const insertionIndex = resolveReturnNormalizedIndex(createNormalizeFunctionBody);
|
|
586
|
-
createNormalizeFunctionBody.body.splice(insertionIndex, 0, insertionStatement);
|
|
587
|
-
changed = true;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const validFieldMetaEntry =
|
|
591
|
-
fieldMetaEntry &&
|
|
592
|
-
typeof fieldMetaEntry === "object" &&
|
|
593
|
-
normalizeText(fieldMetaEntry.key) === normalizedFieldKey
|
|
594
|
-
? fieldMetaEntry
|
|
595
|
-
: null;
|
|
596
|
-
if (validFieldMetaEntry && !hasResourceFieldMetaEntry(programNode, normalizedFieldKey)) {
|
|
597
|
-
const fieldMetaStatement = parseStatement(renderResourceFieldMetaPushStatement(validFieldMetaEntry), context);
|
|
598
|
-
programNode.body.push(fieldMetaStatement);
|
|
599
|
-
changed = true;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const normalizeImports = Array.isArray(normalizeImportNames) ? normalizeImportNames : [];
|
|
603
|
-
for (const importName of normalizeImports) {
|
|
604
|
-
changed =
|
|
605
|
-
ensureNamedImport(programNode, "@jskit-ai/kernel/shared/support/normalize", importName) || changed;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const databaseRuntimeImports = Array.isArray(databaseRuntimeImportNames) ? databaseRuntimeImportNames : [];
|
|
609
|
-
for (const importName of databaseRuntimeImports) {
|
|
610
|
-
changed = ensureNamedImport(programNode, "@jskit-ai/database-runtime/shared", importName) || changed;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const databaseRuntimeRepositoryOptionsImports = Array.isArray(databaseRuntimeRepositoryOptionsImportNames)
|
|
614
|
-
? databaseRuntimeRepositoryOptionsImportNames
|
|
615
|
-
: [];
|
|
616
|
-
for (const importName of databaseRuntimeRepositoryOptionsImports) {
|
|
617
|
-
changed =
|
|
618
|
-
ensureNamedImport(
|
|
619
|
-
programNode,
|
|
620
|
-
"@jskit-ai/database-runtime/shared/repositoryOptions",
|
|
621
|
-
importName
|
|
622
|
-
) || changed;
|
|
623
|
-
}
|
|
267
|
+
const resourceSchemaObject = requireResourceSchemaObject(programNode, context);
|
|
268
|
+
const changed = insertObjectProperty(resourceSchemaObject, normalizedFieldKey, resourceSchemaExpression, {
|
|
269
|
+
context
|
|
270
|
+
});
|
|
624
271
|
|
|
625
272
|
return {
|
|
626
273
|
changed,
|