@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,82 +5,75 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { runGeneratorSubcommand } from "../src/server/subcommands/addField.js";
|
|
7
7
|
|
|
8
|
-
const RESOURCE_SOURCE = `import {
|
|
9
|
-
import { normalizeObjectInput, createCursorListValidator } from "@jskit-ai/kernel/shared/validators";
|
|
10
|
-
import { normalizeText, normalizeIfInSource, normalizeIfPresent, normalizeOrNull } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
|
+
const RESOURCE_SOURCE = `import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
|
|
11
9
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
const resource = defineCrudResource({
|
|
11
|
+
namespace: "contacts",
|
|
12
|
+
tableName: "contacts",
|
|
13
|
+
schema: {
|
|
14
|
+
firstName: {
|
|
15
|
+
type: "string",
|
|
16
|
+
nullable: true,
|
|
17
|
+
maxLength: 160,
|
|
18
|
+
operations: {
|
|
19
|
+
output: { required: true },
|
|
20
|
+
create: { required: false },
|
|
21
|
+
patch: { required: false }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
26
24
|
},
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const patchBodySchema = Type.Partial(createBodySchema, { additionalProperties: false });
|
|
34
|
-
|
|
35
|
-
const recordOutputValidator = Object.freeze({
|
|
36
|
-
schema: recordOutputSchema,
|
|
37
|
-
normalize(payload = {}) {
|
|
38
|
-
const source = normalizeObjectInput(payload);
|
|
39
|
-
const normalized = {
|
|
40
|
-
id: normalizeIfPresent(source.id, Number),
|
|
41
|
-
firstName: normalizeOrNull(source.firstName, normalizeText)
|
|
42
|
-
};
|
|
43
|
-
const sourceLookupContainer = source[RESOURCE_LOOKUP_CONTAINER_KEY];
|
|
44
|
-
if (sourceLookupContainer && typeof sourceLookupContainer === "object" && !Array.isArray(sourceLookupContainer)) {
|
|
45
|
-
normalized[RESOURCE_LOOKUP_CONTAINER_KEY] = sourceLookupContainer;
|
|
25
|
+
contract: {
|
|
26
|
+
lookup: {
|
|
27
|
+
containerKey: "lookups"
|
|
46
28
|
}
|
|
47
|
-
return normalized;
|
|
48
29
|
}
|
|
49
30
|
});
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const createBodyValidator = Object.freeze({
|
|
54
|
-
schema: createBodySchema,
|
|
55
|
-
normalize(payload = {}) {
|
|
56
|
-
const source = normalizeObjectInput(payload);
|
|
57
|
-
const normalized = {};
|
|
32
|
+
export { resource };
|
|
33
|
+
`;
|
|
58
34
|
|
|
59
|
-
|
|
35
|
+
const NON_INLINE_RESOURCE_SOURCE = `import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
|
|
60
36
|
|
|
61
|
-
|
|
37
|
+
const resourceConfig = {
|
|
38
|
+
namespace: "contacts",
|
|
39
|
+
tableName: "contacts",
|
|
40
|
+
schema: {
|
|
41
|
+
firstName: {
|
|
42
|
+
type: "string",
|
|
43
|
+
required: true,
|
|
44
|
+
operations: {
|
|
45
|
+
output: { required: true },
|
|
46
|
+
create: { required: true },
|
|
47
|
+
patch: { required: false }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
62
50
|
}
|
|
63
|
-
}
|
|
51
|
+
};
|
|
64
52
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
const resource = defineCrudResource(resourceConfig);
|
|
54
|
+
|
|
55
|
+
export { resource };
|
|
56
|
+
`;
|
|
69
57
|
|
|
70
|
-
const
|
|
58
|
+
const NON_INLINE_SCHEMA_RESOURCE_SOURCE = `import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
|
|
71
59
|
|
|
72
|
-
const
|
|
60
|
+
const schemaFields = {
|
|
61
|
+
firstName: {
|
|
62
|
+
type: "string",
|
|
63
|
+
required: true,
|
|
64
|
+
operations: {
|
|
65
|
+
output: { required: true },
|
|
66
|
+
create: { required: true },
|
|
67
|
+
patch: { required: false }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const resource = defineCrudResource({
|
|
73
73
|
namespace: "contacts",
|
|
74
74
|
tableName: "contacts",
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
list: { method: "GET", outputValidator: listOutputValidator },
|
|
78
|
-
view: { method: "GET", outputValidator: recordOutputValidator },
|
|
79
|
-
create: { method: "POST", bodyValidator: createBodyValidator, outputValidator: recordOutputValidator },
|
|
80
|
-
patch: { method: "PATCH", bodyValidator: patchBodyValidator, outputValidator: recordOutputValidator }
|
|
81
|
-
},
|
|
82
|
-
fieldMeta: RESOURCE_FIELD_META
|
|
83
|
-
};
|
|
75
|
+
schema: schemaFields
|
|
76
|
+
});
|
|
84
77
|
|
|
85
78
|
export { resource };
|
|
86
79
|
`;
|
|
@@ -145,21 +138,15 @@ test("scaffold-field patches CRUD resource file using DB snapshot metadata", asy
|
|
|
145
138
|
assert.deepEqual(result.touchedFiles, [resourceFile]);
|
|
146
139
|
|
|
147
140
|
const content = await readFile(path.join(appRoot, resourceFile), "utf8");
|
|
148
|
-
assert.match(content, /categoryId:
|
|
149
|
-
assert.match(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
assert.match(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
assert.match(content, /RESOURCE_FIELD_META\.push\(\{/);
|
|
158
|
-
assert.match(content, /key: "categoryId"/);
|
|
159
|
-
assert.match(content, /namespace: "customer-categories"/);
|
|
160
|
-
assert.match(content, /valueKey: "id"/);
|
|
161
|
-
assert.match(content, /formControl: "autocomplete" \/\/ or "select"/);
|
|
162
|
-
assert.match(content, /normalizeRecordId/);
|
|
141
|
+
assert.match(content, /categoryId: \{/);
|
|
142
|
+
assert.match(content, /type: "id"/);
|
|
143
|
+
assert.match(content, /nullable: true/);
|
|
144
|
+
assert.match(content, /operations: \{/);
|
|
145
|
+
assert.match(content, /create: \{ required: false \}/);
|
|
146
|
+
assert.match(content, /patch: \{ required: false \}/);
|
|
147
|
+
assert.match(content, /relation: \{ kind: "lookup", namespace: "customer-categories", valueKey: "id" \}/);
|
|
148
|
+
assert.match(content, /ui: \{ formControl: "autocomplete" \}/);
|
|
149
|
+
assert.doesNotMatch(content, /normalizeIfInSource|normalizeIfPresent|normalizeOrNull|normalizeRecordId/);
|
|
163
150
|
|
|
164
151
|
const secondRun = await runGeneratorSubcommand({
|
|
165
152
|
appRoot,
|
|
@@ -171,3 +158,39 @@ test("scaffold-field patches CRUD resource file using DB snapshot metadata", asy
|
|
|
171
158
|
assert.deepEqual(secondRun.touchedFiles, []);
|
|
172
159
|
});
|
|
173
160
|
});
|
|
161
|
+
|
|
162
|
+
test("scaffold-field rejects resource modules that do not inline the defineCrudResource config object", async () => {
|
|
163
|
+
await withTempApp(async (appRoot) => {
|
|
164
|
+
const resourceFile = "packages/contacts/src/shared/contactResource.js";
|
|
165
|
+
await writeAppFile(appRoot, resourceFile, NON_INLINE_RESOURCE_SOURCE);
|
|
166
|
+
|
|
167
|
+
await assert.rejects(
|
|
168
|
+
() => runGeneratorSubcommand({
|
|
169
|
+
appRoot,
|
|
170
|
+
subcommand: "scaffold-field",
|
|
171
|
+
args: ["categoryId", resourceFile],
|
|
172
|
+
options: {},
|
|
173
|
+
resolveSnapshot: async () => createSnapshot()
|
|
174
|
+
}),
|
|
175
|
+
/requires defineCrudResource\(\.\.\.\) to receive an inline object literal\./
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("scaffold-field rejects resource modules without an inline schema object literal", async () => {
|
|
181
|
+
await withTempApp(async (appRoot) => {
|
|
182
|
+
const resourceFile = "packages/contacts/src/shared/contactResource.js";
|
|
183
|
+
await writeAppFile(appRoot, resourceFile, NON_INLINE_SCHEMA_RESOURCE_SOURCE);
|
|
184
|
+
|
|
185
|
+
await assert.rejects(
|
|
186
|
+
() => runGeneratorSubcommand({
|
|
187
|
+
appRoot,
|
|
188
|
+
subcommand: "scaffold-field",
|
|
189
|
+
args: ["categoryId", resourceFile],
|
|
190
|
+
options: {},
|
|
191
|
+
resolveSnapshot: async () => createSnapshot()
|
|
192
|
+
}),
|
|
193
|
+
/requires defineCrudResource\(\{ \.\.\., schema: \{ \.\.\. \} \}\) with an inline schema object literal\./
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
});
|