@mantiq/database 0.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 (42) hide show
  1. package/README.md +19 -0
  2. package/package.json +77 -0
  3. package/src/DatabaseManager.ts +115 -0
  4. package/src/DatabaseServiceProvider.ts +39 -0
  5. package/src/contracts/Connection.ts +13 -0
  6. package/src/contracts/Grammar.ts +16 -0
  7. package/src/contracts/MongoConnection.ts +122 -0
  8. package/src/contracts/Paginator.ts +10 -0
  9. package/src/drivers/BaseGrammar.ts +220 -0
  10. package/src/drivers/MSSQLConnection.ts +154 -0
  11. package/src/drivers/MSSQLGrammar.ts +106 -0
  12. package/src/drivers/MongoConnection.ts +298 -0
  13. package/src/drivers/MongoQueryBuilderImpl.ts +77 -0
  14. package/src/drivers/MySQLConnection.ts +120 -0
  15. package/src/drivers/MySQLGrammar.ts +19 -0
  16. package/src/drivers/PostgresConnection.ts +125 -0
  17. package/src/drivers/PostgresGrammar.ts +24 -0
  18. package/src/drivers/SQLiteConnection.ts +125 -0
  19. package/src/drivers/SQLiteGrammar.ts +19 -0
  20. package/src/errors/ConnectionError.ts +10 -0
  21. package/src/errors/ModelNotFoundError.ts +14 -0
  22. package/src/errors/QueryError.ts +11 -0
  23. package/src/events/DatabaseEvents.ts +101 -0
  24. package/src/factories/Factory.ts +170 -0
  25. package/src/factories/Faker.ts +382 -0
  26. package/src/helpers/db.ts +37 -0
  27. package/src/index.ts +100 -0
  28. package/src/migrations/Migration.ts +12 -0
  29. package/src/migrations/MigrationRepository.ts +50 -0
  30. package/src/migrations/Migrator.ts +201 -0
  31. package/src/orm/Collection.ts +236 -0
  32. package/src/orm/Document.ts +202 -0
  33. package/src/orm/Model.ts +775 -0
  34. package/src/orm/ModelQueryBuilder.ts +415 -0
  35. package/src/orm/Scope.ts +39 -0
  36. package/src/orm/eagerLoad.ts +300 -0
  37. package/src/query/Builder.ts +456 -0
  38. package/src/query/Expression.ts +18 -0
  39. package/src/schema/Blueprint.ts +196 -0
  40. package/src/schema/ColumnDefinition.ts +93 -0
  41. package/src/schema/SchemaBuilder.ts +376 -0
  42. package/src/seeders/Seeder.ts +28 -0
@@ -0,0 +1,170 @@
1
+ import type { Model, ModelStatic } from '../orm/Model.ts'
2
+ import { Faker } from './Faker.ts'
3
+
4
+ type DefinitionFn<T> = (index: number, fake: Faker) => Record<string, any>
5
+ type AfterCreateFn<T extends Model> = (model: T) => Promise<void>
6
+
7
+ export interface BulkCreateOptions {
8
+ /** Rows per INSERT statement (default 500) */
9
+ batchSize?: number
10
+ /** Called after each batch with rows inserted so far */
11
+ onProgress?: (inserted: number, total: number) => void
12
+ }
13
+
14
+ /**
15
+ * Model factory for generating test data.
16
+ *
17
+ * Immutable — state(), count(), and afterCreate() return a new instance,
18
+ * so chaining never pollutes the original factory.
19
+ *
20
+ * @example
21
+ * class UserFactory extends Factory<User> {
22
+ * protected model = User
23
+ * definition(index: number, fake: Faker) {
24
+ * return {
25
+ * name: fake.name(),
26
+ * email: fake.email(),
27
+ * role: fake.pick(['admin', 'user']),
28
+ * }
29
+ * }
30
+ * }
31
+ *
32
+ * const factory = new UserFactory()
33
+ * const user = await factory.create()
34
+ * const users = await factory.count(5).create()
35
+ * const admin = await factory.state({ role: 'admin' }).create()
36
+ */
37
+ export abstract class Factory<T extends Model> {
38
+ protected abstract model: ModelStatic<T>
39
+ protected _count = 1
40
+ protected _states: DefinitionFn<T>[] = []
41
+ protected _afterCreate: AfterCreateFn<T>[] = []
42
+ protected _sequence = 0
43
+
44
+ /** Shared faker instance — override or seed in subclass if needed */
45
+ protected fake = new Faker()
46
+
47
+ abstract definition(index: number, fake: Faker): Record<string, any>
48
+
49
+ /** Clone this factory with shallow-copied mutable arrays */
50
+ protected clone(): this {
51
+ const copy = Object.create(Object.getPrototypeOf(this)) as this
52
+ Object.assign(copy, this)
53
+ copy._states = [...this._states]
54
+ copy._afterCreate = [...this._afterCreate]
55
+ return copy
56
+ }
57
+
58
+ count(n: number): this {
59
+ const copy = this.clone()
60
+ copy._count = n
61
+ return copy
62
+ }
63
+
64
+ state(overrides: Partial<Record<string, any>> | DefinitionFn<T>): this {
65
+ const copy = this.clone()
66
+ if (typeof overrides === 'function') {
67
+ copy._states.push(overrides as DefinitionFn<T>)
68
+ } else {
69
+ copy._states.push(() => overrides)
70
+ }
71
+ return copy
72
+ }
73
+
74
+ afterCreate(fn: AfterCreateFn<T>): this {
75
+ const copy = this.clone()
76
+ copy._afterCreate.push(fn)
77
+ return copy
78
+ }
79
+
80
+ /** Make model instances (not persisted) */
81
+ make(overrides?: Record<string, any>): T | T[] {
82
+ const results: T[] = []
83
+ for (let i = 0; i < this._count; i++) {
84
+ const index = ++this._sequence
85
+ const attrs = this.resolveAttributes(index, overrides)
86
+ const instance = new (this.model as any)()
87
+ instance.forceFill(attrs)
88
+ results.push(instance)
89
+ }
90
+ return this._count === 1 ? results[0]! : results
91
+ }
92
+
93
+ /** Create and persist model instances */
94
+ async create(overrides?: Record<string, any>): Promise<T | T[]> {
95
+ const results: T[] = []
96
+ for (let i = 0; i < this._count; i++) {
97
+ const index = ++this._sequence
98
+ const attrs = this.resolveAttributes(index, overrides)
99
+ const model = await this.model.create(attrs)
100
+ for (const fn of this._afterCreate) await fn(model)
101
+ results.push(model)
102
+ }
103
+ return this._count === 1 ? results[0]! : results
104
+ }
105
+
106
+ /** Create and return raw attribute objects (not persisted) */
107
+ raw(overrides?: Record<string, any>): Record<string, any> | Record<string, any>[] {
108
+ const results: Record<string, any>[] = []
109
+ for (let i = 0; i < this._count; i++) {
110
+ const index = ++this._sequence
111
+ results.push(this.resolveAttributes(index, overrides))
112
+ }
113
+ return this._count === 1 ? results[0]! : results
114
+ }
115
+
116
+ /**
117
+ * Bulk-insert rows using multi-value INSERT statements in a transaction.
118
+ * Generates attributes in streaming batches to stay memory-friendly.
119
+ * Returns the total number of rows inserted.
120
+ *
121
+ * @example
122
+ * await new UserFactory().count(1_000_000).createBulk(db(), {
123
+ * batchSize: 1000,
124
+ * onProgress: (done, total) => console.log(`${done}/${total}`),
125
+ * })
126
+ */
127
+ async createBulk(
128
+ connection: { statement(sql: string, bindings?: any[]): Promise<number>; transaction<R>(fn: (c: any) => Promise<R>): Promise<R> },
129
+ options?: BulkCreateOptions,
130
+ ): Promise<number> {
131
+ const batchSize = options?.batchSize ?? 500
132
+ const total = this._count
133
+ const tableName = (this.model as any).table as string
134
+ let inserted = 0
135
+
136
+ await connection.transaction(async (conn: any) => {
137
+ while (inserted < total) {
138
+ const chunkSize = Math.min(batchSize, total - inserted)
139
+ const rows: Record<string, any>[] = []
140
+
141
+ for (let i = 0; i < chunkSize; i++) {
142
+ const index = ++this._sequence
143
+ rows.push(this.resolveAttributes(index))
144
+ }
145
+
146
+ const columns = Object.keys(rows[0]!)
147
+ const quotedCols = columns.map((c) => `"${c}"`).join(', ')
148
+ const rowPlaceholder = `(${columns.map(() => '?').join(', ')})`
149
+ const allPlaceholders = Array.from({ length: chunkSize }, () => rowPlaceholder).join(', ')
150
+ const bindings = rows.flatMap((r) => columns.map((c) => r[c]))
151
+ const sql = `INSERT INTO "${tableName}" (${quotedCols}) VALUES ${allPlaceholders}`
152
+
153
+ await conn.statement(sql, bindings)
154
+ inserted += chunkSize
155
+ options?.onProgress?.(inserted, total)
156
+ }
157
+ })
158
+
159
+ return inserted
160
+ }
161
+
162
+ private resolveAttributes(index: number, overrides?: Record<string, any>): Record<string, any> {
163
+ let attrs = this.definition(index, this.fake)
164
+ for (const stateFn of this._states) {
165
+ attrs = { ...attrs, ...stateFn(index, this.fake) }
166
+ }
167
+ if (overrides) attrs = { ...attrs, ...overrides }
168
+ return attrs
169
+ }
170
+ }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Lightweight fake data generator — zero dependencies.
3
+ *
4
+ * Covers the most common needs for seeding and testing without pulling in
5
+ * a heavyweight library like faker-js. Every method is deterministic when
6
+ * seeded, or random by default.
7
+ *
8
+ * @example
9
+ * const fake = new Faker()
10
+ * fake.name() // "Liam Chen"
11
+ * fake.email() // "olivia.martinez42@example.com"
12
+ * fake.sentence() // "The quick brown fox jumps over the lazy dog."
13
+ */
14
+ export class Faker {
15
+ private seed: number | null = null
16
+ private state: number = 0
17
+
18
+ constructor(seed?: number) {
19
+ if (seed !== undefined) {
20
+ this.seed = seed
21
+ this.state = seed
22
+ }
23
+ }
24
+
25
+ // ── Random primitives ──────────────────────────────────────────────────────
26
+
27
+ /** Random float in [0, 1) — seeded or Math.random() */
28
+ random(): number {
29
+ if (this.seed === null) return Math.random()
30
+ // xorshift32
31
+ this.state ^= this.state << 13
32
+ this.state ^= this.state >> 17
33
+ this.state ^= this.state << 5
34
+ return (this.state >>> 0) / 4294967296
35
+ }
36
+
37
+ /** Random integer in [min, max] inclusive */
38
+ int(min = 0, max = 100): number {
39
+ return Math.floor(this.random() * (max - min + 1)) + min
40
+ }
41
+
42
+ /** Random float in [min, max) */
43
+ float(min = 0, max = 1, decimals = 2): number {
44
+ const val = this.random() * (max - min) + min
45
+ const factor = 10 ** decimals
46
+ return Math.round(val * factor) / factor
47
+ }
48
+
49
+ /** Random boolean with optional probability of true */
50
+ boolean(truthiness = 0.5): boolean {
51
+ return this.random() < truthiness
52
+ }
53
+
54
+ /** Pick a random element from an array */
55
+ pick<T>(arr: readonly T[]): T {
56
+ return arr[this.int(0, arr.length - 1)]!
57
+ }
58
+
59
+ /** Pick n unique elements from an array */
60
+ pickMultiple<T>(arr: readonly T[], count: number): T[] {
61
+ const shuffled = [...arr].sort(() => this.random() - 0.5)
62
+ return shuffled.slice(0, Math.min(count, arr.length))
63
+ }
64
+
65
+ /** Shuffle an array (returns new array) */
66
+ shuffle<T>(arr: readonly T[]): T[] {
67
+ const copy = [...arr]
68
+ for (let i = copy.length - 1; i > 0; i--) {
69
+ const j = this.int(0, i)
70
+ ;[copy[i], copy[j]] = [copy[j]!, copy[i]!]
71
+ }
72
+ return copy
73
+ }
74
+
75
+ // ── Person ─────────────────────────────────────────────────────────────────
76
+
77
+ firstName(): string {
78
+ return this.pick(FIRST_NAMES)
79
+ }
80
+
81
+ lastName(): string {
82
+ return this.pick(LAST_NAMES)
83
+ }
84
+
85
+ name(): string {
86
+ return `${this.firstName()} ${this.lastName()}`
87
+ }
88
+
89
+ /** Random username: first.last + optional digits */
90
+ username(): string {
91
+ const first = this.firstName().toLowerCase()
92
+ const last = this.lastName().toLowerCase()
93
+ const suffix = this.boolean(0.6) ? String(this.int(1, 99)) : ''
94
+ return `${first}.${last}${suffix}`
95
+ }
96
+
97
+ // ── Internet ───────────────────────────────────────────────────────────────
98
+
99
+ email(domain?: string): string {
100
+ const d = domain ?? this.pick(EMAIL_DOMAINS)
101
+ return `${this.username()}@${d}`
102
+ }
103
+
104
+ url(): string {
105
+ return `https://${this.pick(DOMAINS)}/${this.slug()}`
106
+ }
107
+
108
+ ip(): string {
109
+ return `${this.int(1, 255)}.${this.int(0, 255)}.${this.int(0, 255)}.${this.int(1, 254)}`
110
+ }
111
+
112
+ ipv6(): string {
113
+ const segments: string[] = []
114
+ for (let i = 0; i < 8; i++) segments.push(this.int(0, 0xffff).toString(16).padStart(4, '0'))
115
+ return segments.join(':')
116
+ }
117
+
118
+ /** Random hex color */
119
+ hexColor(): string {
120
+ return '#' + this.int(0, 0xffffff).toString(16).padStart(6, '0')
121
+ }
122
+
123
+ userAgent(): string {
124
+ return this.pick(USER_AGENTS)
125
+ }
126
+
127
+ // ── Text ───────────────────────────────────────────────────────────────────
128
+
129
+ word(): string {
130
+ return this.pick(WORDS)
131
+ }
132
+
133
+ words(count?: number): string {
134
+ const n = count ?? this.int(2, 6)
135
+ return Array.from({ length: n }, () => this.word()).join(' ')
136
+ }
137
+
138
+ sentence(wordCount?: number): string {
139
+ const w = this.words(wordCount ?? this.int(5, 12))
140
+ return w.charAt(0).toUpperCase() + w.slice(1) + '.'
141
+ }
142
+
143
+ sentences(count?: number): string {
144
+ const n = count ?? this.int(2, 5)
145
+ return Array.from({ length: n }, () => this.sentence()).join(' ')
146
+ }
147
+
148
+ paragraph(sentenceCount?: number): string {
149
+ return this.sentences(sentenceCount ?? this.int(3, 6))
150
+ }
151
+
152
+ paragraphs(count?: number): string {
153
+ const n = count ?? this.int(2, 4)
154
+ return Array.from({ length: n }, () => this.paragraph()).join('\n\n')
155
+ }
156
+
157
+ slug(wordCount?: number): string {
158
+ return this.words(wordCount ?? this.int(2, 4)).replace(/\s+/g, '-')
159
+ }
160
+
161
+ // ── Numbers & IDs ──────────────────────────────────────────────────────────
162
+
163
+ uuid(): string {
164
+ const hex = () => this.int(0, 0xffff).toString(16).padStart(4, '0')
165
+ return `${hex()}${hex()}-${hex()}-4${hex().slice(1)}-${this.pick(['8', '9', 'a', 'b'])}${hex().slice(1)}-${hex()}${hex()}${hex()}`
166
+ }
167
+
168
+ /** Numeric string of given length */
169
+ numericId(length = 8): string {
170
+ return Array.from({ length }, () => this.int(0, 9)).join('')
171
+ }
172
+
173
+ // ── Date & Time ────────────────────────────────────────────────────────────
174
+
175
+ /** Random date between two dates */
176
+ date(from?: Date, to?: Date): Date {
177
+ const start = (from ?? new Date('2020-01-01')).getTime()
178
+ const end = (to ?? new Date()).getTime()
179
+ return new Date(this.int(start, end))
180
+ }
181
+
182
+ /** ISO date string */
183
+ dateString(from?: Date, to?: Date): string {
184
+ return this.date(from, to).toISOString()
185
+ }
186
+
187
+ /** Recent date (within last N days, default 7) */
188
+ recent(days = 7): Date {
189
+ const now = Date.now()
190
+ return new Date(now - this.int(0, days * 86400000))
191
+ }
192
+
193
+ /** Future date (within next N days, default 30) */
194
+ future(days = 30): Date {
195
+ const now = Date.now()
196
+ return new Date(now + this.int(1, days * 86400000))
197
+ }
198
+
199
+ // ── Address ────────────────────────────────────────────────────────────────
200
+
201
+ city(): string {
202
+ return this.pick(CITIES)
203
+ }
204
+
205
+ country(): string {
206
+ return this.pick(COUNTRIES)
207
+ }
208
+
209
+ zipCode(): string {
210
+ return this.numericId(5)
211
+ }
212
+
213
+ latitude(): number {
214
+ return this.float(-90, 90, 6)
215
+ }
216
+
217
+ longitude(): number {
218
+ return this.float(-180, 180, 6)
219
+ }
220
+
221
+ // ── Company ────────────────────────────────────────────────────────────────
222
+
223
+ company(): string {
224
+ return this.pick(COMPANIES)
225
+ }
226
+
227
+ jobTitle(): string {
228
+ return `${this.pick(JOB_LEVELS)} ${this.pick(JOB_AREAS)} ${this.pick(JOB_TYPES)}`
229
+ }
230
+
231
+ // ── Phone ──────────────────────────────────────────────────────────────────
232
+
233
+ phone(): string {
234
+ return `+1${this.numericId(10)}`
235
+ }
236
+
237
+ // ── Image / Avatar ─────────────────────────────────────────────────────────
238
+
239
+ /** Placeholder avatar URL */
240
+ avatar(): string {
241
+ return `https://ui-avatars.com/api/?name=${encodeURIComponent(this.name())}&size=128`
242
+ }
243
+
244
+ imageUrl(width = 640, height = 480): string {
245
+ return `https://picsum.photos/seed/${this.int(1, 99999)}/${width}/${height}`
246
+ }
247
+
248
+ // ── Helpers ────────────────────────────────────────────────────────────────
249
+
250
+ /** Generate a value from a pattern: # = digit, ? = lowercase letter, * = alphanumeric */
251
+ fromPattern(pattern: string): string {
252
+ let result = ''
253
+ for (const ch of pattern) {
254
+ if (ch === '#') result += String(this.int(0, 9))
255
+ else if (ch === '?') result += String.fromCharCode(this.int(97, 122))
256
+ else if (ch === '*') result += this.pick('abcdefghijklmnopqrstuvwxyz0123456789'.split(''))
257
+ else result += ch
258
+ }
259
+ return result
260
+ }
261
+
262
+ /** Random hex string */
263
+ hex(length = 16): string {
264
+ return Array.from({ length }, () => this.int(0, 15).toString(16)).join('')
265
+ }
266
+
267
+ /** Random alphanumeric string */
268
+ alphanumeric(length = 10): string {
269
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
270
+ return Array.from({ length }, () => chars[this.int(0, chars.length - 1)]).join('')
271
+ }
272
+
273
+ /** Unique values: returns a proxy that deduplicates */
274
+ unique<K extends keyof this>(method: K, maxRetries = 100): this[K] {
275
+ const seen = new Set<any>()
276
+ const original = (this as any)[method]
277
+ if (typeof original !== 'function') throw new Error(`${String(method)} is not a method`)
278
+ return ((...args: any[]) => {
279
+ for (let i = 0; i < maxRetries; i++) {
280
+ const val = original.call(this, ...args)
281
+ if (!seen.has(val)) { seen.add(val); return val }
282
+ }
283
+ throw new Error(`Faker.unique: could not generate unique ${String(method)} after ${maxRetries} tries`)
284
+ }) as any
285
+ }
286
+ }
287
+
288
+ // ── Data pools ──────────────────────────────────────────────────────────────
289
+
290
+ const FIRST_NAMES = [
291
+ 'Liam', 'Olivia', 'Noah', 'Emma', 'James', 'Sophia', 'Lucas', 'Ava',
292
+ 'Mason', 'Isabella', 'Ethan', 'Mia', 'Alexander', 'Charlotte', 'Henry',
293
+ 'Amelia', 'Sebastian', 'Harper', 'Jack', 'Evelyn', 'Daniel', 'Aria',
294
+ 'Owen', 'Chloe', 'Samuel', 'Ella', 'Ryan', 'Scarlett', 'Leo', 'Grace',
295
+ 'Nathan', 'Lily', 'Caleb', 'Layla', 'Isaac', 'Riley', 'Adam', 'Zoey',
296
+ 'Dylan', 'Nora', 'Aiden', 'Hannah', 'Elijah', 'Stella', 'Logan', 'Luna',
297
+ 'Gabriel', 'Penelope', 'Matthew', 'Violet',
298
+ ] as const
299
+
300
+ const LAST_NAMES = [
301
+ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller',
302
+ 'Davis', 'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez',
303
+ 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin',
304
+ 'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark',
305
+ 'Ramirez', 'Lewis', 'Robinson', 'Walker', 'Young', 'Allen', 'King',
306
+ 'Wright', 'Scott', 'Torres', 'Nguyen', 'Hill', 'Flores', 'Green',
307
+ 'Adams', 'Nelson', 'Baker', 'Hall', 'Rivera', 'Campbell', 'Mitchell',
308
+ 'Carter', 'Chen',
309
+ ] as const
310
+
311
+ const EMAIL_DOMAINS = [
312
+ 'gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'proton.me',
313
+ 'icloud.com', 'mail.com', 'example.com', 'test.com',
314
+ ] as const
315
+
316
+ const DOMAINS = [
317
+ 'example.com', 'test.org', 'sample.net', 'demo.io', 'mysite.dev',
318
+ ] as const
319
+
320
+ const WORDS = [
321
+ 'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'I',
322
+ 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at',
323
+ 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her',
324
+ 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there',
325
+ 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get',
326
+ 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no',
327
+ 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your',
328
+ 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then',
329
+ 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also',
330
+ 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first',
331
+ 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these',
332
+ 'give', 'day', 'most', 'find', 'here', 'thing', 'many', 'right',
333
+ 'large', 'great', 'system', 'part', 'small', 'number', 'place',
334
+ 'point', 'home', 'hand', 'high', 'keep', 'last', 'long', 'world',
335
+ 'school', 'still', 'study', 'every', 'start', 'might', 'story',
336
+ 'city', 'open', 'build', 'group', 'local', 'state', 'power',
337
+ 'data', 'team', 'cloud', 'pixel', 'orbit', 'spark', 'pulse',
338
+ 'swift', 'bloom', 'forge', 'haven', 'ridge', 'ember', 'frost',
339
+ ] as const
340
+
341
+ const CITIES = [
342
+ 'New York', 'London', 'Tokyo', 'Paris', 'Berlin', 'Sydney', 'Toronto',
343
+ 'Mumbai', 'São Paulo', 'Seoul', 'Dubai', 'Singapore', 'Amsterdam',
344
+ 'Stockholm', 'San Francisco', 'Chicago', 'Los Angeles', 'Barcelona',
345
+ 'Melbourne', 'Austin',
346
+ ] as const
347
+
348
+ const COUNTRIES = [
349
+ 'United States', 'United Kingdom', 'Canada', 'Australia', 'Germany',
350
+ 'France', 'Japan', 'Brazil', 'India', 'South Korea', 'Netherlands',
351
+ 'Sweden', 'Singapore', 'Spain', 'Italy', 'Mexico', 'Argentina',
352
+ 'New Zealand', 'Switzerland', 'Portugal',
353
+ ] as const
354
+
355
+ const COMPANIES = [
356
+ 'Acme Corp', 'Globex', 'Initech', 'Hooli', 'Pied Piper', 'Stark Industries',
357
+ 'Wayne Enterprises', 'Umbrella Corp', 'Soylent Corp', 'Tyrell Corp',
358
+ 'Cyberdyne Systems', 'Massive Dynamic', 'Wonka Industries', 'Dunder Mifflin',
359
+ 'Sterling Cooper', 'Weyland-Yutani', 'Aperture Science', 'Oscorp',
360
+ 'LexCorp', 'Prestige Worldwide',
361
+ ] as const
362
+
363
+ const JOB_LEVELS = [
364
+ 'Senior', 'Junior', 'Lead', 'Principal', 'Staff', 'Associate', 'Chief',
365
+ ] as const
366
+
367
+ const JOB_AREAS = [
368
+ 'Software', 'Product', 'Design', 'Marketing', 'Sales', 'Data',
369
+ 'DevOps', 'Security', 'Research', 'Operations',
370
+ ] as const
371
+
372
+ const JOB_TYPES = [
373
+ 'Engineer', 'Manager', 'Analyst', 'Architect', 'Consultant', 'Director',
374
+ 'Specialist', 'Developer', 'Designer', 'Strategist',
375
+ ] as const
376
+
377
+ const USER_AGENTS = [
378
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
379
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
380
+ 'Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0',
381
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
382
+ ] as const
@@ -0,0 +1,37 @@
1
+ import { DatabaseManager } from '../DatabaseManager.ts'
2
+
3
+ let _manager: DatabaseManager | null = null
4
+
5
+ export function setManager(manager: DatabaseManager): void {
6
+ _manager = manager
7
+ }
8
+
9
+ export function getManager(): DatabaseManager {
10
+ if (!_manager) throw new Error('DatabaseManager not initialized. Call setManager() first.')
11
+ return _manager
12
+ }
13
+
14
+ /** Get a SQL connection by name (or the default) */
15
+ export function db(connection?: string) {
16
+ return getManager().connection(connection)
17
+ }
18
+
19
+ /** Shorthand: query a table on the default connection */
20
+ export function table(name: string) {
21
+ return getManager().table(name)
22
+ }
23
+
24
+ /** Shorthand: get the schema builder on the default connection */
25
+ export function schema() {
26
+ return getManager().schema()
27
+ }
28
+
29
+ /** Get a MongoDB connection by name (or the default) */
30
+ export function mongo(connection?: string) {
31
+ return getManager().mongo(connection)
32
+ }
33
+
34
+ /** Shorthand: get a MongoDB collection */
35
+ export function collection(name: string) {
36
+ return getManager().collection(name)
37
+ }