@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luka Matosevic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,62 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <a href="https://appweaver.co" target="_blank"><img src="https://raw.githubusercontent.com/lmatosevic/appweaver/refs/heads/main/resources/appweaver-logo.svg" width="460" alt="Appweaver Logo" /></a>
3
+ </p>
2
4
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
5
+ <p align="center">
6
+ The data layer of Appweaver, the AI-first <a href="https://nodejs.org" target="_blank">Node.js</a> framework for backends built with agents.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@appweaver/data" target="_blank"><img src="https://img.shields.io/npm/v/@appweaver/data.svg" alt="NPM Version" /></a>
11
+ <a href="https://github.com/lmatosevic/appweaver/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@appweaver/data.svg" alt="Package License" /></a>
12
+ <a href="https://www.npmjs.com/package/@appweaver/data" target="_blank"><img src="https://img.shields.io/npm/dw/@appweaver/data.svg" alt="NPM Downloads" /></a>
13
+ </p>
14
+
15
+ ## Description
16
+
17
+ `@appweaver/data` is the database module of [Appweaver](https://github.com/lmatosevic/appweaver), built on
18
+ [Prisma](https://prisma.io):
19
+
20
+ - **Resources** – `createModel`, `createService` and `createPolicy`, the resource services with filtering, sorting,
21
+ cursor pagination, aggregation and CSV export, and the transactions.
22
+ - **Auth data model** – `createAuthModel`, `createAuthService`, roles, permissions, API keys, one-time tokens,
23
+ connected accounts and password hashing.
24
+ - **Files** – `FileService` with the File model, storing the files in any `Storage` provider.
25
+ - **Seeders** – `createSeeder` and the database security store.
26
+
27
+ The framework models reach only the applications using them: the auth tables with an auth model, File with a model
28
+ declaring `files`, Seeder with a seeders directory.
29
+
30
+ ## Installation
31
+
32
+ ```sh
33
+ npm install @appweaver/data @prisma/client prisma @prisma/adapter-pg # the driver adapter of your database
34
+ ```
35
+
36
+ The client connects through the Prisma driver adapter of the database: `@prisma/adapter-pg` (PostgreSQL),
37
+ `@prisma/adapter-mariadb` (MySQL / MariaDB), `@prisma/adapter-better-sqlite3` (SQLite, `@prisma/adapter-libsql` on Bun)
38
+ or `@prisma/adapter-mssql` (SQL Server). `prisma` is a dependency, since `weaver migrate` runs it in production.
39
+
40
+ ```ts
41
+ // src/resources/post/model.ts
42
+ import { createModel } from '@appweaver/data';
43
+
44
+ export default createModel({
45
+ name: 'Post',
46
+ scalars: {
47
+ title: { type: 'string', minLength: 1, maxLength: 200 },
48
+ body: { type: 'string' }
49
+ }
50
+ });
51
+ ```
52
+
53
+ Run `weaver generate` after changing a model to refresh the generated types and the Prisma schema.
54
+
55
+ ## Documentation
56
+
57
+ - [Appweaver README](https://github.com/lmatosevic/appweaver#readme) – getting started, configuration, and the overview of every package
58
+ - [Agent skill](https://github.com/lmatosevic/appweaver/blob/main/skill/SKILL.md) – the complete framework reference shipped to every scaffolded project
59
+
60
+ ## License
61
+
62
+ Appweaver is [MIT licensed](LICENSE).
package/config.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** The config sections of data, importable without loading Prisma, i.e. by the CLI and `prisma.config.ts`. */
2
+ export * from './database/database-config';
3
+ export * from './export/export-config';
4
+ export * from './resource/resource-config';
5
+ export * from './security/auth-config';
6
+ export * from './storage/image-config';
package/config.js ADDED
@@ -0,0 +1,22 @@
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
+ /** The config sections of data, importable without loading Prisma, i.e. by the CLI and `prisma.config.ts`. */
18
+ __exportStar(require("./database/database-config"), exports);
19
+ __exportStar(require("./export/export-config"), exports);
20
+ __exportStar(require("./resource/resource-config"), exports);
21
+ __exportStar(require("./security/auth-config"), exports);
22
+ __exportStar(require("./storage/image-config"), exports);
@@ -0,0 +1,11 @@
1
+ import { PrismaClient } from '../prisma/client/client';
2
+ /**
3
+ * Creates and returns a PrismaClient instance based on the configured database type.
4
+ *
5
+ * The database type is determined by the `DATABASE_TYPE` configuration value or,
6
+ * if unspecified, by parsing the `DATABASE_URL` configuration.
7
+ * Supported database types include SQLite, PostgreSQL, and MySQL.
8
+ *
9
+ * @return {PrismaClient} A PrismaClient instance configured for the selected database type.
10
+ */
11
+ export declare function createClient(): PrismaClient;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createClient = createClient;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const common_1 = require("@appweaver/common");
9
+ const database_config_1 = require("./database-config");
10
+ const options = {
11
+ transactionOptions: {
12
+ maxWait: database_config_1.databaseConfig.TRANSACTION_MAX_WAIT,
13
+ timeout: database_config_1.databaseConfig.TRANSACTION_TIMEOUT
14
+ },
15
+ log: database_config_1.databaseConfig.LOG_EVENTS.map((e) => ({ emit: 'event', level: e }))
16
+ };
17
+ /**
18
+ * Creates and returns a PrismaClient instance based on the configured database type.
19
+ *
20
+ * The database type is determined by the `DATABASE_TYPE` configuration value or,
21
+ * if unspecified, by parsing the `DATABASE_URL` configuration.
22
+ * Supported database types include SQLite, PostgreSQL, and MySQL.
23
+ *
24
+ * @return {PrismaClient} A PrismaClient instance configured for the selected database type.
25
+ */
26
+ function createClient() {
27
+ const dbType = (0, common_1.resolveDatabaseType)(database_config_1.databaseConfig.TYPE, database_config_1.databaseConfig.URL);
28
+ switch (dbType) {
29
+ case common_1.DatabaseType.Sqlite:
30
+ return sqliteClient();
31
+ case common_1.DatabaseType.PostgreSQL:
32
+ return postgresClient();
33
+ case common_1.DatabaseType.MySQL:
34
+ return mysqlClient();
35
+ case common_1.DatabaseType.SQLServer:
36
+ return sqlServerClient();
37
+ default:
38
+ return sqliteClient();
39
+ }
40
+ }
41
+ /**
42
+ * Initializes and returns an instance of the PrismaClient configured with a Better SQLite3 adapter.
43
+ *
44
+ * This method sets up a database connection using the PrismaBetterSqlite3 adapter with the provided
45
+ * database URL and additional options.
46
+ *
47
+ * @return {PrismaClient} A configured instance of PrismaClient for interacting with the SQLite database.
48
+ */
49
+ function sqliteClient() {
50
+ let sqliteAdapter;
51
+ if (common_1.appConfig.RUNTIME === common_1.Runtime.Bun) {
52
+ const { PrismaLibSql } = (0, common_1.requireModule)('@prisma/adapter-libsql', true).value;
53
+ sqliteAdapter = PrismaLibSql;
54
+ }
55
+ else {
56
+ const { PrismaBetterSqlite3 } = (0, common_1.requireModule)('@prisma/adapter-better-sqlite3', true).value;
57
+ sqliteAdapter = PrismaBetterSqlite3;
58
+ }
59
+ const adapter = new sqliteAdapter({
60
+ url: database_config_1.databaseConfig.URL || 'file:./sqlite.db'
61
+ });
62
+ return createPrismaClient(adapter);
63
+ }
64
+ /**
65
+ * Creates and returns an instance of PrismaClient configured with the specified options.
66
+ * The `schema` parameter of the database URL selects the PostgreSQL schema, `public` by default.
67
+ *
68
+ * @return {PrismaClient} An instance of PrismaClient used to interact with a PostgreSQL database.
69
+ */
70
+ function postgresClient() {
71
+ const { PrismaPg } = (0, common_1.requireModule)('@prisma/adapter-pg', true).value;
72
+ // Prisma Migrate reads the schema from the URL, while the adapter ignores it
73
+ // and would query the "public" schema, so pass it on
74
+ const schema = new URL(database_config_1.databaseConfig.URL).searchParams.get('schema') ?? undefined;
75
+ const adapter = new PrismaPg({ connectionString: database_config_1.databaseConfig.URL }, { schema });
76
+ return createPrismaClient(adapter);
77
+ }
78
+ /**
79
+ * Initializes and returns an instance of PrismaClient configured to use
80
+ * a MariaDB adapter with the specified database connection settings.
81
+ *
82
+ * @return {PrismaClient} A PrismaClient instance configured for MariaDB.
83
+ */
84
+ function mysqlClient() {
85
+ const { PrismaMariaDb } = (0, common_1.requireModule)('@prisma/adapter-mariadb', true).value;
86
+ const dbUrl = new URL(database_config_1.databaseConfig.URL);
87
+ const adapter = new PrismaMariaDb({
88
+ host: dbUrl.hostname,
89
+ port: parseInt(dbUrl.port || '3306', 10),
90
+ user: decodeURIComponent(dbUrl.username),
91
+ password: decodeURIComponent(dbUrl.password),
92
+ database: dbUrl.pathname.slice(1)
93
+ });
94
+ return createPrismaClient(adapter);
95
+ }
96
+ /**
97
+ * Creates and returns a PrismaClient configured to connect to a SQL Server database.
98
+ *
99
+ * This method uses the Prisma MSSQL adapter with the connection string from config.
100
+ *
101
+ * @return {PrismaClient} An instance of PrismaClient for interacting with the SQL Server database.
102
+ */
103
+ function sqlServerClient() {
104
+ const { PrismaMssql } = (0, common_1.requireModule)('@prisma/adapter-mssql', true).value;
105
+ const adapter = new PrismaMssql(database_config_1.databaseConfig.URL);
106
+ return createPrismaClient(adapter);
107
+ }
108
+ /**
109
+ * Creates and returns an instance of PrismaClient configured with the specified SQL driver adapter.
110
+ * The PrismaClient class is loaded from the configured output path relative to the root directory.
111
+ *
112
+ * @param {SqlDriverAdapterFactory} adapter - The SQL driver adapter factory used to configure the PrismaClient.
113
+ * @return {PrismaClient} A configured PrismaClient instance.
114
+ */
115
+ function createPrismaClient(adapter) {
116
+ const cwd = process.cwd();
117
+ const mainPath = require.main?.filename || process.argv[1];
118
+ const relativeMainPath = mainPath.replace(`${cwd}${node_path_1.default.sep}`, '');
119
+ const distDirName = relativeMainPath.includes('node_modules')
120
+ ? 'dist'
121
+ : relativeMainPath.split(node_path_1.default.sep)[0];
122
+ const buildDirPath = (0, common_1.isTypeScriptRuntime)()
123
+ ? ''
124
+ : common_1.appConfig.BUILD_PATH || distDirName;
125
+ const testDirPath = !(0, common_1.isTypeScriptRuntime)() && common_1.appConfig.ENV === 'test' ? '..' : '';
126
+ // Construct the Prisma client path supporting multiple different ways of running
127
+ // application (JavaScript runtime, TypeScript runtime, tests, from CLI, ...)
128
+ const clientPath = node_path_1.default.join(cwd, buildDirPath, testDirPath, database_config_1.databaseConfig.CLIENT_OUTPUT_DIR_PATH, 'client');
129
+ const { PrismaClient } = (0, common_1.requireModule)(clientPath, true).value;
130
+ const client = new PrismaClient({ adapter, ...options });
131
+ // Add configured Prisma event logging
132
+ for (const event of database_config_1.databaseConfig.LOG_EVENTS) {
133
+ if (event === common_1.DatabaseEvent.Query) {
134
+ client.$on(event, (e) => {
135
+ common_1.logger.debug(e, 'Prisma query');
136
+ });
137
+ }
138
+ else {
139
+ client.$on(event, (e) => {
140
+ common_1.logger[event](e, `Prisma ${event} log`);
141
+ });
142
+ }
143
+ }
144
+ return client;
145
+ }
@@ -0,0 +1,14 @@
1
+ import { DatabaseEvent, DatabaseType } from '@appweaver/common';
2
+ export declare const databaseConfig: Readonly<{
3
+ TYPE?: DatabaseType | undefined;
4
+ URL: string;
5
+ SCHEMA_PATH: string;
6
+ MIGRATIONS_DIR_PATH: string;
7
+ SEEDERS_DIR_PATH: string;
8
+ CLIENT_OUTPUT_DIR_PATH: string;
9
+ TRANSACTION_MAX_WAIT: number;
10
+ TRANSACTION_TIMEOUT: number;
11
+ AI_DANGEROUS_ACTIONS_ENABLED: boolean;
12
+ LOG_EVENTS: DatabaseEvent[];
13
+ PROVIDER: string;
14
+ }>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.databaseConfig = void 0;
4
+ const typebox_1 = require("@sinclair/typebox");
5
+ const common_1 = require("@appweaver/common");
6
+ exports.databaseConfig = (0, common_1.defineConfig)('database', {
7
+ /** Database type. Values: `sqlite`, `postgresql`, `mysql`, `sqlserver`. */
8
+ TYPE: typebox_1.Type.Optional(typebox_1.Type.Enum(common_1.DatabaseType)),
9
+ /** Database connection URL/DSN. Default: `''`. */
10
+ URL: typebox_1.Type.String({ default: '' }),
11
+ /** Path to a Prisma schema file. Default: `'./database/schema.prisma'`. */
12
+ SCHEMA_PATH: typebox_1.Type.String({
13
+ default: './database/schema.prisma'
14
+ }),
15
+ /** Path to the database migrations directory. Default: `'./database/migrations'`. */
16
+ MIGRATIONS_DIR_PATH: typebox_1.Type.String({
17
+ default: './database/migrations'
18
+ }),
19
+ /** Path to the database seeders directory. Default: `'./database/seeders'`. */
20
+ SEEDERS_DIR_PATH: typebox_1.Type.String({
21
+ default: './database/seeders'
22
+ }),
23
+ /** Path for generated Prisma client output. Default: `'./database/client'`. */
24
+ CLIENT_OUTPUT_DIR_PATH: typebox_1.Type.String({
25
+ default: './database/client'
26
+ }),
27
+ /** Max wait time for acquiring a transaction lock in milliseconds. Default: `2000` (2 sec). */
28
+ TRANSACTION_MAX_WAIT: typebox_1.Type.Integer({ default: 2000 }),
29
+ /** Transaction timeout in milliseconds. Default: `5000` (5 sec). */
30
+ TRANSACTION_TIMEOUT: typebox_1.Type.Integer({ default: 5000 }),
31
+ /** Allow an AI agent to run dangerous Prisma actions, such as a database reset. Disable it in production.
32
+ * Default: `true`. */
33
+ AI_DANGEROUS_ACTIONS_ENABLED: typebox_1.Type.Boolean({ default: true }),
34
+ /** Array of database events to log. Values: `query`, `info`, `warn`, `error`. Default: `[]`. */
35
+ LOG_EVENTS: typebox_1.Type.Array(typebox_1.Type.Enum(common_1.DatabaseEvent), {
36
+ default: []
37
+ }),
38
+ /** Database provider: `auto` or `prisma`, or `none` for an application without a database. Default: `'auto'`. */
39
+ PROVIDER: typebox_1.Type.String({ default: 'auto' })
40
+ });
@@ -0,0 +1,49 @@
1
+ /** The constraint a driver adapter reports as violated. */
2
+ type AdapterConstraint = {
3
+ fields: string[];
4
+ } | {
5
+ index: string;
6
+ } | {
7
+ foreignKey: Record<string, unknown>;
8
+ };
9
+ type PrismaError = Error & {
10
+ code?: string;
11
+ errorCode?: string;
12
+ meta?: {
13
+ modelName?: string;
14
+ target?: string | string[];
15
+ column_name?: string;
16
+ field_name?: string;
17
+ driverAdapterError?: {
18
+ cause?: {
19
+ kind?: string;
20
+ constraint?: AdapterConstraint;
21
+ column?: string;
22
+ };
23
+ };
24
+ };
25
+ };
26
+ /**
27
+ * Checks whether an error was raised by the Prisma client.
28
+ */
29
+ export declare function isPrismaError(error: unknown): error is PrismaError;
30
+ /**
31
+ * Translates an error of the Prisma client into a {@link DatabaseError}
32
+ * describing what failed, i.e. which fields violate a unique constraint. Any
33
+ * other error, i.e. an {@link AppweaverError} or a bug of the code the
34
+ * operation runs, is returned as it is.
35
+ *
36
+ * @param {unknown} error The error the operation failed with.
37
+ * @param {string} [model] The name of the model the operation ran on, used when
38
+ * the error does not name one.
39
+ * @return {unknown} The translated error keeping the Prisma error as its
40
+ * cause, or the error as it is.
41
+ */
42
+ export declare function toDatabaseError(error: unknown, model?: string): unknown;
43
+ /**
44
+ * Returns the explanation of a Prisma error, without the invocation and the
45
+ * code frame its message starts with, i.e. "Unique constraint failed on the
46
+ * fields: (`email`)".
47
+ */
48
+ export declare function prismaErrorMessage(error: Error): string;
49
+ export {};
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPrismaError = isPrismaError;
4
+ exports.toDatabaseError = toDatabaseError;
5
+ exports.prismaErrorMessage = prismaErrorMessage;
6
+ const common_1 = require("@appweaver/common");
7
+ const core_1 = require("@appweaver/core");
8
+ /** The error codes of the error kinds a driver adapter reports. */
9
+ const ADAPTER_KINDS = {
10
+ UniqueConstraintViolation: common_1.ErrorCode.DatabaseUniqueViolation,
11
+ ForeignKeyConstraintViolation: common_1.ErrorCode.DatabaseForeignKeyViolation,
12
+ NullConstraintViolation: common_1.ErrorCode.DatabaseNullViolation,
13
+ LengthMismatch: common_1.ErrorCode.DatabaseValueTooLong,
14
+ ValueOutOfRange: common_1.ErrorCode.DatabaseValueOutOfRange,
15
+ InvalidInputValue: common_1.ErrorCode.DatabaseInvalidValue,
16
+ InconsistentColumnData: common_1.ErrorCode.DatabaseInvalidValue,
17
+ TransactionWriteConflict: common_1.ErrorCode.DatabaseWriteConflict
18
+ };
19
+ /** The error codes of the Prisma error codes. */
20
+ const PRISMA_CODES = {
21
+ P2000: common_1.ErrorCode.DatabaseValueTooLong,
22
+ P2002: common_1.ErrorCode.DatabaseUniqueViolation,
23
+ P2003: common_1.ErrorCode.DatabaseForeignKeyViolation,
24
+ P2006: common_1.ErrorCode.DatabaseInvalidValue,
25
+ P2007: common_1.ErrorCode.DatabaseInvalidValue,
26
+ P2011: common_1.ErrorCode.DatabaseNullViolation,
27
+ P2020: common_1.ErrorCode.DatabaseValueOutOfRange,
28
+ P2023: common_1.ErrorCode.DatabaseInvalidValue,
29
+ P2025: common_1.ErrorCode.DatabaseRecordNotFound,
30
+ P2034: common_1.ErrorCode.DatabaseWriteConflict
31
+ };
32
+ // The connection errors, a connection pool timeout and too many connections
33
+ const UNAVAILABLE_CODES = /^P10\d\d$|^P2024$|^P2037$/;
34
+ /**
35
+ * Checks whether an error was raised by the Prisma client.
36
+ */
37
+ function isPrismaError(error) {
38
+ return (error instanceof Error &&
39
+ (error.name.startsWith('PrismaClient') ||
40
+ /^P\d{4}$/.test(String(error.code))));
41
+ }
42
+ /**
43
+ * Translates an error of the Prisma client into a {@link DatabaseError}
44
+ * describing what failed, i.e. which fields violate a unique constraint. Any
45
+ * other error, i.e. an {@link AppweaverError} or a bug of the code the
46
+ * operation runs, is returned as it is.
47
+ *
48
+ * @param {unknown} error The error the operation failed with.
49
+ * @param {string} [model] The name of the model the operation ran on, used when
50
+ * the error does not name one.
51
+ * @return {unknown} The translated error keeping the Prisma error as its
52
+ * cause, or the error as it is.
53
+ */
54
+ function toDatabaseError(error, model) {
55
+ if ((0, common_1.isAppweaverError)(error) || !isPrismaError(error)) {
56
+ return error;
57
+ }
58
+ const options = { cause: error };
59
+ const prismaCode = error.code ?? error.errorCode;
60
+ const modelName = error.meta?.modelName ?? model;
61
+ const cause = error.meta?.driverAdapterError?.cause;
62
+ const code = (cause?.kind ? ADAPTER_KINDS[cause.kind] : undefined) ??
63
+ (prismaCode ? PRISMA_CODES[prismaCode] : undefined) ??
64
+ validationErrorCode(error);
65
+ if (!code) {
66
+ if (prismaCode && UNAVAILABLE_CODES.test(prismaCode)) {
67
+ return new common_1.DatabaseError(common_1.ErrorCode.DatabaseUnavailable, 'Database is unavailable', {}, options);
68
+ }
69
+ return new common_1.DatabaseError(common_1.ErrorCode.DatabaseOperationFailed, `${modelName ?? 'Database'} operation failed`, { model: modelName, prismaCode }, options);
70
+ }
71
+ const subject = modelName ?? 'Record';
72
+ switch (code) {
73
+ case common_1.ErrorCode.DatabaseRecordNotFound:
74
+ return new common_1.DatabaseError(code, `${subject} not found`, { model: modelName }, options);
75
+ case common_1.ErrorCode.DatabaseWriteConflict:
76
+ return new common_1.DatabaseError(code, `${subject} was changed by a concurrent operation, retry the operation`, { model: modelName }, options);
77
+ }
78
+ const constraint = cause?.constraint;
79
+ const fields = violatedFields(error, modelName, cause?.column);
80
+ const details = {
81
+ model: modelName,
82
+ fields,
83
+ ...(constraint && 'index' in constraint
84
+ ? { constraint: constraint.index }
85
+ : {})
86
+ };
87
+ return new common_1.DatabaseError(code, violationMessage(code, subject, fields), details, options);
88
+ }
89
+ /**
90
+ * Returns the explanation of a Prisma error, without the invocation and the
91
+ * code frame its message starts with, i.e. "Unique constraint failed on the
92
+ * fields: (`email`)".
93
+ */
94
+ function prismaErrorMessage(error) {
95
+ const lines = error.message
96
+ .split('\n')
97
+ .map((line) => line.trim())
98
+ .filter(Boolean);
99
+ return lines[lines.length - 1] ?? error.name;
100
+ }
101
+ /** The violations the Prisma client reports before reaching the database. */
102
+ function validationErrorCode(error) {
103
+ if (error.name !== 'PrismaClientValidationError') {
104
+ return undefined;
105
+ }
106
+ return /Argument `\w+` is missing/.test(error.message)
107
+ ? common_1.ErrorCode.DatabaseNullViolation
108
+ : undefined;
109
+ }
110
+ /** Returns the model fields an error reports as violated, normalized from the
111
+ * columns the database reports. */
112
+ function violatedFields(error, modelName, column) {
113
+ const model = modelName ? (0, core_1.injectModel)(modelName, false) : undefined;
114
+ return reportedColumns(error, column, model).map((name) => modelField(unquote(name), model));
115
+ }
116
+ /** Returns the columns an error reports as violated, the ones of a named index
117
+ * resolved from the constraints of its model. */
118
+ function reportedColumns(error, column, model) {
119
+ const constraint = error.meta?.driverAdapterError?.cause?.constraint;
120
+ if (constraint && 'fields' in constraint) {
121
+ return constraint.fields;
122
+ }
123
+ if (constraint && 'index' in constraint) {
124
+ return indexColumns(constraint.index, model);
125
+ }
126
+ if (column) {
127
+ return [column];
128
+ }
129
+ const target = error.meta?.target;
130
+ if (Array.isArray(target)) {
131
+ return target;
132
+ }
133
+ if (typeof target === 'string') {
134
+ return indexColumns(target, model);
135
+ }
136
+ const missing = error.message.match(/Argument `(\w+)` is missing/);
137
+ if (missing) {
138
+ return [missing[1]];
139
+ }
140
+ const field = error.meta?.field_name ?? error.meta?.column_name;
141
+ return field ? [field] : [];
142
+ }
143
+ /**
144
+ * Returns the model field a column holds: the relation of a foreign key
145
+ * column, i.e. `author` for `authorId`, and the column itself otherwise. A
146
+ * column reported with its table, i.e. `Post.title`, is stripped of it.
147
+ */
148
+ function modelField(column, model) {
149
+ const name = column.slice(column.lastIndexOf('.') + 1);
150
+ const relation = name.endsWith('Id') ? name.slice(0, -2) : undefined;
151
+ return relation && model?.config.relations?.[relation] ? relation : name;
152
+ }
153
+ /**
154
+ * Resolves the columns of a named index from the constraints of its model,
155
+ * following the default Prisma naming of `<Table>_<columns>_key` for a unique
156
+ * constraint and `<Table>_<column>_fkey` for a foreign key.
157
+ */
158
+ function indexColumns(index, model) {
159
+ if (!model) {
160
+ return [];
161
+ }
162
+ const table = model.config.tableName ?? model.name;
163
+ const candidates = [
164
+ ...(model.config.unique ?? []).map((entry) => (Array.isArray(entry) ? entry : [entry]).map(stripOrder)),
165
+ ...Object.entries(model.config.scalars ?? {})
166
+ .filter(([, scalar]) => scalar.unique)
167
+ .map(([name]) => [name]),
168
+ ...Object.keys(model.config.relations ?? {}).map((name) => [`${name}Id`])
169
+ ];
170
+ return (candidates.find((columns) => ['key', 'fkey'].some((suffix) => index === `${table}_${columns.join('_')}_${suffix}`)) ?? []);
171
+ }
172
+ function violationMessage(code, subject, fields) {
173
+ const quoted = fields.map((field) => `'${field}'`).join(', ');
174
+ const plural = fields.length > 1;
175
+ switch (code) {
176
+ case common_1.ErrorCode.DatabaseUniqueViolation:
177
+ return fields.length > 0
178
+ ? `${subject} with the same ${fields.join(' and ')} already exists`
179
+ : `${subject} violates a unique constraint`;
180
+ case common_1.ErrorCode.DatabaseNullViolation:
181
+ return fields.length > 0
182
+ ? `${subject} field${plural ? 's' : ''} ${quoted} ${plural ? 'are' : 'is'} required`
183
+ : `${subject} is missing a required field`;
184
+ case common_1.ErrorCode.DatabaseValueTooLong:
185
+ return fields.length > 0
186
+ ? `${subject} field ${quoted} value is too long`
187
+ : `${subject} field value is too long`;
188
+ case common_1.ErrorCode.DatabaseValueOutOfRange:
189
+ return fields.length > 0
190
+ ? `${subject} field ${quoted} value is out of range`
191
+ : `${subject} field value is out of range`;
192
+ case common_1.ErrorCode.DatabaseForeignKeyViolation:
193
+ return fields.length > 0
194
+ ? `${subject} relation ${quoted} references a missing record, or is still referenced by another record`
195
+ : `${subject} violates a relation constraint`;
196
+ default:
197
+ return fields.length > 0
198
+ ? `${subject} field ${quoted} value is invalid`
199
+ : `${subject} field value is invalid`;
200
+ }
201
+ }
202
+ function unquote(name) {
203
+ return name.replace(/^[`"[]|[`"\]]$/g, '');
204
+ }
205
+ function stripOrder(field) {
206
+ return field.replace(/^[-+]/, '');
207
+ }
208
+ // The Prisma errors reach the API as a DatabaseError naming the violated
209
+ // constraint, with the message of Prisma only when the server enables it
210
+ (0, common_1.defineErrorTranslator)({
211
+ name: 'database',
212
+ matches: isPrismaError,
213
+ translate: (error) => toDatabaseError(error),
214
+ describe: (error) => prismaErrorMessage(error),
215
+ exposeDescription: () => common_1.config.bool('SERVER_ERROR_DATABASE_MESSAGE_ENABLED', false)
216
+ });
@@ -0,0 +1,3 @@
1
+ export * from './database-error';
2
+ export * from './prisma-database';
3
+ export * from './run-transaction';
@@ -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("./database-error"), exports);
18
+ __exportStar(require("./prisma-database"), exports);
19
+ __exportStar(require("./run-transaction"), exports);
@@ -0,0 +1,33 @@
1
+ import { Database, HealthCheckResult, TransactionOptions } from '@appweaver/common';
2
+ import { PrismaClient } from '../prisma/client/client';
3
+ /**
4
+ * Represents a database utility class that provides methods
5
+ * to connect to and disconnect from the database, as well as
6
+ * access to the database Prisma client instance.
7
+ */
8
+ export declare class PrismaDatabase extends Database {
9
+ /**
10
+ * Connects to the database and checks the connection, logging an error when the database is unreachable instead of
11
+ * failing the start, since most drivers connect lazily and the queries reconnect once it is reachable.
12
+ */
13
+ onInit(): Promise<void>;
14
+ onDestroy(): Promise<void>;
15
+ connect(): Promise<void>;
16
+ disconnect(): Promise<void>;
17
+ /**
18
+ * Returns the Prisma client, which runs its queries in the transaction the
19
+ * current call runs in (see `runTransaction`), and on the connection otherwise.
20
+ */
21
+ client<T = PrismaClient>(): T;
22
+ checkHealth(): Promise<HealthCheckResult>;
23
+ /**
24
+ * Runs the given function in a transaction, or in the current one when called
25
+ * inside a transaction already. See `runTransaction`.
26
+ *
27
+ * @param {Function} fn - The function to run, receiving the transaction client.
28
+ * @param {TransactionOptions} [options] - The transaction options.
29
+ * @return {Promise<Object>} The value returned by the function.
30
+ * @throws The error thrown by the function, after the transaction rolled back.
31
+ */
32
+ transaction<T>(fn: (tx: any) => Promise<T>, options?: TransactionOptions): Promise<T>;
33
+ }