adminforth 3.12.3 → 3.13.0-next.1
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/commands/createApp/templates/index.ts.hbs +0 -7
- package/dist/dataConnectors/baseConnector.d.ts +40 -2
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +115 -21
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +9 -12
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/operationalResource.d.ts.map +1 -1
- package/dist/modules/operationalResource.js +6 -1
- package/dist/modules/operationalResource.js.map +1 -1
- package/dist/modules/recordId.d.ts +42 -0
- package/dist/modules/recordId.d.ts.map +1 -0
- package/dist/modules/recordId.js +102 -0
- package/dist/modules/recordId.js.map +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +90 -18
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +7 -3
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/src/stores/core.ts +17 -10
- package/dist/spa/src/types/Back.ts +40 -3
- package/dist/spa/src/utils/listUtils.ts +5 -1
- package/dist/spa/src/utils/recordId.ts +99 -0
- package/dist/spa/src/views/ListView.vue +18 -7
- package/dist/types/Back.d.ts +38 -3
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator used to glue values of composite primary key columns into one record id string.
|
|
3
|
+
* Values are URI-encoded before gluing, and '~' inside of value is escaped to '%7E',
|
|
4
|
+
* so separator can't appear in encoded parts.
|
|
5
|
+
*/
|
|
6
|
+
export const COMPOSITE_RECORD_ID_SEPARATOR = '~';
|
|
7
|
+
/**
|
|
8
|
+
* Returns all columns marked with primaryKey: true, in order in which they are defined in resource.
|
|
9
|
+
* For resources with composite primary key returns more than one column.
|
|
10
|
+
*/
|
|
11
|
+
export function primaryKeyColumns(resource) {
|
|
12
|
+
var _a;
|
|
13
|
+
const columns = (((_a = resource.dataSourceColumns) === null || _a === void 0 ? void 0 : _a.length) ? resource.dataSourceColumns : resource.columns) || [];
|
|
14
|
+
return columns.filter((col) => col.primaryKey);
|
|
15
|
+
}
|
|
16
|
+
export function primaryKeyColumnNames(resource) {
|
|
17
|
+
return primaryKeyColumns(resource).map((col) => col.name);
|
|
18
|
+
}
|
|
19
|
+
export function isCompositePrimaryKey(resource) {
|
|
20
|
+
return primaryKeyColumns(resource).length > 1;
|
|
21
|
+
}
|
|
22
|
+
function escapeRecordIdPart(value) {
|
|
23
|
+
// encodeURIComponent does not escape '~' (it is unreserved), so escape it manually
|
|
24
|
+
return encodeURIComponent(String(value)).replace(/~/g, '%7E');
|
|
25
|
+
}
|
|
26
|
+
function unescapeRecordIdPart(part) {
|
|
27
|
+
return decodeURIComponent(part);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Foreign resource columns are returned to frontend as { pk, label } objects,
|
|
31
|
+
* so unwrap them when record id is built from such record
|
|
32
|
+
*/
|
|
33
|
+
function unwrapPrimaryKeyValue(value) {
|
|
34
|
+
if (value !== null && typeof value === 'object' && 'pk' in value) {
|
|
35
|
+
return value.pk;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Builds record id (value used in urls and in all AdminForth APIs) from record object.
|
|
41
|
+
*
|
|
42
|
+
* For resource with single primary key column returns raw value of that column (no encoding is done,
|
|
43
|
+
* so behavior is fully backward compatible).
|
|
44
|
+
*
|
|
45
|
+
* For resource with composite primary key returns string like 'BTC~ERC20' where each part is
|
|
46
|
+
* URI-encoded value of corresponding primary key column, in order of definition in resource.
|
|
47
|
+
*/
|
|
48
|
+
export function encodeRecordId(resource, record) {
|
|
49
|
+
const pkColumns = primaryKeyColumns(resource);
|
|
50
|
+
if (!pkColumns.length) {
|
|
51
|
+
throw new Error(`Resource '${resource.resourceId}' has no primaryKey column`);
|
|
52
|
+
}
|
|
53
|
+
if (pkColumns.length === 1) {
|
|
54
|
+
return unwrapPrimaryKeyValue(record === null || record === void 0 ? void 0 : record[pkColumns[0].name]);
|
|
55
|
+
}
|
|
56
|
+
return pkColumns.map((col) => {
|
|
57
|
+
const value = unwrapPrimaryKeyValue(record === null || record === void 0 ? void 0 : record[col.name]);
|
|
58
|
+
if (value === undefined || value === null) {
|
|
59
|
+
throw new Error(`Can't build record id for resource '${resource.resourceId}': primary key column '${col.name}' has no value in record`);
|
|
60
|
+
}
|
|
61
|
+
return escapeRecordIdPart(value);
|
|
62
|
+
}).join(COMPOSITE_RECORD_ID_SEPARATOR);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Splits record id back into values of primary key columns.
|
|
66
|
+
* Returns object like { assetSymbol: 'BTC', networkCode: 'ERC20' }.
|
|
67
|
+
*
|
|
68
|
+
* Values are returned as strings for composite keys (they come from url/API),
|
|
69
|
+
* connectors cast them to column types using setFieldValue.
|
|
70
|
+
*/
|
|
71
|
+
export function decodeRecordId(resource, recordId) {
|
|
72
|
+
const pkColumns = primaryKeyColumns(resource);
|
|
73
|
+
if (!pkColumns.length) {
|
|
74
|
+
throw new Error(`Resource '${resource.resourceId}' has no primaryKey column`);
|
|
75
|
+
}
|
|
76
|
+
if (pkColumns.length === 1) {
|
|
77
|
+
return { [pkColumns[0].name]: recordId };
|
|
78
|
+
}
|
|
79
|
+
const parts = String(recordId).split(COMPOSITE_RECORD_ID_SEPARATOR);
|
|
80
|
+
if (parts.length !== pkColumns.length) {
|
|
81
|
+
throw new Error(`Record id '${recordId}' is not valid for resource '${resource.resourceId}': expected ${pkColumns.length} ` +
|
|
82
|
+
`parts (${pkColumns.map((col) => col.name).join(', ')}) separated by '${COMPOSITE_RECORD_ID_SEPARATOR}', got ${parts.length}`);
|
|
83
|
+
}
|
|
84
|
+
return pkColumns.reduce((acc, col, i) => {
|
|
85
|
+
acc[col.name] = unescapeRecordIdPart(parts[i]);
|
|
86
|
+
return acc;
|
|
87
|
+
}, {});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns pkValues argument for connector's updateRecordOriginalValues/deleteRecord.
|
|
91
|
+
*
|
|
92
|
+
* Returns undefined for resources with single primary key, so connectors receive exactly the same
|
|
93
|
+
* arguments as before composite primary key support was added (pure extension, old path untouched).
|
|
94
|
+
*/
|
|
95
|
+
export function compositePkValues(connector, resource, recordId) {
|
|
96
|
+
var _a;
|
|
97
|
+
if (!isCompositePrimaryKey(resource)) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return (_a = connector.getPrimaryKeyValues) === null || _a === void 0 ? void 0 : _a.call(connector, resource, recordId);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=recordId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordId.js","sourceRoot":"","sources":["../../modules/recordId.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restApi.d.ts","sourceRoot":"","sources":["../../modules/restApi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAEhB,kBAAkB,EAKlB,kBAAkB,EAElB,SAAS,EAIV,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"restApi.d.ts","sourceRoot":"","sources":["../../modules/restApi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAEhB,kBAAkB,EAKlB,kBAAkB,EAElB,SAAS,EAIV,MAAM,kBAAkB,CAAC;AAQ1B,OAAO,EAA+F,WAAW,EAAE,MAAM,YAAY,CAAC;AAGtI,OAAO,EAAE,iBAAiB,EAEvB,SAAS,EAAsB,sBAAsB,EAGtC,MAAM,oBAAoB,CAAC;AAuO7C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI/E;AA+VD,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,kBAAkB,EAC5B,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,WAAW,GACtB,OAAO,CAAC;IAAC,cAAc,EAAE,sBAAsB,CAAA;CAAC,CAAC,CAyCnD;AAED,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,kBAAkB;IAElE,UAAU,EAAE,WAAW,CAAC;IACxB,iBAAiB,EAAE,WAAW,EAAE,CAAM;gBAE1B,UAAU,EAAE,WAAW;IAInC,OAAO,CAAC,oBAAoB;IAkBtB,qBAAqB,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,EAAC,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,MAAM;IAsBpL,gBAAgB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAOnD,iBAAiB,CAAC,MAAM,EAAE,WAAW;CA04DtC"}
|
package/dist/modules/restApi.js
CHANGED
|
@@ -11,6 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { Filters, } from "../types/Back.js";
|
|
13
13
|
import { cascadeChildrenDelete } from './utils.js';
|
|
14
|
+
import { encodeRecordId, isCompositePrimaryKey, primaryKeyColumnNames } from './recordId.js';
|
|
14
15
|
import { afLogger } from "./logger.js";
|
|
15
16
|
import { ADMINFORTH_VERSION, listify, getLoginPromptHTML, hookResponseError, parseLooseJson, RateLimiter } from './utils.js';
|
|
16
17
|
import AdminForthAuth from "../auth.js";
|
|
@@ -1119,7 +1120,20 @@ export default class AdminForthRestAPI {
|
|
|
1119
1120
|
}
|
|
1120
1121
|
const meta = { requestBody: body, pk: undefined };
|
|
1121
1122
|
if ((source === 'edit' || source === 'show') && body.filters) {
|
|
1122
|
-
|
|
1123
|
+
if (isCompositePrimaryKey(resource)) {
|
|
1124
|
+
const pkFieldNames = primaryKeyColumnNames(resource);
|
|
1125
|
+
const pkValues = pkFieldNames.reduce((acc, name) => {
|
|
1126
|
+
var _a;
|
|
1127
|
+
acc[name] = (_a = body.filters.find((f) => f.field === name)) === null || _a === void 0 ? void 0 : _a.value;
|
|
1128
|
+
return acc;
|
|
1129
|
+
}, {});
|
|
1130
|
+
if (pkFieldNames.every((name) => pkValues[name] !== undefined)) {
|
|
1131
|
+
meta.pk = encodeRecordId(resource, pkValues);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
meta.pk = (_a = body.filters.find((f) => f.field === resource.columns.find((col) => col.primaryKey).name)) === null || _a === void 0 ? void 0 : _a.value;
|
|
1136
|
+
}
|
|
1123
1137
|
}
|
|
1124
1138
|
const { allowedActions } = await interpretResource(adminUser, resource, meta, {
|
|
1125
1139
|
'show': ActionCheckSource.ShowRequest,
|
|
@@ -1186,6 +1200,12 @@ export default class AdminForthRestAPI {
|
|
|
1186
1200
|
selectedDataSourceColumnNameSet.add(col.foreignResource.polymorphicOn);
|
|
1187
1201
|
}
|
|
1188
1202
|
}
|
|
1203
|
+
if (isCompositePrimaryKey(resource)) {
|
|
1204
|
+
// all key columns are needed to build record id out of the row
|
|
1205
|
+
for (const pkName of primaryKeyColumnNames(resource)) {
|
|
1206
|
+
selectedDataSourceColumnNameSet.add(pkName);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1189
1209
|
}
|
|
1190
1210
|
const selectedDataSourceColumns = selectedDataSourceColumnNameSet && !shouldAddListHelpers
|
|
1191
1211
|
? (resource.dataSourceColumns.some((col) => selectedDataSourceColumnNameSet.has(col.name))
|
|
@@ -1236,6 +1256,12 @@ export default class AdminForthRestAPI {
|
|
|
1236
1256
|
getTotals: source === 'list',
|
|
1237
1257
|
columns: selectedDataSourceColumns,
|
|
1238
1258
|
});
|
|
1259
|
+
if (shouldAddListHelpers && isCompositePrimaryKey(resource)) {
|
|
1260
|
+
// single primary key rows keep exactly the shape they had before, frontend derives id itself
|
|
1261
|
+
for (const item of data.data) {
|
|
1262
|
+
item._primaryKeyValue = encodeRecordId(resource, item);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1239
1265
|
// for foreign keys, add references
|
|
1240
1266
|
await Promise.all(resource.columns.filter((col) => (col.foreignResource && (!selectedColumnNameSet || shouldAddListHelpers || selectedColumnNameSet.has(col.name)))).map(async (col) => {
|
|
1241
1267
|
let targetDataMap = {};
|
|
@@ -1358,6 +1384,9 @@ export default class AdminForthRestAPI {
|
|
|
1358
1384
|
};
|
|
1359
1385
|
for (const item of data.data) {
|
|
1360
1386
|
for (const key of Object.keys(item)) {
|
|
1387
|
+
if (key === '_primaryKeyValue') {
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1361
1390
|
const col = resource.columns.find((c) => c.name === key);
|
|
1362
1391
|
const bo = col ? await isBackendOnly(col, ctx) : true;
|
|
1363
1392
|
if (!col || bo) {
|
|
@@ -1397,15 +1426,20 @@ export default class AdminForthRestAPI {
|
|
|
1397
1426
|
if (selectedColumnNameSet) {
|
|
1398
1427
|
for (const item of data.data) {
|
|
1399
1428
|
for (const key of Object.keys(item)) {
|
|
1400
|
-
if (!selectedColumnNameSet.has(key) && key !== '_label' && key !== '_clickUrl') {
|
|
1429
|
+
if (!selectedColumnNameSet.has(key) && key !== '_label' && key !== '_clickUrl' && key !== '_primaryKeyValue') {
|
|
1401
1430
|
delete item[key];
|
|
1402
1431
|
}
|
|
1403
1432
|
}
|
|
1404
1433
|
}
|
|
1405
1434
|
}
|
|
1406
1435
|
if (source === 'list') {
|
|
1407
|
-
|
|
1408
|
-
|
|
1436
|
+
if (isCompositePrimaryKey(resource)) {
|
|
1437
|
+
data.recordIds = data.data.map((item) => { var _a; return (_a = item._primaryKeyValue) !== null && _a !== void 0 ? _a : encodeRecordId(resource, item); });
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
const pkField = resource.columns.find((col) => col.primaryKey).name;
|
|
1441
|
+
data.recordIds = data.data.map((item) => item[pkField]);
|
|
1442
|
+
}
|
|
1409
1443
|
}
|
|
1410
1444
|
return data;
|
|
1411
1445
|
},
|
|
@@ -1787,11 +1821,25 @@ export default class AdminForthRestAPI {
|
|
|
1787
1821
|
}
|
|
1788
1822
|
}
|
|
1789
1823
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1824
|
+
if (isCompositePrimaryKey(resource)) {
|
|
1825
|
+
const createPkColumnNames = primaryKeyColumnNames(resource);
|
|
1826
|
+
if (createPkColumnNames.every((name) => record[name] !== undefined)) {
|
|
1827
|
+
const existingRecord = await this.adminforth.resource(resource.resourceId).get(createPkColumnNames.map((name) => Filters.EQ(name, record[name])));
|
|
1828
|
+
if (existingRecord) {
|
|
1829
|
+
return {
|
|
1830
|
+
error: `Record with ${createPkColumnNames.map((name) => `${name} '${record[name]}'`).join(', ')} already exists`,
|
|
1831
|
+
ok: false,
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
1838
|
+
if (record[primaryKeyColumn.name] !== undefined) {
|
|
1839
|
+
const existingRecord = await this.adminforth.resource(resource.resourceId).get([Filters.EQ(primaryKeyColumn.name, record[primaryKeyColumn.name])]);
|
|
1840
|
+
if (existingRecord) {
|
|
1841
|
+
return { error: `Record with ${primaryKeyColumn.name} '${record[primaryKeyColumn.name]}' already exists`, ok: false };
|
|
1842
|
+
}
|
|
1795
1843
|
}
|
|
1796
1844
|
}
|
|
1797
1845
|
const ctxCreate = {
|
|
@@ -1887,9 +1935,12 @@ export default class AdminForthRestAPI {
|
|
|
1887
1935
|
};
|
|
1888
1936
|
}
|
|
1889
1937
|
const connector = this.adminforth.connectors[resource.dataSource];
|
|
1938
|
+
const newRecordId = isCompositePrimaryKey(resource)
|
|
1939
|
+
? encodeRecordId(resource, createRecordResponse.createdRecord)
|
|
1940
|
+
: createRecordResponse.createdRecord[connector.getPrimaryKey(resource)];
|
|
1890
1941
|
return {
|
|
1891
|
-
newRecordId
|
|
1892
|
-
redirectToRecordId:
|
|
1942
|
+
newRecordId,
|
|
1943
|
+
redirectToRecordId: newRecordId,
|
|
1893
1944
|
ok: true
|
|
1894
1945
|
};
|
|
1895
1946
|
}
|
|
@@ -1914,7 +1965,7 @@ export default class AdminForthRestAPI {
|
|
|
1914
1965
|
const oldRecord = await connector.getRecordByPrimaryKey(resource, recordId);
|
|
1915
1966
|
if (!oldRecord) {
|
|
1916
1967
|
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
1917
|
-
return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
|
|
1968
|
+
return { error: `Record with ${isCompositePrimaryKey(resource) ? primaryKeyColumnNames(resource).join(', ') : primaryKeyColumn.name} ${recordId} not found` };
|
|
1918
1969
|
}
|
|
1919
1970
|
const record = body['record'];
|
|
1920
1971
|
const { allowedActions } = await interpretResource(adminUser, resource, { requestBody: body, newRecord: record, oldRecord, pk: recordId }, ActionCheckSource.EditRequest, this.adminforth);
|
|
@@ -1922,11 +1973,30 @@ export default class AdminForthRestAPI {
|
|
|
1922
1973
|
if (!allowed) {
|
|
1923
1974
|
return { error: allowedError };
|
|
1924
1975
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
const
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
1976
|
+
if (isCompositePrimaryKey(resource)) {
|
|
1977
|
+
const pkColumnNames = primaryKeyColumnNames(resource);
|
|
1978
|
+
const pkIsChanged = pkColumnNames.some((name) => record[name] !== undefined && String(record[name]) !== String(oldRecord[name]));
|
|
1979
|
+
if (pkIsChanged) {
|
|
1980
|
+
const newPkValues = pkColumnNames.reduce((acc, name) => {
|
|
1981
|
+
acc[name] = record[name] !== undefined ? record[name] : oldRecord[name];
|
|
1982
|
+
return acc;
|
|
1983
|
+
}, {});
|
|
1984
|
+
const existingRecord = await this.adminforth.resource(resource.resourceId).get(pkColumnNames.map((name) => Filters.EQ(name, newPkValues[name])));
|
|
1985
|
+
if (existingRecord) {
|
|
1986
|
+
return {
|
|
1987
|
+
error: `Record with ${pkColumnNames.map((name) => `${name} '${newPkValues[name]}'`).join(', ')} already exists`,
|
|
1988
|
+
ok: false,
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
else {
|
|
1994
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
1995
|
+
if (record[primaryKeyColumn.name] !== undefined) {
|
|
1996
|
+
const existingRecord = await this.adminforth.resource(resource.resourceId).get([Filters.EQ(primaryKeyColumn.name, record[primaryKeyColumn.name])]);
|
|
1997
|
+
if (existingRecord) {
|
|
1998
|
+
return { error: `Record with ${primaryKeyColumn.name} '${record[primaryKeyColumn.name]}' already exists`, ok: false };
|
|
1999
|
+
}
|
|
1930
2000
|
}
|
|
1931
2001
|
}
|
|
1932
2002
|
const ctxEdit = {
|
|
@@ -2017,7 +2087,9 @@ export default class AdminForthRestAPI {
|
|
|
2017
2087
|
return { error };
|
|
2018
2088
|
}
|
|
2019
2089
|
return {
|
|
2020
|
-
recordId:
|
|
2090
|
+
recordId: isCompositePrimaryKey(resource)
|
|
2091
|
+
? encodeRecordId(resource, Object.assign(Object.assign({}, oldRecord), record))
|
|
2092
|
+
: record.id,
|
|
2021
2093
|
ok: true
|
|
2022
2094
|
};
|
|
2023
2095
|
}
|