@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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import test, { after } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
|
|
4
3
|
import { createTemplateServerFixture } from "../test-support/templateServerFixture.js";
|
|
5
4
|
|
|
6
5
|
const fixture = await createTemplateServerFixture();
|
|
@@ -10,6 +9,7 @@ const nonWorkspaceFixture = await createTemplateServerFixture({
|
|
|
10
9
|
});
|
|
11
10
|
const { createActions } = await fixture.importServerModule("actions.js");
|
|
12
11
|
const { createRepository } = await fixture.importServerModule("repository.js");
|
|
12
|
+
const { createService } = await fixture.importServerModule("service.js");
|
|
13
13
|
const { createActions: createNonWorkspaceActions } = await nonWorkspaceFixture.importServerModule("actions.js");
|
|
14
14
|
|
|
15
15
|
after(async () => {
|
|
@@ -17,47 +17,125 @@ after(async () => {
|
|
|
17
17
|
await nonWorkspaceFixture.cleanup();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
test("template createRepository
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
modify(callback) {
|
|
32
|
-
if (typeof callback === "function") {
|
|
33
|
-
callback(query);
|
|
20
|
+
test("template createRepository passes a mutable JSKIT context into json-rest-api", async () => {
|
|
21
|
+
const calls = [];
|
|
22
|
+
const api = {
|
|
23
|
+
resources: {
|
|
24
|
+
customers: {
|
|
25
|
+
async query(params, context) {
|
|
26
|
+
context.method = "query";
|
|
27
|
+
calls.push({ params, context });
|
|
28
|
+
return { data: [] };
|
|
29
|
+
}
|
|
34
30
|
}
|
|
35
|
-
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const knex = {
|
|
34
|
+
async transaction(work) {
|
|
35
|
+
return work("trx");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const sourceContext = Object.freeze({
|
|
39
|
+
visibilityContext: Object.freeze({
|
|
40
|
+
visibility: "workspace",
|
|
41
|
+
scopeOwnerId: "7"
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const repository = createRepository({ api, knex });
|
|
46
|
+
assert.equal(typeof repository.queryDocuments, "function");
|
|
47
|
+
await repository.queryDocuments(
|
|
48
|
+
{
|
|
49
|
+
q: "Merc",
|
|
50
|
+
cursor: "cursor_2",
|
|
51
|
+
limit: 10,
|
|
52
|
+
include: "workspace"
|
|
36
53
|
},
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
{
|
|
55
|
+
context: sourceContext
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
assert.deepEqual(calls[0].params, {
|
|
60
|
+
queryParams: {
|
|
61
|
+
filters: {
|
|
62
|
+
q: "Merc"
|
|
63
|
+
},
|
|
64
|
+
include: ["workspace"],
|
|
65
|
+
page: {
|
|
66
|
+
after: "cursor_2",
|
|
67
|
+
size: "10"
|
|
68
|
+
}
|
|
39
69
|
},
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
transaction: null,
|
|
71
|
+
simplified: false
|
|
72
|
+
});
|
|
73
|
+
assert.notEqual(
|
|
74
|
+
calls[0].context,
|
|
75
|
+
sourceContext
|
|
76
|
+
);
|
|
77
|
+
assert.deepEqual(calls[0].context, {
|
|
78
|
+
method: "query",
|
|
79
|
+
visibilityContext: {
|
|
80
|
+
visibility: "workspace",
|
|
81
|
+
scopeOwnerId: "7"
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("template createRepository builds mutable JSON:API input documents for writes", async () => {
|
|
87
|
+
const calls = [];
|
|
88
|
+
const api = {
|
|
89
|
+
resources: {
|
|
90
|
+
customers: {
|
|
91
|
+
async post(params) {
|
|
92
|
+
calls.push(params);
|
|
93
|
+
return { data: { type: "customers", id: "1", attributes: { name: "Merc" } } };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
42
96
|
}
|
|
43
97
|
};
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
98
|
+
const knex = {
|
|
99
|
+
async transaction(work) {
|
|
100
|
+
return work("trx");
|
|
101
|
+
}
|
|
48
102
|
};
|
|
49
103
|
|
|
50
|
-
const repository = createRepository(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
104
|
+
const repository = createRepository({ api, knex });
|
|
105
|
+
await repository.createDocument({ name: "Merc" }, {});
|
|
106
|
+
|
|
107
|
+
assert.equal(Object.isFrozen(calls[0].inputRecord), false);
|
|
108
|
+
assert.equal(Object.isFrozen(calls[0].inputRecord.data), false);
|
|
109
|
+
assert.equal(Object.isFrozen(calls[0].inputRecord.data.attributes), false);
|
|
110
|
+
assert.deepEqual(calls[0].inputRecord, {
|
|
111
|
+
data: {
|
|
112
|
+
type: "customers",
|
|
113
|
+
attributes: {
|
|
114
|
+
name: "Merc"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
54
117
|
});
|
|
55
118
|
});
|
|
56
119
|
|
|
57
|
-
test("template
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
test("template createService turns missing resource records into 404 errors", async () => {
|
|
121
|
+
const service = createService({
|
|
122
|
+
customersRepository: {
|
|
123
|
+
async getDocumentById() {
|
|
124
|
+
return null;
|
|
125
|
+
},
|
|
126
|
+
async patchDocumentById() {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await assert.rejects(
|
|
133
|
+
() => service.getDocumentById("7", {}),
|
|
134
|
+
(error) => error?.status === 404 && error?.message === "Document not found."
|
|
135
|
+
);
|
|
136
|
+
await assert.rejects(
|
|
137
|
+
() => service.patchDocumentById("7", { name: "Merc" }, {}),
|
|
138
|
+
(error) => error?.status === 404 && error?.message === "Document not found."
|
|
61
139
|
);
|
|
62
140
|
});
|
|
63
141
|
|
|
@@ -76,18 +154,55 @@ test("template createActions requires namespaced CRUD permissions by default", (
|
|
|
76
154
|
);
|
|
77
155
|
});
|
|
78
156
|
|
|
157
|
+
test("template list action strips workspaceSlug before calling the service", async () => {
|
|
158
|
+
const actions = createActions({ surface: "admin" });
|
|
159
|
+
const listAction = actions.find((action) => action.id === "crud.customers.list");
|
|
160
|
+
const calls = [];
|
|
161
|
+
|
|
162
|
+
await listAction.execute(
|
|
163
|
+
{
|
|
164
|
+
workspaceSlug: "acme",
|
|
165
|
+
q: "Merc",
|
|
166
|
+
include: "workspace"
|
|
167
|
+
},
|
|
168
|
+
{ visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } },
|
|
169
|
+
{
|
|
170
|
+
customersService: {
|
|
171
|
+
async queryDocuments(query, options) {
|
|
172
|
+
calls.push({ query, options });
|
|
173
|
+
return { kind: "document", value: { data: [] } };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
assert.deepEqual(calls[0].query, {
|
|
180
|
+
q: "Merc",
|
|
181
|
+
include: "workspace"
|
|
182
|
+
});
|
|
183
|
+
assert.deepEqual(calls[0].options, {
|
|
184
|
+
context: {
|
|
185
|
+
visibilityContext: {
|
|
186
|
+
visibility: "workspace",
|
|
187
|
+
scopeOwnerId: "7"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
79
193
|
test("template createActions omits workspace validators for non-workspace generation", () => {
|
|
80
194
|
const actions = createNonWorkspaceActions({ surface: "home" });
|
|
81
195
|
|
|
82
|
-
assert.equal(Array.isArray(actions[0].
|
|
83
|
-
assert.
|
|
84
|
-
assert.equal(Array.isArray(actions[1].
|
|
85
|
-
assert.
|
|
86
|
-
assert.equal(actions[
|
|
87
|
-
assert.deepEqual(Object.keys(actions[2].
|
|
88
|
-
assert.equal(
|
|
89
|
-
assert.equal(actions[3].
|
|
90
|
-
assert.
|
|
91
|
-
assert.equal(actions[4].
|
|
196
|
+
assert.equal(Array.isArray(actions[0].input), false);
|
|
197
|
+
assert.deepEqual(Object.keys(actions[0].input.schema.getFieldDefinitions()).sort(), ["contactId", "cursor", "include", "limit", "q"]);
|
|
198
|
+
assert.equal(Array.isArray(actions[1].input), false);
|
|
199
|
+
assert.deepEqual(Object.keys(actions[1].input.schema.getFieldDefinitions()).sort(), ["include", "recordId"]);
|
|
200
|
+
assert.equal(Array.isArray(actions[2].input), false);
|
|
201
|
+
assert.deepEqual(Object.keys(actions[2].input.schema.getFieldDefinitions()).sort(), ["contactId", "name"]);
|
|
202
|
+
assert.equal(actions[2].input.mode, "create");
|
|
203
|
+
assert.equal(Array.isArray(actions[3].input), false);
|
|
204
|
+
assert.deepEqual(Object.keys(actions[3].input.schema.getFieldDefinitions()).sort(), ["contactId", "name", "recordId"]);
|
|
205
|
+
assert.equal(Array.isArray(actions[4].input), false);
|
|
206
|
+
assert.deepEqual(Object.keys(actions[4].input.schema.getFieldDefinitions()), ["recordId"]);
|
|
92
207
|
assert.equal(actions[0].permission.require, "authenticated");
|
|
93
208
|
});
|
package/test/crudService.test.js
CHANGED
|
@@ -3,219 +3,137 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { createTemplateServerFixture } from "../test-support/templateServerFixture.js";
|
|
4
4
|
|
|
5
5
|
const fixture = await createTemplateServerFixture();
|
|
6
|
-
const { createService
|
|
6
|
+
const { createService } = await fixture.importServerModule("service.js");
|
|
7
7
|
|
|
8
8
|
after(async () => {
|
|
9
9
|
await fixture.cleanup();
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
test("crudService
|
|
12
|
+
test("crudService exposes the explicit JSON:API CRUD service contract", async () => {
|
|
13
13
|
const calls = [];
|
|
14
14
|
const customersRepository = {
|
|
15
|
-
async
|
|
16
|
-
calls.push(["
|
|
17
|
-
return {
|
|
15
|
+
async queryDocuments(query, options) {
|
|
16
|
+
calls.push(["queryDocuments", query, options]);
|
|
17
|
+
return { data: [] };
|
|
18
18
|
},
|
|
19
|
-
async
|
|
20
|
-
calls.push(["
|
|
21
|
-
return {
|
|
19
|
+
async getDocumentById(recordId, options) {
|
|
20
|
+
calls.push(["getDocumentById", recordId, options]);
|
|
21
|
+
return { data: { id: String(recordId) } };
|
|
22
22
|
},
|
|
23
|
-
async
|
|
24
|
-
calls.push(["
|
|
25
|
-
return { id: 1,
|
|
23
|
+
async createDocument(payload, options) {
|
|
24
|
+
calls.push(["createDocument", payload, options]);
|
|
25
|
+
return { data: { id: "1", attributes: payload } };
|
|
26
26
|
},
|
|
27
|
-
async
|
|
28
|
-
calls.push(["
|
|
29
|
-
return { id: recordId,
|
|
27
|
+
async patchDocumentById(recordId, payload, options) {
|
|
28
|
+
calls.push(["patchDocumentById", recordId, payload, options]);
|
|
29
|
+
return { data: { id: String(recordId), attributes: payload } };
|
|
30
30
|
},
|
|
31
|
-
async
|
|
32
|
-
calls.push(["
|
|
33
|
-
return
|
|
31
|
+
async deleteDocumentById(recordId, options) {
|
|
32
|
+
calls.push(["deleteDocumentById", recordId, options]);
|
|
33
|
+
return true;
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const service = createService({ customersRepository });
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
assert.deepEqual(Object.keys(service), [
|
|
40
|
+
"queryDocuments",
|
|
41
|
+
"getDocumentById",
|
|
42
|
+
"createDocument",
|
|
43
|
+
"patchDocumentById",
|
|
44
|
+
"deleteDocumentById"
|
|
45
|
+
]);
|
|
46
|
+
assert.equal(Object.isFrozen(service), true);
|
|
47
|
+
|
|
48
|
+
const options = {
|
|
49
|
+
trx: "trx-1",
|
|
50
|
+
context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } },
|
|
51
|
+
include: ["workspace"]
|
|
52
|
+
};
|
|
53
|
+
const listResult = await service.queryDocuments({ limit: 10 }, options);
|
|
54
|
+
const recordResult = await service.getDocumentById(3, options);
|
|
55
|
+
const createResult = await service.createDocument({ textField: "Example", dateField: "2026-03-11", numberField: 3 }, options);
|
|
56
|
+
const updateResult = await service.patchDocumentById(4, { textField: "Changed" }, options);
|
|
57
|
+
const deleteResult = await service.deleteDocumentById(5, options);
|
|
45
58
|
|
|
46
59
|
assert.deepEqual(calls, [
|
|
47
|
-
["
|
|
48
|
-
["
|
|
49
|
-
["
|
|
50
|
-
["
|
|
51
|
-
["
|
|
52
|
-
["deleteById", 5]
|
|
60
|
+
["queryDocuments", { limit: 10 }, { trx: "trx-1", context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } } }],
|
|
61
|
+
["getDocumentById", 3, { trx: "trx-1", context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } }, include: ["workspace"] }],
|
|
62
|
+
["createDocument", { textField: "Example", dateField: "2026-03-11", numberField: 3 }, { trx: "trx-1", context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } } }],
|
|
63
|
+
["patchDocumentById", 4, { textField: "Changed" }, { trx: "trx-1", context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } } }],
|
|
64
|
+
["deleteDocumentById", 5, { trx: "trx-1", context: { visibilityContext: { visibility: "workspace", scopeOwnerId: "7" } } }]
|
|
53
65
|
]);
|
|
66
|
+
assert.equal(listResult.__jskitJsonApiResult, true);
|
|
67
|
+
assert.equal(listResult.kind, "document");
|
|
68
|
+
assert.deepEqual(listResult.value, { data: [] });
|
|
69
|
+
assert.equal(recordResult.__jskitJsonApiResult, true);
|
|
70
|
+
assert.equal(recordResult.kind, "document");
|
|
71
|
+
assert.deepEqual(recordResult.value, { data: { id: "3" } });
|
|
72
|
+
assert.equal(createResult.__jskitJsonApiResult, true);
|
|
73
|
+
assert.equal(createResult.kind, "document");
|
|
74
|
+
assert.deepEqual(createResult.value, {
|
|
75
|
+
data: {
|
|
76
|
+
id: "1",
|
|
77
|
+
attributes: {
|
|
78
|
+
textField: "Example",
|
|
79
|
+
dateField: "2026-03-11",
|
|
80
|
+
numberField: 3
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
assert.equal(updateResult.__jskitJsonApiResult, true);
|
|
85
|
+
assert.equal(updateResult.kind, "document");
|
|
86
|
+
assert.deepEqual(updateResult.value, {
|
|
87
|
+
data: {
|
|
88
|
+
id: "4",
|
|
89
|
+
attributes: {
|
|
90
|
+
textField: "Changed"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
assert.equal(deleteResult, null);
|
|
54
95
|
});
|
|
55
96
|
|
|
56
|
-
test("crudService throws
|
|
97
|
+
test("crudService throws immediately when the repository dependency is missing", () => {
|
|
98
|
+
assert.throws(
|
|
99
|
+
() => createService({}),
|
|
100
|
+
/createService requires customersRepository\./
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("crudService throws 404 when a document is missing", async () => {
|
|
57
105
|
const service = createService({
|
|
58
106
|
customersRepository: {
|
|
59
|
-
async
|
|
60
|
-
return {
|
|
107
|
+
async queryDocuments() {
|
|
108
|
+
return { data: [] };
|
|
61
109
|
},
|
|
62
|
-
async
|
|
110
|
+
async getDocumentById() {
|
|
63
111
|
return null;
|
|
64
112
|
},
|
|
65
|
-
async
|
|
66
|
-
return { id: 1,
|
|
113
|
+
async createDocument(payload) {
|
|
114
|
+
return { data: { id: "1", attributes: payload } };
|
|
67
115
|
},
|
|
68
|
-
async
|
|
116
|
+
async patchDocumentById() {
|
|
69
117
|
return null;
|
|
70
118
|
},
|
|
71
|
-
async
|
|
119
|
+
async deleteDocumentById() {
|
|
72
120
|
return null;
|
|
73
121
|
}
|
|
74
122
|
}
|
|
75
123
|
});
|
|
76
124
|
|
|
77
125
|
await assert.rejects(
|
|
78
|
-
() => service.
|
|
79
|
-
(error) => error?.status === 404 && error?.message === "
|
|
126
|
+
() => service.getDocumentById(9, {}),
|
|
127
|
+
(error) => error?.status === 404 && error?.message === "Document not found."
|
|
80
128
|
);
|
|
81
129
|
|
|
82
130
|
await assert.rejects(
|
|
83
|
-
() => service.
|
|
84
|
-
(error) => error?.status === 404 && error?.message === "
|
|
131
|
+
() => service.patchDocumentById(9, { textField: "Changed" }, {}),
|
|
132
|
+
(error) => error?.status === 404 && error?.message === "Document not found."
|
|
85
133
|
);
|
|
86
134
|
|
|
87
135
|
await assert.rejects(
|
|
88
|
-
() => service.
|
|
89
|
-
(error) => error?.status === 404 && error?.message === "
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("crudService exports default realtime events for create/update/delete", () => {
|
|
94
|
-
assert.equal(serviceEvents.createRecord[0].realtime.event, "customers.record.changed");
|
|
95
|
-
assert.equal(serviceEvents.updateRecord[0].realtime.event, "customers.record.changed");
|
|
96
|
-
assert.equal(serviceEvents.deleteRecord[0].realtime.event, "customers.record.changed");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("crudService passes existing records into repository update options via the shared CRUD service", async () => {
|
|
100
|
-
const calls = [];
|
|
101
|
-
const service = createService({
|
|
102
|
-
customersRepository: {
|
|
103
|
-
async list() {
|
|
104
|
-
return { items: [], nextCursor: null };
|
|
105
|
-
},
|
|
106
|
-
async findById(recordId) {
|
|
107
|
-
calls.push(["findById", recordId]);
|
|
108
|
-
return {
|
|
109
|
-
id: recordId,
|
|
110
|
-
textField: "Existing",
|
|
111
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
112
|
-
numberField: 3
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
async create(payload) {
|
|
116
|
-
return { id: 1, ...payload };
|
|
117
|
-
},
|
|
118
|
-
async updateById(recordId, payload, options = {}) {
|
|
119
|
-
calls.push(["updateById", recordId, payload, options]);
|
|
120
|
-
return {
|
|
121
|
-
id: recordId,
|
|
122
|
-
textField: payload.textField || "",
|
|
123
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
124
|
-
numberField: payload.numberField ?? 0
|
|
125
|
-
};
|
|
126
|
-
},
|
|
127
|
-
async deleteById(recordId) {
|
|
128
|
-
return { id: recordId, deleted: true };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
await service.updateRecord(4, { textField: "Changed" }, {});
|
|
134
|
-
|
|
135
|
-
assert.deepEqual(calls, [
|
|
136
|
-
["findById", 4],
|
|
137
|
-
["updateById", 4, { textField: "Changed" }, {
|
|
138
|
-
existingRecord: {
|
|
139
|
-
id: 4,
|
|
140
|
-
textField: "Existing",
|
|
141
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
142
|
-
numberField: 3
|
|
143
|
-
}
|
|
144
|
-
}]
|
|
145
|
-
]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("crudService supports optional fieldAccess hooks for writable filtering", async () => {
|
|
149
|
-
const calls = [];
|
|
150
|
-
const service = createService({
|
|
151
|
-
customersRepository: {
|
|
152
|
-
async list() {
|
|
153
|
-
return {
|
|
154
|
-
items: [
|
|
155
|
-
{
|
|
156
|
-
id: 1,
|
|
157
|
-
textField: "A",
|
|
158
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
159
|
-
numberField: 1
|
|
160
|
-
}
|
|
161
|
-
],
|
|
162
|
-
nextCursor: null
|
|
163
|
-
};
|
|
164
|
-
},
|
|
165
|
-
async findById() {
|
|
166
|
-
return {
|
|
167
|
-
id: 1,
|
|
168
|
-
textField: "A",
|
|
169
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
170
|
-
numberField: 1
|
|
171
|
-
};
|
|
172
|
-
},
|
|
173
|
-
async create(payload) {
|
|
174
|
-
calls.push(payload);
|
|
175
|
-
return {
|
|
176
|
-
id: 1,
|
|
177
|
-
textField: payload.textField || "",
|
|
178
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
179
|
-
numberField: payload.numberField ?? 0
|
|
180
|
-
};
|
|
181
|
-
},
|
|
182
|
-
async updateById(recordId, payload) {
|
|
183
|
-
calls.push([recordId, payload]);
|
|
184
|
-
return {
|
|
185
|
-
id: recordId,
|
|
186
|
-
textField: payload.textField || "",
|
|
187
|
-
dateField: "2026-03-11T00:00:00.000Z",
|
|
188
|
-
numberField: payload.numberField ?? 0
|
|
189
|
-
};
|
|
190
|
-
},
|
|
191
|
-
async deleteById(recordId) {
|
|
192
|
-
return { id: recordId, deleted: true };
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
fieldAccess: {
|
|
196
|
-
writable: () => ["textField"],
|
|
197
|
-
writeMode: "strip"
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await service.createRecord(
|
|
202
|
-
{
|
|
203
|
-
textField: "Allowed",
|
|
204
|
-
numberField: 99
|
|
205
|
-
},
|
|
206
|
-
{}
|
|
207
|
-
);
|
|
208
|
-
await service.updateRecord(
|
|
209
|
-
2,
|
|
210
|
-
{
|
|
211
|
-
textField: "Updated",
|
|
212
|
-
numberField: 88
|
|
213
|
-
},
|
|
214
|
-
{}
|
|
136
|
+
() => service.deleteDocumentById(9, {}),
|
|
137
|
+
(error) => error?.status === 404 && error?.message === "Document not found."
|
|
215
138
|
);
|
|
216
|
-
|
|
217
|
-
assert.deepEqual(calls, [
|
|
218
|
-
{ textField: "Allowed" },
|
|
219
|
-
[2, { textField: "Updated" }]
|
|
220
|
-
]);
|
|
221
139
|
});
|
|
@@ -21,19 +21,11 @@ test("crud-server-generator surface option validates against enabled surface ids
|
|
|
21
21
|
assert.equal(descriptor.metadata?.generatorSubcommands?.scaffold?.createTarget?.pathTemplate, "packages/${option:namespace|kebab}");
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
test("crud-server-generator installs
|
|
24
|
+
test("crud-server-generator no longer installs a separate jsonRestResource server template", () => {
|
|
25
25
|
const files = descriptor.mutations?.files || [];
|
|
26
|
-
const
|
|
26
|
+
const jsonRestResourceTemplate = files.find((entry) => entry.from === "templates/src/local-package/server/jsonRestResource.js");
|
|
27
27
|
|
|
28
|
-
assert.
|
|
29
|
-
assert.equal(
|
|
30
|
-
listConfigTemplate.to,
|
|
31
|
-
"packages/${option:namespace|kebab}/src/server/listConfig.js"
|
|
32
|
-
);
|
|
33
|
-
assert.deepEqual(listConfigTemplate.templateContext, {
|
|
34
|
-
entrypoint: "src/server/buildTemplateContext.js",
|
|
35
|
-
export: "buildTemplateContext"
|
|
36
|
-
});
|
|
28
|
+
assert.equal(jsonRestResourceTemplate, undefined);
|
|
37
29
|
});
|
|
38
30
|
|
|
39
31
|
test("crud-server-generator wires action and role mutations through template context", () => {
|