@friggframework/core 2.0.0--canary.461.5ebf96a.0 → 2.0.0--canary.461.6c79fb2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/database/prisma.js
CHANGED
|
@@ -201,6 +201,15 @@ async function disconnectPrisma() {
|
|
|
201
201
|
|
|
202
202
|
async function connectPrisma() {
|
|
203
203
|
await getPrismaClient().$connect();
|
|
204
|
+
|
|
205
|
+
// Initialize MongoDB schema - ensure all collections exist
|
|
206
|
+
// Only run for MongoDB/DocumentDB (not PostgreSQL)
|
|
207
|
+
// This prevents "Cannot create namespace in multi-document transaction" errors
|
|
208
|
+
if (config.DB_TYPE === 'mongodb') {
|
|
209
|
+
const { initializeMongoDBSchema } = require('./utils/mongodb-schema-init');
|
|
210
|
+
await initializeMongoDBSchema();
|
|
211
|
+
}
|
|
212
|
+
|
|
204
213
|
return getPrismaClient();
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -38,6 +38,9 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async createCredential(credentialData) {
|
|
41
|
+
// Note: Collection existence is ensured at application startup via
|
|
42
|
+
// initializeMongoDBSchema() in database/utils/mongodb-schema-init.js
|
|
43
|
+
// This prevents "Cannot create namespace in multi-document transaction" errors
|
|
41
44
|
return await prisma.credential.create({
|
|
42
45
|
data: credentialData,
|
|
43
46
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB Collection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for managing MongoDB collections, particularly for
|
|
5
|
+
* handling the constraint that collections cannot be created inside
|
|
6
|
+
* multi-document transactions.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/prisma/prisma/issues/8305
|
|
9
|
+
* @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { mongoose } = require('../mongoose');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ensures a MongoDB collection exists
|
|
16
|
+
*
|
|
17
|
+
* MongoDB doesn't allow creating collections (namespaces) inside multi-document
|
|
18
|
+
* transactions. This function checks if a collection exists and creates it if needed,
|
|
19
|
+
* preventing "Cannot create namespace in multi-document transaction" errors.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} collectionName - Name of the collection to ensure exists
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```js
|
|
26
|
+
* await ensureCollectionExists('Credential');
|
|
27
|
+
* // Now safe to create documents in Credential collection
|
|
28
|
+
* await prisma.credential.create({ data: {...} });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
async function ensureCollectionExists(collectionName) {
|
|
32
|
+
try {
|
|
33
|
+
const collections = await mongoose.connection.db
|
|
34
|
+
.listCollections({ name: collectionName })
|
|
35
|
+
.toArray();
|
|
36
|
+
|
|
37
|
+
if (collections.length === 0) {
|
|
38
|
+
// Collection doesn't exist, create it outside of any transaction
|
|
39
|
+
await mongoose.connection.db.createCollection(collectionName);
|
|
40
|
+
console.log(`Created MongoDB collection: ${collectionName}`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Collection might already exist due to race condition, or other error
|
|
44
|
+
// Log warning but don't fail - let subsequent operations handle errors
|
|
45
|
+
if (error.codeName === 'NamespaceExists') {
|
|
46
|
+
// This is expected in race conditions, silently continue
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.warn(`Error ensuring collection ${collectionName} exists:`, error.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Ensures multiple MongoDB collections exist
|
|
55
|
+
*
|
|
56
|
+
* @param {string[]} collectionNames - Array of collection names to ensure exist
|
|
57
|
+
* @returns {Promise<void>}
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```js
|
|
61
|
+
* await ensureCollectionsExist(['Credential', 'User', 'Token']);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
async function ensureCollectionsExist(collectionNames) {
|
|
65
|
+
await Promise.all(collectionNames.map(name => ensureCollectionExists(name)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a collection exists in MongoDB
|
|
70
|
+
*
|
|
71
|
+
* @param {string} collectionName - Name of the collection to check
|
|
72
|
+
* @returns {Promise<boolean>} True if collection exists, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
async function collectionExists(collectionName) {
|
|
75
|
+
try {
|
|
76
|
+
const collections = await mongoose.connection.db
|
|
77
|
+
.listCollections({ name: collectionName })
|
|
78
|
+
.toArray();
|
|
79
|
+
|
|
80
|
+
return collections.length > 0;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(`Error checking if collection ${collectionName} exists:`, error.message);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
ensureCollectionExists,
|
|
89
|
+
ensureCollectionsExist,
|
|
90
|
+
collectionExists,
|
|
91
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB Schema Initialization for Prisma
|
|
3
|
+
*
|
|
4
|
+
* Dynamically parses the Prisma schema and ensures all collections exist before
|
|
5
|
+
* the application starts handling requests. This prevents
|
|
6
|
+
* "Cannot create namespace in multi-document transaction" errors.
|
|
7
|
+
*
|
|
8
|
+
* MongoDB does not allow creating collections inside transactions.
|
|
9
|
+
* By pre-creating all collections at startup, we ensure all Prisma
|
|
10
|
+
* operations can safely use transactions without namespace creation errors.
|
|
11
|
+
*
|
|
12
|
+
* Collection names are extracted dynamically from the Prisma schema file,
|
|
13
|
+
* ensuring they stay in sync with schema changes without manual updates.
|
|
14
|
+
*
|
|
15
|
+
* @see https://github.com/prisma/prisma/issues/8305
|
|
16
|
+
* @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { mongoose } = require('../mongoose');
|
|
20
|
+
const { ensureCollectionsExist } = require('./mongodb-collection-utils');
|
|
21
|
+
const { getCollectionsFromSchemaSync } = require('./prisma-schema-parser');
|
|
22
|
+
const config = require('../config');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize MongoDB schema by ensuring all collections exist
|
|
26
|
+
*
|
|
27
|
+
* This should be called once at application startup, after the database
|
|
28
|
+
* connection is established but before handling any requests.
|
|
29
|
+
*
|
|
30
|
+
* Dynamically parses the Prisma schema to extract collection names,
|
|
31
|
+
* ensuring automatic sync with schema changes.
|
|
32
|
+
*
|
|
33
|
+
* Benefits:
|
|
34
|
+
* - Prevents transaction namespace creation errors
|
|
35
|
+
* - Fails fast if there are database connection issues
|
|
36
|
+
* - Ensures consistent state across all instances
|
|
37
|
+
* - Idempotent - safe to run multiple times
|
|
38
|
+
* - Automatically syncs with Prisma schema changes
|
|
39
|
+
*
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```js
|
|
44
|
+
* await connectPrisma();
|
|
45
|
+
* await initializeMongoDBSchema(); // Run after connection
|
|
46
|
+
* // Now safe to handle requests
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
async function initializeMongoDBSchema() {
|
|
50
|
+
// Only run for MongoDB
|
|
51
|
+
if (config.DB_TYPE !== 'mongodb') {
|
|
52
|
+
console.log('Schema initialization skipped - not using MongoDB');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if database is connected
|
|
57
|
+
if (mongoose.connection.readyState !== 1) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Cannot initialize MongoDB schema - database not connected. ' +
|
|
60
|
+
'Call connectPrisma() before initializeMongoDBSchema()'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('Initializing MongoDB schema - ensuring all collections exist...');
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Dynamically parse Prisma schema to get collection names
|
|
69
|
+
const collections = getCollectionsFromSchemaSync();
|
|
70
|
+
|
|
71
|
+
if (collections.length === 0) {
|
|
72
|
+
console.warn('No collections found in Prisma schema - skipping initialization');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await ensureCollectionsExist(collections);
|
|
77
|
+
|
|
78
|
+
const duration = Date.now() - startTime;
|
|
79
|
+
console.log(
|
|
80
|
+
`MongoDB schema initialization complete - ${collections.length} collections verified (${duration}ms)`
|
|
81
|
+
);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Failed to initialize MongoDB schema:', error.message);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get list of Prisma collection names by parsing the schema
|
|
90
|
+
* Useful for testing and introspection
|
|
91
|
+
*
|
|
92
|
+
* @returns {string[]} Array of collection names from Prisma schema
|
|
93
|
+
*/
|
|
94
|
+
function getPrismaCollections() {
|
|
95
|
+
try {
|
|
96
|
+
return getCollectionsFromSchemaSync();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.warn('Could not parse Prisma collections:', error.message);
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
initializeMongoDBSchema,
|
|
105
|
+
getPrismaCollections,
|
|
106
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma Schema Parser for MongoDB Collections
|
|
3
|
+
*
|
|
4
|
+
* Dynamically parses the Prisma schema file to extract MongoDB collection names.
|
|
5
|
+
* This ensures collection names stay in sync with the schema without hardcoding.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - @@map() directives (custom collection names)
|
|
9
|
+
* - Models without @@map() (uses model name)
|
|
10
|
+
* - Comments and whitespace
|
|
11
|
+
* - Multiple schema file locations
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse Prisma schema file to extract collection names
|
|
19
|
+
*
|
|
20
|
+
* Reads the schema.prisma file and extracts all model definitions,
|
|
21
|
+
* returning the actual MongoDB collection names (from @@map directives).
|
|
22
|
+
*
|
|
23
|
+
* @param {string} schemaPath - Path to schema.prisma file
|
|
24
|
+
* @returns {Promise<string[]>} Array of collection names
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```js
|
|
28
|
+
* const collections = await parseCollectionsFromSchema('./prisma/schema.prisma');
|
|
29
|
+
* // Returns: ['User', 'Token', 'Credential', ...]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
async function parseCollectionsFromSchema(schemaPath) {
|
|
33
|
+
try {
|
|
34
|
+
const schemaContent = await fs.promises.readFile(schemaPath, 'utf-8');
|
|
35
|
+
return extractCollectionNames(schemaContent);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Failed to parse Prisma schema at ${schemaPath}: ${error.message}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Synchronous version of parseCollectionsFromSchema
|
|
45
|
+
*
|
|
46
|
+
* @param {string} schemaPath - Path to schema.prisma file
|
|
47
|
+
* @returns {string[]} Array of collection names
|
|
48
|
+
*/
|
|
49
|
+
function parseCollectionsFromSchemaSync(schemaPath) {
|
|
50
|
+
try {
|
|
51
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
52
|
+
return extractCollectionNames(schemaContent);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Failed to parse Prisma schema at ${schemaPath}: ${error.message}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract collection names from Prisma schema content
|
|
62
|
+
*
|
|
63
|
+
* Parses the schema content to find:
|
|
64
|
+
* 1. All model definitions
|
|
65
|
+
* 2. Their @@map() directives (if present)
|
|
66
|
+
* 3. Falls back to model name if no @@map()
|
|
67
|
+
*
|
|
68
|
+
* @param {string} schemaContent - Content of schema.prisma file
|
|
69
|
+
* @returns {string[]} Array of collection names
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
function extractCollectionNames(schemaContent) {
|
|
73
|
+
const collections = [];
|
|
74
|
+
|
|
75
|
+
// Match model blocks: "model ModelName { ... }"
|
|
76
|
+
// Using non-greedy match to handle multiple models
|
|
77
|
+
const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
78
|
+
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = modelRegex.exec(schemaContent)) !== null) {
|
|
81
|
+
const modelName = match[1];
|
|
82
|
+
const modelBody = match[2];
|
|
83
|
+
|
|
84
|
+
// Look for @@map("CollectionName") directive
|
|
85
|
+
const mapMatch = modelBody.match(/@@map\s*\(\s*["'](\w+)["']\s*\)/);
|
|
86
|
+
|
|
87
|
+
if (mapMatch) {
|
|
88
|
+
// Use mapped collection name
|
|
89
|
+
collections.push(mapMatch[1]);
|
|
90
|
+
} else {
|
|
91
|
+
// Use model name as collection name (Prisma default)
|
|
92
|
+
collections.push(modelName);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return collections;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find Prisma MongoDB schema file
|
|
101
|
+
*
|
|
102
|
+
* Searches for the schema.prisma file in common locations:
|
|
103
|
+
* 1. prisma-mongodb/schema.prisma (Frigg convention)
|
|
104
|
+
* 2. prisma/schema.prisma (Prisma default)
|
|
105
|
+
* 3. schema.prisma (root)
|
|
106
|
+
*
|
|
107
|
+
* @param {string} startDir - Directory to start searching from
|
|
108
|
+
* @returns {string|null} Path to schema file, or null if not found
|
|
109
|
+
*/
|
|
110
|
+
function findMongoDBSchemaFile(startDir = __dirname) {
|
|
111
|
+
// Start from database directory and work up
|
|
112
|
+
const baseDir = path.resolve(startDir, '../..');
|
|
113
|
+
|
|
114
|
+
const searchPaths = [
|
|
115
|
+
path.join(baseDir, 'prisma-mongodb', 'schema.prisma'),
|
|
116
|
+
path.join(baseDir, 'prisma', 'schema.prisma'),
|
|
117
|
+
path.join(baseDir, 'schema.prisma'),
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const schemaPath of searchPaths) {
|
|
121
|
+
if (fs.existsSync(schemaPath)) {
|
|
122
|
+
return schemaPath;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get MongoDB collection names from Prisma schema
|
|
131
|
+
*
|
|
132
|
+
* Convenience function that finds and parses the schema automatically.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<string[]>} Array of collection names
|
|
135
|
+
* @throws {Error} If schema file not found or parsing fails
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```js
|
|
139
|
+
* const collections = await getCollectionsFromSchema();
|
|
140
|
+
* await ensureCollectionsExist(collections);
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
async function getCollectionsFromSchema() {
|
|
144
|
+
const schemaPath = findMongoDBSchemaFile();
|
|
145
|
+
|
|
146
|
+
if (!schemaPath) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
'Could not find Prisma MongoDB schema file. ' +
|
|
149
|
+
'Searched: prisma-mongodb/schema.prisma, prisma/schema.prisma, schema.prisma'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return await parseCollectionsFromSchema(schemaPath);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Synchronous version of getCollectionsFromSchema
|
|
158
|
+
*
|
|
159
|
+
* @returns {string[]} Array of collection names
|
|
160
|
+
* @throws {Error} If schema file not found or parsing fails
|
|
161
|
+
*/
|
|
162
|
+
function getCollectionsFromSchemaSync() {
|
|
163
|
+
const schemaPath = findMongoDBSchemaFile();
|
|
164
|
+
|
|
165
|
+
if (!schemaPath) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'Could not find Prisma MongoDB schema file. ' +
|
|
168
|
+
'Searched: prisma-mongodb/schema.prisma, prisma/schema.prisma, schema.prisma'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return parseCollectionsFromSchemaSync(schemaPath);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
parseCollectionsFromSchema,
|
|
177
|
+
parseCollectionsFromSchemaSync,
|
|
178
|
+
extractCollectionNames,
|
|
179
|
+
findMongoDBSchemaFile,
|
|
180
|
+
getCollectionsFromSchema,
|
|
181
|
+
getCollectionsFromSchemaSync,
|
|
182
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.461.
|
|
4
|
+
"version": "2.0.0--canary.461.6c79fb2.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0--canary.461.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
43
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.6c79fb2.0",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.6c79fb2.0",
|
|
43
|
+
"@friggframework/test": "2.0.0--canary.461.6c79fb2.0",
|
|
44
44
|
"@prisma/client": "^6.17.0",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "6c79fb214a75cd9bfec200a5a5cde9a2ebc51513"
|
|
84
84
|
}
|