@mongodb-js/mock-data-generator 0.0.0-init → 1.2.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/LICENSE +557 -0
- package/dist/.esm-wrapper.mjs +18 -0
- package/dist/format-schema-for-prompt.d.ts +6 -0
- package/dist/format-schema-for-prompt.d.ts.map +1 -0
- package/dist/format-schema-for-prompt.js +34 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +280 -0
- package/dist/schema-batching.d.ts +21 -0
- package/dist/schema-batching.d.ts.map +1 -0
- package/dist/schema-batching.js +46 -0
- package/dist/schema.d.ts +24 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +26 -0
- package/dist/script-generation-utils.d.ts +42 -0
- package/dist/script-generation-utils.d.ts.map +1 -0
- package/dist/script-generation-utils.js +586 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +67 -15
- package/README.md +0 -2
|
@@ -0,0 +1,586 @@
|
|
|
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.generateScript = generateScript;
|
|
7
|
+
exports.getDefaultFakerMethod = getDefaultFakerMethod;
|
|
8
|
+
exports.formatFakerArgs = formatFakerArgs;
|
|
9
|
+
exports.generateDocument = generateDocument;
|
|
10
|
+
const standalone_1 = __importDefault(require("prettier/standalone"));
|
|
11
|
+
const parser_babel_1 = __importDefault(require("prettier/parser-babel"));
|
|
12
|
+
const en_1 = require("@faker-js/faker/locale/en");
|
|
13
|
+
const types_1 = require("./types");
|
|
14
|
+
const DEFAULT_ARRAY_LENGTH = 3;
|
|
15
|
+
/**
|
|
16
|
+
* Entry point method: Generate the final script
|
|
17
|
+
*/
|
|
18
|
+
function generateScript(schema, options) {
|
|
19
|
+
try {
|
|
20
|
+
const structure = buildDocumentStructure(schema);
|
|
21
|
+
const documentCode = renderDocumentCode(structure, options.arrayLengthMap || {});
|
|
22
|
+
// Generate unformatted script
|
|
23
|
+
const unformattedScript = `// Mock Data Generator Script
|
|
24
|
+
// Document count: ${options.documentCount}
|
|
25
|
+
|
|
26
|
+
const { faker } = require('@faker-js/faker');
|
|
27
|
+
|
|
28
|
+
// Database and collection configuration - edit these to target a different location
|
|
29
|
+
const DB_NAME = ${toStringLiteral(options.databaseName)};
|
|
30
|
+
const COLL_NAME = ${toStringLiteral(options.collectionName)};
|
|
31
|
+
|
|
32
|
+
// Connect to database
|
|
33
|
+
use(DB_NAME);
|
|
34
|
+
|
|
35
|
+
// Document generation function
|
|
36
|
+
function generateDocument() {
|
|
37
|
+
return ${documentCode};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const BATCH_SIZE = 1000; // Number of documents to insert per batch
|
|
41
|
+
const TOTAL_DOCUMENTS = ${options.documentCount};
|
|
42
|
+
const numBatches = Math.ceil(TOTAL_DOCUMENTS / BATCH_SIZE);
|
|
43
|
+
|
|
44
|
+
console.log(\`Starting mock data generation for \${DB_NAME}.\${COLL_NAME}\`);
|
|
45
|
+
console.log(\`Total documents to generate: \${TOTAL_DOCUMENTS} documents\`);
|
|
46
|
+
console.log(\`Batch size: \${BATCH_SIZE} documents per batch\`);
|
|
47
|
+
|
|
48
|
+
const startTime = new Date();
|
|
49
|
+
|
|
50
|
+
for (let batchStart = 0; batchStart < TOTAL_DOCUMENTS; batchStart += BATCH_SIZE) {
|
|
51
|
+
const batchEnd = Math.min(batchStart + BATCH_SIZE, TOTAL_DOCUMENTS);
|
|
52
|
+
const batchSize = batchEnd - batchStart;
|
|
53
|
+
|
|
54
|
+
console.log(\`Generating batch \${Math.floor(batchStart / BATCH_SIZE) + 1} of \${numBatches} (\${batchSize} documents)...\`);
|
|
55
|
+
|
|
56
|
+
// Generate documents for this batch
|
|
57
|
+
const batchDocuments = [];
|
|
58
|
+
for (let i = 0; i < batchSize; i++) {
|
|
59
|
+
batchDocuments.push(generateDocument());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Insert the batch
|
|
63
|
+
db.getCollection(COLL_NAME).insertMany(batchDocuments);
|
|
64
|
+
|
|
65
|
+
console.log(\`Batch inserted successfully.\`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const endTime = new Date();
|
|
69
|
+
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
70
|
+
|
|
71
|
+
console.log(\`\\n=== Mock Data Generation Complete ===\`);
|
|
72
|
+
console.log(\`Total time: \${duration} seconds\`);
|
|
73
|
+
console.log(\`Collection: \${DB_NAME}.\${COLL_NAME}\`);`;
|
|
74
|
+
// Format the script using prettier
|
|
75
|
+
const script = standalone_1.default.format(unformattedScript, {
|
|
76
|
+
plugins: [parser_babel_1.default],
|
|
77
|
+
parser: 'babel',
|
|
78
|
+
printWidth: 50,
|
|
79
|
+
trailingComma: 'none',
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
script,
|
|
83
|
+
success: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse a field path into simple parts
|
|
95
|
+
*
|
|
96
|
+
* Examples:
|
|
97
|
+
* "name" → ["name"]
|
|
98
|
+
* "user.email" → ["user", "email"]
|
|
99
|
+
* "tags[]" → ["tags", "[]"]
|
|
100
|
+
* "users[].name" → ["users", "[]", "name"]
|
|
101
|
+
* "matrix[][]" → ["matrix", "[]", "[]"]
|
|
102
|
+
*/
|
|
103
|
+
function parseFieldPath(fieldPath) {
|
|
104
|
+
const parts = [];
|
|
105
|
+
let current = '';
|
|
106
|
+
for (let i = 0; i < fieldPath.length; i++) {
|
|
107
|
+
const char = fieldPath[i];
|
|
108
|
+
if (char === '.') {
|
|
109
|
+
if (current) {
|
|
110
|
+
parts.push(current);
|
|
111
|
+
current = '';
|
|
112
|
+
}
|
|
113
|
+
else if (parts.length > 0 && parts[parts.length - 1] === '[]') {
|
|
114
|
+
// This is valid: "users[].name" - dot after array notation
|
|
115
|
+
// Continue parsing
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
throw new Error(`Invalid field path "${fieldPath}": empty field name before dot`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (char === '[' && fieldPath[i + 1] === ']') {
|
|
122
|
+
// Only treat [] as array notation if it's at the end, followed by a dot, or followed by another [
|
|
123
|
+
const isAtEnd = i + 2 >= fieldPath.length;
|
|
124
|
+
const isFollowedByDot = i + 2 < fieldPath.length && fieldPath[i + 2] === '.';
|
|
125
|
+
const isFollowedByBracket = i + 2 < fieldPath.length && fieldPath[i + 2] === '[';
|
|
126
|
+
if (isAtEnd || isFollowedByDot || isFollowedByBracket) {
|
|
127
|
+
// This is array notation
|
|
128
|
+
if (current) {
|
|
129
|
+
parts.push(current);
|
|
130
|
+
current = '';
|
|
131
|
+
}
|
|
132
|
+
parts.push('[]');
|
|
133
|
+
i++; // Skip the ]
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// This is just part of the field name
|
|
137
|
+
current += char;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
current += char;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (current) {
|
|
145
|
+
parts.push(current);
|
|
146
|
+
}
|
|
147
|
+
if (parts.length === 0) {
|
|
148
|
+
throw new Error(`Invalid field path "${fieldPath}": no valid field names found`);
|
|
149
|
+
}
|
|
150
|
+
return parts;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Build the document structure from all field paths
|
|
154
|
+
*/
|
|
155
|
+
function buildDocumentStructure(schema) {
|
|
156
|
+
const result = {};
|
|
157
|
+
// Process each field path
|
|
158
|
+
for (const [fieldPath, mapping] of Object.entries(schema)) {
|
|
159
|
+
const pathParts = parseFieldPath(fieldPath);
|
|
160
|
+
insertIntoStructure(result, pathParts, mapping);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Insert a field mapping into the structure at the given path
|
|
166
|
+
*/
|
|
167
|
+
function insertIntoStructure(structure, pathParts, mapping) {
|
|
168
|
+
if (pathParts.length === 0) {
|
|
169
|
+
throw new Error('Cannot insert field mapping: empty path parts array');
|
|
170
|
+
}
|
|
171
|
+
// Base case: insert root-level field mapping
|
|
172
|
+
if (pathParts.length === 1) {
|
|
173
|
+
const part = pathParts[0];
|
|
174
|
+
if (part === '[]') {
|
|
175
|
+
throw new Error('Invalid field path: array notation "[]" cannot be used without a field name');
|
|
176
|
+
}
|
|
177
|
+
structure[part] = mapping;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Recursive case
|
|
181
|
+
const [firstPart, secondPart, ...remainingParts] = pathParts;
|
|
182
|
+
if (secondPart === '[]') {
|
|
183
|
+
// This is an array field
|
|
184
|
+
// Initialize array structure if it doesn't exist yet
|
|
185
|
+
if (!structure[firstPart] ||
|
|
186
|
+
typeof structure[firstPart] !== 'object' ||
|
|
187
|
+
!('type' in structure[firstPart]) ||
|
|
188
|
+
structure[firstPart].type !== 'array') {
|
|
189
|
+
structure[firstPart] = {
|
|
190
|
+
type: 'array',
|
|
191
|
+
elementType: {},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const arrayStructure = structure[firstPart];
|
|
195
|
+
if (remainingParts.length === 0) {
|
|
196
|
+
// Terminal case: Array of primitives (e.g., "tags[]")
|
|
197
|
+
// Directly assign the field mapping as the element type
|
|
198
|
+
arrayStructure.elementType = mapping;
|
|
199
|
+
}
|
|
200
|
+
else if (remainingParts[0] === '[]') {
|
|
201
|
+
// Nested array case: Multi-dimensional arrays (e.g., "matrix[][]")
|
|
202
|
+
// Build nested array structure
|
|
203
|
+
let currentArray = arrayStructure;
|
|
204
|
+
let i = 0;
|
|
205
|
+
// Process consecutive [] markers to build nested array structure
|
|
206
|
+
// Each iteration creates one array dimension (eg. matrix[][] = 2 iterations)
|
|
207
|
+
while (i < remainingParts.length && remainingParts[i] === '[]') {
|
|
208
|
+
// Create the next array dimension
|
|
209
|
+
currentArray.elementType = {
|
|
210
|
+
type: 'array',
|
|
211
|
+
elementType: {},
|
|
212
|
+
};
|
|
213
|
+
// Move to the next nesting level
|
|
214
|
+
currentArray = currentArray.elementType;
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
if (i < remainingParts.length) {
|
|
218
|
+
// This is an multi-dimensional array of documents (e.g., "matrix[][].name")
|
|
219
|
+
// Ensure we have a document structure for the remaining fields
|
|
220
|
+
if (typeof currentArray.elementType !== 'object' ||
|
|
221
|
+
'mongoType' in currentArray.elementType ||
|
|
222
|
+
'type' in currentArray.elementType) {
|
|
223
|
+
currentArray.elementType = {};
|
|
224
|
+
}
|
|
225
|
+
// Recursively build the document
|
|
226
|
+
insertIntoStructure(currentArray.elementType, remainingParts.slice(i), mapping);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Pure multi-dimensional array - assign the mapping
|
|
230
|
+
currentArray.elementType = mapping;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Object case: Array of documents with fields (e.g., "users[].name", "users[].profile.email")
|
|
235
|
+
// Only initialize if elementType isn't already a proper object structure
|
|
236
|
+
if (typeof arrayStructure.elementType !== 'object' ||
|
|
237
|
+
'mongoType' in arrayStructure.elementType ||
|
|
238
|
+
'type' in arrayStructure.elementType) {
|
|
239
|
+
arrayStructure.elementType = {};
|
|
240
|
+
}
|
|
241
|
+
// Recursively build the object structure for array elements
|
|
242
|
+
insertIntoStructure(arrayStructure.elementType, remainingParts, mapping);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// This is a regular object field
|
|
247
|
+
// Only initialize if it doesn't exist or isn't a plain object
|
|
248
|
+
if (!structure[firstPart] ||
|
|
249
|
+
typeof structure[firstPart] !== 'object' ||
|
|
250
|
+
'type' in structure[firstPart] ||
|
|
251
|
+
'mongoType' in structure[firstPart]) {
|
|
252
|
+
structure[firstPart] = {};
|
|
253
|
+
}
|
|
254
|
+
insertIntoStructure(structure[firstPart], [secondPart, ...remainingParts], mapping);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Generate JavaScript object code from document structure
|
|
259
|
+
*/
|
|
260
|
+
function renderDocumentCode(structure, arrayLengthMap = {}, currentPath = '') {
|
|
261
|
+
// For each field in structure:
|
|
262
|
+
// - If FakerFieldMapping: generate faker call
|
|
263
|
+
// - If DocumentStructure: generate nested object
|
|
264
|
+
// - If ArrayStructure: generate array
|
|
265
|
+
const documentFields = [];
|
|
266
|
+
for (const [fieldName, value] of Object.entries(structure)) {
|
|
267
|
+
if ('mongoType' in value) {
|
|
268
|
+
// It's a field mapping
|
|
269
|
+
const mapping = value;
|
|
270
|
+
const fakerCall = generateFakerCall(mapping);
|
|
271
|
+
// Default to 1.0 for invalid probability values
|
|
272
|
+
let probability = 1.0;
|
|
273
|
+
if (mapping.probability !== undefined &&
|
|
274
|
+
typeof mapping.probability === 'number' &&
|
|
275
|
+
mapping.probability >= 0 &&
|
|
276
|
+
mapping.probability <= 1) {
|
|
277
|
+
probability = mapping.probability;
|
|
278
|
+
}
|
|
279
|
+
if (probability < 1.0) {
|
|
280
|
+
// Use Math.random for conditional field inclusion
|
|
281
|
+
documentFields.push(`...(Math.random() < ${probability} ? { ${formatFieldName(fieldName)}: ${fakerCall} } : {})`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// Normal field inclusion
|
|
285
|
+
documentFields.push(`${formatFieldName(fieldName)}: ${fakerCall}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if ('type' in value && value.type === 'array') {
|
|
289
|
+
// It's an array
|
|
290
|
+
const fieldPath = currentPath
|
|
291
|
+
? `${currentPath}.${fieldName}[]`
|
|
292
|
+
: `${fieldName}[]`;
|
|
293
|
+
const arrayCode = renderArrayCode(value, fieldName, arrayLengthMap, fieldPath);
|
|
294
|
+
documentFields.push(`${formatFieldName(fieldName)}: ${arrayCode}`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// It's a nested object: recursive call
|
|
298
|
+
const nestedPath = currentPath
|
|
299
|
+
? `${currentPath}.${fieldName}`
|
|
300
|
+
: fieldName;
|
|
301
|
+
const nestedCode = renderDocumentCode(value, arrayLengthMap, nestedPath);
|
|
302
|
+
documentFields.push(`${formatFieldName(fieldName)}: ${nestedCode}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Handle empty objects
|
|
306
|
+
if (documentFields.length === 0) {
|
|
307
|
+
return '{}';
|
|
308
|
+
}
|
|
309
|
+
return `{${documentFields.join(',')}}`;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Serializes a value into a JS literal that is safe to embed in generated code.
|
|
313
|
+
* JSON.stringify alone leaves characters that can break out of some contexts.
|
|
314
|
+
*/
|
|
315
|
+
function toStringLiteral(value) {
|
|
316
|
+
return JSON.stringify(value).replace(/[<>/\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Formats a field name for use in JavaScript object literal syntax.
|
|
320
|
+
* Only quotes field names that need it.
|
|
321
|
+
*/
|
|
322
|
+
function formatFieldName(fieldName) {
|
|
323
|
+
// If it's a valid JavaScript identifier, don't quote it
|
|
324
|
+
const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(fieldName);
|
|
325
|
+
if (isValidIdentifier) {
|
|
326
|
+
return fieldName;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
return toStringLiteral(fieldName);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Generate array code
|
|
334
|
+
*/
|
|
335
|
+
function renderArrayCode(arrayStructure, fieldName = '', arrayLengthMap = {}, currentFieldPath = '') {
|
|
336
|
+
const elementType = arrayStructure.elementType;
|
|
337
|
+
// Get array length for this dimension
|
|
338
|
+
let arrayLength = DEFAULT_ARRAY_LENGTH;
|
|
339
|
+
if (currentFieldPath && arrayLengthMap[currentFieldPath] !== undefined) {
|
|
340
|
+
arrayLength = arrayLengthMap[currentFieldPath];
|
|
341
|
+
}
|
|
342
|
+
if ('mongoType' in elementType) {
|
|
343
|
+
// Array of primitives
|
|
344
|
+
const fakerCall = generateFakerCall(elementType);
|
|
345
|
+
return `Array.from({length: ${arrayLength}}, () => ${fakerCall})`;
|
|
346
|
+
}
|
|
347
|
+
else if ('type' in elementType && elementType.type === 'array') {
|
|
348
|
+
// Nested array (e.g., matrix[][]) - append another [] to the path
|
|
349
|
+
const fieldPath = currentFieldPath + '[]';
|
|
350
|
+
const nestedArrayCode = renderArrayCode(elementType, fieldName, arrayLengthMap, fieldPath);
|
|
351
|
+
return `Array.from({length: ${arrayLength}}, () => ${nestedArrayCode})`;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const objectCode = renderDocumentCode(elementType, arrayLengthMap, currentFieldPath);
|
|
355
|
+
return `Array.from({length: ${arrayLength}}, () => (${objectCode}))`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Generate faker.js call from field mapping
|
|
360
|
+
*/
|
|
361
|
+
function generateFakerCall(mapping) {
|
|
362
|
+
const method = mapping.fakerMethod === types_1.UNRECOGNIZED_FAKER_METHOD
|
|
363
|
+
? getDefaultFakerMethod(mapping.mongoType)
|
|
364
|
+
: mapping.fakerMethod;
|
|
365
|
+
// Use direct ObjectId generation for MongoDB ObjectIds
|
|
366
|
+
if (method === 'database.mongodbObjectId') {
|
|
367
|
+
return 'new ObjectId()';
|
|
368
|
+
}
|
|
369
|
+
const args = formatFakerArgs(mapping.fakerArgs);
|
|
370
|
+
return `faker.${method}(${args})`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Gets default faker method for unrecognized fields based on MongoDB type
|
|
374
|
+
*/
|
|
375
|
+
function getDefaultFakerMethod(mongoType) {
|
|
376
|
+
switch (mongoType) {
|
|
377
|
+
// String types
|
|
378
|
+
case 'String':
|
|
379
|
+
return 'lorem.word';
|
|
380
|
+
// Numeric types
|
|
381
|
+
case 'Number':
|
|
382
|
+
case 'Int32':
|
|
383
|
+
case 'Long':
|
|
384
|
+
return 'number.int';
|
|
385
|
+
case 'Decimal128':
|
|
386
|
+
return 'number.float';
|
|
387
|
+
// Date and time types
|
|
388
|
+
case 'Date':
|
|
389
|
+
case 'Timestamp':
|
|
390
|
+
return 'date.recent';
|
|
391
|
+
// Object identifier
|
|
392
|
+
case 'ObjectId':
|
|
393
|
+
return 'database.mongodbObjectId';
|
|
394
|
+
// Boolean
|
|
395
|
+
case 'Boolean':
|
|
396
|
+
return 'datatype.boolean';
|
|
397
|
+
// Binary
|
|
398
|
+
case 'Binary':
|
|
399
|
+
return 'string.hexadecimal';
|
|
400
|
+
// Regular expression
|
|
401
|
+
case 'RegExp':
|
|
402
|
+
return 'lorem.word';
|
|
403
|
+
// JavaScript code
|
|
404
|
+
case 'Code':
|
|
405
|
+
return 'lorem.sentence';
|
|
406
|
+
// MinKey and MaxKey
|
|
407
|
+
case 'MinKey':
|
|
408
|
+
return 'number.int';
|
|
409
|
+
case 'MaxKey':
|
|
410
|
+
return 'number.int';
|
|
411
|
+
// Symbol (deprecated)
|
|
412
|
+
case 'Symbol':
|
|
413
|
+
return 'lorem.word';
|
|
414
|
+
// DBRef
|
|
415
|
+
case 'DBRef':
|
|
416
|
+
return 'database.mongodbObjectId';
|
|
417
|
+
// Default fallback
|
|
418
|
+
default:
|
|
419
|
+
return 'lorem.word';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Converts array of faker arguments to comma separated string for function calls.
|
|
424
|
+
*
|
|
425
|
+
* Serializes various argument types into valid JavaScript syntax:
|
|
426
|
+
* - Strings: Uses JSON.stringify() to handle quotes, newlines, and special characters
|
|
427
|
+
* - Numbers: Validates finite numbers and converts to string representation
|
|
428
|
+
* - Booleans: Converts to 'true' or 'false' literals
|
|
429
|
+
* - Objects with 'json' property: Parses and re-stringifies JSON for validation
|
|
430
|
+
*
|
|
431
|
+
* @param fakerArgs - Array of arguments to convert to JavaScript code
|
|
432
|
+
* @returns Comma-separated string of JavaScript arguments, or empty string if no args
|
|
433
|
+
* @throws Error if arguments contain invalid values (non-finite numbers, malformed JSON)
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* formatFakerArgs(['male', 25, true]) // Returns: '"male", 25, true'
|
|
437
|
+
* formatFakerArgs([{json: '{"min": 1}'}]) // Returns: '{"min": 1}'
|
|
438
|
+
*/
|
|
439
|
+
function formatFakerArgs(fakerArgs) {
|
|
440
|
+
const stringifiedArgs = [];
|
|
441
|
+
for (let i = 0; i < fakerArgs.length; i++) {
|
|
442
|
+
const arg = fakerArgs[i];
|
|
443
|
+
if (typeof arg === 'string') {
|
|
444
|
+
stringifiedArgs.push(toStringLiteral(arg));
|
|
445
|
+
}
|
|
446
|
+
else if (typeof arg === 'number') {
|
|
447
|
+
if (!Number.isFinite(arg)) {
|
|
448
|
+
throw new Error(`Invalid number argument at index ${i}: must be a finite number`);
|
|
449
|
+
}
|
|
450
|
+
stringifiedArgs.push(`${arg}`);
|
|
451
|
+
}
|
|
452
|
+
else if (typeof arg === 'boolean') {
|
|
453
|
+
stringifiedArgs.push(`${arg}`);
|
|
454
|
+
}
|
|
455
|
+
else if (typeof arg === 'object' && arg !== null && 'json' in arg) {
|
|
456
|
+
// Re-serialize so only a JSON value, never arbitrary code, is emitted
|
|
457
|
+
stringifiedArgs.push(toStringLiteral(JSON.parse(arg.json)));
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
throw new Error(`Invalid argument type at index ${i}: expected string, number, boolean, or {json: string}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return stringifiedArgs.join(', ');
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Generates documents for the PreviewScreen component.
|
|
467
|
+
* Executes faker methods to create actual document objects.
|
|
468
|
+
*/
|
|
469
|
+
function generateDocument(fakerSchema, arrayLengthMap = {}) {
|
|
470
|
+
const structure = buildDocumentStructure(fakerSchema);
|
|
471
|
+
return constructDocumentValues(structure, arrayLengthMap);
|
|
472
|
+
}
|
|
473
|
+
function computeValue(elementType, arrayLengthMap, currentPath) {
|
|
474
|
+
try {
|
|
475
|
+
if ('mongoType' in elementType) {
|
|
476
|
+
// It's a field mapping
|
|
477
|
+
const mapping = elementType;
|
|
478
|
+
// Default to 1.0 for invalid probability values
|
|
479
|
+
let probability = 1.0;
|
|
480
|
+
if (typeof mapping.probability === 'number' &&
|
|
481
|
+
mapping.probability >= 0 &&
|
|
482
|
+
mapping.probability <= 1) {
|
|
483
|
+
probability = mapping.probability;
|
|
484
|
+
}
|
|
485
|
+
const shouldIncludeField = probability >= 1.0 || Math.random() < probability;
|
|
486
|
+
if (shouldIncludeField) {
|
|
487
|
+
return generateFakerValue(mapping);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else if ('type' in elementType && elementType.type === 'array') {
|
|
491
|
+
return constructArrayValues(elementType, arrayLengthMap, `${currentPath}[]`);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
return constructDocumentValues(elementType, arrayLengthMap, currentPath);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Skip invalid faker methods
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Construct actual document values from document structure.
|
|
503
|
+
* Mirrors renderDocumentCode but executes faker calls instead of generating code.
|
|
504
|
+
*/
|
|
505
|
+
function constructDocumentValues(structure, arrayLengthMap = {}, currentPath = '') {
|
|
506
|
+
const result = {};
|
|
507
|
+
for (const [fieldName, value] of Object.entries(structure)) {
|
|
508
|
+
const newPath = currentPath ? `${currentPath}.${fieldName}` : fieldName;
|
|
509
|
+
const val = computeValue(value, arrayLengthMap, newPath);
|
|
510
|
+
if (val !== undefined) {
|
|
511
|
+
result[fieldName] = val;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Construct array values from array structure.
|
|
518
|
+
* Mirrors renderArrayCode but executes faker calls instead of generating code.
|
|
519
|
+
*/
|
|
520
|
+
function constructArrayValues(arrayStructure, arrayLengthMap, currentPath) {
|
|
521
|
+
const elementType = arrayStructure.elementType;
|
|
522
|
+
// Get array length for this dimension
|
|
523
|
+
let arrayLength = DEFAULT_ARRAY_LENGTH;
|
|
524
|
+
if (arrayLengthMap[currentPath] !== undefined) {
|
|
525
|
+
arrayLength = arrayLengthMap[currentPath];
|
|
526
|
+
}
|
|
527
|
+
const result = [];
|
|
528
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
529
|
+
result.push(computeValue(elementType, arrayLengthMap, currentPath));
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Prepare faker arguments for execution.
|
|
535
|
+
* Converts FakerArg[] to actual values that can be passed to faker methods.
|
|
536
|
+
*/
|
|
537
|
+
function prepareFakerArgs(fakerArgs) {
|
|
538
|
+
const preparedArgs = [];
|
|
539
|
+
for (const arg of fakerArgs) {
|
|
540
|
+
if (typeof arg === 'string' ||
|
|
541
|
+
typeof arg === 'number' ||
|
|
542
|
+
typeof arg === 'boolean') {
|
|
543
|
+
preparedArgs.push(arg);
|
|
544
|
+
}
|
|
545
|
+
else if (typeof arg === 'object' && arg !== null && 'json' in arg) {
|
|
546
|
+
// Parse JSON objects
|
|
547
|
+
try {
|
|
548
|
+
const jsonArg = arg;
|
|
549
|
+
preparedArgs.push(JSON.parse(jsonArg.json));
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// Skip invalid JSON
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return preparedArgs;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Execute faker method to generate actual values.
|
|
561
|
+
* Mirrors generateFakerCall but executes the call instead of generating code.
|
|
562
|
+
*/
|
|
563
|
+
function generateFakerValue(mapping) {
|
|
564
|
+
const method = mapping.fakerMethod === types_1.UNRECOGNIZED_FAKER_METHOD
|
|
565
|
+
? getDefaultFakerMethod(mapping.mongoType)
|
|
566
|
+
: mapping.fakerMethod;
|
|
567
|
+
try {
|
|
568
|
+
// Navigate to the faker method
|
|
569
|
+
const methodParts = method.split('.');
|
|
570
|
+
let fakerMethod = en_1.faker;
|
|
571
|
+
for (const part of methodParts) {
|
|
572
|
+
fakerMethod = fakerMethod[part];
|
|
573
|
+
if (!fakerMethod) {
|
|
574
|
+
throw new Error(`Faker method not found: ${method}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Prepare arguments
|
|
578
|
+
const args = prepareFakerArgs(mapping.fakerArgs);
|
|
579
|
+
// Call the faker method
|
|
580
|
+
const result = fakerMethod.apply(en_1.faker, args);
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PrimitiveSchemaType } from '@mongodb-js/mongodb-schema';
|
|
2
|
+
/** A MongoDB primitive type name, as reported by schema analysis. */
|
|
3
|
+
export type MongoDBFieldType = PrimitiveSchemaType['name'];
|
|
4
|
+
/** Sentinel used when the LLM does not map a field to a known faker method. */
|
|
5
|
+
export declare const UNRECOGNIZED_FAKER_METHOD = "Unrecognized";
|
|
6
|
+
export type FakerArg = string | number | boolean | {
|
|
7
|
+
json: string;
|
|
8
|
+
} | FakerArg[];
|
|
9
|
+
export type ArrayLengthMap = Record<string, number>;
|
|
10
|
+
export interface ScriptOptions {
|
|
11
|
+
documentCount: number;
|
|
12
|
+
databaseName: string;
|
|
13
|
+
collectionName: string;
|
|
14
|
+
arrayLengthMap?: ArrayLengthMap;
|
|
15
|
+
}
|
|
16
|
+
export interface FakerFieldMapping {
|
|
17
|
+
mongoType: MongoDBFieldType;
|
|
18
|
+
fakerMethod: string;
|
|
19
|
+
fakerArgs: FakerArg[];
|
|
20
|
+
probability?: number;
|
|
21
|
+
}
|
|
22
|
+
export type FakerSchema = Record<string, FakerFieldMapping>;
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAE3D,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAExD,MAAM,MAAM,QAAQ,GAClB,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,EAAE,CAAC;AAM5D,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC"}
|
package/dist/types.js
ADDED