@jskit-ai/crud-core 0.1.30 → 0.1.32
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 +2 -2
- package/package.json +8 -6
- package/src/server/createCrudRepositoryFromResource.js +18 -12
- package/src/server/createCrudServiceFromResource.js +14 -60
- package/src/server/createHooksToCollectChildren.js +247 -0
- package/src/server/listQueryValidators.js +57 -9
- package/src/server/lookupHydration.js +172 -11
- package/src/server/lookupProviders.js +33 -1
- package/src/server/repositoryMethods.js +1136 -62
- package/src/server/repositorySupport.js +30 -4
- package/src/server/serviceMethods.js +140 -0
- package/test/createCrudRepositoryFromResource.test.js +1447 -121
- package/test/createCrudServiceFromResource.test.js +92 -0
- package/test/createHooksToCollectChildren.test.js +251 -0
- package/test/listQueryValidators.test.js +60 -0
- package/test/lookupProviders.test.js +32 -0
- package/test/repositorySupport.test.js +59 -0
- package/test/serviceMethods.test.js +161 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.32",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Shared CRUD helpers used by CRUD modules.",
|
|
7
7
|
dependsOn: [
|
|
@@ -26,7 +26,7 @@ export default Object.freeze({
|
|
|
26
26
|
mutations: {
|
|
27
27
|
dependencies: {
|
|
28
28
|
runtime: {
|
|
29
|
-
"@jskit-ai/crud-core": "0.1.
|
|
29
|
+
"@jskit-ai/crud-core": "0.1.32"
|
|
30
30
|
},
|
|
31
31
|
dev: {}
|
|
32
32
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
"./shared/crudNamespaceSupport": "./src/shared/crudNamespaceSupport.js",
|
|
15
15
|
"./server/repositorySupport": "./src/server/repositorySupport.js",
|
|
16
16
|
"./server/repositoryMethods": "./src/server/repositoryMethods.js",
|
|
17
|
+
"./server/createHooksToCollectChildren": "./src/server/createHooksToCollectChildren.js",
|
|
17
18
|
"./server/createCrudRepositoryFromResource": "./src/server/createCrudRepositoryFromResource.js",
|
|
18
19
|
"./server/lookupProviders": "./src/server/lookupProviders.js",
|
|
19
20
|
"./server/serviceEvents": "./src/server/serviceEvents.js",
|
|
21
|
+
"./server/serviceMethods": "./src/server/serviceMethods.js",
|
|
20
22
|
"./server/fieldAccess": "./src/server/fieldAccess.js",
|
|
21
23
|
"./server/createCrudServiceFromResource": "./src/server/createCrudServiceFromResource.js",
|
|
22
24
|
"./server/crudModuleConfig": "./src/server/crudModuleConfig.js",
|
|
@@ -24,11 +26,11 @@
|
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
26
28
|
"@tanstack/vue-query": "^5.90.5",
|
|
27
|
-
"@jskit-ai/kernel": "0.1.
|
|
28
|
-
"@jskit-ai/realtime": "0.1.
|
|
29
|
-
"@jskit-ai/shell-web": "0.1.
|
|
30
|
-
"@jskit-ai/users-core": "0.1.
|
|
31
|
-
"@jskit-ai/users-web": "0.1.
|
|
29
|
+
"@jskit-ai/kernel": "0.1.24",
|
|
30
|
+
"@jskit-ai/realtime": "0.1.23",
|
|
31
|
+
"@jskit-ai/shell-web": "0.1.23",
|
|
32
|
+
"@jskit-ai/users-core": "0.1.33",
|
|
33
|
+
"@jskit-ai/users-web": "0.1.38",
|
|
32
34
|
"typebox": "^1.0.81"
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
crudRepositoryList,
|
|
4
4
|
crudRepositoryFindById,
|
|
5
5
|
crudRepositoryListByIds,
|
|
6
|
+
crudRepositoryListByForeignIds,
|
|
6
7
|
crudRepositoryCreate,
|
|
7
8
|
crudRepositoryUpdateById,
|
|
8
9
|
crudRepositoryDeleteById
|
|
@@ -19,34 +20,39 @@ function createCrudRepositoryFromResource(resource = {}, { context = "crudReposi
|
|
|
19
20
|
throw new TypeError("crudRepository requires knex.");
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
async function listRecords(query = {}, callOptions = {}) {
|
|
23
|
-
return crudRepositoryList(runtime, knex, query, options, callOptions);
|
|
23
|
+
async function listRecords(query = {}, callOptions = {}, hooks = null) {
|
|
24
|
+
return crudRepositoryList(runtime, knex, query, options, callOptions, hooks);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
async function findById(recordId, callOptions = {}) {
|
|
27
|
-
return crudRepositoryFindById(runtime, knex, recordId, options, callOptions);
|
|
27
|
+
async function findById(recordId, callOptions = {}, hooks = null) {
|
|
28
|
+
return crudRepositoryFindById(runtime, knex, recordId, options, callOptions, hooks);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
async function listByIds(ids = [], callOptions = {}) {
|
|
31
|
-
return crudRepositoryListByIds(runtime, knex, ids, options, callOptions);
|
|
31
|
+
async function listByIds(ids = [], callOptions = {}, hooks = null) {
|
|
32
|
+
return crudRepositoryListByIds(runtime, knex, ids, options, callOptions, hooks);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
async function
|
|
35
|
-
return
|
|
35
|
+
async function listByForeignIds(ids = [], foreignKey = "", callOptions = {}, hooks = null) {
|
|
36
|
+
return crudRepositoryListByForeignIds(runtime, knex, ids, foreignKey, options, callOptions, hooks);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
async function
|
|
39
|
-
return
|
|
39
|
+
async function create(payload = {}, callOptions = {}, hooks = null) {
|
|
40
|
+
return crudRepositoryCreate(runtime, knex, payload, options, callOptions, hooks);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
async function
|
|
43
|
-
return
|
|
43
|
+
async function updateById(recordId, patch = {}, callOptions = {}, hooks = null) {
|
|
44
|
+
return crudRepositoryUpdateById(runtime, knex, recordId, patch, options, callOptions, hooks);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function deleteById(recordId, callOptions = {}, hooks = null) {
|
|
48
|
+
return crudRepositoryDeleteById(runtime, knex, recordId, options, callOptions, hooks);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
return Object.freeze({
|
|
47
52
|
list: listRecords,
|
|
48
53
|
findById,
|
|
49
54
|
listByIds,
|
|
55
|
+
listByForeignIds,
|
|
50
56
|
create,
|
|
51
57
|
updateById,
|
|
52
58
|
deleteById
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
2
|
-
import { requireCrudNamespace } from "../shared/crudNamespaceSupport.js";
|
|
3
|
-
import { createCrudFieldAccessRuntime } from "./fieldAccess.js";
|
|
4
1
|
import { createCrudServiceEvents } from "./serviceEvents.js";
|
|
2
|
+
import {
|
|
3
|
+
createCrudServiceRuntime,
|
|
4
|
+
crudServiceListRecords,
|
|
5
|
+
crudServiceGetRecord,
|
|
6
|
+
crudServiceCreateRecord,
|
|
7
|
+
crudServiceUpdateRecord,
|
|
8
|
+
crudServiceDeleteRecord
|
|
9
|
+
} from "./serviceMethods.js";
|
|
5
10
|
|
|
6
11
|
function createCrudServiceFromResource(resource = {}, { context = "crudService" } = {}) {
|
|
7
|
-
const
|
|
12
|
+
const runtime = createCrudServiceRuntime(resource, { context });
|
|
8
13
|
const baseServiceEvents = createCrudServiceEvents(resource, { context });
|
|
9
|
-
const fieldAccessRuntime = createCrudFieldAccessRuntime(resource, { context });
|
|
10
14
|
|
|
11
15
|
function createBaseService({ repository, fieldAccess = {} } = {}) {
|
|
12
16
|
if (!repository) {
|
|
@@ -14,73 +18,23 @@ function createCrudServiceFromResource(resource = {}, { context = "crudService"
|
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
async function listRecords(query = {}, options = {}) {
|
|
17
|
-
|
|
18
|
-
return fieldAccessRuntime.filterReadableListResult(result, fieldAccess, {
|
|
19
|
-
action: "list",
|
|
20
|
-
query,
|
|
21
|
-
options,
|
|
22
|
-
context: options?.context
|
|
23
|
-
});
|
|
21
|
+
return crudServiceListRecords(runtime, repository, fieldAccess, query, options);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
async function getRecord(recordId, options = {}) {
|
|
27
|
-
|
|
28
|
-
if (!record) {
|
|
29
|
-
throw new AppError(404, "Record not found.");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return fieldAccessRuntime.filterReadableRecord(record, fieldAccess, {
|
|
33
|
-
action: "view",
|
|
34
|
-
recordId,
|
|
35
|
-
options,
|
|
36
|
-
context: options?.context
|
|
37
|
-
});
|
|
25
|
+
return crudServiceGetRecord(runtime, repository, fieldAccess, recordId, options);
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
async function createRecord(payload = {}, options = {}) {
|
|
41
|
-
|
|
42
|
-
action: "create",
|
|
43
|
-
payload,
|
|
44
|
-
options,
|
|
45
|
-
context: options?.context
|
|
46
|
-
});
|
|
47
|
-
const record = await repository.create(writablePayload, options);
|
|
48
|
-
if (!record) {
|
|
49
|
-
throw new Error(`${namespace}Service could not load the created record.`);
|
|
50
|
-
}
|
|
51
|
-
return fieldAccessRuntime.filterReadableRecord(record, fieldAccess, {
|
|
52
|
-
action: "create",
|
|
53
|
-
options,
|
|
54
|
-
context: options?.context
|
|
55
|
-
});
|
|
29
|
+
return crudServiceCreateRecord(runtime, repository, fieldAccess, payload, options);
|
|
56
30
|
}
|
|
57
31
|
|
|
58
32
|
async function updateRecord(recordId, payload = {}, options = {}) {
|
|
59
|
-
|
|
60
|
-
action: "update",
|
|
61
|
-
recordId,
|
|
62
|
-
payload,
|
|
63
|
-
options,
|
|
64
|
-
context: options?.context
|
|
65
|
-
});
|
|
66
|
-
const record = await repository.updateById(recordId, writablePayload, options);
|
|
67
|
-
if (!record) {
|
|
68
|
-
throw new AppError(404, "Record not found.");
|
|
69
|
-
}
|
|
70
|
-
return fieldAccessRuntime.filterReadableRecord(record, fieldAccess, {
|
|
71
|
-
action: "update",
|
|
72
|
-
recordId,
|
|
73
|
-
options,
|
|
74
|
-
context: options?.context
|
|
75
|
-
});
|
|
33
|
+
return crudServiceUpdateRecord(runtime, repository, fieldAccess, recordId, payload, options);
|
|
76
34
|
}
|
|
77
35
|
|
|
78
36
|
async function deleteRecord(recordId, options = {}) {
|
|
79
|
-
|
|
80
|
-
if (!deleted) {
|
|
81
|
-
throw new AppError(404, "Record not found.");
|
|
82
|
-
}
|
|
83
|
-
return deleted;
|
|
37
|
+
return crudServiceDeleteRecord(runtime, repository, fieldAccess, recordId, options);
|
|
84
38
|
}
|
|
85
39
|
|
|
86
40
|
return Object.freeze({
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
function normalizeOwnerKey(value) {
|
|
4
|
+
if (value === null || value === undefined) {
|
|
5
|
+
return "";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
return value.trim();
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
return Number.isFinite(value) ? String(value) : "";
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === "bigint") {
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return String(value).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toDefaultChildCallOptions(callOptions = {}) {
|
|
22
|
+
const nextOptions = {};
|
|
23
|
+
if (Object.hasOwn(callOptions, "trx")) {
|
|
24
|
+
nextOptions.trx = callOptions.trx;
|
|
25
|
+
}
|
|
26
|
+
if (Object.hasOwn(callOptions, "visibilityContext")) {
|
|
27
|
+
nextOptions.visibilityContext = callOptions.visibilityContext;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return nextOptions;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveListChildrenHandler(options = {}, { context = "createHooksToCollectChildren" } = {}) {
|
|
34
|
+
if (typeof options.listChildren === "function") {
|
|
35
|
+
return options.listChildren;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const childRepository = options.childRepository;
|
|
39
|
+
const childListMethod = normalizeText(options.childListMethod) || "listByIds";
|
|
40
|
+
const childForeignKey = normalizeText(options.childForeignKey);
|
|
41
|
+
if (!childRepository || typeof childRepository !== "object" || Array.isArray(childRepository)) {
|
|
42
|
+
throw new TypeError(
|
|
43
|
+
`${context} requires listChildren(ids, options, ctx) or childRepository.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const listChildren = childRepository[childListMethod];
|
|
48
|
+
if (typeof listChildren !== "function") {
|
|
49
|
+
throw new TypeError(`${context} requires childRepository.${childListMethod} to be a function.`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (childListMethod === "listByIds") {
|
|
53
|
+
if (!childForeignKey) {
|
|
54
|
+
throw new TypeError(`${context} requires childForeignKey when using childRepository.listByIds.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (ids = [], childCallOptions = {}, hookContext = {}) =>
|
|
58
|
+
listChildren.call(childRepository, ids, {
|
|
59
|
+
...childCallOptions,
|
|
60
|
+
valueKey: childForeignKey
|
|
61
|
+
}, hookContext);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (childListMethod === "listByForeignIds") {
|
|
65
|
+
if (!childForeignKey) {
|
|
66
|
+
throw new TypeError(`${context} requires childForeignKey when using childRepository.listByForeignIds.`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (ids = [], childCallOptions = {}, hookContext = {}) =>
|
|
70
|
+
listChildren.call(childRepository, ids, childForeignKey, childCallOptions, hookContext);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (ids = [], childCallOptions = {}, hookContext = {}) => {
|
|
74
|
+
if (childForeignKey) {
|
|
75
|
+
return listChildren.call(childRepository, ids, childForeignKey, childCallOptions, hookContext);
|
|
76
|
+
}
|
|
77
|
+
return listChildren.call(childRepository, ids, childCallOptions, hookContext);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolveGetChildOwnerId(options = {}, { context = "createHooksToCollectChildren" } = {}) {
|
|
82
|
+
if (typeof options.getChildOwnerId === "function") {
|
|
83
|
+
return options.getChildOwnerId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const childOwnerIdKey = normalizeText(options.childOwnerIdKey);
|
|
87
|
+
const childForeignKey = normalizeText(options.childForeignKey);
|
|
88
|
+
const ownerKey = childOwnerIdKey || childForeignKey;
|
|
89
|
+
if (!ownerKey) {
|
|
90
|
+
throw new TypeError(`${context} requires childOwnerIdKey, childForeignKey, or getChildOwnerId.`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (child = {}) => {
|
|
94
|
+
if (!child || typeof child !== "object" || Array.isArray(child)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (childOwnerIdKey && Object.hasOwn(child, childOwnerIdKey)) {
|
|
98
|
+
return child[childOwnerIdKey];
|
|
99
|
+
}
|
|
100
|
+
return child[ownerKey];
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveLookupContainerKey(record = {}, hookContext = {}, options = {}) {
|
|
105
|
+
const explicitContainerKey = normalizeText(options.lookupContainerKey);
|
|
106
|
+
if (explicitContainerKey) {
|
|
107
|
+
return explicitContainerKey;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const runtimeContainerKey = normalizeText(hookContext?.runtime?.lookup?.containerKey);
|
|
111
|
+
if (runtimeContainerKey) {
|
|
112
|
+
return runtimeContainerKey;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Object.hasOwn(record, "lookups")) {
|
|
116
|
+
return "lookups";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return "lookups";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function createHooksToCollectChildren(options = {}) {
|
|
123
|
+
const context = normalizeText(options.context) || "createHooksToCollectChildren";
|
|
124
|
+
const childKey = normalizeText(options.childKey);
|
|
125
|
+
if (!childKey) {
|
|
126
|
+
throw new TypeError(`${context} requires childKey.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const listChildren = resolveListChildrenHandler(options, {
|
|
130
|
+
context
|
|
131
|
+
});
|
|
132
|
+
const getParentId = typeof options.getParentId === "function"
|
|
133
|
+
? options.getParentId
|
|
134
|
+
: (record = {}) => record?.id;
|
|
135
|
+
const getChildOwnerId = resolveGetChildOwnerId(options, {
|
|
136
|
+
context
|
|
137
|
+
});
|
|
138
|
+
const normalizeCollectionOwnerKey = typeof options.normalizeOwnerKey === "function"
|
|
139
|
+
? options.normalizeOwnerKey
|
|
140
|
+
: normalizeOwnerKey;
|
|
141
|
+
const buildChildCallOptions = typeof options.buildChildCallOptions === "function"
|
|
142
|
+
? options.buildChildCallOptions
|
|
143
|
+
: ({ callOptions = {} } = {}) => toDefaultChildCallOptions(callOptions);
|
|
144
|
+
const stateMapKey = options.stateMapKey || Symbol(`crud.children.${childKey}`);
|
|
145
|
+
const attachToLookupContainer = options.attachToLookupContainer !== false;
|
|
146
|
+
const attachChildren = typeof options.attachChildren === "function"
|
|
147
|
+
? options.attachChildren
|
|
148
|
+
: (record = {}, children = [], hookContext = {}) => {
|
|
149
|
+
if (!attachToLookupContainer) {
|
|
150
|
+
return {
|
|
151
|
+
...record,
|
|
152
|
+
[childKey]: children
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const containerKey = resolveLookupContainerKey(record, hookContext, options);
|
|
157
|
+
const sourceContainer = record?.[containerKey];
|
|
158
|
+
const normalizedContainer =
|
|
159
|
+
sourceContainer && typeof sourceContainer === "object" && !Array.isArray(sourceContainer)
|
|
160
|
+
? sourceContainer
|
|
161
|
+
: {};
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...record,
|
|
165
|
+
[containerKey]: {
|
|
166
|
+
...normalizedContainer,
|
|
167
|
+
[childKey]: children
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return Object.freeze({
|
|
173
|
+
async afterQuery(records = [], ctx = {}) {
|
|
174
|
+
const normalizedRecords = Array.isArray(records) ? records : [];
|
|
175
|
+
const ownerIds = [];
|
|
176
|
+
const seenOwnerKeys = new Set();
|
|
177
|
+
|
|
178
|
+
for (const record of normalizedRecords) {
|
|
179
|
+
const ownerId = getParentId(record, ctx);
|
|
180
|
+
const ownerKey = normalizeCollectionOwnerKey(ownerId);
|
|
181
|
+
if (!ownerKey || seenOwnerKeys.has(ownerKey)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
seenOwnerKeys.add(ownerKey);
|
|
186
|
+
ownerIds.push(ownerId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const state = ctx?.state && typeof ctx.state === "object" ? ctx.state : null;
|
|
190
|
+
if (!state) {
|
|
191
|
+
throw new TypeError(`${context} requires ctx.state object.`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (ownerIds.length < 1) {
|
|
195
|
+
state[stateMapKey] = new Map();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const childCallOptions = buildChildCallOptions({
|
|
200
|
+
callOptions: ctx.callOptions || {},
|
|
201
|
+
records: normalizedRecords,
|
|
202
|
+
ownerIds,
|
|
203
|
+
context: ctx
|
|
204
|
+
});
|
|
205
|
+
const children = await listChildren(ownerIds, childCallOptions, ctx);
|
|
206
|
+
if (!Array.isArray(children)) {
|
|
207
|
+
throw new TypeError(`${context} listChildren must return an array.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const childrenByOwnerKey = new Map();
|
|
211
|
+
for (const child of children) {
|
|
212
|
+
const childOwnerId = getChildOwnerId(child, ctx);
|
|
213
|
+
const childOwnerKey = normalizeCollectionOwnerKey(childOwnerId);
|
|
214
|
+
if (!childOwnerKey) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const currentList = childrenByOwnerKey.get(childOwnerKey);
|
|
219
|
+
if (currentList) {
|
|
220
|
+
currentList.push(child);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
childrenByOwnerKey.set(childOwnerKey, [child]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
state[stateMapKey] = childrenByOwnerKey;
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
transformReturnedRecord(record = {}, ctx = {}) {
|
|
231
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
232
|
+
return record;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const state = ctx?.state && typeof ctx.state === "object" ? ctx.state : null;
|
|
236
|
+
const childrenByOwnerKey = state ? state[stateMapKey] : null;
|
|
237
|
+
const ownerKey = normalizeCollectionOwnerKey(getParentId(record, ctx));
|
|
238
|
+
const children = ownerKey && childrenByOwnerKey instanceof Map
|
|
239
|
+
? (childrenByOwnerKey.get(ownerKey) || [])
|
|
240
|
+
: [];
|
|
241
|
+
|
|
242
|
+
return attachChildren(record, children, ctx);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export { createHooksToCollectChildren };
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
normalizeObjectInput,
|
|
4
|
+
positiveIntegerValidator,
|
|
5
|
+
cursorPaginationQueryValidator
|
|
6
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
3
7
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
-
import {
|
|
8
|
+
import { resolveCrudParentFilterKeys as resolveSharedCrudParentFilterKeys } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
5
9
|
|
|
6
10
|
const listSearchQueryValidator = Object.freeze({
|
|
7
11
|
schema: Type.Object(
|
|
@@ -41,16 +45,59 @@ const lookupIncludeQueryValidator = Object.freeze({
|
|
|
41
45
|
}
|
|
42
46
|
});
|
|
43
47
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
function resolveCrudListUsesOrderedCursor(list = {}) {
|
|
49
|
+
const orderBy = Array.isArray(list?.orderBy) ? list.orderBy : [];
|
|
50
|
+
for (const entry of orderBy) {
|
|
51
|
+
if (typeof entry === "string" && normalizeText(entry)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry) && normalizeText(entry.column)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createCrudCursorPaginationQueryValidator(list = {}) {
|
|
63
|
+
if (resolveCrudListUsesOrderedCursor(list) !== true) {
|
|
64
|
+
return cursorPaginationQueryValidator;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
schema: Type.Object(
|
|
69
|
+
{
|
|
70
|
+
cursor: Type.Optional(
|
|
71
|
+
Type.Union([
|
|
72
|
+
positiveIntegerValidator.schema,
|
|
73
|
+
Type.String({ minLength: 1 })
|
|
74
|
+
])
|
|
75
|
+
),
|
|
76
|
+
limit: Type.Optional(positiveIntegerValidator.schema)
|
|
77
|
+
},
|
|
78
|
+
{ additionalProperties: false }
|
|
79
|
+
),
|
|
80
|
+
normalize(payload = {}) {
|
|
81
|
+
const source = normalizeObjectInput(payload);
|
|
82
|
+
const normalized = {};
|
|
83
|
+
|
|
84
|
+
if (Object.hasOwn(source, "cursor")) {
|
|
85
|
+
normalized.cursor = normalizeText(source.cursor);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (Object.hasOwn(source, "limit")) {
|
|
89
|
+
normalized.limit = positiveIntegerValidator.normalize(source.limit);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
51
94
|
});
|
|
52
95
|
}
|
|
53
96
|
|
|
97
|
+
function resolveCrudParentFilterKeys(resource = {}) {
|
|
98
|
+
return resolveSharedCrudParentFilterKeys(resource);
|
|
99
|
+
}
|
|
100
|
+
|
|
54
101
|
function createCrudParentFilterQueryValidator(resource = {}) {
|
|
55
102
|
const keys = resolveCrudParentFilterKeys(resource);
|
|
56
103
|
const schemaProperties = {};
|
|
@@ -80,6 +127,7 @@ function createCrudParentFilterQueryValidator(resource = {}) {
|
|
|
80
127
|
}
|
|
81
128
|
|
|
82
129
|
export {
|
|
130
|
+
createCrudCursorPaginationQueryValidator,
|
|
83
131
|
listSearchQueryValidator,
|
|
84
132
|
lookupIncludeQueryValidator,
|
|
85
133
|
resolveCrudParentFilterKeys,
|