@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.
@@ -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 { Type } from "typebox";
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 RESOURCE_LOOKUP_CONTAINER_KEY = "lookups";
13
-
14
- const recordOutputSchema = Type.Object(
15
- {
16
- id: Type.Integer({ minimum: 1 }),
17
- firstName: Type.Union([Type.String(), Type.Null()]),
18
- [RESOURCE_LOOKUP_CONTAINER_KEY]: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
19
- },
20
- { additionalProperties: false }
21
- );
22
-
23
- const createBodySchema = Type.Object(
24
- {
25
- firstName: Type.Union([Type.String({ maxLength: 160 }), Type.Null()])
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
- additionalProperties: false,
29
- required: []
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
- const listOutputValidator = createCursorListValidator(recordOutputValidator);
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
- normalizeIfInSource(source, normalized, "firstName", normalizeText);
35
+ const NON_INLINE_RESOURCE_SOURCE = `import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
60
36
 
61
- return normalized;
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 patchBodyValidator = Object.freeze({
66
- schema: patchBodySchema,
67
- normalize: createBodyValidator.normalize
68
- });
53
+ const resource = defineCrudResource(resourceConfig);
54
+
55
+ export { resource };
56
+ `;
69
57
 
70
- const RESOURCE_FIELD_META = [];
58
+ const NON_INLINE_SCHEMA_RESOURCE_SOURCE = `import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
71
59
 
72
- const resource = {
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
- idColumn: "id",
76
- operations: {
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: nullableRecordIdSchema/);
149
- assert.match(
150
- content,
151
- /normalizeIfInSource\(source, normalized, "categoryId", \(value\) => normalizeRecordId\(value, \{ fallback: null \}\)\);/
152
- );
153
- assert.match(
154
- content,
155
- /categoryId: normalizeOrNull\(source\.categoryId, \(value\) => normalizeRecordId\(value, \{ fallback: null \}\)\)/
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
+ });