@jskit-ai/crud 0.1.4
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 +322 -0
- package/package.json +22 -0
- package/src/client/index.js +3 -0
- package/src/server/CrudServiceProvider.js +11 -0
- package/src/server/actionIds.js +22 -0
- package/src/server/actions.js +152 -0
- package/src/server/registerRoutes.js +235 -0
- package/src/server/repository.js +162 -0
- package/src/server/service.js +96 -0
- package/src/shared/crud/crudModuleConfig.js +310 -0
- package/src/shared/crud/crudResource.js +191 -0
- package/src/shared/index.js +12 -0
- package/templates/migrations/crud_initial.cjs +42 -0
- package/templates/src/elements/CreateElement.vue +115 -0
- package/templates/src/elements/EditElement.vue +140 -0
- package/templates/src/elements/ListElement.vue +88 -0
- package/templates/src/elements/ViewElement.vue +126 -0
- package/templates/src/elements/clientSupport.js +41 -0
- package/templates/src/local-package/client/index.js +4 -0
- package/templates/src/local-package/package.descriptor.mjs +83 -0
- package/templates/src/local-package/package.json +14 -0
- package/templates/src/local-package/server/CrudServiceProvider.js +87 -0
- package/templates/src/local-package/server/actionIds.js +9 -0
- package/templates/src/local-package/server/actions.js +151 -0
- package/templates/src/local-package/server/diTokens.js +4 -0
- package/templates/src/local-package/server/registerRoutes.js +196 -0
- package/templates/src/local-package/server/repository.js +1 -0
- package/templates/src/local-package/server/service.js +96 -0
- package/templates/src/local-package/shared/crudResource.js +1 -0
- package/templates/src/local-package/shared/index.js +8 -0
- package/templates/src/local-package/shared/moduleConfig.js +169 -0
- package/templates/src/pages/admin/crud/[recordId]/edit.vue +7 -0
- package/templates/src/pages/admin/crud/[recordId]/index.vue +7 -0
- package/templates/src/pages/admin/crud/index.vue +7 -0
- package/templates/src/pages/admin/crud/new.vue +7 -0
- package/test/crudModuleConfig.test.js +225 -0
- package/test/crudResource.test.js +41 -0
- package/test/crudServerGuards.test.js +61 -0
- package/test/crudService.test.js +83 -0
- package/test/routeInputContracts.test.js +211 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
2
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
+
import { KERNEL_TOKENS } from "@jskit-ai/kernel/shared/support/tokens";
|
|
4
|
+
import {
|
|
5
|
+
cursorPaginationQueryValidator,
|
|
6
|
+
recordIdParamsValidator
|
|
7
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
8
|
+
import { routeParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
9
|
+
import { normalizeScopedRouteVisibility } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
10
|
+
import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
|
|
11
|
+
import { resolveApiBasePath } from "@jskit-ai/users-core/shared/support/usersApiPaths";
|
|
12
|
+
import { crudResource } from "../shared/crud/crudResource.js";
|
|
13
|
+
|
|
14
|
+
function joinRoutePath(basePath = "", suffix = "") {
|
|
15
|
+
const base = String(basePath || "").trim().replace(/\/+$/g, "");
|
|
16
|
+
const end = String(suffix || "").trim();
|
|
17
|
+
if (!end) {
|
|
18
|
+
return base;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return `${base}/${end.replace(/^\/+/, "")}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireRouteRelativePath(routeRelativePath) {
|
|
25
|
+
const routePath = String(routeRelativePath || "").trim();
|
|
26
|
+
if (!routePath) {
|
|
27
|
+
throw new TypeError("registerRoutes requires routeRelativePath.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return routePath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function requireActionIds(actionIds) {
|
|
34
|
+
const source = actionIds && typeof actionIds === "object" && !Array.isArray(actionIds) ? actionIds : null;
|
|
35
|
+
if (!source) {
|
|
36
|
+
throw new TypeError("registerRoutes requires actionIds.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const requiredKeys = ["list", "view", "create", "update", "delete"];
|
|
40
|
+
const normalized = {};
|
|
41
|
+
for (const key of requiredKeys) {
|
|
42
|
+
const value = String(source[key] || "").trim();
|
|
43
|
+
if (!value) {
|
|
44
|
+
throw new TypeError(`registerRoutes requires actionIds.${key}.`);
|
|
45
|
+
}
|
|
46
|
+
normalized[key] = value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Object.freeze(normalized);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function registerRoutes(
|
|
53
|
+
app,
|
|
54
|
+
{
|
|
55
|
+
routeRelativePath,
|
|
56
|
+
routeOwnershipFilter = "public",
|
|
57
|
+
routeSurface = "",
|
|
58
|
+
routeSurfaceRequiresWorkspace = false,
|
|
59
|
+
actionIds
|
|
60
|
+
} = {}
|
|
61
|
+
) {
|
|
62
|
+
if (!app || typeof app.make !== "function") {
|
|
63
|
+
throw new Error("registerRoutes requires application make().");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const router = app.make(KERNEL_TOKENS.HttpRouter);
|
|
67
|
+
const relativePath = requireRouteRelativePath(routeRelativePath);
|
|
68
|
+
const routeBase = resolveApiBasePath({
|
|
69
|
+
surfaceRequiresWorkspace: routeSurfaceRequiresWorkspace === true,
|
|
70
|
+
relativePath
|
|
71
|
+
});
|
|
72
|
+
const routeVisibility = normalizeScopedRouteVisibility(routeOwnershipFilter, {
|
|
73
|
+
fallback: "public"
|
|
74
|
+
});
|
|
75
|
+
const surface = normalizeSurfaceId(routeSurface);
|
|
76
|
+
const resolvedActionIds = requireActionIds(actionIds);
|
|
77
|
+
|
|
78
|
+
router.register(
|
|
79
|
+
"GET",
|
|
80
|
+
routeBase,
|
|
81
|
+
{
|
|
82
|
+
auth: "required",
|
|
83
|
+
surface,
|
|
84
|
+
visibility: routeVisibility,
|
|
85
|
+
meta: {
|
|
86
|
+
tags: ["crud"],
|
|
87
|
+
summary: "List records."
|
|
88
|
+
},
|
|
89
|
+
paramsValidator: routeParamsValidator,
|
|
90
|
+
queryValidator: cursorPaginationQueryValidator,
|
|
91
|
+
responseValidators: withStandardErrorResponses({
|
|
92
|
+
200: crudResource.operations.list.outputValidator
|
|
93
|
+
})
|
|
94
|
+
},
|
|
95
|
+
async function (request, reply) {
|
|
96
|
+
const listInput = {
|
|
97
|
+
...buildWorkspaceInputFromRouteParams(request.input.params)
|
|
98
|
+
};
|
|
99
|
+
if (request.input.query.cursor != null) {
|
|
100
|
+
listInput.cursor = request.input.query.cursor;
|
|
101
|
+
}
|
|
102
|
+
if (request.input.query.limit != null) {
|
|
103
|
+
listInput.limit = request.input.query.limit;
|
|
104
|
+
}
|
|
105
|
+
const response = await request.executeAction({
|
|
106
|
+
actionId: resolvedActionIds.list,
|
|
107
|
+
input: listInput
|
|
108
|
+
});
|
|
109
|
+
reply.code(200).send(response);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
router.register(
|
|
114
|
+
"GET",
|
|
115
|
+
joinRoutePath(routeBase, ":recordId"),
|
|
116
|
+
{
|
|
117
|
+
auth: "required",
|
|
118
|
+
surface,
|
|
119
|
+
visibility: routeVisibility,
|
|
120
|
+
meta: {
|
|
121
|
+
tags: ["crud"],
|
|
122
|
+
summary: "View a record."
|
|
123
|
+
},
|
|
124
|
+
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
125
|
+
responseValidators: withStandardErrorResponses({
|
|
126
|
+
200: crudResource.operations.view.outputValidator
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
async function (request, reply) {
|
|
130
|
+
const response = await request.executeAction({
|
|
131
|
+
actionId: resolvedActionIds.view,
|
|
132
|
+
input: {
|
|
133
|
+
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
134
|
+
recordId: request.input.params.recordId
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
reply.code(200).send(response);
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
router.register(
|
|
142
|
+
"POST",
|
|
143
|
+
routeBase,
|
|
144
|
+
{
|
|
145
|
+
auth: "required",
|
|
146
|
+
surface,
|
|
147
|
+
visibility: routeVisibility,
|
|
148
|
+
meta: {
|
|
149
|
+
tags: ["crud"],
|
|
150
|
+
summary: "Create a record."
|
|
151
|
+
},
|
|
152
|
+
paramsValidator: routeParamsValidator,
|
|
153
|
+
bodyValidator: crudResource.operations.create.bodyValidator,
|
|
154
|
+
responseValidators: withStandardErrorResponses(
|
|
155
|
+
{
|
|
156
|
+
201: crudResource.operations.create.outputValidator
|
|
157
|
+
},
|
|
158
|
+
{ includeValidation400: true }
|
|
159
|
+
)
|
|
160
|
+
},
|
|
161
|
+
async function (request, reply) {
|
|
162
|
+
const response = await request.executeAction({
|
|
163
|
+
actionId: resolvedActionIds.create,
|
|
164
|
+
input: {
|
|
165
|
+
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
166
|
+
payload: request.input.body
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
reply.code(201).send(response);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
router.register(
|
|
174
|
+
"PATCH",
|
|
175
|
+
joinRoutePath(routeBase, ":recordId"),
|
|
176
|
+
{
|
|
177
|
+
auth: "required",
|
|
178
|
+
surface,
|
|
179
|
+
visibility: routeVisibility,
|
|
180
|
+
meta: {
|
|
181
|
+
tags: ["crud"],
|
|
182
|
+
summary: "Update a record."
|
|
183
|
+
},
|
|
184
|
+
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
185
|
+
bodyValidator: crudResource.operations.patch.bodyValidator,
|
|
186
|
+
responseValidators: withStandardErrorResponses(
|
|
187
|
+
{
|
|
188
|
+
200: crudResource.operations.patch.outputValidator
|
|
189
|
+
},
|
|
190
|
+
{ includeValidation400: true }
|
|
191
|
+
)
|
|
192
|
+
},
|
|
193
|
+
async function (request, reply) {
|
|
194
|
+
const response = await request.executeAction({
|
|
195
|
+
actionId: resolvedActionIds.update,
|
|
196
|
+
input: {
|
|
197
|
+
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
198
|
+
recordId: request.input.params.recordId,
|
|
199
|
+
patch: request.input.body
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
reply.code(200).send(response);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
router.register(
|
|
207
|
+
"DELETE",
|
|
208
|
+
joinRoutePath(routeBase, ":recordId"),
|
|
209
|
+
{
|
|
210
|
+
auth: "required",
|
|
211
|
+
surface,
|
|
212
|
+
visibility: routeVisibility,
|
|
213
|
+
meta: {
|
|
214
|
+
tags: ["crud"],
|
|
215
|
+
summary: "Delete a record."
|
|
216
|
+
},
|
|
217
|
+
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
218
|
+
responseValidators: withStandardErrorResponses({
|
|
219
|
+
200: crudResource.operations.delete.outputValidator
|
|
220
|
+
})
|
|
221
|
+
},
|
|
222
|
+
async function (request, reply) {
|
|
223
|
+
const response = await request.executeAction({
|
|
224
|
+
actionId: resolvedActionIds.delete,
|
|
225
|
+
input: {
|
|
226
|
+
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
227
|
+
recordId: request.input.params.recordId
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
reply.code(200).send(response);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export { registerRoutes };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { toInsertDateTime } from "@jskit-ai/database-runtime/shared";
|
|
2
|
+
import { applyVisibility, applyVisibilityOwners } from "@jskit-ai/database-runtime/shared/visibility";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_LIST_LIMIT,
|
|
5
|
+
normalizeCrudListLimit,
|
|
6
|
+
requireCrudTableName
|
|
7
|
+
} from "@jskit-ai/crud-core/server/repositorySupport";
|
|
8
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
9
|
+
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
10
|
+
|
|
11
|
+
function mapRecordRow(row) {
|
|
12
|
+
if (!row) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
id: row.id,
|
|
18
|
+
textField: row.text_field,
|
|
19
|
+
dateField: row.date_field,
|
|
20
|
+
numberField: row.number_field,
|
|
21
|
+
createdAt: row.created_at,
|
|
22
|
+
updatedAt: row.updated_at
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createRepository(knex, { tableName } = {}) {
|
|
27
|
+
if (typeof knex !== "function") {
|
|
28
|
+
throw new TypeError("crudRepository requires knex.");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolvedTableName = requireCrudTableName(tableName);
|
|
32
|
+
|
|
33
|
+
async function list({ cursor = 0, limit = DEFAULT_LIST_LIMIT } = {}, options = {}) {
|
|
34
|
+
const client = options?.trx || knex;
|
|
35
|
+
const normalizedCursor = Number.isInteger(Number(cursor)) && Number(cursor) > 0 ? Number(cursor) : 0;
|
|
36
|
+
const normalizedLimit = normalizeCrudListLimit(limit);
|
|
37
|
+
const visible = (queryBuilder) => applyVisibility(queryBuilder, options.visibilityContext);
|
|
38
|
+
|
|
39
|
+
let query = client(resolvedTableName)
|
|
40
|
+
.select("id", "text_field", "date_field", "number_field", "created_at", "updated_at")
|
|
41
|
+
.where(visible)
|
|
42
|
+
.orderBy("id", "asc")
|
|
43
|
+
.limit(normalizedLimit + 1);
|
|
44
|
+
|
|
45
|
+
if (normalizedCursor > 0) {
|
|
46
|
+
query = query.where("id", ">", normalizedCursor);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rows = await query;
|
|
50
|
+
const hasMore = rows.length > normalizedLimit;
|
|
51
|
+
const pageRows = hasMore ? rows.slice(0, normalizedLimit) : rows;
|
|
52
|
+
const items = pageRows.map((row) => mapRecordRow(row));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
items,
|
|
56
|
+
nextCursor: hasMore && items.length > 0 ? String(items[items.length - 1].id) : null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function findById(recordId, options = {}) {
|
|
61
|
+
const client = options?.trx || knex;
|
|
62
|
+
const visible = (queryBuilder) => applyVisibility(queryBuilder, options.visibilityContext);
|
|
63
|
+
const row = await client(resolvedTableName)
|
|
64
|
+
.select("id", "text_field", "date_field", "number_field", "created_at", "updated_at")
|
|
65
|
+
.where(visible)
|
|
66
|
+
.where({ id: Number(recordId) })
|
|
67
|
+
.first();
|
|
68
|
+
|
|
69
|
+
return mapRecordRow(row);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function create(payload = {}, options = {}) {
|
|
73
|
+
const client = options?.trx || knex;
|
|
74
|
+
const source = normalizeObjectInput(payload);
|
|
75
|
+
const timestamp = toInsertDateTime();
|
|
76
|
+
const insertPayload = applyVisibilityOwners(
|
|
77
|
+
{
|
|
78
|
+
text_field: source.textField,
|
|
79
|
+
date_field: source.dateField,
|
|
80
|
+
number_field: source.numberField,
|
|
81
|
+
created_at: timestamp,
|
|
82
|
+
updated_at: timestamp
|
|
83
|
+
},
|
|
84
|
+
options.visibilityContext
|
|
85
|
+
);
|
|
86
|
+
const [recordId] = await client(resolvedTableName).insert({
|
|
87
|
+
...insertPayload
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return findById(recordId, {
|
|
91
|
+
...options,
|
|
92
|
+
trx: client
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function updateById(recordId, patch = {}, options = {}) {
|
|
97
|
+
const client = options?.trx || knex;
|
|
98
|
+
const source = normalizeObjectInput(patch);
|
|
99
|
+
const dbPatch = {};
|
|
100
|
+
const patchSource = pickOwnProperties(source, ["textField", "dateField", "numberField"]);
|
|
101
|
+
if (Object.hasOwn(patchSource, "textField")) {
|
|
102
|
+
dbPatch.text_field = patchSource.textField;
|
|
103
|
+
}
|
|
104
|
+
if (Object.hasOwn(patchSource, "dateField")) {
|
|
105
|
+
dbPatch.date_field = patchSource.dateField;
|
|
106
|
+
}
|
|
107
|
+
if (Object.hasOwn(patchSource, "numberField")) {
|
|
108
|
+
dbPatch.number_field = patchSource.numberField;
|
|
109
|
+
}
|
|
110
|
+
const visible = (queryBuilder) => applyVisibility(queryBuilder, options.visibilityContext);
|
|
111
|
+
|
|
112
|
+
if (Object.keys(dbPatch).length === 0) {
|
|
113
|
+
return findById(recordId, {
|
|
114
|
+
...options,
|
|
115
|
+
trx: client
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await client(resolvedTableName)
|
|
120
|
+
.where(visible)
|
|
121
|
+
.where({ id: Number(recordId) })
|
|
122
|
+
.update({
|
|
123
|
+
...dbPatch,
|
|
124
|
+
updated_at: toInsertDateTime()
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return findById(recordId, {
|
|
128
|
+
...options,
|
|
129
|
+
trx: client
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function deleteById(recordId, options = {}) {
|
|
134
|
+
const client = options?.trx || knex;
|
|
135
|
+
const visible = (queryBuilder) => applyVisibility(queryBuilder, options.visibilityContext);
|
|
136
|
+
const existing = await findById(recordId, {
|
|
137
|
+
...options,
|
|
138
|
+
trx: client
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!existing) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await client(resolvedTableName).where(visible).where({ id: Number(recordId) }).delete();
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: existing.id,
|
|
149
|
+
deleted: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
list,
|
|
155
|
+
findById,
|
|
156
|
+
create,
|
|
157
|
+
updateById,
|
|
158
|
+
deleteById
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { createRepository };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
2
|
+
|
|
3
|
+
const serviceEvents = Object.freeze({
|
|
4
|
+
createRecord: Object.freeze([
|
|
5
|
+
Object.freeze({
|
|
6
|
+
type: "entity.changed",
|
|
7
|
+
source: "crud",
|
|
8
|
+
entity: "record",
|
|
9
|
+
operation: "created",
|
|
10
|
+
entityId: ({ result }) => result?.id,
|
|
11
|
+
realtime: Object.freeze({
|
|
12
|
+
event: "crud.record.changed",
|
|
13
|
+
audience: "event_scope"
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
]),
|
|
17
|
+
updateRecord: Object.freeze([
|
|
18
|
+
Object.freeze({
|
|
19
|
+
type: "entity.changed",
|
|
20
|
+
source: "crud",
|
|
21
|
+
entity: "record",
|
|
22
|
+
operation: "updated",
|
|
23
|
+
entityId: ({ result }) => result?.id,
|
|
24
|
+
realtime: Object.freeze({
|
|
25
|
+
event: "crud.record.changed",
|
|
26
|
+
audience: "event_scope"
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
]),
|
|
30
|
+
deleteRecord: Object.freeze([
|
|
31
|
+
Object.freeze({
|
|
32
|
+
type: "entity.changed",
|
|
33
|
+
source: "crud",
|
|
34
|
+
entity: "record",
|
|
35
|
+
operation: "deleted",
|
|
36
|
+
entityId: ({ result }) => result?.id,
|
|
37
|
+
realtime: Object.freeze({
|
|
38
|
+
event: "crud.record.changed",
|
|
39
|
+
audience: "event_scope"
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
])
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function createService({ crudRepository } = {}) {
|
|
46
|
+
if (!crudRepository) {
|
|
47
|
+
throw new Error("crudService requires crudRepository.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function listRecords(query = {}, options = {}) {
|
|
51
|
+
return crudRepository.list(query, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getRecord(recordId, options = {}) {
|
|
55
|
+
const record = await crudRepository.findById(recordId, options);
|
|
56
|
+
if (!record) {
|
|
57
|
+
throw new AppError(404, "Record not found.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return record;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function createRecord(payload = {}, options = {}) {
|
|
64
|
+
const record = await crudRepository.create(payload, options);
|
|
65
|
+
if (!record) {
|
|
66
|
+
throw new Error("crudService could not load the created record.");
|
|
67
|
+
}
|
|
68
|
+
return record;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function updateRecord(recordId, payload = {}, options = {}) {
|
|
72
|
+
const record = await crudRepository.updateById(recordId, payload, options);
|
|
73
|
+
if (!record) {
|
|
74
|
+
throw new AppError(404, "Record not found.");
|
|
75
|
+
}
|
|
76
|
+
return record;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function deleteRecord(recordId, options = {}) {
|
|
80
|
+
const deleted = await crudRepository.deleteById(recordId, options);
|
|
81
|
+
if (!deleted) {
|
|
82
|
+
throw new AppError(404, "Record not found.");
|
|
83
|
+
}
|
|
84
|
+
return deleted;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
listRecords,
|
|
89
|
+
getRecord,
|
|
90
|
+
createRecord,
|
|
91
|
+
updateRecord,
|
|
92
|
+
deleteRecord
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { createService, serviceEvents };
|