@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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/config.d.ts +6 -0
  4. package/config.js +22 -0
  5. package/database/create-client.d.ts +11 -0
  6. package/database/create-client.js +145 -0
  7. package/database/database-config.d.ts +14 -0
  8. package/database/database-config.js +40 -0
  9. package/database/database-error.d.ts +49 -0
  10. package/database/database-error.js +216 -0
  11. package/database/index.d.ts +3 -0
  12. package/database/index.js +19 -0
  13. package/database/prisma-database.d.ts +33 -0
  14. package/database/prisma-database.js +178 -0
  15. package/database/run-transaction.d.ts +21 -0
  16. package/database/run-transaction.js +15 -0
  17. package/export/export-config.d.ts +7 -0
  18. package/export/export-config.js +17 -0
  19. package/export/export-service.d.ts +22 -0
  20. package/export/export-service.js +188 -0
  21. package/export/index.d.ts +1 -0
  22. package/export/index.js +17 -0
  23. package/factory/create-model.d.ts +11 -0
  24. package/factory/create-model.js +479 -0
  25. package/factory/create-policy.d.ts +12 -0
  26. package/factory/create-policy.js +20 -0
  27. package/factory/create-service.d.ts +14 -0
  28. package/factory/create-service.js +116 -0
  29. package/index.d.ts +22 -0
  30. package/index.js +38 -0
  31. package/internal.d.ts +2 -0
  32. package/internal.js +6 -0
  33. package/module.d.ts +3 -0
  34. package/module.js +81 -0
  35. package/package.json +67 -3
  36. package/prisma/client/browser.d.ts +39 -0
  37. package/prisma/client/browser.js +56 -0
  38. package/prisma/client/client.d.ts +58 -0
  39. package/prisma/client/client.js +70 -0
  40. package/prisma/client/commonInputTypes.d.ts +345 -0
  41. package/prisma/client/commonInputTypes.js +11 -0
  42. package/prisma/client/enums.d.ts +1 -0
  43. package/prisma/client/enums.js +11 -0
  44. package/prisma/client/internal/class.d.ts +198 -0
  45. package/prisma/client/internal/class.js +85 -0
  46. package/prisma/client/internal/prismaNamespace.d.ts +1125 -0
  47. package/prisma/client/internal/prismaNamespace.js +204 -0
  48. package/prisma/client/internal/prismaNamespaceBrowser.d.ts +137 -0
  49. package/prisma/client/internal/prismaNamespaceBrowser.js +175 -0
  50. package/prisma/client/models/ApiKey.d.ts +1167 -0
  51. package/prisma/client/models/ApiKey.js +2 -0
  52. package/prisma/client/models/ConnectedAccount.d.ts +1094 -0
  53. package/prisma/client/models/ConnectedAccount.js +2 -0
  54. package/prisma/client/models/File.d.ts +1291 -0
  55. package/prisma/client/models/File.js +2 -0
  56. package/prisma/client/models/OneTimeToken.d.ts +1055 -0
  57. package/prisma/client/models/OneTimeToken.js +2 -0
  58. package/prisma/client/models/Permission.d.ts +1209 -0
  59. package/prisma/client/models/Permission.js +2 -0
  60. package/prisma/client/models/Role.d.ts +1209 -0
  61. package/prisma/client/models/Role.js +2 -0
  62. package/prisma/client/models/Seeder.d.ts +1023 -0
  63. package/prisma/client/models/Seeder.js +2 -0
  64. package/prisma/client/models.d.ts +8 -0
  65. package/prisma/client/models.js +2 -0
  66. package/resource/load-data-resources.d.ts +13 -0
  67. package/resource/load-data-resources.js +37 -0
  68. package/resource/model-schema.d.ts +34 -0
  69. package/resource/model-schema.js +54 -0
  70. package/resource/resource-config.d.ts +16 -0
  71. package/resource/resource-config.js +62 -0
  72. package/resource/resource-loader.d.ts +25 -0
  73. package/resource/resource-loader.js +138 -0
  74. package/resource/resource-service.d.ts +462 -0
  75. package/resource/resource-service.js +1042 -0
  76. package/resource/schemas/index.d.ts +3 -0
  77. package/resource/schemas/index.js +19 -0
  78. package/resource/schemas/resource-aggregate-schema.d.ts +53 -0
  79. package/resource/schemas/resource-aggregate-schema.js +130 -0
  80. package/resource/schemas/resource-filter-schema.d.ts +39 -0
  81. package/resource/schemas/resource-filter-schema.js +153 -0
  82. package/resource/schemas/resource-sort-schema.d.ts +38 -0
  83. package/resource/schemas/resource-sort-schema.js +113 -0
  84. package/resource/utils/aggregate-util.d.ts +113 -0
  85. package/resource/utils/aggregate-util.js +322 -0
  86. package/resource/utils/cursor-util.d.ts +60 -0
  87. package/resource/utils/cursor-util.js +116 -0
  88. package/resource/utils/date-util.d.ts +22 -0
  89. package/resource/utils/date-util.js +112 -0
  90. package/resource/utils/delete-util.d.ts +131 -0
  91. package/resource/utils/delete-util.js +385 -0
  92. package/resource/utils/filter-util.d.ts +24 -0
  93. package/resource/utils/filter-util.js +289 -0
  94. package/resource/utils/prune-util.d.ts +11 -0
  95. package/resource/utils/prune-util.js +56 -0
  96. package/resource/utils/relation-util.d.ts +75 -0
  97. package/resource/utils/relation-util.js +400 -0
  98. package/resource/utils/sort-util.d.ts +31 -0
  99. package/resource/utils/sort-util.js +235 -0
  100. package/resource/utils/text-search-util.d.ts +14 -0
  101. package/resource/utils/text-search-util.js +39 -0
  102. package/resources.d.ts +10 -0
  103. package/resources.js +26 -0
  104. package/security/auth-config.d.ts +23 -0
  105. package/security/auth-config.js +43 -0
  106. package/security/create-auth-resources.d.ts +28 -0
  107. package/security/create-auth-resources.js +163 -0
  108. package/security/helper.d.ts +109 -0
  109. package/security/helper.js +298 -0
  110. package/security/resources/api-key/model.d.ts +2 -0
  111. package/security/resources/api-key/model.js +67 -0
  112. package/security/resources/api-key/policy.d.ts +2 -0
  113. package/security/resources/api-key/policy.js +27 -0
  114. package/security/resources/api-key/service.d.ts +3 -0
  115. package/security/resources/api-key/service.js +34 -0
  116. package/security/resources/connected-account/model.d.ts +2 -0
  117. package/security/resources/connected-account/model.js +57 -0
  118. package/security/resources/connected-account/service.d.ts +2 -0
  119. package/security/resources/connected-account/service.js +7 -0
  120. package/security/resources/one-time-token/model.d.ts +2 -0
  121. package/security/resources/one-time-token/model.js +36 -0
  122. package/security/resources/permission/model.d.ts +2 -0
  123. package/security/resources/permission/model.js +16 -0
  124. package/security/resources/role/model.d.ts +2 -0
  125. package/security/resources/role/model.js +22 -0
  126. package/security/store/database-security-store.d.ts +5 -0
  127. package/security/store/database-security-store.js +54 -0
  128. package/seeder/create-seeder.d.ts +29 -0
  129. package/seeder/create-seeder.js +28 -0
  130. package/seeder/index.d.ts +2 -0
  131. package/seeder/index.js +18 -0
  132. package/seeder/resources/seeder/model.d.ts +2 -0
  133. package/seeder/resources/seeder/model.js +38 -0
  134. package/seeder/seeder.d.ts +29 -0
  135. package/seeder/seeder.js +197 -0
  136. package/storage/file-service.d.ts +133 -0
  137. package/storage/file-service.js +586 -0
  138. package/storage/image-config.d.ts +3 -0
  139. package/storage/image-config.js +11 -0
  140. package/storage/resources/file/model.d.ts +2 -0
  141. package/storage/resources/file/model.js +80 -0
  142. package/types/auth.d.ts +23 -0
  143. package/types/auth.js +2 -0
  144. package/types/generated.d.ts +366 -0
  145. package/types/generated.js +3 -0
  146. package/types/storage.d.ts +35 -0
  147. package/types/storage.js +2 -0
  148. package/utils/export-util.d.ts +8 -0
  149. package/utils/export-util.js +26 -0
  150. package/utils/file-util.d.ts +106 -0
  151. package/utils/file-util.js +254 -0
  152. package/utils/model-util.d.ts +36 -0
  153. package/utils/model-util.js +154 -0
  154. package/utils/policy-util.d.ts +18 -0
  155. package/utils/policy-util.js +28 -0
  156. package/utils/virtual-util.d.ts +11 -0
  157. package/utils/virtual-util.js +53 -0
@@ -0,0 +1,3 @@
1
+ export * from './resource-aggregate-schema';
2
+ export * from './resource-filter-schema';
3
+ export * from './resource-sort-schema';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./resource-aggregate-schema"), exports);
18
+ __exportStar(require("./resource-filter-schema"), exports);
19
+ __exportStar(require("./resource-sort-schema"), exports);
@@ -0,0 +1,53 @@
1
+ import { TObject, TSchema } from '@sinclair/typebox';
2
+ import { ResourceModel } from '@appweaver/common';
3
+ /** The schema name the numeric field operator object is registered under. */
4
+ export declare const AGGREGATE_NUMERIC_SCHEMA_NAME = "AggregateNumericOperators";
5
+ /** The schema name the date field operator object is registered under. */
6
+ export declare const AGGREGATE_DATE_SCHEMA_NAME = "AggregateDateOperators";
7
+ /**
8
+ * Resolves the schema name the aggregate selection of a model is registered
9
+ * under.
10
+ *
11
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
12
+ * @return {string} The aggregate selection schema name (i.e. `UserAggregateSelect`).
13
+ */
14
+ export declare const aggregateSelectName: (modelName: string) => string;
15
+ /**
16
+ * The operators applicable to a numeric field. Every operator selected on the
17
+ * same field is applied to it, and the field is left out of the response of the
18
+ * operators that were not selected.
19
+ */
20
+ export declare const AggregateNumericSchema: TObject;
21
+ /**
22
+ * The operators applicable to a date field. Dates cannot be summed or averaged,
23
+ * so only the counting and the ordering operators are offered.
24
+ */
25
+ export declare const AggregateDateSchema: TObject;
26
+ /**
27
+ * Builds the aggregate selection schema of a resource model. Only the fields
28
+ * the database can aggregate are declared, which are the numeric and the date
29
+ * scalars of the model together with its numeric id and audit fields, so the
30
+ * request validation rejects every other field (including hidden fields, array
31
+ * scalars, and the virtual fields that have no column of their own).
32
+ *
33
+ * @param {ResourceModel} model The resource model to build the selection schema
34
+ * for.
35
+ * @return {TObject} The aggregate selection schema of the model.
36
+ */
37
+ export declare function buildAggregateSelectSchema(model: ResourceModel): TObject;
38
+ /**
39
+ * Builds the schema of the date field property of a model, listing the date
40
+ * fields the aggregated range can be applied on.
41
+ *
42
+ * @param {ResourceModel} model The resource model the property belongs to.
43
+ * @return {TSchema} The optional date field property schema, restricted to the
44
+ * date fields of the model when it declares any.
45
+ */
46
+ export declare function aggregateDateFieldSchema(model: ResourceModel): TSchema;
47
+ /**
48
+ * Registers the shared operator schemas and the aggregate selection schema of
49
+ * every loaded resource model on the server, so route schemas can reference them
50
+ * by name (i.e. `UserAggregateSelect`). Schemas already registered under the
51
+ * same name are skipped, which makes repeated calls cheap.
52
+ */
53
+ export declare function registerAggregateSelectSchemas(): void;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AggregateDateSchema = exports.AggregateNumericSchema = exports.aggregateSelectName = exports.AGGREGATE_DATE_SCHEMA_NAME = exports.AGGREGATE_NUMERIC_SCHEMA_NAME = void 0;
4
+ exports.buildAggregateSelectSchema = buildAggregateSelectSchema;
5
+ exports.aggregateDateFieldSchema = aggregateDateFieldSchema;
6
+ exports.registerAggregateSelectSchemas = registerAggregateSelectSchemas;
7
+ const typebox_1 = require("@sinclair/typebox");
8
+ const internal_1 = require("@appweaver/core/internal");
9
+ const core_1 = require("@appweaver/core");
10
+ const aggregate_util_1 = require("../utils/aggregate-util");
11
+ /** The schema name the numeric field operator object is registered under. */
12
+ exports.AGGREGATE_NUMERIC_SCHEMA_NAME = 'AggregateNumericOperators';
13
+ /** The schema name the date field operator object is registered under. */
14
+ exports.AGGREGATE_DATE_SCHEMA_NAME = 'AggregateDateOperators';
15
+ /**
16
+ * Resolves the schema name the aggregate selection of a model is registered
17
+ * under.
18
+ *
19
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
20
+ * @return {string} The aggregate selection schema name (i.e. `UserAggregateSelect`).
21
+ */
22
+ const aggregateSelectName = (modelName) => `${modelName}AggregateSelect`;
23
+ exports.aggregateSelectName = aggregateSelectName;
24
+ const Operator = (description) => typebox_1.Type.Optional(typebox_1.Type.Boolean({ description, example: true }));
25
+ // The boundary operators read the record itself instead of aggregating the
26
+ // range, so their value is the one the record holds, null included
27
+ const FIRST_DESCRIPTION = 'Takes the value held by the earliest record of the range, ordered by the ' +
28
+ 'aggregated date field';
29
+ const LAST_DESCRIPTION = 'Takes the value held by the latest record of the range, ordered by the ' +
30
+ 'aggregated date field';
31
+ /**
32
+ * The operators applicable to a numeric field. Every operator selected on the
33
+ * same field is applied to it, and the field is left out of the response of the
34
+ * operators that were not selected.
35
+ */
36
+ exports.AggregateNumericSchema = typebox_1.Type.Object({
37
+ count: Operator('Counts the records with a non-null value'),
38
+ sum: Operator('Sums the values'),
39
+ avg: Operator('Averages the values'),
40
+ min: Operator('Takes the lowest value'),
41
+ max: Operator('Takes the highest value'),
42
+ first: Operator(FIRST_DESCRIPTION),
43
+ last: Operator(LAST_DESCRIPTION)
44
+ }, {
45
+ description: 'Aggregation operators applicable to a numeric field'
46
+ });
47
+ /**
48
+ * The operators applicable to a date field. Dates cannot be summed or averaged,
49
+ * so only the counting and the ordering operators are offered.
50
+ */
51
+ exports.AggregateDateSchema = typebox_1.Type.Object({
52
+ count: Operator('Counts the records with a non-null value'),
53
+ min: Operator('Takes the earliest date'),
54
+ max: Operator('Takes the latest date'),
55
+ first: Operator(FIRST_DESCRIPTION),
56
+ last: Operator(LAST_DESCRIPTION)
57
+ }, {
58
+ description: 'Aggregation operators applicable to a date field'
59
+ });
60
+ /**
61
+ * Builds the aggregate selection schema of a resource model. Only the fields
62
+ * the database can aggregate are declared, which are the numeric and the date
63
+ * scalars of the model together with its numeric id and audit fields, so the
64
+ * request validation rejects every other field (including hidden fields, array
65
+ * scalars, and the virtual fields that have no column of their own).
66
+ *
67
+ * @param {ResourceModel} model The resource model to build the selection schema
68
+ * for.
69
+ * @return {TObject} The aggregate selection schema of the model.
70
+ */
71
+ function buildAggregateSelectSchema(model) {
72
+ const properties = {};
73
+ for (const [fieldName, type] of (0, aggregate_util_1.aggregateFields)(model)) {
74
+ properties[fieldName] = typebox_1.Type.Optional(typebox_1.Type.Ref(type === 'numeric'
75
+ ? exports.AGGREGATE_NUMERIC_SCHEMA_NAME
76
+ : exports.AGGREGATE_DATE_SCHEMA_NAME));
77
+ }
78
+ return typebox_1.Type.Object(properties, {
79
+ description: `Aggregate selection for the ${model.name} resource, holding the ` +
80
+ 'operators to apply per aggregated field'
81
+ });
82
+ }
83
+ /**
84
+ * Builds the schema of the date field property of a model, listing the date
85
+ * fields the aggregated range can be applied on.
86
+ *
87
+ * @param {ResourceModel} model The resource model the property belongs to.
88
+ * @return {TSchema} The optional date field property schema, restricted to the
89
+ * date fields of the model when it declares any.
90
+ */
91
+ function aggregateDateFieldSchema(model) {
92
+ const dateFields = (0, aggregate_util_1.aggregateFields)(model)
93
+ .filter(([, type]) => type === 'date')
94
+ .map(([fieldName]) => fieldName);
95
+ const description = 'The date field the aggregated range is applied on';
96
+ // A model without a single date field has no range to aggregate over, so the
97
+ // property is left unrestricted instead of declaring an empty enum that
98
+ // nothing can satisfy
99
+ return typebox_1.Type.Optional(dateFields.length > 0
100
+ ? typebox_1.Type.Unsafe({
101
+ type: 'string',
102
+ enum: dateFields,
103
+ // The default date field of the aggregate action, when the model
104
+ // audits it, so the example matches what an omitted value resolves to
105
+ example: dateFields.includes('createdAt')
106
+ ? 'createdAt'
107
+ : dateFields[0],
108
+ description
109
+ })
110
+ : typebox_1.Type.String({ example: 'createdAt', description }));
111
+ }
112
+ /**
113
+ * Registers the shared operator schemas and the aggregate selection schema of
114
+ * every loaded resource model on the server, so route schemas can reference them
115
+ * by name (i.e. `UserAggregateSelect`). Schemas already registered under the
116
+ * same name are skipped, which makes repeated calls cheap.
117
+ */
118
+ function registerAggregateSelectSchemas() {
119
+ (0, core_1.createSchemaModel)(exports.AggregateNumericSchema, {
120
+ name: exports.AGGREGATE_NUMERIC_SCHEMA_NAME
121
+ });
122
+ (0, core_1.createSchemaModel)(exports.AggregateDateSchema, {
123
+ name: exports.AGGREGATE_DATE_SCHEMA_NAME
124
+ });
125
+ for (const model of internal_1.context.resource.models.values()) {
126
+ (0, core_1.createSchemaModel)(buildAggregateSelectSchema(model), {
127
+ name: (0, exports.aggregateSelectName)(model.name)
128
+ });
129
+ }
130
+ }
@@ -0,0 +1,39 @@
1
+ import { TObject } from '@sinclair/typebox';
2
+ import { ResourceModel } from '@appweaver/common';
3
+ /** The schema name the shared field condition object is registered under. */
4
+ export declare const QUERY_CONDITION_SCHEMA_NAME = "QueryCondition";
5
+ /**
6
+ * Resolves the schema name the query filter of a model is registered under.
7
+ *
8
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
9
+ * @return {string} The query filter schema name (i.e. `UserQueryFilter`).
10
+ */
11
+ export declare const queryFilterName: (modelName: string) => string;
12
+ /**
13
+ * The shared schema of a field condition object, holding the comparison
14
+ * operators applicable to a single field. Operators combined inside the same
15
+ * object are merged into a single database condition, and unknown operators
16
+ * are rejected by the request validation.
17
+ */
18
+ export declare const QueryConditionSchema: TObject;
19
+ /**
20
+ * Builds the query filter schema of a resource model, mirroring the WHERE part
21
+ * of a database query. Every filterable field of the model is declared
22
+ * explicitly, so the request validation rejects every other field (including
23
+ * hidden fields). Scalar fields accept a plain
24
+ * value, a list of values, or a condition object, relation and file fields
25
+ * additionally accept an id shorthand or the filter of the related model, and
26
+ * the `_`-prefixed logical operators combine nested conditions recursively.
27
+ *
28
+ * @param {ResourceModel} model The resource model to build the filter schema
29
+ * for.
30
+ * @return {TObject} The query filter schema of the model.
31
+ */
32
+ export declare function buildQueryFilterSchema(model: ResourceModel): TObject;
33
+ /**
34
+ * Registers the shared field condition schema and the query filter schema of
35
+ * every loaded resource model on the server, so route schemas can reference
36
+ * them by name (i.e. `UserQueryFilter`). Schemas already registered under the
37
+ * same name are skipped, which makes repeated calls cheap.
38
+ */
39
+ export declare function registerQueryFilterSchemas(): void;
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueryConditionSchema = exports.queryFilterName = exports.QUERY_CONDITION_SCHEMA_NAME = void 0;
4
+ exports.buildQueryFilterSchema = buildQueryFilterSchema;
5
+ exports.registerQueryFilterSchemas = registerQueryFilterSchemas;
6
+ const typebox_1 = require("@sinclair/typebox");
7
+ const common_1 = require("@appweaver/common");
8
+ const internal_1 = require("@appweaver/core/internal");
9
+ const core_1 = require("@appweaver/core");
10
+ /** The schema name the shared field condition object is registered under. */
11
+ exports.QUERY_CONDITION_SCHEMA_NAME = 'QueryCondition';
12
+ /**
13
+ * Resolves the schema name the query filter of a model is registered under.
14
+ *
15
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
16
+ * @return {string} The query filter schema name (i.e. `UserQueryFilter`).
17
+ */
18
+ const queryFilterName = (modelName) => `${modelName}QueryFilter`;
19
+ exports.queryFilterName = queryFilterName;
20
+ // The primitive types a plain filter value can take. They are declared as a
21
+ // single JSON Schema type list rather than a union of typed branches, because
22
+ // the request validator coerces a value into the first branch of a typed
23
+ // union that accepts it (turning 10 into '10'), while a type list leaves a
24
+ // value that already matches one of its types untouched. The exact type of
25
+ // each field is enforced by the database layer instead.
26
+ const PRIMITIVE_TYPES = ['string', 'number', 'boolean', 'null'];
27
+ const PlainValue = () => typebox_1.Type.Unsafe({ type: PRIMITIVE_TYPES });
28
+ const PlainValueList = (options = {}) => typebox_1.Type.Unsafe({
29
+ type: 'array',
30
+ items: { type: PRIMITIVE_TYPES },
31
+ ...options
32
+ });
33
+ const Operator = (schema, description) => typebox_1.Type.Optional({ ...schema, description });
34
+ /**
35
+ * The shared schema of a field condition object, holding the comparison
36
+ * operators applicable to a single field. Operators combined inside the same
37
+ * object are merged into a single database condition, and unknown operators
38
+ * are rejected by the request validation.
39
+ */
40
+ exports.QueryConditionSchema = typebox_1.Type.Object({
41
+ _eq: Operator(PlainValue(), 'Matches values equal to the given value'),
42
+ _ne: Operator(PlainValue(), 'Matches values not equal to the given value'),
43
+ _gt: Operator(PlainValue(), 'Matches values greater than the given value'),
44
+ _gte: Operator(PlainValue(), 'Matches values greater than or equal to the given value'),
45
+ _lt: Operator(PlainValue(), 'Matches values lower than the given value'),
46
+ _lte: Operator(PlainValue(), 'Matches values lower than or equal to the given value'),
47
+ _in: Operator(PlainValueList(), 'Matches values included in the given list'),
48
+ _nin: Operator(PlainValueList(), 'Matches values not included in the given list'),
49
+ _between: Operator(PlainValueList({ minItems: 2, maxItems: 2 }), 'Matches values inside the inclusive [min, max] range'),
50
+ _like: Operator(typebox_1.Type.String(), 'Matches strings against an SQL LIKE pattern with % wildcards'),
51
+ _ilike: Operator(typebox_1.Type.String(), 'Case-insensitive variant of the _like operator'),
52
+ _starts: Operator(typebox_1.Type.String(), 'Matches strings starting with the given value'),
53
+ _ends: Operator(typebox_1.Type.String(), 'Matches strings ending with the given value'),
54
+ _contains: Operator(typebox_1.Type.String(), 'Matches strings containing the given value'),
55
+ _exists: Operator(typebox_1.Type.Boolean(), 'Matches values that are not null (true) or null (false)'),
56
+ _has: Operator(PlainValue(), 'Matches lists containing the given value'),
57
+ _hasSome: Operator(PlainValueList(), 'Matches lists containing at least one of the given values'),
58
+ _hasEvery: Operator(PlainValueList(), 'Matches lists containing all the given values'),
59
+ _isEmpty: Operator(typebox_1.Type.Boolean(), 'Matches empty (true) or non-empty (false) lists'),
60
+ _not: Operator(typebox_1.Type.Union([PlainValue(), typebox_1.Type.Ref(exports.QUERY_CONDITION_SCHEMA_NAME)]), 'Negates the nested condition or plain value')
61
+ }, {
62
+ description: 'Field condition object combining comparison operators on a single field'
63
+ });
64
+ /**
65
+ * Builds the query filter schema of a resource model, mirroring the WHERE part
66
+ * of a database query. Every filterable field of the model is declared
67
+ * explicitly, so the request validation rejects every other field (including
68
+ * hidden fields). Scalar fields accept a plain
69
+ * value, a list of values, or a condition object, relation and file fields
70
+ * additionally accept an id shorthand or the filter of the related model, and
71
+ * the `_`-prefixed logical operators combine nested conditions recursively.
72
+ *
73
+ * @param {ResourceModel} model The resource model to build the filter schema
74
+ * for.
75
+ * @return {TObject} The query filter schema of the model.
76
+ */
77
+ function buildQueryFilterSchema(model) {
78
+ const name = (0, exports.queryFilterName)(model.name);
79
+ const self = () => typebox_1.Type.Ref(name);
80
+ const logical = (description) => typebox_1.Type.Optional(typebox_1.Type.Union([self(), typebox_1.Type.Array(self())], { description }));
81
+ const scalarValue = (description) => typebox_1.Type.Optional(typebox_1.Type.Union([PlainValue(), PlainValueList(), typebox_1.Type.Ref(exports.QUERY_CONDITION_SCHEMA_NAME)], { description }));
82
+ // A list of related filters is declared as its own branch, so the validator
83
+ // still rejects the unknown properties of the filters nested inside it
84
+ const relationValue = (refName, description) => typebox_1.Type.Optional(typebox_1.Type.Union([
85
+ PlainValue(),
86
+ PlainValueList(),
87
+ typebox_1.Type.Ref(refName),
88
+ typebox_1.Type.Array(typebox_1.Type.Ref(refName))
89
+ ], { description }));
90
+ const properties = {
91
+ id: scalarValue('Filter by the record id')
92
+ };
93
+ const auditFields = {
94
+ updatedAt: true,
95
+ createdAt: true,
96
+ createdById: true,
97
+ ...(model.config.audit ?? {})
98
+ };
99
+ for (const [fieldName, included] of Object.entries(auditFields)) {
100
+ if (included) {
101
+ properties[fieldName] = scalarValue(`Filter by the ${fieldName} field`);
102
+ }
103
+ }
104
+ for (const [fieldName, scalar] of Object.entries(model.config.scalars ?? {})) {
105
+ if (scalar.hidden) {
106
+ continue;
107
+ }
108
+ // JSON fields hold arbitrary nested structures, so their conditions are
109
+ // passed through without validation
110
+ properties[fieldName] =
111
+ scalar.type === 'json'
112
+ ? typebox_1.Type.Optional(typebox_1.Type.Any({ description: `Filter by the ${fieldName} field` }))
113
+ : scalarValue(`Filter by the ${fieldName} field`);
114
+ }
115
+ for (const [fieldName, relation] of Object.entries(model.config.relations ?? {})) {
116
+ properties[fieldName] = relationValue((0, exports.queryFilterName)((0, common_1.capitalize)(relation.model)), `Filter by the ${fieldName} relation, matching an id, a list of ids, ` +
117
+ `or a nested ${(0, common_1.capitalize)(relation.model)} filter`);
118
+ }
119
+ for (const fieldName of Object.keys(model.config.files ?? {})) {
120
+ properties[fieldName] = relationValue((0, exports.queryFilterName)('File'), `Filter by the ${fieldName} file field, matching an id, a list of ids, ` +
121
+ 'or a nested File filter');
122
+ }
123
+ return typebox_1.Type.Object({
124
+ _and: logical('Matches records satisfying all the nested conditions'),
125
+ _or: logical('Matches records satisfying at least one nested condition'),
126
+ _not: logical('Matches records satisfying none of the nested conditions'),
127
+ _nor: logical('Matches records satisfying none of the nested conditions (alias of _not)'),
128
+ _some: Operator(self(), 'Matches records where at least one related record matches the filter'),
129
+ _every: Operator(self(), 'Matches records where every related record matches the filter'),
130
+ _none: Operator(self(), 'Matches records where no related record matches the filter'),
131
+ _exists: Operator(typebox_1.Type.Boolean(), 'Matches records with a related record (true) or without one (false)'),
132
+ searchText: Operator(typebox_1.Type.String(), 'Full-text search input resolved by the resource service'),
133
+ ...properties
134
+ }, {
135
+ description: `Query filter for the ${model.name} resource`
136
+ });
137
+ }
138
+ /**
139
+ * Registers the shared field condition schema and the query filter schema of
140
+ * every loaded resource model on the server, so route schemas can reference
141
+ * them by name (i.e. `UserQueryFilter`). Schemas already registered under the
142
+ * same name are skipped, which makes repeated calls cheap.
143
+ */
144
+ function registerQueryFilterSchemas() {
145
+ (0, core_1.createSchemaModel)(exports.QueryConditionSchema, {
146
+ name: exports.QUERY_CONDITION_SCHEMA_NAME
147
+ });
148
+ for (const model of internal_1.context.resource.models.values()) {
149
+ (0, core_1.createSchemaModel)(buildQueryFilterSchema(model), {
150
+ name: (0, exports.queryFilterName)(model.name)
151
+ });
152
+ }
153
+ }
@@ -0,0 +1,38 @@
1
+ import { TObject, TSchema } from '@sinclair/typebox';
2
+ import { ResourceModel } from '@appweaver/common';
3
+ /**
4
+ * Resolves the schema name the query sort object of a model is registered under.
5
+ *
6
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
7
+ * @return {string} The query sort schema name (i.e. `UserQuerySort`).
8
+ */
9
+ export declare const querySortName: (modelName: string) => string;
10
+ /**
11
+ * Builds the query sort schema of a resource model, mirroring the ORDER BY part
12
+ * of a database query. Every sortable field of the model is declared explicitly,
13
+ * so the request validation rejects every other field.
14
+ * Scalar fields accept a sort direction, to-one relations accept the sort object
15
+ * of the related model, and to-many relations accept a direction applied to
16
+ * their related record count, both under the relation name and under its count
17
+ * field name.
18
+ *
19
+ * @param {ResourceModel} model The resource model to build the sort schema for.
20
+ * @return {TObject} The query sort schema of the model.
21
+ */
22
+ export declare function buildQuerySortSchema(model: ResourceModel): TObject;
23
+ /**
24
+ * Registers the query sort schema of every loaded resource model on the server,
25
+ * so route schemas can reference them by name (i.e. `UserQuerySort`). Schemas
26
+ * already registered under the same name are skipped, which makes repeated calls
27
+ * cheap.
28
+ */
29
+ export declare function registerQuerySortSchemas(): void;
30
+ /**
31
+ * Builds the schema of the sort property of a model, accepting either a
32
+ * comma-separated field list or the query sort object of the model.
33
+ *
34
+ * @param {string} modelName The resource model name the sort property belongs to.
35
+ * @param {string} [description] The description of the property, replacing the default one.
36
+ * @return {TSchema} The optional sort property schema.
37
+ */
38
+ export declare function querySortSchema(modelName: string, description?: string): TSchema;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.querySortName = void 0;
4
+ exports.buildQuerySortSchema = buildQuerySortSchema;
5
+ exports.registerQuerySortSchemas = registerQuerySortSchemas;
6
+ exports.querySortSchema = querySortSchema;
7
+ const typebox_1 = require("@sinclair/typebox");
8
+ const common_1 = require("@appweaver/common");
9
+ const internal_1 = require("@appweaver/core/internal");
10
+ const core_1 = require("@appweaver/core");
11
+ /**
12
+ * Resolves the schema name the query sort object of a model is registered under.
13
+ *
14
+ * @param {string} modelName The resource model name (i.e. `User`, `Post`).
15
+ * @return {string} The query sort schema name (i.e. `UserQuerySort`).
16
+ */
17
+ const querySortName = (modelName) => `${modelName}QuerySort`;
18
+ exports.querySortName = querySortName;
19
+ /** The accepted sort directions of a single field. */
20
+ const SORT_DIRECTIONS = ['asc', 'desc'];
21
+ const SortDirection = () => typebox_1.Type.Optional((0, common_1.StringEnum)(SORT_DIRECTIONS, { example: 'desc' }));
22
+ /**
23
+ * Builds the query sort schema of a resource model, mirroring the ORDER BY part
24
+ * of a database query. Every sortable field of the model is declared explicitly,
25
+ * so the request validation rejects every other field.
26
+ * Scalar fields accept a sort direction, to-one relations accept the sort object
27
+ * of the related model, and to-many relations accept a direction applied to
28
+ * their related record count, both under the relation name and under its count
29
+ * field name.
30
+ *
31
+ * @param {ResourceModel} model The resource model to build the sort schema for.
32
+ * @return {TObject} The query sort schema of the model.
33
+ */
34
+ function buildQuerySortSchema(model) {
35
+ const properties = {
36
+ id: SortDirection()
37
+ };
38
+ const auditFields = {
39
+ updatedAt: true,
40
+ createdAt: true,
41
+ createdById: true,
42
+ ...(model.config.audit ?? {})
43
+ };
44
+ for (const [fieldName, included] of Object.entries(auditFields)) {
45
+ if (included) {
46
+ properties[fieldName] = SortDirection();
47
+ }
48
+ }
49
+ // Array scalars hold no single value to order by, and virtual fields have no
50
+ // column of their own, so neither of them is sortable
51
+ for (const [fieldName, scalar] of Object.entries(model.config.scalars ?? {})) {
52
+ if (scalar.hidden || scalar.array) {
53
+ continue;
54
+ }
55
+ properties[fieldName] = SortDirection();
56
+ }
57
+ // A relation holding a list of records is ordered by their count, under the
58
+ // relation name and under its count field name alike
59
+ for (const [fieldName, relation] of Object.entries(model.config.relations ?? {})) {
60
+ if ((0, common_1.isRelationArray)(relation)) {
61
+ properties[fieldName] = SortDirection();
62
+ properties[(0, common_1.countFieldName)(fieldName)] = SortDirection();
63
+ continue;
64
+ }
65
+ properties[fieldName] = typebox_1.Type.Optional(typebox_1.Type.Ref((0, exports.querySortName)((0, common_1.capitalize)(relation.model))));
66
+ }
67
+ for (const [fieldName, file] of Object.entries(model.config.files ?? {})) {
68
+ if (file.array) {
69
+ properties[fieldName] = SortDirection();
70
+ properties[(0, common_1.countFieldName)(fieldName)] = SortDirection();
71
+ continue;
72
+ }
73
+ properties[fieldName] = typebox_1.Type.Optional(typebox_1.Type.Ref((0, exports.querySortName)('File')));
74
+ }
75
+ return typebox_1.Type.Object(properties, {
76
+ description: `Query sort for the ${model.name} resource, applying its fields in the ` +
77
+ 'order they are declared. A nested object sorts by a field of a relation, ' +
78
+ 'which has to be included in the response.'
79
+ });
80
+ }
81
+ /**
82
+ * Registers the query sort schema of every loaded resource model on the server,
83
+ * so route schemas can reference them by name (i.e. `UserQuerySort`). Schemas
84
+ * already registered under the same name are skipped, which makes repeated calls
85
+ * cheap.
86
+ */
87
+ function registerQuerySortSchemas() {
88
+ for (const model of internal_1.context.resource.models.values()) {
89
+ (0, core_1.createSchemaModel)(buildQuerySortSchema(model), {
90
+ name: (0, exports.querySortName)(model.name)
91
+ });
92
+ }
93
+ }
94
+ /**
95
+ * Builds the schema of the sort property of a model, accepting either a
96
+ * comma-separated field list or the query sort object of the model.
97
+ *
98
+ * @param {string} modelName The resource model name the sort property belongs to.
99
+ * @param {string} [description] The description of the property, replacing the default one.
100
+ * @return {TSchema} The optional sort property schema.
101
+ */
102
+ function querySortSchema(modelName, description = 'Fields to sort the results by, given as a comma-separated field list ' +
103
+ 'or as an object of field directions') {
104
+ return typebox_1.Type.Optional(typebox_1.Type.Union([
105
+ typebox_1.Type.String({
106
+ example: '-createdAt,id',
107
+ description: 'Comma-separated list of fields to sort by, where a field prefixed ' +
108
+ 'with `-` is sorted in descending order and a dot notation path ' +
109
+ 'targets a field of an included relation'
110
+ }),
111
+ typebox_1.Type.Ref((0, exports.querySortName)(modelName))
112
+ ], { description }));
113
+ }