@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.
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,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaDatabase = void 0;
4
+ const common_1 = require("@appweaver/common");
5
+ const database_config_1 = require("./database-config");
6
+ const create_client_1 = require("./create-client");
7
+ const internal_1 = require("@appweaver/core/internal");
8
+ // Members that act on the connection itself, never on a transaction
9
+ const ROOT_MEMBERS = new Set([
10
+ '$connect',
11
+ '$disconnect',
12
+ '$on',
13
+ '$extends'
14
+ ]);
15
+ /**
16
+ * Represents a database utility class that provides methods
17
+ * to connect to and disconnect from the database, as well as
18
+ * access to the database Prisma client instance.
19
+ */
20
+ class PrismaDatabase extends common_1.Database {
21
+ /** @internal */
22
+ _client;
23
+ /** @internal */
24
+ _proxy;
25
+ /**
26
+ * Connects to the database and checks the connection, logging an error when the database is unreachable instead of
27
+ * failing the start, since most drivers connect lazily and the queries reconnect once it is reachable.
28
+ */
29
+ async onInit() {
30
+ try {
31
+ await this.connect();
32
+ await this.clientInstance().$queryRaw `SELECT 1`;
33
+ }
34
+ catch (error) {
35
+ const dbType = (0, common_1.resolveDatabaseType)(database_config_1.databaseConfig.TYPE, database_config_1.databaseConfig.URL);
36
+ const url = this.redactUrl(database_config_1.databaseConfig.URL);
37
+ common_1.logger.error(error, `Cannot connect to the ${dbType} database${url ? ` at ${url}` : ''}`);
38
+ // The next query creates a new client, which detects the capabilities of the database again
39
+ await this.disconnect().catch(() => undefined);
40
+ this._client = undefined;
41
+ }
42
+ }
43
+ async onDestroy() {
44
+ await this.disconnect();
45
+ }
46
+ async connect() {
47
+ if (this._client) {
48
+ return;
49
+ }
50
+ await this.clientInstance().$connect();
51
+ }
52
+ async disconnect() {
53
+ if (!this._client) {
54
+ return;
55
+ }
56
+ await this._client.$disconnect();
57
+ this._client = undefined;
58
+ }
59
+ /**
60
+ * Returns the Prisma client, which runs its queries in the transaction the
61
+ * current call runs in (see `runTransaction`), and on the connection otherwise.
62
+ */
63
+ client() {
64
+ this._proxy ??= this.transactionAwareClient();
65
+ return this._proxy;
66
+ }
67
+ async checkHealth() {
68
+ try {
69
+ await this.client().$queryRaw `SELECT 1`;
70
+ return { success: true };
71
+ }
72
+ catch (e) {
73
+ return { success: false, message: e.message };
74
+ }
75
+ }
76
+ /**
77
+ * Runs the given function in a transaction, or in the current one when called
78
+ * inside a transaction already. See `runTransaction`.
79
+ *
80
+ * @param {Function} fn - The function to run, receiving the transaction client.
81
+ * @param {TransactionOptions} [options] - The transaction options.
82
+ * @return {Promise<Object>} The value returned by the function.
83
+ * @throws The error thrown by the function, after the transaction rolled back.
84
+ */
85
+ async transaction(fn, options = {}) {
86
+ const isolationLevel = this.isolationLevel(options.isolationLevel);
87
+ const current = (0, internal_1.currentTransaction)();
88
+ if (current) {
89
+ if (options.isolationLevel && current.isolationLevel !== isolationLevel) {
90
+ throw new common_1.DatabaseError(common_1.ErrorCode.DatabaseInvalidTransaction, `Cannot run a ${isolationLevel} transaction inside a ` +
91
+ `${current.isolationLevel ?? 'default'} isolation level transaction`);
92
+ }
93
+ return fn(current.tx);
94
+ }
95
+ for (let attempt = 0;; attempt++) {
96
+ const context = {
97
+ tx: undefined,
98
+ commitCallbacks: new Map(),
99
+ rollbackCallbacks: [],
100
+ isolationLevel
101
+ };
102
+ let result;
103
+ try {
104
+ result = await this.clientInstance().$transaction((tx) => {
105
+ context.tx = tx;
106
+ return (0, internal_1.runInTransaction)(context, () => fn(tx));
107
+ }, { isolationLevel, timeout: options.timeout, maxWait: options.maxWait });
108
+ }
109
+ catch (e) {
110
+ await (0, internal_1.settleTransaction)(context, false);
111
+ if (attempt < (options.retries ?? 0) && this.isWriteConflict(e)) {
112
+ continue;
113
+ }
114
+ throw e;
115
+ }
116
+ await (0, internal_1.settleTransaction)(context, true);
117
+ return result;
118
+ }
119
+ }
120
+ /** @internal */
121
+ clientInstance() {
122
+ if (!this._client) {
123
+ this._client = (0, create_client_1.createClient)();
124
+ }
125
+ return this._client;
126
+ }
127
+ /** @internal */
128
+ isolationLevel(level) {
129
+ // SQLite runs every transaction serializable and rejects the other levels
130
+ const dbType = (0, common_1.resolveDatabaseType)(database_config_1.databaseConfig.TYPE, database_config_1.databaseConfig.URL);
131
+ if (dbType === common_1.DatabaseType.Sqlite) {
132
+ return common_1.IsolationLevel.Serializable;
133
+ }
134
+ return level;
135
+ }
136
+ /** @internal */
137
+ transactionAwareClient() {
138
+ return new Proxy({}, {
139
+ get: (_target, property) => {
140
+ const root = this.clientInstance();
141
+ const context = (0, internal_1.currentTransaction)();
142
+ if (property === '$transaction') {
143
+ return (arg, options) => this.joinTransaction(root, context, arg, options);
144
+ }
145
+ const source = context && !ROOT_MEMBERS.has(property) ? context.tx : root;
146
+ const value = Reflect.get(source, property);
147
+ return typeof value === 'function' ? value.bind(source) : value;
148
+ }
149
+ });
150
+ }
151
+ /** @internal */
152
+ joinTransaction(root, context, arg, options) {
153
+ if (typeof arg === 'function') {
154
+ return this.transaction(arg, options);
155
+ }
156
+ // The batch form, whose queries were created on the current transaction
157
+ return context
158
+ ? Promise.all(arg)
159
+ : root.$transaction(arg, options);
160
+ }
161
+ /** @internal */
162
+ redactUrl(url) {
163
+ return url
164
+ .replace(/(\/\/[^:/@]*):[^@/]*@/, '$1:********@')
165
+ .replace(/((?:password|pwd)=)[^;&]*/gi, '$1********');
166
+ }
167
+ /** @internal */
168
+ isWriteConflict(error) {
169
+ // Followed through the errors wrapping it, i.e. a service's DatabaseError
170
+ for (let e = error; e; e = e.cause) {
171
+ if (e.code === 'P2034' || e.code === common_1.ErrorCode.DatabaseWriteConflict) {
172
+ return true;
173
+ }
174
+ }
175
+ return false;
176
+ }
177
+ }
178
+ exports.PrismaDatabase = PrismaDatabase;
@@ -0,0 +1,21 @@
1
+ import { IsolationLevel, TransactionOptions } from '@appweaver/common';
2
+ type TransactionFn<T, Tx> = (tx: Tx) => Promise<T>;
3
+ /**
4
+ * Runs the given function in a database transaction. Every service call and
5
+ * every query of the database client made while it runs is part of the
6
+ * transaction, which commits once the function resolves. An error thrown out of
7
+ * the function rolls the transaction back and is rethrown, while an error caught
8
+ * inside it does not.
9
+ *
10
+ * The resource events, the cache invalidation, and the removal of stored files
11
+ * wait for the commit, and are dropped on rollback. A call made inside another
12
+ * transaction joins it.
13
+ *
14
+ * @param {IsolationLevel | TransactionOptions} [options] - The isolation level or
15
+ * the transaction options, falling back to the database default and the config.
16
+ * @param {Function} fn - The function to run, receiving the transaction client.
17
+ * @return {Promise<Object>} The value returned by the function.
18
+ */
19
+ export declare function runTransaction<T, Tx = any>(fn: TransactionFn<T, Tx>): Promise<T>;
20
+ export declare function runTransaction<T, Tx = any>(options: IsolationLevel | `${IsolationLevel}` | TransactionOptions, fn: TransactionFn<T, Tx>): Promise<T>;
21
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runTransaction = runTransaction;
4
+ const common_1 = require("@appweaver/common");
5
+ const core_1 = require("@appweaver/core");
6
+ function runTransaction(optionsOrFn, fn) {
7
+ const db = (0, core_1.inject)(common_1.Database);
8
+ if (typeof optionsOrFn === 'function') {
9
+ return db.transaction(optionsOrFn);
10
+ }
11
+ const options = typeof optionsOrFn === 'string'
12
+ ? { isolationLevel: optionsOrFn }
13
+ : optionsOrFn;
14
+ return db.transaction(fn, options);
15
+ }
@@ -0,0 +1,7 @@
1
+ export declare const exportConfig: Readonly<{
2
+ BATCH_SIZE: number;
3
+ CSV_DELIMITER: string;
4
+ CSV_JOIN_DELIMITER: string;
5
+ CSV_ADD_HEADERS: boolean;
6
+ CSV_ADD_SEP_ROW: boolean;
7
+ }>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportConfig = void 0;
4
+ const typebox_1 = require("@sinclair/typebox");
5
+ const common_1 = require("@appweaver/common");
6
+ exports.exportConfig = (0, common_1.defineConfig)('export', {
7
+ /** Number of records per batch during export. Default: `1000`. */
8
+ BATCH_SIZE: typebox_1.Type.Integer({ default: 1000 }),
9
+ /** CSV field delimiter character. Default: `';'`. */
10
+ CSV_DELIMITER: typebox_1.Type.String({ default: ';' }),
11
+ /** Delimiter for joining array values in CSV cells. Default: `','`. */
12
+ CSV_JOIN_DELIMITER: typebox_1.Type.String({ default: ',' }),
13
+ /** Include a header row in CSV exports. Default: `true`. */
14
+ CSV_ADD_HEADERS: typebox_1.Type.Boolean({ default: true }),
15
+ /** Add a separator row (BOM) for Excel compatibility. Default: `false`. */
16
+ CSV_ADD_SEP_ROW: typebox_1.Type.Boolean({ default: false })
17
+ });
@@ -0,0 +1,22 @@
1
+ import { Readable } from 'node:stream';
2
+ import { QuerySort } from '@appweaver/common';
3
+ export type ExportStream = {
4
+ stream: Readable;
5
+ fileName: string;
6
+ mimeType: string;
7
+ };
8
+ export declare class ExportService {
9
+ /**
10
+ * Exports data as a CSV stream based on the provided filter and sort parameters.
11
+ * The records are read batch by batch, each following the cursor of the
12
+ * previous one, so the export never counts the records up front.
13
+ *
14
+ * @param {string} modelName - The resource name for which to export data.
15
+ * @param {Object} [filter={}] - The filter conditions to apply when retrieving the data. Default is an empty object.
16
+ * @param {QuerySort} [sort='-createdAt'] - The sorting criteria for the data, given either as a comma-separated
17
+ * field list or as an object of field directions.
18
+ * @return {Promise<ExportStream>} A promise resolving to the export stream object, which includes the readable
19
+ * stream, MIME type, and file name for the CSV.
20
+ */
21
+ exportCsv(modelName: string, filter?: any, sort?: QuerySort): Promise<ExportStream>;
22
+ }
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExportService = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const common_1 = require("@appweaver/common");
6
+ const export_config_1 = require("./export-config");
7
+ const core_1 = require("@appweaver/core");
8
+ const file_util_1 = require("../utils/file-util");
9
+ const export_util_1 = require("../utils/export-util");
10
+ class ExportService {
11
+ /**
12
+ * Exports data as a CSV stream based on the provided filter and sort parameters.
13
+ * The records are read batch by batch, each following the cursor of the
14
+ * previous one, so the export never counts the records up front.
15
+ *
16
+ * @param {string} modelName - The resource name for which to export data.
17
+ * @param {Object} [filter={}] - The filter conditions to apply when retrieving the data. Default is an empty object.
18
+ * @param {QuerySort} [sort='-createdAt'] - The sorting criteria for the data, given either as a comma-separated
19
+ * field list or as an object of field directions.
20
+ * @return {Promise<ExportStream>} A promise resolving to the export stream object, which includes the readable
21
+ * stream, MIME type, and file name for the CSV.
22
+ */
23
+ async exportCsv(modelName, filter = {}, sort = '-createdAt') {
24
+ const service = (0, core_1.injectService)(modelName);
25
+ let exportStream;
26
+ try {
27
+ const batchSize = export_config_1.exportConfig.BATCH_SIZE;
28
+ const mapValues = (items) => this.mapProperties(modelName, items);
29
+ const readBatch = (cursor) => service.query({
30
+ filter,
31
+ size: batchSize,
32
+ sort,
33
+ cursor,
34
+ totalCount: false
35
+ });
36
+ // Read before the stream is handed out, so a failing query becomes an
37
+ // error response instead of a truncated download
38
+ const firstBatch = await readBatch();
39
+ // A generator rather than a `read` callback, which the stream may re-enter
40
+ // while an awaited batch is pending and would then write it twice
41
+ async function* rows() {
42
+ if (export_config_1.exportConfig.CSV_ADD_SEP_ROW) {
43
+ yield `SEP=${export_config_1.exportConfig.CSV_DELIMITER}\n`;
44
+ }
45
+ let batch = firstBatch;
46
+ let addHeaders = export_config_1.exportConfig.CSV_ADD_HEADERS;
47
+ while (batch) {
48
+ let chunk;
49
+ let next;
50
+ try {
51
+ if (batch.resultCount > 0) {
52
+ chunk = (0, export_util_1.toCsv)(mapValues(batch.items), addHeaders);
53
+ addHeaders = false;
54
+ }
55
+ // A next cursor is returned only while further records match
56
+ next = batch.nextCursor
57
+ ? await readBatch(batch.nextCursor)
58
+ : undefined;
59
+ }
60
+ catch (e) {
61
+ // End early rather than fail a download already in flight
62
+ common_1.logger.error(e, `${modelName} export error`);
63
+ next = undefined;
64
+ }
65
+ if (chunk !== undefined) {
66
+ yield chunk;
67
+ }
68
+ batch = next;
69
+ }
70
+ }
71
+ exportStream = node_stream_1.Readable.from(rows(), { objectMode: false });
72
+ }
73
+ catch (e) {
74
+ throw new common_1.ExportError(common_1.ErrorCode.ExportFailed, `${modelName} export failed`, { model: modelName }, { cause: e });
75
+ }
76
+ const fileName = this.generateExportFileName(modelName, 'csv');
77
+ common_1.logger.debug({ modelName, filter, fileName }, 'CSV File export');
78
+ return { stream: exportStream, mimeType: 'text/csv', fileName };
79
+ }
80
+ /** @internal */
81
+ mapProperties(resourceName, items) {
82
+ const properties = [];
83
+ for (const item of items) {
84
+ const property = this.mapProperty(resourceName, item);
85
+ properties.push(property);
86
+ }
87
+ return properties;
88
+ }
89
+ /** @internal */
90
+ mapProperty(resourceName, item, resourceExportConfig, parentKey = '') {
91
+ const resourceModel = (0, core_1.injectModel)(resourceName);
92
+ const readModel = resourceModel.readModel;
93
+ const relationsModel = resourceModel.relationsModel;
94
+ const filesModel = resourceModel.filesModel;
95
+ const fieldsConfig = resourceExportConfig ?? resourceModel.config.export;
96
+ const property = {};
97
+ for (const key in item) {
98
+ let value = item[key];
99
+ const modelSchema = (0, common_1.extractSchemaProperties)(readModel, key);
100
+ const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
101
+ const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
102
+ if (!modelSchema &&
103
+ !relationSchema &&
104
+ !fileSchema &&
105
+ !(0, common_1.isCountField)(key)) {
106
+ // Skip mapping for non-exposed fields.
107
+ continue;
108
+ }
109
+ // The records are read from the database rather than through a response
110
+ // schema, so the fields no response carries are dropped here instead.
111
+ if (modelSchema?.['hidden'] === true ||
112
+ resourceModel.config.virtual?.[key]?.output?.type === 'none') {
113
+ continue;
114
+ }
115
+ let header = parentKey ? `${parentKey}.${key}` : key;
116
+ const isArrayValue = (0, common_1.isArray)(value);
117
+ const exportField = fieldsConfig?.[key];
118
+ if (exportField) {
119
+ if (exportField.exclude === true) {
120
+ // Skip mapping for excluded fields.
121
+ continue;
122
+ }
123
+ if ((0, common_1.isString)(exportField.headerName)) {
124
+ header = parentKey
125
+ ? `${parentKey}.${exportField.headerName}`
126
+ : exportField.headerName;
127
+ }
128
+ const mapValue = exportField.mapValue;
129
+ if (mapValue) {
130
+ // A scalar value has no properties to read the configured field name
131
+ // off, so it is resolved against the exported record itself.
132
+ if ((0, common_1.isString)(mapValue) && !relationSchema && !fileSchema) {
133
+ value = item?.[mapValue];
134
+ }
135
+ else {
136
+ const mappedValues = [];
137
+ // Transform value items using mapValue configuration.
138
+ const subItems = isArrayValue ? value : [value];
139
+ for (const subItem of subItems) {
140
+ if ((0, common_1.isFunction)(mapValue)) {
141
+ try {
142
+ mappedValues.push(mapValue(subItem));
143
+ }
144
+ catch (e) {
145
+ mappedValues.push('');
146
+ common_1.logger.error(e, 'Export value mapping error.');
147
+ }
148
+ }
149
+ else if ((0, common_1.isString)(mapValue)) {
150
+ mappedValues.push(subItem?.[mapValue]);
151
+ }
152
+ }
153
+ value = mappedValues.join(',');
154
+ }
155
+ }
156
+ }
157
+ // Recursively map relation and file fields if the value is an object.
158
+ // Arrays of plain values are written as a single column below.
159
+ if ((0, common_1.isPlainObject)(value) || (isArrayValue && (0, common_1.isPlainObject)(value[0]))) {
160
+ const relationName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
161
+ if (!relationName) {
162
+ continue;
163
+ }
164
+ const subItems = isArrayValue ? value : [value];
165
+ for (const subItem of subItems) {
166
+ const mappedItem = this.mapProperty(relationName, subItem, exportField, header);
167
+ for (const subKey in mappedItem) {
168
+ if (subKey in property) {
169
+ property[subKey] = [property[subKey], mappedItem[subKey]].join(export_config_1.exportConfig.CSV_JOIN_DELIMITER);
170
+ }
171
+ else {
172
+ property[subKey] = mappedItem[subKey];
173
+ }
174
+ }
175
+ }
176
+ }
177
+ else {
178
+ property[header] = value;
179
+ }
180
+ }
181
+ return property;
182
+ }
183
+ /** @internal */
184
+ generateExportFileName(modelName, extension) {
185
+ return (0, file_util_1.generateFileName)(`${(0, common_1.plural)(modelName)}.${extension}`, '{name}_{year}{month}{day}_{hours}{minutes}{seconds}.{extension}');
186
+ }
187
+ }
188
+ exports.ExportService = ExportService;
@@ -0,0 +1 @@
1
+ export * from './export-service';
@@ -0,0 +1,17 @@
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("./export-service"), exports);
@@ -0,0 +1,11 @@
1
+ import { ResourceModel, ResourceModelConfig } from '@appweaver/common';
2
+ /**
3
+ * Creates a resource model from its configuration and defines it in the application context. The names of the
4
+ * declared fields are inferred from the configuration, so the `read`, `create`, and `update` options only accept
5
+ * existing fields.
6
+ *
7
+ * @param {ResourceModelConfig} config - The model configuration.
8
+ * @param {boolean} [override=false] - Whether to replace a model already defined under the same name.
9
+ * @return {ResourceModel} The created resource model.
10
+ */
11
+ export declare function createModel<S extends string, R extends string, F extends string, V extends string>(modelConfig: ResourceModelConfig<S, R, F, V>, override?: boolean): ResourceModel;