@zero-server/orm 0.9.0 → 0.9.2

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 (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1 -1
  3. package/index.js +35 -35
  4. package/lib/debug.js +372 -0
  5. package/lib/orm/adapters/json.js +290 -0
  6. package/lib/orm/adapters/memory.js +764 -0
  7. package/lib/orm/adapters/mongo.js +764 -0
  8. package/lib/orm/adapters/mysql.js +933 -0
  9. package/lib/orm/adapters/postgres.js +1144 -0
  10. package/lib/orm/adapters/redis.js +1534 -0
  11. package/lib/orm/adapters/sql-base.js +212 -0
  12. package/lib/orm/adapters/sqlite.js +858 -0
  13. package/lib/orm/audit.js +649 -0
  14. package/lib/orm/cache.js +394 -0
  15. package/lib/orm/geo.js +387 -0
  16. package/lib/orm/index.js +784 -0
  17. package/lib/orm/migrate.js +432 -0
  18. package/lib/orm/model.js +1706 -0
  19. package/lib/orm/plugin.js +375 -0
  20. package/lib/orm/procedures.js +836 -0
  21. package/lib/orm/profiler.js +233 -0
  22. package/lib/orm/query.js +1772 -0
  23. package/lib/orm/replicas.js +241 -0
  24. package/lib/orm/schema.js +307 -0
  25. package/lib/orm/search.js +380 -0
  26. package/lib/orm/seed/data/commerce.js +136 -0
  27. package/lib/orm/seed/data/internet.js +111 -0
  28. package/lib/orm/seed/data/locations.js +204 -0
  29. package/lib/orm/seed/data/names.js +338 -0
  30. package/lib/orm/seed/data/person.js +128 -0
  31. package/lib/orm/seed/data/phone.js +211 -0
  32. package/lib/orm/seed/data/words.js +134 -0
  33. package/lib/orm/seed/factory.js +178 -0
  34. package/lib/orm/seed/fake.js +1186 -0
  35. package/lib/orm/seed/index.js +18 -0
  36. package/lib/orm/seed/rng.js +71 -0
  37. package/lib/orm/seed/seeder.js +125 -0
  38. package/lib/orm/seed/unique.js +68 -0
  39. package/lib/orm/snapshot.js +366 -0
  40. package/lib/orm/tenancy.js +605 -0
  41. package/lib/orm/views.js +350 -0
  42. package/package.json +12 -3
@@ -0,0 +1,380 @@
1
+ /**
2
+ * @module orm/search
3
+ * @description Full-text search integration for the ORM.
4
+ * Provides a unified API across PostgreSQL (tsvector/tsquery),
5
+ * MySQL (FULLTEXT), SQLite (FTS5), and in-memory (regex-based).
6
+ *
7
+ * @section Full-Text Search
8
+ *
9
+ * @example
10
+ * const { FullTextSearch } = require('@zero-server/sdk');
11
+ *
12
+ * // Create a search index
13
+ * const search = new FullTextSearch(Article, {
14
+ * fields: ['title', 'body'],
15
+ * weights: { title: 'A', body: 'B' },
16
+ * });
17
+ *
18
+ * // Create the index in the database
19
+ * await search.createIndex(db);
20
+ *
21
+ * // Search
22
+ * const results = await search.search('javascript framework');
23
+ * const ranked = await search.search('node.js', { rank: true, limit: 10 });
24
+ */
25
+
26
+ const log = require('../debug')('zero:orm:search');
27
+
28
+ // -- FullTextSearch class ---------------------------------
29
+
30
+ /**
31
+ * Full-text search engine for ORM models.
32
+ * Provides a unified search API that adapts to the underlying database engine.
33
+ *
34
+ * @param {typeof Model} ModelClass - Model class to search.
35
+ * @param {object} options - Search configuration.
36
+ * @param {string[]} options.fields - Column names to include in the search index.
37
+ * @param {Object<string, string>} [options.weights] - Weight map for fields. PostgreSQL: 'A'–'D'. Others: numeric multiplier.
38
+ * @param {string} [options.language='english'] - Language for stemming/tokenisation.
39
+ * @param {string} [options.indexName] - Custom index name.
40
+ */
41
+ class FullTextSearch
42
+ {
43
+ /**
44
+ * @constructor
45
+ * @param {typeof Model} ModelClass - Model class to search.
46
+ * @param {object} options - Configuration options.
47
+ * @param {string[]} options.fields - Column names to include in the search index.
48
+ * @param {Object<string, string>} [options.weights] - Weight map for fields (e.g. `{ title: 'A', body: 'B' }`).
49
+ * @param {string} [options.language='english'] - Language for stemming.
50
+ * @param {string} [options.indexName] - Custom index name.
51
+ */
52
+ constructor(ModelClass, options = {})
53
+ {
54
+ if (!ModelClass) throw new Error('FullTextSearch requires a Model class');
55
+ if (!options.fields || !options.fields.length) throw new Error('FullTextSearch requires at least one field');
56
+
57
+ /** @type {typeof Model} */
58
+ this._model = ModelClass;
59
+
60
+ /** @type {string[]} Fields to index. */
61
+ this._fields = options.fields;
62
+
63
+ /** @type {Object<string, string>} Field weight configuration. */
64
+ this._weights = options.weights || {};
65
+
66
+ /** @type {string} Language for stemming. */
67
+ this._language = options.language || 'english';
68
+
69
+ /** @type {string} Index name. */
70
+ this._indexName = options.indexName || `fts_${ModelClass.table}_${this._fields.join('_')}`;
71
+
72
+ /** @type {object|null} Database adapter. */
73
+ this._adapter = null;
74
+
75
+ /** @type {string|null} Detected adapter type. */
76
+ this._adapterType = null;
77
+ }
78
+
79
+ /**
80
+ * Create the full-text search index.
81
+ * Adapts to the underlying database:
82
+ * - PostgreSQL: creates a GIN index on tsvector columns
83
+ * - MySQL: creates a FULLTEXT index
84
+ * - SQLite: creates an FTS5 virtual table
85
+ * - Memory/JSON: no-op (search operates with in-memory regex)
86
+ *
87
+ * @param {object} db - Database instance.
88
+ * @returns {Promise<FullTextSearch>} `this` for chaining.
89
+ *
90
+ * @example
91
+ * await search.createIndex(db);
92
+ */
93
+ async createIndex(db)
94
+ {
95
+ this._adapter = db.adapter;
96
+ this._adapterType = this._detectAdapterType();
97
+
98
+ if (typeof this._adapter.createFullTextIndex === 'function')
99
+ {
100
+ await this._adapter.createFullTextIndex(this._model.table, this._fields, {
101
+ name: this._indexName,
102
+ weights: this._weights,
103
+ language: this._language,
104
+ });
105
+ }
106
+
107
+ log.debug('fts index %s created on %s', this._indexName, this._model.table);
108
+ return this;
109
+ }
110
+
111
+ /**
112
+ * Drop the full-text search index.
113
+ *
114
+ * @param {object} db - Database instance.
115
+ * @returns {Promise<void>}
116
+ */
117
+ async dropIndex(db)
118
+ {
119
+ const adapter = db ? db.adapter : this._adapter;
120
+ if (!adapter) throw new Error('No database adapter available');
121
+
122
+ if (typeof adapter.dropFullTextIndex === 'function')
123
+ {
124
+ await adapter.dropFullTextIndex(this._model.table, this._indexName);
125
+ }
126
+
127
+ log.debug('fts index %s dropped', this._indexName);
128
+ }
129
+
130
+ /**
131
+ * Perform a full-text search.
132
+ *
133
+ * @param {string} query - Search query string.
134
+ * @param {object} [options] - Search options.
135
+ * @param {boolean} [options.rank=false] - Include relevance ranking in results.
136
+ * @param {number} [options.limit] - Maximum number of results.
137
+ * @param {number} [options.offset] - Offset for pagination.
138
+ * @param {object} [options.where] - Additional WHERE conditions.
139
+ * @param {string} [options.orderBy] - Custom order ('rank' for relevance, or a column name).
140
+ * @returns {Promise<Array<object>>} Search results, optionally with `_rank` scores.
141
+ *
142
+ * @example
143
+ * // Simple search
144
+ * const results = await search.search('javascript');
145
+ *
146
+ * // Ranked search with filters
147
+ * const results = await search.search('node.js framework', {
148
+ * rank: true,
149
+ * limit: 10,
150
+ * where: { published: true },
151
+ * });
152
+ * // results[0]._rank => 0.95 (relevance score)
153
+ */
154
+ async search(query, options = {})
155
+ {
156
+ if (!query || typeof query !== 'string') return [];
157
+
158
+ const adapter = this._adapter || this._model._adapter;
159
+ if (!adapter) throw new Error('Model is not registered with a database');
160
+
161
+ // Use adapter native FTS if available
162
+ if (typeof adapter.fullTextSearch === 'function')
163
+ {
164
+ return adapter.fullTextSearch(this._model.table, this._fields, query, {
165
+ ...options,
166
+ language: this._language,
167
+ weights: this._weights,
168
+ model: this._model,
169
+ });
170
+ }
171
+
172
+ // Fallback: in-memory search
173
+ return this._memorySearch(query, options);
174
+ }
175
+
176
+ /**
177
+ * Search and return model instances instead of plain objects.
178
+ *
179
+ * @param {string} query - Search query string.
180
+ * @param {object} [options] - Search options (same as search()).
181
+ * @returns {Promise<Array<Model>>} Model instances matching the search query.
182
+ *
183
+ * @example
184
+ * const articles = await search.searchModels('javascript');
185
+ * articles[0].title // => 'Learning JavaScript'
186
+ */
187
+ async searchModels(query, options = {})
188
+ {
189
+ const rows = await this.search(query, options);
190
+ return rows.map(row =>
191
+ {
192
+ const inst = this._model._fromRow(row);
193
+ if (row._rank !== undefined) inst._rank = row._rank;
194
+ return inst;
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Count matching search results.
200
+ *
201
+ * @param {string} query - Search query string.
202
+ * @param {object} [options] - Additional WHERE conditions in `options.where`.
203
+ * @returns {Promise<number>} Number of matching records.
204
+ */
205
+ async count(query, options = {})
206
+ {
207
+ const results = await this.search(query, { ...options, rank: false });
208
+ return results.length;
209
+ }
210
+
211
+ /**
212
+ * Build search suggestions (autocomplete) from indexed fields.
213
+ *
214
+ * @param {string} prefix - Partial search term.
215
+ * @param {object} [options] - Configuration options.
216
+ * @param {number} [options.limit=10] - Max suggestions.
217
+ * @param {string} [options.field] - Specific field to suggest from.
218
+ * @returns {Promise<string[]>} Matching suggestions.
219
+ *
220
+ * @example
221
+ * const suggestions = await search.suggest('jav', { limit: 5 });
222
+ * // => ['JavaScript', 'Java', 'Javelin']
223
+ */
224
+ async suggest(prefix, options = {})
225
+ {
226
+ const { limit = 10, field } = options;
227
+ if (!prefix || typeof prefix !== 'string') return [];
228
+
229
+ const searchFields = field ? [field] : this._fields;
230
+ const adapter = this._adapter || this._model._adapter;
231
+ if (!adapter) throw new Error('Model is not registered with a database');
232
+
233
+ // Use adapter-native suggest if available
234
+ if (typeof adapter.fullTextSuggest === 'function')
235
+ {
236
+ return adapter.fullTextSuggest(this._model.table, searchFields, prefix, { limit });
237
+ }
238
+
239
+ // Fallback: in-memory suggestion
240
+ const q = this._model.query();
241
+ const results = await q.exec();
242
+ const seen = new Set();
243
+ const suggestions = [];
244
+ const lowerPrefix = prefix.toLowerCase();
245
+
246
+ for (const row of results)
247
+ {
248
+ for (const f of searchFields)
249
+ {
250
+ const val = row[f];
251
+ if (!val) continue;
252
+ const words = String(val).split(/\s+/);
253
+ for (const word of words)
254
+ {
255
+ const lower = word.toLowerCase();
256
+ if (lower.startsWith(lowerPrefix) && !seen.has(lower))
257
+ {
258
+ seen.add(lower);
259
+ suggestions.push(word);
260
+ if (suggestions.length >= limit) return suggestions;
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return suggestions;
267
+ }
268
+
269
+ /**
270
+ * In-memory full-text search using regex matching and scoring.
271
+ * Used as fallback for memory/json adapters.
272
+ * @param {string} query - Search query.
273
+ * @param {object} options - Search options.
274
+ * @returns {Promise<Array>} Matching rows with optional _rank.
275
+ * @private
276
+ */
277
+ async _memorySearch(query, options = {})
278
+ {
279
+ const { rank = false, limit, offset = 0, where = {} } = options;
280
+
281
+ // Tokenise query into words
282
+ const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
283
+ if (!tokens.length) return [];
284
+
285
+ // Build regex for each token (escape special chars)
286
+ const patterns = tokens.map(t =>
287
+ {
288
+ const escaped = t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
289
+ return new RegExp(escaped, 'i');
290
+ });
291
+
292
+ // Get all records, with optional WHERE conditions
293
+ let q = this._model.query();
294
+ if (Object.keys(where).length) q = q.where(where);
295
+ const allRows = await q.exec();
296
+
297
+ // Score each row
298
+ const scored = [];
299
+ for (const row of allRows)
300
+ {
301
+ let score = 0;
302
+ let matched = false;
303
+
304
+ for (const field of this._fields)
305
+ {
306
+ const val = row[field];
307
+ if (!val) continue;
308
+ const text = String(val).toLowerCase();
309
+ const weight = this._getWeight(field);
310
+
311
+ for (const pattern of patterns)
312
+ {
313
+ const matches = text.match(new RegExp(pattern.source, 'gi'));
314
+ if (matches)
315
+ {
316
+ matched = true;
317
+ score += matches.length * weight;
318
+ }
319
+ }
320
+ }
321
+
322
+ if (matched)
323
+ {
324
+ const data = row.toJSON ? row.toJSON() : { ...row };
325
+ if (rank) data._rank = score;
326
+ scored.push({ data, score });
327
+ }
328
+ }
329
+
330
+ // Sort by score (descending)
331
+ scored.sort((a, b) => b.score - a.score);
332
+
333
+ // Apply pagination
334
+ let results = scored.map(s => s.data);
335
+ if (offset) results = results.slice(offset);
336
+ if (limit) results = results.slice(0, limit);
337
+
338
+ return results;
339
+ }
340
+
341
+ /**
342
+ * Get numeric weight for a field.
343
+ * @param {string} field - Field name.
344
+ * @returns {number} Numeric weight multiplier.
345
+ * @private
346
+ */
347
+ _getWeight(field)
348
+ {
349
+ const w = this._weights[field];
350
+ if (!w) return 1;
351
+ // PostgreSQL-style weights: A=4, B=3, C=2, D=1
352
+ if (typeof w === 'string')
353
+ {
354
+ const map = { A: 4, B: 3, C: 2, D: 1 };
355
+ return map[w.toUpperCase()] || 1;
356
+ }
357
+ return Number(w) || 1;
358
+ }
359
+
360
+ /**
361
+ * Detect the adapter type from its constructor or methods.
362
+ * @returns {string} Adapter type identifier.
363
+ * @private
364
+ */
365
+ _detectAdapterType()
366
+ {
367
+ const adapter = this._adapter;
368
+ if (!adapter) return 'memory';
369
+ const name = adapter.constructor.name.toLowerCase();
370
+ if (name.includes('postgres')) return 'postgres';
371
+ if (name.includes('mysql')) return 'mysql';
372
+ if (name.includes('sqlite')) return 'sqlite';
373
+ if (name.includes('mongo')) return 'mongo';
374
+ if (name.includes('redis')) return 'redis';
375
+ if (name.includes('json')) return 'json';
376
+ return 'memory';
377
+ }
378
+ }
379
+
380
+ module.exports = { FullTextSearch };
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module seed/data/commerce
5
+ * @description Data pools for commerce, product, and company fake data.
6
+ */
7
+
8
+ /** Adjectives applied to product names. */
9
+ const PRODUCT_ADJECTIVES = [
10
+ 'Ergonomic', 'Electronic', 'Smart', 'Professional', 'Ultra', 'Premium',
11
+ 'Heavy-Duty', 'Lightweight', 'Compact', 'Portable', 'Advanced', 'Wireless',
12
+ 'Digital', 'Classic', 'Modern', 'Handcrafted', 'Luxurious', 'Eco-Friendly',
13
+ 'Industrial', 'Vintage', 'Rustic', 'Sleek', 'Durable', 'Flexible',
14
+ 'Multi-Purpose', 'Automated', 'Intelligent', 'Refined', 'Next-Gen', 'Essential',
15
+ ];
16
+
17
+ /** Materials for product names. */
18
+ const PRODUCT_MATERIALS = [
19
+ 'Steel', 'Wooden', 'Leather', 'Plastic', 'Marble', 'Metal', 'Concrete',
20
+ 'Bronze', 'Bamboo', 'Aluminum', 'Carbon Fiber', 'Ceramic', 'Glass',
21
+ 'Crystal', 'Silk', 'Cotton', 'Copper', 'Iron', 'Granite', 'Rubber',
22
+ 'Titanium', 'Brass', 'Linen', 'Mesh', 'Polyester', 'Nylon', 'Wool',
23
+ ];
24
+
25
+ /** Product category nouns. */
26
+ const PRODUCT_NOUNS = [
27
+ 'Chair', 'Table', 'Shirt', 'Ball', 'Gloves', 'Pants', 'Shoes', 'Hat',
28
+ 'Keyboard', 'Mouse', 'Monitor', 'Laptop', 'Phone', 'Tablet', 'Speaker',
29
+ 'Watch', 'Bag', 'Wallet', 'Coat', 'Glasses', 'Lamp', 'Desk', 'Shelf',
30
+ 'Pillow', 'Blanket', 'Towel', 'Mug', 'Bottle', 'Bowl', 'Knife', 'Pan',
31
+ 'Bike', 'Helmet', 'Rack', 'Stand', 'Holder', 'Case', 'Cover', 'Mount',
32
+ 'Charger', 'Cable', 'Drive', 'Camera', 'Headphones', 'Microphone', 'Printer',
33
+ 'Mat', 'Brush', 'Comb', 'Mirror', 'Clock', 'Frame', 'Candle', 'Vase',
34
+ ];
35
+
36
+ /** Top-level product category names. */
37
+ const CATEGORIES = [
38
+ 'Electronics', 'Clothing & Apparel', 'Food & Beverages', 'Home & Garden',
39
+ 'Sports & Outdoors', 'Books & Media', 'Toys & Games', 'Health & Beauty',
40
+ 'Automotive', 'Office Supplies', 'Jewelry & Accessories', 'Pet Supplies',
41
+ 'Baby & Kids', 'Tools & Hardware', 'Travel & Luggage', 'Music & Instruments',
42
+ 'Art & Crafts', 'Software & Apps', 'Industrial Equipment', 'Collectibles',
43
+ 'Furniture', 'Kitchen & Dining', 'Lighting', 'Garden Tools', 'Fitness',
44
+ ];
45
+
46
+ /** Business department names. */
47
+ const DEPARTMENTS = [
48
+ 'Engineering', 'Marketing', 'Sales', 'Finance', 'Human Resources',
49
+ 'Operations', 'Legal', 'Product', 'Design', 'Customer Success',
50
+ 'Research & Development', 'IT', 'Security', 'Data Science', 'Supply Chain',
51
+ 'Quality Assurance', 'Business Development', 'Communications', 'Analytics',
52
+ 'Compliance', 'Strategy', 'Procurement', 'Manufacturing', 'Logistics',
53
+ ];
54
+
55
+ /** First part of a generated company name. */
56
+ const COMPANY_ADJECTIVES = [
57
+ 'Innovative', 'Global', 'Dynamic', 'Advanced', 'Premier', 'Elite',
58
+ 'National', 'Digital', 'Strategic', 'Integrated', 'Unified', 'Nexus',
59
+ 'Alpha', 'Apex', 'Vertex', 'Summit', 'Pinnacle', 'Horizon', 'Stellar',
60
+ 'Quantum', 'Fusion', 'Synergy', 'Core', 'Prime', 'Precision', 'Vantage',
61
+ 'Catalyst', 'Axiom', 'Aether', 'Titanium', 'Ironclad', 'Luminary',
62
+ ];
63
+
64
+ /** Second part of a generated company name. */
65
+ const COMPANY_NOUNS = [
66
+ 'Technologies', 'Solutions', 'Systems', 'Industries', 'Group',
67
+ 'Partners', 'Ventures', 'Labs', 'Works', 'Consulting', 'Services',
68
+ 'Analytics', 'Media', 'Networks', 'Designs', 'Innovations', 'Enterprises',
69
+ 'Digital', 'Cloud', 'Data', 'Security', 'Health', 'Finance', 'Capital',
70
+ 'Dynamics', 'Intelligence', 'Platforms', 'Insights', 'Studio', 'Agency',
71
+ ];
72
+
73
+ /** Company legal-entity suffixes. */
74
+ const COMPANY_SUFFIXES = [
75
+ 'Inc.', 'LLC', 'Corp.', 'Ltd.', 'Co.', 'Group', 'Holdings', 'International',
76
+ ];
77
+
78
+ /** Industry sector names. */
79
+ const INDUSTRIES = [
80
+ 'Information Technology', 'Financial Services', 'Healthcare', 'Retail',
81
+ 'Manufacturing', 'Transportation & Logistics', 'Media & Entertainment',
82
+ 'Telecommunications', 'Education', 'Real Estate', 'Hospitality & Tourism',
83
+ 'Aerospace & Defense', 'Energy & Utilities', 'Agriculture', 'Construction',
84
+ 'Pharmaceuticals', 'Automotive', 'Insurance', 'Non-Profit', 'Government',
85
+ 'Consulting', 'E-commerce', 'Cybersecurity', 'Artificial Intelligence',
86
+ 'Blockchain & Crypto', 'Biotechnology', 'Clean Energy', 'Legal Services',
87
+ ];
88
+
89
+ /** Buzzword adjectives for catch phrases. */
90
+ const CATCH_PHRASE_ADJECTIVES = [
91
+ 'Adaptive', 'Advanced', 'Automated', 'Balanced', 'Business-focused',
92
+ 'Centralized', 'Compatible', 'Configurable', 'Cross-platform', 'Customer-focused',
93
+ 'Decentralized', 'Digitized', 'Distributed', 'Enhanced', 'Enterprise-wide',
94
+ 'Ergonomic', 'Expanded', 'Face to face', 'Focused', 'Front-line',
95
+ 'Fully-configurable', 'Function-based', 'Future-proofed', 'Horizontal',
96
+ 'Implemented', 'Innovative', 'Integrated', 'Intuitive', 'Managed', 'Monitored',
97
+ 'Multi-channelled', 'Multi-lateral', 'Multi-tiered', 'Networked', 'Object-based',
98
+ 'Open-architected', 'Open-source', 'Optimized', 'Organic', 'Organized',
99
+ 'Pre-emptive', 'Proactive', 'Profit-focused', 'Programmable', 'Progressive',
100
+ 'Quality-focused', 'Re-engineered', 'Reactive', 'Reduced', 'Right-sized',
101
+ 'Robust', 'Seamless', 'Secured', 'Self-enabling', 'Streamlined', 'Synchronized',
102
+ 'Synergistic', 'Team-oriented', 'Universal', 'User-centric', 'User-friendly',
103
+ 'Versatile', 'Virtual', 'Visionary', 'Vision-oriented',
104
+ ];
105
+
106
+ /** Buzzword nouns for catch phrases. */
107
+ const CATCH_PHRASE_NOUNS = [
108
+ 'ability', 'access', 'adapter', 'algorithm', 'alliance', 'analyzer', 'archive',
109
+ 'array', 'attitude', 'benchmark', 'capability', 'capacity', 'challenge',
110
+ 'circuit', 'collaboration', 'complexity', 'concept', 'contingency',
111
+ 'core', 'database', 'data-warehouse', 'definition', 'emulation', 'encoding',
112
+ 'encryption', 'extranet', 'firmware', 'flexibility', 'focus group',
113
+ 'forecast', 'frame', 'framework', 'function', 'functionalities', 'hub',
114
+ 'implementation', 'info-mediaries', 'infrastructure', 'initiative', 'installation',
115
+ 'instruction set', 'interface', 'internet solution', 'intranet', 'knowledge user',
116
+ 'knowledge base', 'local area network', 'leverage', 'matrix', 'methodology',
117
+ 'middleware', 'migration', 'model', 'moderator', 'moratorium', 'neural-net',
118
+ 'open architecture', 'open system', 'orchestration', 'paradigm', 'policy',
119
+ 'portal', 'pricing structure', 'process improvement', 'product', 'productivity',
120
+ 'project', 'projection', 'protocol', 'throughput', 'time-frame', 'toolset',
121
+ 'transition', 'utilisation', 'website', 'workforce',
122
+ ];
123
+
124
+ module.exports = {
125
+ PRODUCT_ADJECTIVES,
126
+ PRODUCT_MATERIALS,
127
+ PRODUCT_NOUNS,
128
+ CATEGORIES,
129
+ DEPARTMENTS,
130
+ COMPANY_ADJECTIVES,
131
+ COMPANY_NOUNS,
132
+ COMPANY_SUFFIXES,
133
+ INDUSTRIES,
134
+ CATCH_PHRASE_ADJECTIVES,
135
+ CATCH_PHRASE_NOUNS,
136
+ };
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module seed/data/internet
5
+ * @description Data pools for internet-related fake data generation.
6
+ */
7
+
8
+ /** Public email providers (for Fake.email when provider is not specified). */
9
+ const EMAIL_PROVIDERS = [
10
+ 'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com',
11
+ 'protonmail.com', 'aol.com', 'live.com', 'msn.com', 'ymail.com',
12
+ 'mail.com', 'zoho.com', 'fastmail.com', 'hey.com', 'tutanota.com',
13
+ ];
14
+
15
+ /** Safe example domains that cannot belong to real people (RFC 2606). */
16
+ const SAFE_DOMAINS = [
17
+ 'example.com', 'example.org', 'example.net', 'test.com', 'test.org',
18
+ 'demo.com', 'demo.org', 'sample.com', 'fake.io', 'acme.example',
19
+ 'placeholder.dev', 'noreply.example', 'sandbox.example',
20
+ ];
21
+
22
+ /** Common TLDs used for domain generation. */
23
+ const TLDS = [
24
+ 'com', 'net', 'org', 'io', 'dev', 'app', 'co', 'info', 'biz', 'us',
25
+ 'uk', 'ca', 'au', 'de', 'fr', 'jp', 'in', 'br', 'mx', 'tech',
26
+ 'site', 'online', 'store', 'shop', 'cloud', 'ai', 'xyz', 'me',
27
+ ];
28
+
29
+ /** Adjectives for generated domain names. */
30
+ const DOMAIN_ADJECTIVES = [
31
+ 'quick', 'bright', 'dark', 'silent', 'loud', 'smart', 'wild', 'swift',
32
+ 'bold', 'calm', 'crisp', 'deep', 'fast', 'fresh', 'grand', 'happy',
33
+ 'keen', 'light', 'mighty', 'neat', 'open', 'proud', 'rapid', 'sharp',
34
+ 'steady', 'strong', 'thin', 'tough', 'warm', 'wise', 'young', 'agile',
35
+ 'blue', 'clever', 'cool', 'epic', 'free', 'great', 'huge', 'ideal',
36
+ ];
37
+
38
+ /** Nouns for generated domain names. */
39
+ const DOMAIN_NOUNS = [
40
+ 'apple', 'arrow', 'atlas', 'base', 'beam', 'blade', 'bridge', 'byte',
41
+ 'cache', 'chain', 'cloud', 'code', 'core', 'crest', 'cube', 'data',
42
+ 'deck', 'edge', 'field', 'flame', 'fleet', 'flow', 'forge', 'frame',
43
+ 'gate', 'grid', 'grove', 'harbor', 'heap', 'hub', 'index', 'kit',
44
+ 'lab', 'leaf', 'link', 'loop', 'map', 'matrix', 'mesh', 'mint',
45
+ 'node', 'orbit', 'path', 'peak', 'pixel', 'plugin', 'pod', 'port',
46
+ 'prism', 'pulse', 'realm', 'ridge', 'root', 'route', 'scope', 'seed',
47
+ 'shift', 'signal', 'site', 'snap', 'socket', 'source', 'spike', 'stack',
48
+ 'storm', 'stream', 'summit', 'sync', 'thread', 'tide', 'token', 'tower',
49
+ 'trace', 'vault', 'vector', 'vibe', 'vista', 'wave', 'wire', 'zone',
50
+ ];
51
+
52
+ /**
53
+ * Username separator styles.
54
+ * dot → john.doe99
55
+ * underscore → john_doe99
56
+ * none → johndoe99
57
+ */
58
+ const USERNAME_SEPARATORS = ['.', '_', ''];
59
+ const USERNAME_STYLES = ['dot', 'underscore', 'none', 'random'];
60
+
61
+ /** Realistic browser user-agent strings for 5 major browsers. */
62
+ const USER_AGENTS = [
63
+ // Chrome
64
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
65
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
66
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
67
+ // Firefox
68
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
69
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:121.0) Gecko/20100101 Firefox/121.0',
70
+ 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0',
71
+ // Safari
72
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
73
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
74
+ // Edge
75
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
76
+ // Android Chrome
77
+ 'Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.43 Mobile Safari/537.36',
78
+ // curl / bot style
79
+ 'curl/7.88.1',
80
+ 'python-requests/2.31.0',
81
+ ];
82
+
83
+ /** MAC address segment values (OUIs from well-known vendors for realism). */
84
+ const MAC_OUIS = [
85
+ '00:1A:2B', '00:50:56', '08:00:27', 'AC:BC:32', 'B8:27:EB',
86
+ 'DC:A6:32', 'E4:5F:01', '3C:22:FB', '70:B3:D5', 'F4:5C:89',
87
+ ];
88
+
89
+ /** Password character pool constants. */
90
+ const CHARSET_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
91
+ const CHARSET_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
92
+ const CHARSET_DIGITS = '0123456789';
93
+ const CHARSET_SPECIAL = '!@#$%^&*()-_=+[]{}|;:,.<>?';
94
+ const CHARSET_AMBIGUOUS = 'lI1O0'; // chars to optionally exclude
95
+
96
+ module.exports = {
97
+ EMAIL_PROVIDERS,
98
+ SAFE_DOMAINS,
99
+ TLDS,
100
+ DOMAIN_ADJECTIVES,
101
+ DOMAIN_NOUNS,
102
+ USERNAME_SEPARATORS,
103
+ USERNAME_STYLES,
104
+ USER_AGENTS,
105
+ MAC_OUIS,
106
+ CHARSET_LOWERCASE,
107
+ CHARSET_UPPERCASE,
108
+ CHARSET_DIGITS,
109
+ CHARSET_SPECIAL,
110
+ CHARSET_AMBIGUOUS,
111
+ };