@appweaver/data 0.0.0-stage → 2.0.0
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/LICENSE +21 -0
- package/README.md +61 -2
- package/config.d.ts +6 -0
- package/config.js +22 -0
- package/database/create-client.d.ts +11 -0
- package/database/create-client.js +145 -0
- package/database/database-config.d.ts +14 -0
- package/database/database-config.js +40 -0
- package/database/database-error.d.ts +49 -0
- package/database/database-error.js +216 -0
- package/database/index.d.ts +3 -0
- package/database/index.js +19 -0
- package/database/prisma-database.d.ts +33 -0
- package/database/prisma-database.js +178 -0
- package/database/run-transaction.d.ts +21 -0
- package/database/run-transaction.js +15 -0
- package/export/export-config.d.ts +7 -0
- package/export/export-config.js +17 -0
- package/export/export-service.d.ts +22 -0
- package/export/export-service.js +188 -0
- package/export/index.d.ts +1 -0
- package/export/index.js +17 -0
- package/factory/create-model.d.ts +11 -0
- package/factory/create-model.js +479 -0
- package/factory/create-policy.d.ts +12 -0
- package/factory/create-policy.js +20 -0
- package/factory/create-service.d.ts +14 -0
- package/factory/create-service.js +116 -0
- package/index.d.ts +22 -0
- package/index.js +38 -0
- package/internal.d.ts +2 -0
- package/internal.js +6 -0
- package/module.d.ts +3 -0
- package/module.js +81 -0
- package/package.json +67 -3
- package/prisma/client/browser.d.ts +39 -0
- package/prisma/client/browser.js +56 -0
- package/prisma/client/client.d.ts +58 -0
- package/prisma/client/client.js +70 -0
- package/prisma/client/commonInputTypes.d.ts +345 -0
- package/prisma/client/commonInputTypes.js +11 -0
- package/prisma/client/enums.d.ts +1 -0
- package/prisma/client/enums.js +11 -0
- package/prisma/client/internal/class.d.ts +198 -0
- package/prisma/client/internal/class.js +85 -0
- package/prisma/client/internal/prismaNamespace.d.ts +1125 -0
- package/prisma/client/internal/prismaNamespace.js +204 -0
- package/prisma/client/internal/prismaNamespaceBrowser.d.ts +137 -0
- package/prisma/client/internal/prismaNamespaceBrowser.js +175 -0
- package/prisma/client/models/ApiKey.d.ts +1167 -0
- package/prisma/client/models/ApiKey.js +2 -0
- package/prisma/client/models/ConnectedAccount.d.ts +1094 -0
- package/prisma/client/models/ConnectedAccount.js +2 -0
- package/prisma/client/models/File.d.ts +1291 -0
- package/prisma/client/models/File.js +2 -0
- package/prisma/client/models/OneTimeToken.d.ts +1055 -0
- package/prisma/client/models/OneTimeToken.js +2 -0
- package/prisma/client/models/Permission.d.ts +1209 -0
- package/prisma/client/models/Permission.js +2 -0
- package/prisma/client/models/Role.d.ts +1209 -0
- package/prisma/client/models/Role.js +2 -0
- package/prisma/client/models/Seeder.d.ts +1023 -0
- package/prisma/client/models/Seeder.js +2 -0
- package/prisma/client/models.d.ts +8 -0
- package/prisma/client/models.js +2 -0
- package/resource/load-data-resources.d.ts +13 -0
- package/resource/load-data-resources.js +37 -0
- package/resource/model-schema.d.ts +34 -0
- package/resource/model-schema.js +54 -0
- package/resource/resource-config.d.ts +16 -0
- package/resource/resource-config.js +62 -0
- package/resource/resource-loader.d.ts +25 -0
- package/resource/resource-loader.js +138 -0
- package/resource/resource-service.d.ts +462 -0
- package/resource/resource-service.js +1042 -0
- package/resource/schemas/index.d.ts +3 -0
- package/resource/schemas/index.js +19 -0
- package/resource/schemas/resource-aggregate-schema.d.ts +53 -0
- package/resource/schemas/resource-aggregate-schema.js +130 -0
- package/resource/schemas/resource-filter-schema.d.ts +39 -0
- package/resource/schemas/resource-filter-schema.js +153 -0
- package/resource/schemas/resource-sort-schema.d.ts +38 -0
- package/resource/schemas/resource-sort-schema.js +113 -0
- package/resource/utils/aggregate-util.d.ts +113 -0
- package/resource/utils/aggregate-util.js +322 -0
- package/resource/utils/cursor-util.d.ts +60 -0
- package/resource/utils/cursor-util.js +116 -0
- package/resource/utils/date-util.d.ts +22 -0
- package/resource/utils/date-util.js +112 -0
- package/resource/utils/delete-util.d.ts +131 -0
- package/resource/utils/delete-util.js +385 -0
- package/resource/utils/filter-util.d.ts +24 -0
- package/resource/utils/filter-util.js +289 -0
- package/resource/utils/prune-util.d.ts +11 -0
- package/resource/utils/prune-util.js +56 -0
- package/resource/utils/relation-util.d.ts +75 -0
- package/resource/utils/relation-util.js +400 -0
- package/resource/utils/sort-util.d.ts +31 -0
- package/resource/utils/sort-util.js +235 -0
- package/resource/utils/text-search-util.d.ts +14 -0
- package/resource/utils/text-search-util.js +39 -0
- package/resources.d.ts +10 -0
- package/resources.js +26 -0
- package/security/auth-config.d.ts +23 -0
- package/security/auth-config.js +43 -0
- package/security/create-auth-resources.d.ts +28 -0
- package/security/create-auth-resources.js +163 -0
- package/security/helper.d.ts +109 -0
- package/security/helper.js +298 -0
- package/security/resources/api-key/model.d.ts +2 -0
- package/security/resources/api-key/model.js +67 -0
- package/security/resources/api-key/policy.d.ts +2 -0
- package/security/resources/api-key/policy.js +27 -0
- package/security/resources/api-key/service.d.ts +3 -0
- package/security/resources/api-key/service.js +34 -0
- package/security/resources/connected-account/model.d.ts +2 -0
- package/security/resources/connected-account/model.js +57 -0
- package/security/resources/connected-account/service.d.ts +2 -0
- package/security/resources/connected-account/service.js +7 -0
- package/security/resources/one-time-token/model.d.ts +2 -0
- package/security/resources/one-time-token/model.js +36 -0
- package/security/resources/permission/model.d.ts +2 -0
- package/security/resources/permission/model.js +16 -0
- package/security/resources/role/model.d.ts +2 -0
- package/security/resources/role/model.js +22 -0
- package/security/store/database-security-store.d.ts +5 -0
- package/security/store/database-security-store.js +54 -0
- package/seeder/create-seeder.d.ts +29 -0
- package/seeder/create-seeder.js +28 -0
- package/seeder/index.d.ts +2 -0
- package/seeder/index.js +18 -0
- package/seeder/resources/seeder/model.d.ts +2 -0
- package/seeder/resources/seeder/model.js +38 -0
- package/seeder/seeder.d.ts +29 -0
- package/seeder/seeder.js +197 -0
- package/storage/file-service.d.ts +133 -0
- package/storage/file-service.js +586 -0
- package/storage/image-config.d.ts +3 -0
- package/storage/image-config.js +11 -0
- package/storage/resources/file/model.d.ts +2 -0
- package/storage/resources/file/model.js +80 -0
- package/types/auth.d.ts +23 -0
- package/types/auth.js +2 -0
- package/types/generated.d.ts +366 -0
- package/types/generated.js +3 -0
- package/types/storage.d.ts +35 -0
- package/types/storage.js +2 -0
- package/utils/export-util.d.ts +8 -0
- package/utils/export-util.js +26 -0
- package/utils/file-util.d.ts +106 -0
- package/utils/file-util.js +254 -0
- package/utils/model-util.d.ts +36 -0
- package/utils/model-util.js +154 -0
- package/utils/policy-util.d.ts +18 -0
- package/utils/policy-util.js +28 -0
- package/utils/virtual-util.d.ts +11 -0
- package/utils/virtual-util.js +53 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseIsoDate = parseIsoDate;
|
|
4
|
+
exports.addSeconds = addSeconds;
|
|
5
|
+
exports.addMinutes = addMinutes;
|
|
6
|
+
exports.addHours = addHours;
|
|
7
|
+
exports.addDays = addDays;
|
|
8
|
+
exports.addMonths = addMonths;
|
|
9
|
+
exports.addYears = addYears;
|
|
10
|
+
exports.differenceInSeconds = differenceInSeconds;
|
|
11
|
+
exports.differenceInMonths = differenceInMonths;
|
|
12
|
+
exports.differenceInYears = differenceInYears;
|
|
13
|
+
const MS_IN_SECOND = 1000;
|
|
14
|
+
const MS_IN_MINUTE = 60 * MS_IN_SECOND;
|
|
15
|
+
const MS_IN_HOUR = 60 * MS_IN_MINUTE;
|
|
16
|
+
/** A date-only ISO string, which reads as the local midnight instead of the UTC one of the `Date` constructor. */
|
|
17
|
+
const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
18
|
+
/**
|
|
19
|
+
* Parses an ISO 8601 date string. A date without a time is the local midnight of that day, a date and time without an
|
|
20
|
+
* offset is local, and one with an offset is that exact instant.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} value - The ISO date string.
|
|
23
|
+
* @return {Date} The parsed date, invalid when the string is not a date.
|
|
24
|
+
*/
|
|
25
|
+
function parseIsoDate(value) {
|
|
26
|
+
const dateOnly = DATE_ONLY_PATTERN.exec(value);
|
|
27
|
+
if (dateOnly) {
|
|
28
|
+
const [, year, month, day] = dateOnly;
|
|
29
|
+
return new Date(Number(year), Number(month) - 1, Number(day));
|
|
30
|
+
}
|
|
31
|
+
return new Date(value);
|
|
32
|
+
}
|
|
33
|
+
function addSeconds(date, amount) {
|
|
34
|
+
return new Date(date.getTime() + amount * MS_IN_SECOND);
|
|
35
|
+
}
|
|
36
|
+
function addMinutes(date, amount) {
|
|
37
|
+
return new Date(date.getTime() + amount * MS_IN_MINUTE);
|
|
38
|
+
}
|
|
39
|
+
function addHours(date, amount) {
|
|
40
|
+
return new Date(date.getTime() + amount * MS_IN_HOUR);
|
|
41
|
+
}
|
|
42
|
+
/** Adds whole local days, keeping the time of day across daylight saving time changes. */
|
|
43
|
+
function addDays(date, amount) {
|
|
44
|
+
const result = new Date(date.getTime());
|
|
45
|
+
if (amount) {
|
|
46
|
+
result.setDate(result.getDate() + amount);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
/** Adds whole local months, clamping the day to the last day of a shorter month (January 31 plus one is February 28). */
|
|
51
|
+
function addMonths(date, amount) {
|
|
52
|
+
const result = new Date(date.getTime());
|
|
53
|
+
if (!amount) {
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
const dayOfMonth = result.getDate();
|
|
57
|
+
const endOfDesiredMonth = new Date(date.getTime());
|
|
58
|
+
endOfDesiredMonth.setMonth(result.getMonth() + amount + 1, 0);
|
|
59
|
+
if (dayOfMonth >= endOfDesiredMonth.getDate()) {
|
|
60
|
+
return endOfDesiredMonth;
|
|
61
|
+
}
|
|
62
|
+
result.setFullYear(endOfDesiredMonth.getFullYear(), endOfDesiredMonth.getMonth(), dayOfMonth);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function addYears(date, amount) {
|
|
66
|
+
return addMonths(date, amount * 12);
|
|
67
|
+
}
|
|
68
|
+
/** Returns the whole seconds between two dates, truncated toward zero. */
|
|
69
|
+
function differenceInSeconds(laterDate, earlierDate) {
|
|
70
|
+
return Math.trunc((laterDate.getTime() - earlierDate.getTime()) / MS_IN_SECOND);
|
|
71
|
+
}
|
|
72
|
+
/** Returns the full local months between two dates, negative when the first date is the earlier one. */
|
|
73
|
+
function differenceInMonths(laterDate, earlierDate) {
|
|
74
|
+
const sign = compare(laterDate, earlierDate);
|
|
75
|
+
const difference = Math.abs((laterDate.getFullYear() - earlierDate.getFullYear()) * 12 +
|
|
76
|
+
laterDate.getMonth() -
|
|
77
|
+
earlierDate.getMonth());
|
|
78
|
+
if (difference < 1) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const working = new Date(laterDate.getTime());
|
|
82
|
+
if (working.getMonth() === 1 && working.getDate() > 27) {
|
|
83
|
+
working.setDate(30);
|
|
84
|
+
}
|
|
85
|
+
working.setMonth(working.getMonth() - sign * difference);
|
|
86
|
+
// The last month counts only once its day is reached, unless the later date ends a month
|
|
87
|
+
let isLastMonthNotFull = compare(working, earlierDate) === -sign;
|
|
88
|
+
if (isLastDayOfMonth(laterDate) && difference === 1 && sign === 1) {
|
|
89
|
+
isLastMonthNotFull = false;
|
|
90
|
+
}
|
|
91
|
+
return sign * (difference - Number(isLastMonthNotFull)) || 0;
|
|
92
|
+
}
|
|
93
|
+
/** Returns the full local years between two dates, negative when the first date is the earlier one. */
|
|
94
|
+
function differenceInYears(laterDate, earlierDate) {
|
|
95
|
+
const sign = compare(laterDate, earlierDate);
|
|
96
|
+
const difference = Math.abs(laterDate.getFullYear() - earlierDate.getFullYear());
|
|
97
|
+
// Compares the dates within the same leap year to tell whether the last year is full
|
|
98
|
+
const later = new Date(laterDate.getTime());
|
|
99
|
+
const earlier = new Date(earlierDate.getTime());
|
|
100
|
+
later.setFullYear(1584);
|
|
101
|
+
earlier.setFullYear(1584);
|
|
102
|
+
const isLastYearNotFull = compare(later, earlier) === -sign;
|
|
103
|
+
return sign * (difference - Number(isLastYearNotFull)) || 0;
|
|
104
|
+
}
|
|
105
|
+
function compare(left, right) {
|
|
106
|
+
return Math.sign(left.getTime() - right.getTime());
|
|
107
|
+
}
|
|
108
|
+
function isLastDayOfMonth(date) {
|
|
109
|
+
const next = new Date(date.getTime());
|
|
110
|
+
next.setDate(next.getDate() + 1);
|
|
111
|
+
return next.getMonth() !== date.getMonth();
|
|
112
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ResourceId } from '@appweaver/common';
|
|
2
|
+
/** The nested write actions a single relation field can be mapped to. */
|
|
3
|
+
export type RelationActions = Record<string, Partial<{
|
|
4
|
+
connect: any;
|
|
5
|
+
create: any;
|
|
6
|
+
update: any;
|
|
7
|
+
connectOrCreate: any;
|
|
8
|
+
disconnect: any;
|
|
9
|
+
delete: any;
|
|
10
|
+
}>>;
|
|
11
|
+
/** The records of every model affected by a delete, keyed by model name. */
|
|
12
|
+
export type AffectedRecords = Record<string, ResourceId[]>;
|
|
13
|
+
/** The records deleted by one action, split by how they were deleted. */
|
|
14
|
+
export type DeletedRecords = {
|
|
15
|
+
/** Records marked with the soft delete columns */
|
|
16
|
+
soft: AffectedRecords;
|
|
17
|
+
/** Records removed from the database */
|
|
18
|
+
hard: AffectedRecords;
|
|
19
|
+
};
|
|
20
|
+
/** The columns marking a record as soft deleted. */
|
|
21
|
+
export type SoftDeleteData = {
|
|
22
|
+
deletedAt: Date;
|
|
23
|
+
deletedById?: ResourceId | null;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Restricts a mapped relation filter to the related records that are not soft deleted, so a filter never matches
|
|
27
|
+
* through a deleted record, and a null check treats a deleted related record as missing.
|
|
28
|
+
*
|
|
29
|
+
* @param {*} condition - The mapped database condition of the relation field.
|
|
30
|
+
* @param {string} resourceName - The name of the related model.
|
|
31
|
+
* @param {boolean} isArrayType - Whether the relation is a list (to-many) relation.
|
|
32
|
+
* @return {*} The condition restricted to the live related records, or unchanged if the related model does not soft
|
|
33
|
+
* delete its records.
|
|
34
|
+
*/
|
|
35
|
+
export declare function liveRelationFilter(condition: any, resourceName: string, isArrayType: boolean): any;
|
|
36
|
+
/**
|
|
37
|
+
* Builds the inclusion filter of a list relation, so only the related records that are not soft deleted are read.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} [resourceName] - The name of the related model.
|
|
40
|
+
* @return {Object|undefined} The `where` clause of the inclusion, or undefined if the related model does not soft
|
|
41
|
+
* delete its records.
|
|
42
|
+
*/
|
|
43
|
+
export declare function liveInclusionFilter(resourceName?: string): {
|
|
44
|
+
where: Record<string, any>;
|
|
45
|
+
} | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Replaces the soft deleted records in the single relations of a resource with null, recursively. Unlike the list
|
|
48
|
+
* relations, a single relation cannot be filtered when it is read.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} resource - The resource as returned by the database client.
|
|
51
|
+
* @param {string} resourceName - The name of the model describing the resource.
|
|
52
|
+
* @return {Object} A copy of the resource without the soft deleted related records, or the resource itself if it
|
|
53
|
+
* holds no relations.
|
|
54
|
+
*/
|
|
55
|
+
export declare function hideDeletedRelations<T>(resource: T, resourceName: string): T;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the column values marking a record as soft deleted now by the current user, who is only recorded when the
|
|
58
|
+
* application defines an auth model.
|
|
59
|
+
*
|
|
60
|
+
* @return {SoftDeleteData} The soft delete column values.
|
|
61
|
+
*/
|
|
62
|
+
export declare function softDeleteData(): SoftDeleteData;
|
|
63
|
+
/**
|
|
64
|
+
* Mirrors the referential actions of a database delete for soft deleted records: cascading records are soft deleted
|
|
65
|
+
* with the same column values, level by level, a restricting relation aborts the delete, and references set to null on
|
|
66
|
+
* delete are kept for a manual restore. The records themselves are not updated.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
69
|
+
* @param {string} resourceName - The name of the model of the deleted records.
|
|
70
|
+
* @param {ResourceId[]} ids - The ids of the deleted records.
|
|
71
|
+
* @param {SoftDeleteData} data - The soft delete column values applied to the cascaded records.
|
|
72
|
+
* @return {Promise<AffectedRecords>} The ids of the soft deleted records per model, excluding the records themselves.
|
|
73
|
+
* @throws {ResourceError} `RESOURCE_DELETE_RESTRICTED` if a live record references a deleted record through a
|
|
74
|
+
* restricting relation.
|
|
75
|
+
*/
|
|
76
|
+
export declare function softDeleteCascade(tx: any, resourceName: string, ids: ResourceId[], data: SoftDeleteData): Promise<AffectedRecords>;
|
|
77
|
+
/**
|
|
78
|
+
* Collects the records the database cascade removes together with deleted records, so their files and caches can be
|
|
79
|
+
* cleaned up once the delete commits.
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
82
|
+
* @param {string} resourceName - The name of the model of the deleted records.
|
|
83
|
+
* @param {ResourceId[]} ids - The ids of the deleted records.
|
|
84
|
+
* @return {Promise<AffectedRecords>} The ids of the cascaded records per model, excluding the records themselves.
|
|
85
|
+
*/
|
|
86
|
+
export declare function cascadedRecords(tx: any, resourceName: string, ids: ResourceId[]): Promise<AffectedRecords>;
|
|
87
|
+
/**
|
|
88
|
+
* Removes the orphans an update maps to a nested `delete` action (`orphanRemoval`). Orphans of a soft deleted model are
|
|
89
|
+
* soft deleted with their cascade and keep their references, so their `delete` action is dropped; the others are left
|
|
90
|
+
* to the `delete` action, collecting the records its cascade removes.
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} tx - The transaction client the update runs in.
|
|
93
|
+
* @param {string} resourceName - The name of the model the relation actions belong to.
|
|
94
|
+
* @param {RelationActions} relationActions - The mapped relation write actions, from which the nested `delete` actions
|
|
95
|
+
* of the soft deleted orphans are removed.
|
|
96
|
+
* @param {SoftDeleteData} data - The soft delete column values applied to the soft deleted orphans.
|
|
97
|
+
* @return {Promise<DeletedRecords>} The ids of the soft deleted and the removed records per model.
|
|
98
|
+
* @throws {ResourceError} `RESOURCE_DELETE_RESTRICTED` if a live record references a soft deleted orphan through a
|
|
99
|
+
* restricting relation.
|
|
100
|
+
*/
|
|
101
|
+
export declare function removeOrphans(tx: any, resourceName: string, relationActions: RelationActions, data: SoftDeleteData): Promise<DeletedRecords>;
|
|
102
|
+
/**
|
|
103
|
+
* Marks the file rows of the files the deleted records keep (`onResourceDeleted` / `onResourceSoftDeleted: 'keep'`) as
|
|
104
|
+
* deleted with the values of the delete, so they stay stored, e.g. for audit, but are no longer served.
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
107
|
+
* @param {DeletedRecords} deleted - The soft deleted and the removed records.
|
|
108
|
+
* @param {SoftDeleteData} data - The soft delete column values of the delete.
|
|
109
|
+
* @return {Promise<void>} Resolves when the kept files are marked deleted.
|
|
110
|
+
*/
|
|
111
|
+
export declare function retainDeletedFiles(tx: any, deleted: DeletedRecords, data: SoftDeleteData): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Merges the affected records of several deletes into the target, keeping the ids of every model unique.
|
|
114
|
+
*
|
|
115
|
+
* @param {AffectedRecords} target - The affected records merged into.
|
|
116
|
+
* @param {AffectedRecords[]} sources - The affected records to add.
|
|
117
|
+
* @return {AffectedRecords} The target holding the ids of every source.
|
|
118
|
+
*/
|
|
119
|
+
export declare function mergeAffectedRecords(target: AffectedRecords, ...sources: AffectedRecords[]): AffectedRecords;
|
|
120
|
+
/**
|
|
121
|
+
* Rejects the relation write actions pointing at soft deleted records, which the database would otherwise connect or
|
|
122
|
+
* update, since it does not know the record is deleted.
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} client - The database client, or the transaction client the write runs in.
|
|
125
|
+
* @param {string} resourceName - The name of the model the relation actions belong to.
|
|
126
|
+
* @param {RelationActions} relationActions - The mapped relation write actions.
|
|
127
|
+
* @return {Promise<void>} Resolves when no action points at a soft deleted record.
|
|
128
|
+
* @throws {ResourceError} `RESOURCE_INVALID_RELATION` if a connect, update or connect-or-create action matches a soft
|
|
129
|
+
* deleted record.
|
|
130
|
+
*/
|
|
131
|
+
export declare function assertLiveRelationTargets(client: any, resourceName: string, relationActions: RelationActions): Promise<void>;
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.liveRelationFilter = liveRelationFilter;
|
|
4
|
+
exports.liveInclusionFilter = liveInclusionFilter;
|
|
5
|
+
exports.hideDeletedRelations = hideDeletedRelations;
|
|
6
|
+
exports.softDeleteData = softDeleteData;
|
|
7
|
+
exports.softDeleteCascade = softDeleteCascade;
|
|
8
|
+
exports.cascadedRecords = cascadedRecords;
|
|
9
|
+
exports.removeOrphans = removeOrphans;
|
|
10
|
+
exports.retainDeletedFiles = retainDeletedFiles;
|
|
11
|
+
exports.mergeAffectedRecords = mergeAffectedRecords;
|
|
12
|
+
exports.assertLiveRelationTargets = assertLiveRelationTargets;
|
|
13
|
+
const common_1 = require("@appweaver/common");
|
|
14
|
+
const internal_1 = require("@appweaver/core/internal");
|
|
15
|
+
const core_1 = require("@appweaver/core");
|
|
16
|
+
const helper_1 = require("../../security/helper");
|
|
17
|
+
const file_util_1 = require("../../utils/file-util");
|
|
18
|
+
const model_util_1 = require("../../utils/model-util");
|
|
19
|
+
/** The condition matching the records that are not soft deleted. */
|
|
20
|
+
const LIVE_RECORD = Object.freeze({ deletedAt: null });
|
|
21
|
+
/** The condition matching the soft deleted records. */
|
|
22
|
+
const DELETED_RECORD = Object.freeze({ deletedAt: { not: null } });
|
|
23
|
+
/** Referential actions that prevent deleting a record while others reference it. */
|
|
24
|
+
const RESTRICTING_ACTIONS = ['restrict', 'noAction'];
|
|
25
|
+
/**
|
|
26
|
+
* Restricts a mapped relation filter to the related records that are not soft deleted, so a filter never matches
|
|
27
|
+
* through a deleted record, and a null check treats a deleted related record as missing.
|
|
28
|
+
*
|
|
29
|
+
* @param {*} condition - The mapped database condition of the relation field.
|
|
30
|
+
* @param {string} resourceName - The name of the related model.
|
|
31
|
+
* @param {boolean} isArrayType - Whether the relation is a list (to-many) relation.
|
|
32
|
+
* @return {*} The condition restricted to the live related records, or unchanged if the related model does not soft
|
|
33
|
+
* delete its records.
|
|
34
|
+
*/
|
|
35
|
+
function liveRelationFilter(condition, resourceName, isArrayType) {
|
|
36
|
+
if (!(0, model_util_1.isSoftDeleteModel)(resourceName)) {
|
|
37
|
+
return condition;
|
|
38
|
+
}
|
|
39
|
+
if (isArrayType) {
|
|
40
|
+
if (!(0, common_1.isPlainObject)(condition)) {
|
|
41
|
+
return condition;
|
|
42
|
+
}
|
|
43
|
+
const mapped = { ...condition };
|
|
44
|
+
for (const quantifier of ['some', 'none']) {
|
|
45
|
+
if (mapped[quantifier] !== undefined) {
|
|
46
|
+
mapped[quantifier] = { AND: [mapped[quantifier], LIVE_RECORD] };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (mapped.every !== undefined) {
|
|
50
|
+
mapped.every = { OR: [mapped.every, DELETED_RECORD] };
|
|
51
|
+
}
|
|
52
|
+
return mapped;
|
|
53
|
+
}
|
|
54
|
+
if (condition === null) {
|
|
55
|
+
return { isNot: LIVE_RECORD };
|
|
56
|
+
}
|
|
57
|
+
if (!(0, common_1.isPlainObject)(condition)) {
|
|
58
|
+
return condition;
|
|
59
|
+
}
|
|
60
|
+
const { is, isNot, ...fields } = condition;
|
|
61
|
+
const matches = [];
|
|
62
|
+
const mapped = {};
|
|
63
|
+
if (is === null) {
|
|
64
|
+
mapped.isNot = LIVE_RECORD;
|
|
65
|
+
}
|
|
66
|
+
else if (is !== undefined) {
|
|
67
|
+
matches.push(is);
|
|
68
|
+
}
|
|
69
|
+
if (isNot === null) {
|
|
70
|
+
matches.push({});
|
|
71
|
+
}
|
|
72
|
+
else if (isNot !== undefined) {
|
|
73
|
+
mapped.isNot = { AND: [isNot, LIVE_RECORD] };
|
|
74
|
+
}
|
|
75
|
+
// The fields given without `is` are its shorthand
|
|
76
|
+
if (Object.keys(fields).length > 0) {
|
|
77
|
+
matches.push(fields);
|
|
78
|
+
}
|
|
79
|
+
if (matches.length > 0) {
|
|
80
|
+
mapped.is = { AND: [...matches, LIVE_RECORD] };
|
|
81
|
+
}
|
|
82
|
+
return mapped;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Builds the inclusion filter of a list relation, so only the related records that are not soft deleted are read.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} [resourceName] - The name of the related model.
|
|
88
|
+
* @return {Object|undefined} The `where` clause of the inclusion, or undefined if the related model does not soft
|
|
89
|
+
* delete its records.
|
|
90
|
+
*/
|
|
91
|
+
function liveInclusionFilter(resourceName) {
|
|
92
|
+
return (0, model_util_1.isSoftDeleteModel)(resourceName)
|
|
93
|
+
? { where: { ...LIVE_RECORD } }
|
|
94
|
+
: undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Replaces the soft deleted records in the single relations of a resource with null, recursively. Unlike the list
|
|
98
|
+
* relations, a single relation cannot be filtered when it is read.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} resource - The resource as returned by the database client.
|
|
101
|
+
* @param {string} resourceName - The name of the model describing the resource.
|
|
102
|
+
* @return {Object} A copy of the resource without the soft deleted related records, or the resource itself if it
|
|
103
|
+
* holds no relations.
|
|
104
|
+
*/
|
|
105
|
+
function hideDeletedRelations(resource, resourceName) {
|
|
106
|
+
const relations = (0, core_1.injectModel)(resourceName, false)?.config?.relations;
|
|
107
|
+
if (!(0, common_1.isPlainObject)(resource) || !relations) {
|
|
108
|
+
return resource;
|
|
109
|
+
}
|
|
110
|
+
const projected = { ...resource };
|
|
111
|
+
for (const [key, relation] of Object.entries(relations)) {
|
|
112
|
+
const value = projected[key];
|
|
113
|
+
const relatedName = (0, common_1.capitalize)(relation.model);
|
|
114
|
+
if ((0, common_1.isArray)(value)) {
|
|
115
|
+
projected[key] = value.map((item) => hideDeletedRelations(item, relatedName));
|
|
116
|
+
}
|
|
117
|
+
else if ((0, common_1.isPlainObject)(value)) {
|
|
118
|
+
projected[key] =
|
|
119
|
+
value.deletedAt != null && (0, model_util_1.isSoftDeleteModel)(relatedName)
|
|
120
|
+
? null
|
|
121
|
+
: hideDeletedRelations(value, relatedName);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return projected;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds the column values marking a record as soft deleted now by the current user, who is only recorded when the
|
|
128
|
+
* application defines an auth model.
|
|
129
|
+
*
|
|
130
|
+
* @return {SoftDeleteData} The soft delete column values.
|
|
131
|
+
*/
|
|
132
|
+
function softDeleteData() {
|
|
133
|
+
const data = { deletedAt: new Date() };
|
|
134
|
+
if ((0, helper_1.resourceAuthModel)()) {
|
|
135
|
+
data.deletedById = (0, core_1.currentAuthUser)()?.id ?? null;
|
|
136
|
+
}
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Mirrors the referential actions of a database delete for soft deleted records: cascading records are soft deleted
|
|
141
|
+
* with the same column values, level by level, a restricting relation aborts the delete, and references set to null on
|
|
142
|
+
* delete are kept for a manual restore. The records themselves are not updated.
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
145
|
+
* @param {string} resourceName - The name of the model of the deleted records.
|
|
146
|
+
* @param {ResourceId[]} ids - The ids of the deleted records.
|
|
147
|
+
* @param {SoftDeleteData} data - The soft delete column values applied to the cascaded records.
|
|
148
|
+
* @return {Promise<AffectedRecords>} The ids of the soft deleted records per model, excluding the records themselves.
|
|
149
|
+
* @throws {ResourceError} `RESOURCE_DELETE_RESTRICTED` if a live record references a deleted record through a
|
|
150
|
+
* restricting relation.
|
|
151
|
+
*/
|
|
152
|
+
async function softDeleteCascade(tx, resourceName, ids, data) {
|
|
153
|
+
return walkReferencingRecords(tx, resourceName, ids, async (relation) => {
|
|
154
|
+
const client = tx[(0, common_1.uncapitalize)(relation.modelName)];
|
|
155
|
+
const where = {
|
|
156
|
+
[relation.foreignKey]: { in: relation.ids },
|
|
157
|
+
...(0, model_util_1.liveRecordFilter)(relation.modelName)
|
|
158
|
+
};
|
|
159
|
+
if (RESTRICTING_ACTIONS.includes(relation.onDelete)) {
|
|
160
|
+
await assertNotReferenced(client, where, relation);
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
if (relation.onDelete !== 'cascade') {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
// Loading validates every cascade of a soft deleted model, so this only
|
|
167
|
+
// guards the models registered after it
|
|
168
|
+
if (!(0, model_util_1.isSoftDeleteModel)(relation.modelName)) {
|
|
169
|
+
throw new common_1.ConfigurationError(common_1.ErrorCode.ConfigurationInvalid, `${relation.modelName} must enable soft delete to cascade from ${relation.referencedName}`);
|
|
170
|
+
}
|
|
171
|
+
const records = await client.findMany({ where, select: { id: true } });
|
|
172
|
+
const cascadedIds = records.map((record) => record.id);
|
|
173
|
+
if (cascadedIds.length > 0) {
|
|
174
|
+
await client.updateMany({ where: { id: { in: cascadedIds } }, data });
|
|
175
|
+
}
|
|
176
|
+
return cascadedIds;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Collects the records the database cascade removes together with deleted records, so their files and caches can be
|
|
181
|
+
* cleaned up once the delete commits.
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
184
|
+
* @param {string} resourceName - The name of the model of the deleted records.
|
|
185
|
+
* @param {ResourceId[]} ids - The ids of the deleted records.
|
|
186
|
+
* @return {Promise<AffectedRecords>} The ids of the cascaded records per model, excluding the records themselves.
|
|
187
|
+
*/
|
|
188
|
+
async function cascadedRecords(tx, resourceName, ids) {
|
|
189
|
+
return walkReferencingRecords(tx, resourceName, ids, async (relation) => {
|
|
190
|
+
if (relation.onDelete !== 'cascade') {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
// Soft deleted records are removed by the cascade as well
|
|
194
|
+
const records = await tx[(0, common_1.uncapitalize)(relation.modelName)].findMany({
|
|
195
|
+
where: { [relation.foreignKey]: { in: relation.ids } },
|
|
196
|
+
select: { id: true }
|
|
197
|
+
});
|
|
198
|
+
return records.map((record) => record.id);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Removes the orphans an update maps to a nested `delete` action (`orphanRemoval`). Orphans of a soft deleted model are
|
|
203
|
+
* soft deleted with their cascade and keep their references, so their `delete` action is dropped; the others are left
|
|
204
|
+
* to the `delete` action, collecting the records its cascade removes.
|
|
205
|
+
*
|
|
206
|
+
* @param {Object} tx - The transaction client the update runs in.
|
|
207
|
+
* @param {string} resourceName - The name of the model the relation actions belong to.
|
|
208
|
+
* @param {RelationActions} relationActions - The mapped relation write actions, from which the nested `delete` actions
|
|
209
|
+
* of the soft deleted orphans are removed.
|
|
210
|
+
* @param {SoftDeleteData} data - The soft delete column values applied to the soft deleted orphans.
|
|
211
|
+
* @return {Promise<DeletedRecords>} The ids of the soft deleted and the removed records per model.
|
|
212
|
+
* @throws {ResourceError} `RESOURCE_DELETE_RESTRICTED` if a live record references a soft deleted orphan through a
|
|
213
|
+
* restricting relation.
|
|
214
|
+
*/
|
|
215
|
+
async function removeOrphans(tx, resourceName, relationActions, data) {
|
|
216
|
+
const relations = (0, core_1.injectModel)(resourceName, false)?.config?.relations ?? {};
|
|
217
|
+
const deleted = { soft: {}, hard: {} };
|
|
218
|
+
for (const [key, actions] of Object.entries(relationActions)) {
|
|
219
|
+
const relation = relations[key];
|
|
220
|
+
if (!actions?.delete || !relation) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const relatedName = (0, common_1.capitalize)(relation.model);
|
|
224
|
+
const client = tx[(0, common_1.uncapitalize)(relatedName)];
|
|
225
|
+
const orphans = await client.findMany({
|
|
226
|
+
where: { OR: asList(actions.delete) },
|
|
227
|
+
select: { id: true }
|
|
228
|
+
});
|
|
229
|
+
const ids = orphans.map((orphan) => orphan.id);
|
|
230
|
+
if (ids.length === 0) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if ((0, model_util_1.isSoftDeleteModel)(relatedName)) {
|
|
234
|
+
// Left undefined rather than removed, so the raw input value of the
|
|
235
|
+
// relation does not reach the database write in its place
|
|
236
|
+
delete actions.delete;
|
|
237
|
+
if (Object.keys(actions).length === 0) {
|
|
238
|
+
relationActions[key] = undefined;
|
|
239
|
+
}
|
|
240
|
+
const cascaded = await softDeleteCascade(tx, relatedName, ids, data);
|
|
241
|
+
await client.updateMany({ where: { id: { in: ids } }, data });
|
|
242
|
+
mergeAffectedRecords(deleted.soft, { [relatedName]: ids }, cascaded);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const cascaded = await cascadedRecords(tx, relatedName, ids);
|
|
246
|
+
mergeAffectedRecords(deleted.hard, { [relatedName]: ids }, cascaded);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return deleted;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Marks the file rows of the files the deleted records keep (`onResourceDeleted` / `onResourceSoftDeleted: 'keep'`) as
|
|
253
|
+
* deleted with the values of the delete, so they stay stored, e.g. for audit, but are no longer served.
|
|
254
|
+
*
|
|
255
|
+
* @param {Object} tx - The transaction client the delete runs in.
|
|
256
|
+
* @param {DeletedRecords} deleted - The soft deleted and the removed records.
|
|
257
|
+
* @param {SoftDeleteData} data - The soft delete column values of the delete.
|
|
258
|
+
* @return {Promise<void>} Resolves when the kept files are marked deleted.
|
|
259
|
+
*/
|
|
260
|
+
async function retainDeletedFiles(tx, deleted, data) {
|
|
261
|
+
const groups = [
|
|
262
|
+
[true, deleted.soft],
|
|
263
|
+
[false, deleted.hard]
|
|
264
|
+
];
|
|
265
|
+
for (const [softDeleted, records] of groups) {
|
|
266
|
+
for (const [modelName, ids] of Object.entries(records)) {
|
|
267
|
+
const files = (0, core_1.injectModel)(modelName, false)?.config.files;
|
|
268
|
+
const { kept } = (0, file_util_1.deletedResourceFileFields)(files, softDeleted);
|
|
269
|
+
if (kept.length === 0 || ids.length === 0) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
await tx.file.updateMany({
|
|
273
|
+
where: {
|
|
274
|
+
resourceName: modelName,
|
|
275
|
+
// Owning ids are stored as text, whatever the model primary key is
|
|
276
|
+
resourceId: { in: ids.map(String) },
|
|
277
|
+
resourceField: { in: kept },
|
|
278
|
+
...(0, model_util_1.liveRecordFilter)('File')
|
|
279
|
+
},
|
|
280
|
+
data
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Merges the affected records of several deletes into the target, keeping the ids of every model unique.
|
|
287
|
+
*
|
|
288
|
+
* @param {AffectedRecords} target - The affected records merged into.
|
|
289
|
+
* @param {AffectedRecords[]} sources - The affected records to add.
|
|
290
|
+
* @return {AffectedRecords} The target holding the ids of every source.
|
|
291
|
+
*/
|
|
292
|
+
function mergeAffectedRecords(target, ...sources) {
|
|
293
|
+
for (const source of sources) {
|
|
294
|
+
for (const [modelName, ids] of Object.entries(source)) {
|
|
295
|
+
const seen = new Set((target[modelName] ?? []).map(String));
|
|
296
|
+
target[modelName] = [
|
|
297
|
+
...(target[modelName] ?? []),
|
|
298
|
+
...ids.filter((id) => !seen.has(String(id)))
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return target;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Rejects the relation write actions pointing at soft deleted records, which the database would otherwise connect or
|
|
306
|
+
* update, since it does not know the record is deleted.
|
|
307
|
+
*
|
|
308
|
+
* @param {Object} client - The database client, or the transaction client the write runs in.
|
|
309
|
+
* @param {string} resourceName - The name of the model the relation actions belong to.
|
|
310
|
+
* @param {RelationActions} relationActions - The mapped relation write actions.
|
|
311
|
+
* @return {Promise<void>} Resolves when no action points at a soft deleted record.
|
|
312
|
+
* @throws {ResourceError} `RESOURCE_INVALID_RELATION` if a connect, update or connect-or-create action matches a soft
|
|
313
|
+
* deleted record.
|
|
314
|
+
*/
|
|
315
|
+
async function assertLiveRelationTargets(client, resourceName, relationActions) {
|
|
316
|
+
const relations = (0, core_1.injectModel)(resourceName, false)?.config?.relations ?? {};
|
|
317
|
+
for (const [key, actions] of Object.entries(relationActions)) {
|
|
318
|
+
const relatedName = relations[key]?.model;
|
|
319
|
+
if (!actions || !(0, model_util_1.isSoftDeleteModel)(relatedName)) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const matches = [
|
|
323
|
+
...asList(actions.connect).map((item) => ({ id: item.id })),
|
|
324
|
+
...asList(actions.update).map((item) => item.where),
|
|
325
|
+
...asList(actions.connectOrCreate).map((item) => item.where)
|
|
326
|
+
];
|
|
327
|
+
if (matches.length === 0) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const deletedCount = await client[(0, common_1.uncapitalize)(relatedName)].count({
|
|
331
|
+
where: { AND: [DELETED_RECORD, { OR: matches }] }
|
|
332
|
+
});
|
|
333
|
+
if (deletedCount > 0) {
|
|
334
|
+
throw new common_1.ResourceError(common_1.ErrorCode.ResourceInvalidRelation, `${resourceName} relation '${key}' references a ${(0, common_1.capitalize)(relatedName)} record that does not exist`, { model: resourceName, relation: key });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** Wraps a single relation action value into a list. */
|
|
339
|
+
function asList(value) {
|
|
340
|
+
return value === undefined ? [] : (0, common_1.isArray)(value) ? value : [value];
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Rejects a delete while records reference the deleted ones through a restricting relation.
|
|
344
|
+
*/
|
|
345
|
+
async function assertNotReferenced(client, where, relation) {
|
|
346
|
+
const count = await client.count({ where });
|
|
347
|
+
if (count > 0) {
|
|
348
|
+
throw new common_1.ResourceError(common_1.ErrorCode.ResourceDeleteRestricted, `${relation.referencedName} cannot be deleted while ${relation.modelName} records reference it`, { model: relation.referencedName, referencedBy: relation.modelName });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Walks the records referencing the given records breadth first. The visitor gets every referencing relation with the
|
|
353
|
+
* referenced ids and returns the ids of the records the walk continues from.
|
|
354
|
+
*/
|
|
355
|
+
async function walkReferencingRecords(tx, resourceName, ids, visit) {
|
|
356
|
+
const models = Object.fromEntries(internal_1.context.resource.models);
|
|
357
|
+
const affected = {};
|
|
358
|
+
const queue = [
|
|
359
|
+
{ modelName: resourceName, ids }
|
|
360
|
+
];
|
|
361
|
+
while (queue.length > 0) {
|
|
362
|
+
const level = queue.shift();
|
|
363
|
+
for (const relation of (0, common_1.referencingRelations)(models, level.modelName)) {
|
|
364
|
+
const visitedIds = await visit({
|
|
365
|
+
...relation,
|
|
366
|
+
ids: level.ids,
|
|
367
|
+
referencedName: level.modelName
|
|
368
|
+
});
|
|
369
|
+
// A record reached twice, i.e. through a self relation, is walked once
|
|
370
|
+
const seen = new Set((affected[relation.modelName] ?? []).map(String));
|
|
371
|
+
if (relation.modelName === resourceName) {
|
|
372
|
+
ids.forEach((id) => seen.add(String(id)));
|
|
373
|
+
}
|
|
374
|
+
const newIds = visitedIds.filter((value) => !seen.has(String(value)));
|
|
375
|
+
if (newIds.length > 0) {
|
|
376
|
+
affected[relation.modelName] = [
|
|
377
|
+
...(affected[relation.modelName] ?? []),
|
|
378
|
+
...newIds
|
|
379
|
+
];
|
|
380
|
+
queue.push({ modelName: relation.modelName, ids: newIds });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return affected;
|
|
385
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a request filter to a Prisma `where` clause based on the schema of the
|
|
3
|
+
* given resource model. The logical operators (`_and`, `_or`, `_not`, `_nor`)
|
|
4
|
+
* combine their nested conditions with the matching connective, the field
|
|
5
|
+
* operator objects (`_eq`, `_gt`, `_like`, `_exists`, ...) are translated to
|
|
6
|
+
* their database conditions, and the list relation quantifiers (`_some`,
|
|
7
|
+
* `_every`, `_none`) filter the related records. Plain values keep their
|
|
8
|
+
* shorthand meaning: relation and file fields are matched by id (wrapped in a
|
|
9
|
+
* `some` condition for the array relations), array fields use the `has` and
|
|
10
|
+
* `hasSome` operators, an array value on a numeric or date field becomes an
|
|
11
|
+
* inclusive `gte`/`lte` range, other array values become an `in` inclusion,
|
|
12
|
+
* nested objects are mapped recursively against the related model, and null
|
|
13
|
+
* values are passed through to match records without a value or a related
|
|
14
|
+
* record. Values that match no known field are passed through with only their
|
|
15
|
+
* operators translated, so the Prisma operators can still be used directly.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} filter The request filter object to map.
|
|
18
|
+
* @param {string} resourceName The name of the model the filter is matched
|
|
19
|
+
* against. It is set to the related model name when recursing into a nested
|
|
20
|
+
* relation or file filter.
|
|
21
|
+
* @return {Object} The `where` clause with every recognized field mapped to its
|
|
22
|
+
* database condition and the unrecognized values passed through unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mapQueryFilter(filter: any, resourceName: string): any;
|