@appweaver/data 0.0.0-stage → 2.0.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/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,479 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createModel = createModel;
|
|
4
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
5
|
+
const common_1 = require("@appweaver/common");
|
|
6
|
+
const internal_1 = require("@appweaver/core/internal");
|
|
7
|
+
const core_1 = require("@appweaver/core");
|
|
8
|
+
const model_schema_1 = require("../resource/model-schema");
|
|
9
|
+
/**
|
|
10
|
+
* Creates a resource model from its configuration and defines it in the application context. The names of the
|
|
11
|
+
* declared fields are inferred from the configuration, so the `read`, `create`, and `update` options only accept
|
|
12
|
+
* existing fields.
|
|
13
|
+
*
|
|
14
|
+
* @param {ResourceModelConfig} config - The model configuration.
|
|
15
|
+
* @param {boolean} [override=false] - Whether to replace a model already defined under the same name.
|
|
16
|
+
* @return {ResourceModel} The created resource model.
|
|
17
|
+
*/
|
|
18
|
+
function createModel(modelConfig, override = false) {
|
|
19
|
+
const config = normalizeRelations(modelConfig);
|
|
20
|
+
const resourceModel = buildModel(config);
|
|
21
|
+
common_1.logger.debug({ modelName: resourceModel.name }, 'Created resource model');
|
|
22
|
+
(0, core_1.define)(resourceModel, undefined, override ? 'override' : undefined);
|
|
23
|
+
// The relation inputs and audit fields reference the primary key of another
|
|
24
|
+
// model, which may not be loaded yet. Loaders call this hook once every model
|
|
25
|
+
// is registered, so those references resolve against their final id type.
|
|
26
|
+
resourceModel[common_1.RESOURCE_MODEL_RELINK] = () => {
|
|
27
|
+
Object.assign(resourceModel, buildModel(config));
|
|
28
|
+
};
|
|
29
|
+
return resourceModel;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Replaces every `manyToOne` relation with the `oneToMany` relation it stands for, owning the foreign key, so the
|
|
33
|
+
* rest of the framework handles a single form.
|
|
34
|
+
*
|
|
35
|
+
* @throws {Error} If a `manyToOne` relation is declared with `owner: false`.
|
|
36
|
+
*/
|
|
37
|
+
function normalizeRelations(config) {
|
|
38
|
+
if (!config.relations) {
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
const relations = {};
|
|
42
|
+
for (const [field, relation] of Object.entries(config.relations)) {
|
|
43
|
+
if (relation.type !== 'manyToOne') {
|
|
44
|
+
relations[field] = relation;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (relation.owner === false) {
|
|
48
|
+
throw new common_1.ConfigurationError(common_1.ErrorCode.ConfigurationInvalid, `Relation '${config.name}.${field}' of type 'manyToOne' holds the foreign key, so it cannot set 'owner: false'`);
|
|
49
|
+
}
|
|
50
|
+
relations[field] = { ...relation, type: 'oneToMany', owner: true };
|
|
51
|
+
}
|
|
52
|
+
return { ...config, relations };
|
|
53
|
+
}
|
|
54
|
+
function buildModel(config) {
|
|
55
|
+
const name = (0, common_1.capitalize)(config.name);
|
|
56
|
+
const idSchema = (0, model_schema_1.idSchema)(config?.id);
|
|
57
|
+
const auditSchema = buildAuditSchema(config?.audit, (0, common_1.hasSoftDelete)(config));
|
|
58
|
+
const scalarsSchema = buildScalarsSchema(config?.scalars);
|
|
59
|
+
const virtualSchema = buildScalarsSchema(config?.virtual);
|
|
60
|
+
const filesSchema = buildFilesSchema(config?.files);
|
|
61
|
+
const relationsSchema = buildRelationsSchema(config?.relations);
|
|
62
|
+
const readModel = typebox_1.Type.Composite([
|
|
63
|
+
idSchema,
|
|
64
|
+
scalarsSchema,
|
|
65
|
+
virtualSchema,
|
|
66
|
+
relationsSchema,
|
|
67
|
+
filesSchema,
|
|
68
|
+
auditSchema
|
|
69
|
+
], {
|
|
70
|
+
$id: name
|
|
71
|
+
});
|
|
72
|
+
const virtualModel = typebox_1.Type.Composite([virtualSchema], {
|
|
73
|
+
$id: `${name}Virtual`
|
|
74
|
+
});
|
|
75
|
+
const relationsModel = typebox_1.Type.Composite([relationsSchema], {
|
|
76
|
+
$id: `${name}Relations`
|
|
77
|
+
});
|
|
78
|
+
const filesModel = typebox_1.Type.Composite([filesSchema], { $id: `${name}Files` });
|
|
79
|
+
const baseReadModel = omitOrPickScalars(typebox_1.Type.Composite([idSchema, scalarsSchema, virtualSchema, auditSchema]), config.read);
|
|
80
|
+
const createModel = omitOrPickScalars(resolveDefaultScalars(typebox_1.Type.Composite([scalarsSchema, virtualSchema]), config), config.create);
|
|
81
|
+
const updateModel = omitOrPickScalars(resolveDefaultScalars(typebox_1.Type.Partial(typebox_1.Type.Composite([scalarsSchema, virtualSchema])), config), config.update);
|
|
82
|
+
// The fields of an update, its scalars required the same way as on a
|
|
83
|
+
// create, since a replacement carries the whole stored state of the
|
|
84
|
+
// resource. The virtual fields are no part of it, so they stay optional
|
|
85
|
+
const replaceModel = omitOrPickScalars(resolveDefaultScalars(typebox_1.Type.Composite([scalarsSchema, typebox_1.Type.Partial(virtualSchema)]), config), config.update);
|
|
86
|
+
const { readOneModel, readManyModel } = buildOutputModels(baseReadModel, relationsModel, filesModel, config);
|
|
87
|
+
const readOneNullableModel = buildNullableOutputModel(name);
|
|
88
|
+
const { createOneModel, updateOneModel, replaceOneModel, relationCreateModel, relationUpdateModel } = buildInputModels(createModel, updateModel, replaceModel, relationsModel, idSchema, config);
|
|
89
|
+
const { fileUploadModel, fileDeleteModel } = buildFileInputModels(config);
|
|
90
|
+
const resourceModel = {
|
|
91
|
+
name,
|
|
92
|
+
config: config,
|
|
93
|
+
idModel: idSchema,
|
|
94
|
+
readModel,
|
|
95
|
+
createModel,
|
|
96
|
+
updateModel,
|
|
97
|
+
relationsModel,
|
|
98
|
+
filesModel,
|
|
99
|
+
virtualModel,
|
|
100
|
+
readOneModel,
|
|
101
|
+
readManyModel,
|
|
102
|
+
readOneNullableModel,
|
|
103
|
+
createOneModel,
|
|
104
|
+
updateOneModel,
|
|
105
|
+
replaceOneModel,
|
|
106
|
+
relationCreateModel,
|
|
107
|
+
relationUpdateModel,
|
|
108
|
+
fileUploadModel,
|
|
109
|
+
fileDeleteModel
|
|
110
|
+
};
|
|
111
|
+
for (const value of Object.values(resourceModel)) {
|
|
112
|
+
if ((0, common_1.isPlainObject)(value)) {
|
|
113
|
+
value[common_1.RESOURCE_NAME] = name;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
resourceModel[common_1.RESOURCE_NAME] = name;
|
|
117
|
+
resourceModel[common_1.RESOURCE_TYPE] = common_1.RESOURCE_MODEL_TYPE;
|
|
118
|
+
return resourceModel;
|
|
119
|
+
}
|
|
120
|
+
function referencedIdField(modelName) {
|
|
121
|
+
if (!modelName) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return internal_1.context.resource.models.get((0, common_1.capitalize)(modelName))?.config?.id;
|
|
125
|
+
}
|
|
126
|
+
function authModel() {
|
|
127
|
+
for (const model of internal_1.context.resource.models.values()) {
|
|
128
|
+
if ((0, common_1.isResourceAuthModel)(model)) {
|
|
129
|
+
return model;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function authIdField() {
|
|
134
|
+
return authModel()?.config?.id;
|
|
135
|
+
}
|
|
136
|
+
function buildAuditSchema(audit = {}, softDelete = false) {
|
|
137
|
+
const defaultAudit = {
|
|
138
|
+
updatedAt: true,
|
|
139
|
+
createdAt: true,
|
|
140
|
+
createdById: true
|
|
141
|
+
};
|
|
142
|
+
const mergedAudit = { ...defaultAudit, ...audit };
|
|
143
|
+
const fields = [];
|
|
144
|
+
for (const [name, included] of Object.entries(mergedAudit)) {
|
|
145
|
+
if (included) {
|
|
146
|
+
fields.push(name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const auditSchema = typebox_1.Type.Pick((0, model_schema_1.auditSchema)(authIdField()), fields);
|
|
150
|
+
if (!softDelete) {
|
|
151
|
+
return auditSchema;
|
|
152
|
+
}
|
|
153
|
+
// The soft delete columns are typed on the model, but hidden like any hidden
|
|
154
|
+
// field, so no request or response carries them
|
|
155
|
+
return typebox_1.Type.Object({
|
|
156
|
+
...auditSchema.properties,
|
|
157
|
+
deletedAt: (0, common_1.Nullable)((0, common_1.StringDate)({ hidden: true })),
|
|
158
|
+
...(authModel()
|
|
159
|
+
? {
|
|
160
|
+
deletedById: (0, common_1.Nullable)({
|
|
161
|
+
...(0, model_schema_1.idValueSchema)(authIdField()),
|
|
162
|
+
hidden: true
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
: {})
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function buildScalarsSchema(fields = {}) {
|
|
169
|
+
const properties = {};
|
|
170
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
171
|
+
properties[name] = buildScalarSchema(field);
|
|
172
|
+
}
|
|
173
|
+
return typebox_1.Type.Object(properties);
|
|
174
|
+
}
|
|
175
|
+
function buildScalarSchema(field) {
|
|
176
|
+
let fieldType;
|
|
177
|
+
switch (field.type) {
|
|
178
|
+
case 'string':
|
|
179
|
+
fieldType = typebox_1.Type.String((0, common_1.pickProperties)(field, [
|
|
180
|
+
'minLength',
|
|
181
|
+
'maxLength',
|
|
182
|
+
'format',
|
|
183
|
+
'pattern',
|
|
184
|
+
'hidden',
|
|
185
|
+
'example'
|
|
186
|
+
]));
|
|
187
|
+
break;
|
|
188
|
+
case 'int':
|
|
189
|
+
case 'bigInt':
|
|
190
|
+
fieldType = typebox_1.Type.Integer((0, common_1.pickProperties)(field, ['minimum', 'maximum', 'hidden', 'example']));
|
|
191
|
+
break;
|
|
192
|
+
case 'float':
|
|
193
|
+
fieldType = typebox_1.Type.Number((0, common_1.pickProperties)(field, ['minimum', 'maximum', 'hidden', 'example']));
|
|
194
|
+
break;
|
|
195
|
+
case 'boolean':
|
|
196
|
+
fieldType = typebox_1.Type.Boolean((0, common_1.pickProperties)(field, ['hidden', 'example']));
|
|
197
|
+
break;
|
|
198
|
+
case 'dateTime':
|
|
199
|
+
fieldType = (0, common_1.StringDate)((0, common_1.pickProperties)(field, ['format', 'hidden', 'example']));
|
|
200
|
+
break;
|
|
201
|
+
case 'json':
|
|
202
|
+
fieldType = (0, common_1.AnyJson)((0, common_1.pickProperties)(field, ['hidden', 'example']));
|
|
203
|
+
break;
|
|
204
|
+
case 'enum':
|
|
205
|
+
fieldType = (0, common_1.StringEnum)(field.values ?? [], (0, common_1.pickProperties)(field, ['hidden', 'example']));
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
if (field.array === true) {
|
|
209
|
+
fieldType = typebox_1.Type.Array(fieldType);
|
|
210
|
+
}
|
|
211
|
+
if (field.required === false) {
|
|
212
|
+
fieldType = (0, common_1.Nullable)(fieldType);
|
|
213
|
+
}
|
|
214
|
+
return fieldType;
|
|
215
|
+
}
|
|
216
|
+
function omitOrPickScalars(type, operationConfig) {
|
|
217
|
+
let restrictedType = type;
|
|
218
|
+
if (operationConfig?.pick) {
|
|
219
|
+
restrictedType = typebox_1.Type.Pick(type, operationConfig.pick);
|
|
220
|
+
}
|
|
221
|
+
else if (operationConfig?.omit) {
|
|
222
|
+
restrictedType = typebox_1.Type.Omit(type, operationConfig.omit);
|
|
223
|
+
}
|
|
224
|
+
return typebox_1.Type.Composite([restrictedType]);
|
|
225
|
+
}
|
|
226
|
+
function resolveDefaultScalars(type, config) {
|
|
227
|
+
for (const name of Object.keys(type.properties)) {
|
|
228
|
+
const field = type.properties[name];
|
|
229
|
+
if (config?.scalars?.[name]?.default !== undefined ||
|
|
230
|
+
config?.virtual?.[name]?.default !== undefined) {
|
|
231
|
+
type.properties[name] = typebox_1.Type.Optional(field);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return type;
|
|
235
|
+
}
|
|
236
|
+
function buildFilesSchema(files = {}) {
|
|
237
|
+
const properties = {};
|
|
238
|
+
for (const [name, file] of Object.entries(files)) {
|
|
239
|
+
properties[name] = buildFileSchema(file);
|
|
240
|
+
}
|
|
241
|
+
return typebox_1.Type.Object(properties);
|
|
242
|
+
}
|
|
243
|
+
function buildFileSchema(file) {
|
|
244
|
+
const fileSchema = typebox_1.Type.Ref('FileSingle');
|
|
245
|
+
return file.array ? typebox_1.Type.Array(fileSchema) : (0, common_1.Nullable)(fileSchema);
|
|
246
|
+
}
|
|
247
|
+
function buildRelationsSchema(relations = {}) {
|
|
248
|
+
const properties = {};
|
|
249
|
+
for (const [name, relation] of Object.entries(relations)) {
|
|
250
|
+
properties[name] = buildRelationSchema(relation);
|
|
251
|
+
}
|
|
252
|
+
return typebox_1.Type.Object(properties);
|
|
253
|
+
}
|
|
254
|
+
function buildRelationSchema(relation) {
|
|
255
|
+
const modelName = (0, common_1.capitalize)(relation.model);
|
|
256
|
+
const modelRefName = `${modelName}Single`;
|
|
257
|
+
let relationType = typebox_1.Type.Ref(modelRefName);
|
|
258
|
+
relationType = (0, common_1.isRelationArray)(relation)
|
|
259
|
+
? typebox_1.Type.Array(relationType, (0, common_1.pickProperties)(relation, ['minItems']))
|
|
260
|
+
: relationType;
|
|
261
|
+
return relation.required === false
|
|
262
|
+
? typebox_1.Type.Optional(relationType)
|
|
263
|
+
: relationType;
|
|
264
|
+
}
|
|
265
|
+
function buildOutputModels(readModel, relationsModel, filesModel, config) {
|
|
266
|
+
const virtualConfig = config.virtual;
|
|
267
|
+
const relationsConfig = config.relations;
|
|
268
|
+
const filesConfig = config.files;
|
|
269
|
+
const adjustedReadModel = removeHiddenFields(readModel);
|
|
270
|
+
const readOneModel = typebox_1.Type.Composite([
|
|
271
|
+
resolveOutputVirtualFields(adjustedReadModel, virtualConfig, 'single'),
|
|
272
|
+
relationOutputProperties(relationsModel, relationsConfig, 'single'),
|
|
273
|
+
relationOutputProperties(filesModel, filesConfig, 'single')
|
|
274
|
+
], { $id: `${config.name}Single` });
|
|
275
|
+
const readManyModel = typebox_1.Type.Composite([
|
|
276
|
+
resolveOutputVirtualFields(adjustedReadModel, virtualConfig, 'multiple'),
|
|
277
|
+
relationOutputProperties(relationsModel, relationsConfig, 'multiple'),
|
|
278
|
+
relationOutputProperties(filesModel, filesConfig, 'multiple')
|
|
279
|
+
], { $id: `${config.name}Multiple` });
|
|
280
|
+
return { readOneModel, readManyModel };
|
|
281
|
+
}
|
|
282
|
+
function buildNullableOutputModel(name) {
|
|
283
|
+
// Builds the nullable variant of the single read model, registered under its own
|
|
284
|
+
// name so a nullable relation can reference it instead of inlining the union.
|
|
285
|
+
// The response serializer cannot compile an inline `anyOf` union that cycles
|
|
286
|
+
// back to a model it is already writing, since it expands every union branch
|
|
287
|
+
// into a fresh schema. A union behind a name is expanded once and reused, which
|
|
288
|
+
// keeps a self-referencing relation (a category and its parent) serializable.
|
|
289
|
+
return typebox_1.Type.Union([typebox_1.Type.Ref(`${name}Single`), typebox_1.Type.Null()], {
|
|
290
|
+
$id: `${name}SingleNullable`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function buildInputModels(createModel, updateModel, replaceModel, relationsModel, idSchema, config) {
|
|
294
|
+
const virtualConfig = config.virtual;
|
|
295
|
+
const relationsConfig = config.relations;
|
|
296
|
+
const adjustedCreateModel = resolveInputVirtualFields(removeHiddenFields(createModel), virtualConfig, 'create');
|
|
297
|
+
const adjustedUpdateModel = resolveInputVirtualFields(removeHiddenFields(updateModel), virtualConfig, 'update');
|
|
298
|
+
const adjustedReplaceModel = resolveInputVirtualFields(removeHiddenFields(replaceModel), virtualConfig, 'update');
|
|
299
|
+
// Data models accepted when this model is written inline through another
|
|
300
|
+
// model's relation input: created without an id, or updated by its id.
|
|
301
|
+
// Relations and files are excluded, so nested writes stay limited to the
|
|
302
|
+
// model's own columns.
|
|
303
|
+
const relationCreateModel = typebox_1.Type.Composite([adjustedCreateModel], {
|
|
304
|
+
$id: `${config.name}RelationCreate`
|
|
305
|
+
});
|
|
306
|
+
const relationUpdateModel = typebox_1.Type.Composite([idSchema, adjustedUpdateModel], {
|
|
307
|
+
$id: `${config.name}RelationUpdate`
|
|
308
|
+
});
|
|
309
|
+
if (Object.keys(relationsConfig ?? {}).length === 0) {
|
|
310
|
+
return {
|
|
311
|
+
createOneModel: adjustedCreateModel,
|
|
312
|
+
updateOneModel: adjustedUpdateModel,
|
|
313
|
+
replaceOneModel: adjustedReplaceModel,
|
|
314
|
+
relationCreateModel,
|
|
315
|
+
relationUpdateModel
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const relationCreateInputs = relationInputProperties(relationsModel, relationsConfig, 'create');
|
|
319
|
+
const createOneModel = typebox_1.Type.Composite([adjustedCreateModel, relationCreateInputs], {
|
|
320
|
+
$id: `${config.name}Create`
|
|
321
|
+
});
|
|
322
|
+
const relationUpdateInputs = relationInputProperties(relationsModel, relationsConfig, 'update');
|
|
323
|
+
const updateOneModel = typebox_1.Type.Composite([adjustedUpdateModel, typebox_1.Type.Partial(relationUpdateInputs)], { $id: `${config.name}Update` });
|
|
324
|
+
// The required relations are required, while the optional ones a
|
|
325
|
+
// replacement leaves out stay untouched
|
|
326
|
+
const replaceOneModel = typebox_1.Type.Composite([adjustedReplaceModel, relationUpdateInputs], { $id: `${config.name}Replace` });
|
|
327
|
+
return {
|
|
328
|
+
createOneModel,
|
|
329
|
+
updateOneModel,
|
|
330
|
+
replaceOneModel,
|
|
331
|
+
relationCreateModel,
|
|
332
|
+
relationUpdateModel
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function buildFileInputModels(config) {
|
|
336
|
+
const fileConfig = config.files ?? {};
|
|
337
|
+
const FileUpload = typebox_1.Type.Unsafe({
|
|
338
|
+
type: 'string',
|
|
339
|
+
format: 'binary',
|
|
340
|
+
[typebox_1.Kind]: 'String'
|
|
341
|
+
});
|
|
342
|
+
const FileDelete = typebox_1.Type.String({ example: 'image_123.png' });
|
|
343
|
+
const fileUploadModel = typebox_1.Type.Object(Object.fromEntries(Object.entries(fileConfig).map(([key, conf]) => [
|
|
344
|
+
key,
|
|
345
|
+
typebox_1.Type.Optional(conf.array ? typebox_1.Type.Array(FileUpload) : FileUpload)
|
|
346
|
+
])), { $id: `${config.name}FileUpload` });
|
|
347
|
+
const fileDeleteModel = typebox_1.Type.Object(Object.fromEntries(Object.entries(fileConfig).map(([key, conf]) => [
|
|
348
|
+
key,
|
|
349
|
+
typebox_1.Type.Optional(conf.array ? typebox_1.Type.Array(FileDelete) : FileDelete)
|
|
350
|
+
])), { $id: `${config.name}FileDelete` });
|
|
351
|
+
return { fileUploadModel, fileDeleteModel };
|
|
352
|
+
}
|
|
353
|
+
function relationInputProperties(object, relationConfig, inputType) {
|
|
354
|
+
const relationInputType = (key) => {
|
|
355
|
+
const { type, ...options } = object.properties[key];
|
|
356
|
+
const config = relationConfig?.[key];
|
|
357
|
+
if (config && shouldSkipInputField(config.input?.type, inputType)) {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
// Inline updates carry the related record id, so only parent update
|
|
361
|
+
// requests accept them, since the database updates relations only within
|
|
362
|
+
// an update action
|
|
363
|
+
const acceptsUpdate = config?.input?.allowUpdate && inputType === 'update';
|
|
364
|
+
// Existing records are connected by an id object or a bare id value, both
|
|
365
|
+
// typed after the primary key of the related model. An item with an id is
|
|
366
|
+
// an inline update when the relation accepts them (a lone id still
|
|
367
|
+
// connects), and an item without one is an inline create
|
|
368
|
+
const relatedIdField = referencedIdField(config?.model);
|
|
369
|
+
const itemSchemas = [
|
|
370
|
+
acceptsUpdate
|
|
371
|
+
? typebox_1.Type.Ref(`${config.model}RelationUpdate`)
|
|
372
|
+
: (0, model_schema_1.idSchema)(relatedIdField),
|
|
373
|
+
...(config?.input?.allowCreate
|
|
374
|
+
? [typebox_1.Type.Ref(`${config.model}RelationCreate`)]
|
|
375
|
+
: []),
|
|
376
|
+
(0, model_schema_1.idValueSchema)(relatedIdField)
|
|
377
|
+
];
|
|
378
|
+
const isArrayType = type === 'array';
|
|
379
|
+
const isOptional = config?.required === false;
|
|
380
|
+
let inputSchema;
|
|
381
|
+
if (isArrayType) {
|
|
382
|
+
// One array of union items, so connect, create, and update inputs mix
|
|
383
|
+
const arraySchema = typebox_1.Type.Array(typebox_1.Type.Union(itemSchemas), options);
|
|
384
|
+
inputSchema = isOptional ? (0, common_1.Nullable)(arraySchema) : arraySchema;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
inputSchema = typebox_1.Type.Union(itemSchemas.map((itemSchema) => isOptional ? (0, common_1.Nullable)(itemSchema) : itemSchema));
|
|
388
|
+
}
|
|
389
|
+
return isOptional ? typebox_1.Type.Optional(inputSchema) : inputSchema;
|
|
390
|
+
};
|
|
391
|
+
return typebox_1.Type.Object({
|
|
392
|
+
...Object.keys(object.properties).reduce((acc, key) => {
|
|
393
|
+
const type = relationInputType(key);
|
|
394
|
+
if (type) {
|
|
395
|
+
acc[key] = type;
|
|
396
|
+
}
|
|
397
|
+
return acc;
|
|
398
|
+
}, {})
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function relationOutputProperties(object, relationConfig, outputType) {
|
|
402
|
+
const relationOutputType = (key) => {
|
|
403
|
+
let schema = object.properties[key];
|
|
404
|
+
const config = relationConfig?.[key];
|
|
405
|
+
if (config) {
|
|
406
|
+
if (shouldSkipOutputField(config.output?.type, outputType)) {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
if (config.required === false && schema.type !== 'array') {
|
|
410
|
+
schema = nullableOutputSchema(schema);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return schema;
|
|
414
|
+
};
|
|
415
|
+
const relationCountType = (key) => {
|
|
416
|
+
const config = relationConfig?.[key];
|
|
417
|
+
if (!config?.output?.count) {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
return typebox_1.Type.Integer({ minimum: 0 });
|
|
421
|
+
};
|
|
422
|
+
// Relation, file, and count fields are optional in the output models: their
|
|
423
|
+
// presence depends on the inclusion depth of the query. The same schema
|
|
424
|
+
// describes a record nested in another model's output, without relations.
|
|
425
|
+
return typebox_1.Type.Object({
|
|
426
|
+
...Object.keys(object.properties).reduce((acc, key) => {
|
|
427
|
+
const type = relationOutputType(key);
|
|
428
|
+
if (type) {
|
|
429
|
+
acc[key] = typebox_1.Type.Optional(type);
|
|
430
|
+
}
|
|
431
|
+
const countType = relationCountType(key);
|
|
432
|
+
if (countType) {
|
|
433
|
+
acc[(0, common_1.countFieldName)(key)] = typebox_1.Type.Optional(countType);
|
|
434
|
+
}
|
|
435
|
+
return acc;
|
|
436
|
+
}, {})
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function nullableOutputSchema(schema) {
|
|
440
|
+
const modelRef = schema.$ref;
|
|
441
|
+
return modelRef ? typebox_1.Type.Ref(`${modelRef}Nullable`) : (0, common_1.Nullable)(schema);
|
|
442
|
+
}
|
|
443
|
+
function removeHiddenFields(schema) {
|
|
444
|
+
const properties = {};
|
|
445
|
+
for (const [name, field] of Object.entries(schema.properties)) {
|
|
446
|
+
if (!field.hidden) {
|
|
447
|
+
properties[name] = field;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return typebox_1.Type.Object(properties);
|
|
451
|
+
}
|
|
452
|
+
function resolveInputVirtualFields(schema, virtualConfig, inputType) {
|
|
453
|
+
const excludeFields = [];
|
|
454
|
+
for (const [name, virtual] of Object.entries(virtualConfig ?? {})) {
|
|
455
|
+
if (shouldSkipInputField(virtual.input?.type, inputType)) {
|
|
456
|
+
excludeFields.push(name);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return typebox_1.Type.Omit(schema, excludeFields);
|
|
460
|
+
}
|
|
461
|
+
function resolveOutputVirtualFields(schema, virtualConfig, outputType) {
|
|
462
|
+
const excludeFields = [];
|
|
463
|
+
for (const [name, virtual] of Object.entries(virtualConfig ?? {})) {
|
|
464
|
+
if (shouldSkipOutputField(virtual.output?.type, outputType)) {
|
|
465
|
+
excludeFields.push(name);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return typebox_1.Type.Omit(schema, excludeFields);
|
|
469
|
+
}
|
|
470
|
+
function shouldSkipInputField(fieldInputType, methodInputType) {
|
|
471
|
+
return (fieldInputType === 'none' ||
|
|
472
|
+
(methodInputType === 'create' && fieldInputType === 'update') ||
|
|
473
|
+
(methodInputType === 'update' && fieldInputType === 'create'));
|
|
474
|
+
}
|
|
475
|
+
function shouldSkipOutputField(fieldOutputType, methodOutputType) {
|
|
476
|
+
return (fieldOutputType === 'none' ||
|
|
477
|
+
(methodOutputType === 'single' && fieldOutputType === 'multiple') ||
|
|
478
|
+
(methodOutputType === 'multiple' && fieldOutputType === 'single'));
|
|
479
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ModelName, ResourcePolicyConfig, ResourceRecord } from '@appweaver/common';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the policy of a model, checking the access to its records and restricting the reads and writes. Once the
|
|
4
|
+
* types are generated, the records the callbacks receive are typed by the model name.
|
|
5
|
+
*
|
|
6
|
+
* @param {ResourcePolicyConfig} config - The policy configuration with the model name and the policy callbacks.
|
|
7
|
+
* @param {boolean} [override=false] - Whether to replace a policy already defined for the model.
|
|
8
|
+
* @return {ResourcePolicyConfig} The created policy, defined in the application context.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createPolicy<N extends ModelName, T = ResourceRecord<N>>(config: ResourcePolicyConfig<T> & {
|
|
11
|
+
modelName: N;
|
|
12
|
+
}, override?: boolean): ResourcePolicyConfig<T>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPolicy = createPolicy;
|
|
4
|
+
const common_1 = require("@appweaver/common");
|
|
5
|
+
const core_1 = require("@appweaver/core");
|
|
6
|
+
/**
|
|
7
|
+
* Creates the policy of a model, checking the access to its records and restricting the reads and writes. Once the
|
|
8
|
+
* types are generated, the records the callbacks receive are typed by the model name.
|
|
9
|
+
*
|
|
10
|
+
* @param {ResourcePolicyConfig} config - The policy configuration with the model name and the policy callbacks.
|
|
11
|
+
* @param {boolean} [override=false] - Whether to replace a policy already defined for the model.
|
|
12
|
+
* @return {ResourcePolicyConfig} The created policy, defined in the application context.
|
|
13
|
+
*/
|
|
14
|
+
function createPolicy(config, override = false) {
|
|
15
|
+
config[common_1.RESOURCE_NAME] = (0, common_1.capitalize)(config.modelName);
|
|
16
|
+
config[common_1.RESOURCE_TYPE] = common_1.RESOURCE_POLICY_TYPE;
|
|
17
|
+
common_1.logger.debug({ modelName: config.modelName }, 'Created resource policy');
|
|
18
|
+
(0, core_1.define)(config, undefined, override ? 'override' : undefined);
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Ctor, ModelName, QueryFilter, RegistryType, ResourceServiceConfig } from '@appweaver/common';
|
|
2
|
+
import { ResourceService } from '../resource/resource-service';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the resource service of a model, running the configured hooks around its operations and applying the
|
|
5
|
+
* policy of the model. The update hooks run around both an update and a replacement. Once the types are generated,
|
|
6
|
+
* the model, create, and update types of the hooks are inferred from the model name.
|
|
7
|
+
*
|
|
8
|
+
* @param {ResourceServiceConfig} config - The service configuration with the model name, hooks, and text search.
|
|
9
|
+
* @param {boolean} [override=false] - Whether to replace a service already defined for the model.
|
|
10
|
+
* @return {Ctor<ResourceService>} The created service class, defined in the application context.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createService<N extends ModelName, T = RegistryType<N, 'model'>, C = RegistryType<N, 'create'>, U = RegistryType<N, 'update'>, R = RegistryType<N, 'replace', C>>(config: ResourceServiceConfig<T, C, U> & {
|
|
13
|
+
modelName: N;
|
|
14
|
+
}, override?: boolean): Ctor<ResourceService<T, T, C, U, QueryFilter<T>, R>>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createService = createService;
|
|
4
|
+
const common_1 = require("@appweaver/common");
|
|
5
|
+
const core_1 = require("@appweaver/core");
|
|
6
|
+
const resource_service_1 = require("../resource/resource-service");
|
|
7
|
+
const text_search_util_1 = require("../resource/utils/text-search-util");
|
|
8
|
+
/**
|
|
9
|
+
* Creates the resource service of a model, running the configured hooks around its operations and applying the
|
|
10
|
+
* policy of the model. The update hooks run around both an update and a replacement. Once the types are generated,
|
|
11
|
+
* the model, create, and update types of the hooks are inferred from the model name.
|
|
12
|
+
*
|
|
13
|
+
* @param {ResourceServiceConfig} config - The service configuration with the model name, hooks, and text search.
|
|
14
|
+
* @param {boolean} [override=false] - Whether to replace a service already defined for the model.
|
|
15
|
+
* @return {Ctor<ResourceService>} The created service class, defined in the application context.
|
|
16
|
+
*/
|
|
17
|
+
function createService(config, override = false) {
|
|
18
|
+
const name = (0, common_1.capitalize)(config.modelName);
|
|
19
|
+
class Service extends resource_service_1.ResourceService {
|
|
20
|
+
[common_1.CONFIG] = config;
|
|
21
|
+
[common_1.RESOURCE_NAME] = name;
|
|
22
|
+
[common_1.RESOURCE_TYPE] = common_1.RESOURCE_SERVICE_TYPE;
|
|
23
|
+
constructor() {
|
|
24
|
+
super(name);
|
|
25
|
+
}
|
|
26
|
+
async find(id) {
|
|
27
|
+
await config.beforeFind?.(id);
|
|
28
|
+
const result = await super.find(id);
|
|
29
|
+
await config.afterFind?.(result);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
async query(options = {}) {
|
|
33
|
+
await config.beforeQuery?.(options);
|
|
34
|
+
const result = await super.query(options);
|
|
35
|
+
await config.afterQuery?.(result);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
async aggregate(options) {
|
|
39
|
+
await config.beforeAggregate?.(options);
|
|
40
|
+
const result = await super.aggregate(options);
|
|
41
|
+
// The hook is declared once for the model, so it takes the response of
|
|
42
|
+
// every selection, of which this one holds a subset of the fields
|
|
43
|
+
await config.afterAggregate?.(result);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
async create(data) {
|
|
47
|
+
await config.beforeCreate?.(data);
|
|
48
|
+
const result = await super.create(data);
|
|
49
|
+
await config.afterCreate?.(result);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
async update(id, data) {
|
|
53
|
+
await config.beforeUpdate?.(id, data);
|
|
54
|
+
const { previous, current } = await this.updateWithPrevious(id, data);
|
|
55
|
+
await config.afterUpdate?.(current, previous);
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
async replace(id, data) {
|
|
59
|
+
// The hooks receive the whole replacement, the reset fields included
|
|
60
|
+
const replacement = this.replacementData(data);
|
|
61
|
+
await config.beforeUpdate?.(id, replacement);
|
|
62
|
+
const { previous, current } = await this.updateWithPrevious(id, replacement);
|
|
63
|
+
await config.afterUpdate?.(current, previous);
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
async delete(id) {
|
|
67
|
+
await config.beforeDelete?.(id);
|
|
68
|
+
const result = await super.delete(id);
|
|
69
|
+
await config.afterDelete?.(result);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
textSearchQuery(searchText) {
|
|
73
|
+
if ((0, common_1.isFunction)(config.textSearch)) {
|
|
74
|
+
return config.textSearch(searchText);
|
|
75
|
+
}
|
|
76
|
+
if ((0, common_1.isPlainObject)(config.textSearch)) {
|
|
77
|
+
return (0, text_search_util_1.bindTextSearch)(config.textSearch, searchText);
|
|
78
|
+
}
|
|
79
|
+
return super.textSearchQuery(searchText);
|
|
80
|
+
}
|
|
81
|
+
async readRestrictions(action, data) {
|
|
82
|
+
const policy = (0, core_1.injectPolicy)(name, false);
|
|
83
|
+
if (policy?.readRestrictions) {
|
|
84
|
+
const user = (0, core_1.currentAuthUser)() ?? null;
|
|
85
|
+
return (await policy.readRestrictions(user, data, action)) ?? {};
|
|
86
|
+
}
|
|
87
|
+
return super.readRestrictions(action, data);
|
|
88
|
+
}
|
|
89
|
+
async writeRestrictions(action, data) {
|
|
90
|
+
const policy = (0, core_1.injectPolicy)(name, false);
|
|
91
|
+
if (policy?.writeRestrictions) {
|
|
92
|
+
const user = (0, core_1.currentAuthUser)() ?? null;
|
|
93
|
+
return (await policy.writeRestrictions(user, data, action)) ?? {};
|
|
94
|
+
}
|
|
95
|
+
return super.writeRestrictions(action, data);
|
|
96
|
+
}
|
|
97
|
+
async checkAccess(action, resource) {
|
|
98
|
+
const policy = (0, core_1.injectPolicy)(name, false);
|
|
99
|
+
if (policy?.checkAccess) {
|
|
100
|
+
const user = (0, core_1.currentAuthUser)() ?? null;
|
|
101
|
+
return policy.checkAccess(user, resource, action);
|
|
102
|
+
}
|
|
103
|
+
return super.checkAccess(action, resource);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
Object.defineProperty(Service, 'name', {
|
|
107
|
+
value: `${name}Service`,
|
|
108
|
+
configurable: true
|
|
109
|
+
});
|
|
110
|
+
Service[common_1.CONFIG] = config;
|
|
111
|
+
Service[common_1.RESOURCE_NAME] = name;
|
|
112
|
+
Service[common_1.RESOURCE_TYPE] = common_1.RESOURCE_SERVICE_TYPE;
|
|
113
|
+
common_1.logger.debug({ modelName: config.modelName }, 'Created resource service');
|
|
114
|
+
(0, core_1.define)(Service, undefined, override ? 'override' : undefined);
|
|
115
|
+
return Service;
|
|
116
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './config';
|
|
2
|
+
export * from './database';
|
|
3
|
+
export * from './export/export-service';
|
|
4
|
+
export * from './factory/create-model';
|
|
5
|
+
export * from './factory/create-policy';
|
|
6
|
+
export * from './factory/create-service';
|
|
7
|
+
export * from './resource/model-schema';
|
|
8
|
+
export * from './resource/resource-loader';
|
|
9
|
+
export * from './resource/resource-service';
|
|
10
|
+
export * from './resource/schemas';
|
|
11
|
+
export * from './security/create-auth-resources';
|
|
12
|
+
export * from './security/helper';
|
|
13
|
+
export * from './seeder';
|
|
14
|
+
export * from './storage/file-service';
|
|
15
|
+
export * from './types/auth';
|
|
16
|
+
export * from './types/generated';
|
|
17
|
+
export * from './types/storage';
|
|
18
|
+
export * from './utils/export-util';
|
|
19
|
+
export * from './utils/file-util';
|
|
20
|
+
export * from './utils/model-util';
|
|
21
|
+
export * from './utils/policy-util';
|
|
22
|
+
export * from './utils/virtual-util';
|