@uql/core 3.4.3 → 3.4.4

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.
@@ -1,876 +1,1018 @@
1
- import sqlstring from 'sqlstring-sqlite';
2
- import { AbstractSqlDialect } from '../dialect/index.js';
3
- import { getMeta } from '../entity/index.js';
4
- import { AbstractSqlQuerier, AbstractQuerierPool } from '../querier/index.js';
1
+ import { _ as _apply_decs_2203_r } from './cc-BEf4wTUm.js';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { Id, Field, ManyToOne, Entity, OneToOne, OneToMany, ManyToMany } from '../entity/index.js';
4
+ import '../type/index.js';
5
+ import { raw, lowerFirst, upperFirst, getKeys } from '../util/index.js';
6
+ import 'reflect-metadata';
7
+ import { describe, afterAll, afterEach, beforeEach, beforeAll, it } from 'bun:test';
5
8
 
6
- class SqliteDialect extends AbstractSqlDialect {
7
- constructor(namingStrategy){
8
- super(namingStrategy, '`', 'BEGIN TRANSACTION');
9
- }
10
- addValue(values, value) {
11
- if (value instanceof Date) {
12
- value = value.getTime();
13
- } else if (typeof value === 'boolean') {
14
- value = value ? 1 : 0;
15
- }
16
- return super.addValue(values, value);
9
+ var _dec, _dec1, _dec2, _dec3, _dec4, _dec5, _dec6, /**
10
+ * auto-generated primary-key (when the `onInsert` property is omitted).
11
+ */ _init_id, /**
12
+ * foreign-keys are really simple to specify with the `reference` property.
13
+ */ _init_companyId, /**
14
+ * The `Relation` wrapper type can be used in ESM projects for the relations to
15
+ * avoid circular dependency issues.
16
+ */ _init_company, _init_creatorId, _init_creator, /**
17
+ * 'onInsert' property can be used to specify a custom mechanism for
18
+ * obtaining the value of a field when inserting:
19
+ */ _init_createdAt, /**
20
+ * 'onUpdate' property can be used to specify a custom mechanism for
21
+ * obtaining the value of a field when updating:
22
+ */ _init_updatedAt, _dec7, _initClass, _BaseEntity, _dec8, _dec9, _dec10, _init_name, _init_description, _init_kind, _dec11, _initClass1, _BaseEntity1, _dec12, _dec13, /**
23
+ * an entity can specify its own ID Field and still inherit the others
24
+ * columns/relations from its parent entity.
25
+ */ _init_pk, _init_picture, _dec14, _initClass2, _BaseEntity2, _dec15, _dec16, _dec17, _dec18, _dec19, _init_name1, _init_email, _init_password, /**
26
+ * `mappedBy` property can be a callback or a string (callback is useful for auto-refactoring).
27
+ */ _init_profile, _init_users, _dec20, _initClass3, _dec21, _dec22, _init_id1, _init_name2, _dec23, _initClass4, _BaseEntity3, _dec24, _dec25, _dec26, _dec27, _init_name3, _init_description1, _init_parentLedgerId, _init_parentLedger, _dec28, _initClass5, _BaseEntity4, _dec29, _dec30, _dec31, /**
28
+ * an entity can override the ID Field and still inherit the others
29
+ * columns/relations from its parent entity.
30
+ * 'onInsert' property can be used to specify a custom mechanism for
31
+ * auto-generating the primary-key's value when inserting.
32
+ */ _init_pk1, _init_name4, _init_description2, _dec32, _initClass6, _BaseEntity5, _dec33, _dec34, _dec35, _dec36, _dec37, _init_name5, _init_percentage, _init_categoryId, _init_category, _init_description3, _dec38, _initClass7, _BaseEntity6, _dec39, _dec40, _dec41, _init_name6, _init_measureUnits, /**
33
+ * `onDelete` callback allows to specify which field will be used when deleting/querying this entity.
34
+ */ _init_deletedAt, _dec42, _initClass8, _BaseEntity7, _dec43, _dec44, _dec45, _dec46, _init_name7, _init_categoryId1, _init_category1, _init_deletedAt1, _dec47, _initClass9, _BaseEntity8, _dec48, _dec49, _dec50, _init_name8, _init_address, _init_description4, _dec51, _initClass10, _BaseEntity9, _dec52, _dec53, _dec54, _dec55, _dec56, _dec57, _dec58, _dec59, _dec60, _dec61, _dec62, _dec63, _dec64, _dec65, _dec66, _init_name9, _init_description5, _init_code, _init_buyLedgerAccountId, _init_buyLedgerAccount, _init_saleLedgerAccountId, _init_saleLedgerAccount, _init_taxId, _init_tax, _init_measureUnitId, _init_measureUnit, _init_salePrice, _init_inventoryable, _init_tags, _init_tagsCount, _dec67, _initClass11, _BaseEntity10, _dec68, _dec69, _dec70, _init_name10, _init_items, _init_itemsCount, _dec71, _initClass12, _dec72, _dec73, _dec74, _init_id2, _init_itemId, _init_tagId, _dec75, _initClass13, _BaseEntity11, _dec76, _dec77, _dec78, _init_itemAdjustments, _init_date, _init_description6, _dec79, _initClass14, _BaseEntity12, _dec80, _dec81, _dec82, _dec83, _dec84, _dec85, _dec86, _dec87, _init_itemId1, _init_item, _init_number, _init_buyPrice, _init_storehouseId, _init_storehouse, _init_inventoryAdjustmentId, _init_inventoryAdjustment;
35
+ _dec = Id(), _dec1 = Field({
36
+ reference: ()=>_Company
37
+ }), _dec2 = ManyToOne({
38
+ entity: ()=>_Company
39
+ }), _dec3 = Field({
40
+ reference: ()=>_User
41
+ }), _dec4 = ManyToOne({
42
+ entity: ()=>_User
43
+ }), _dec5 = Field({
44
+ onInsert: Date.now
45
+ }), _dec6 = Field({
46
+ onUpdate: Date.now
47
+ });
48
+ /**
49
+ * an `abstract` class can (optionally) be used as the base "template" for the entities
50
+ * (so common fields' declaration is easily reused).
51
+ */ class BaseEntity {
52
+ static{
53
+ ({ e: [_init_id, _init_companyId, _init_company, _init_creatorId, _init_creator, _init_createdAt, _init_updatedAt] } = _apply_decs_2203_r(this, [
54
+ [
55
+ _dec,
56
+ 0,
57
+ "id"
58
+ ],
59
+ [
60
+ _dec1,
61
+ 0,
62
+ "companyId"
63
+ ],
64
+ [
65
+ _dec2,
66
+ 0,
67
+ "company"
68
+ ],
69
+ [
70
+ _dec3,
71
+ 0,
72
+ "creatorId"
73
+ ],
74
+ [
75
+ _dec4,
76
+ 0,
77
+ "creator"
78
+ ],
79
+ [
80
+ _dec5,
81
+ 0,
82
+ "createdAt"
83
+ ],
84
+ [
85
+ _dec6,
86
+ 0,
87
+ "updatedAt"
88
+ ]
89
+ ], []));
90
+ }
91
+ constructor(){
92
+ this.id = _init_id(this);
93
+ this.companyId = _init_companyId(this);
94
+ this.company = _init_company(this);
95
+ this.creatorId = _init_creatorId(this);
96
+ this.creator = _init_creator(this);
97
+ this.createdAt = _init_createdAt(this);
98
+ this.updatedAt = _init_updatedAt(this);
17
99
  }
18
- compare(ctx, entity, key, val, opts) {
19
- if (key === '$text') {
20
- const meta = getMeta(entity);
21
- const search = val;
22
- const fields = search.$fields.map((fKey)=>{
23
- const field = meta.fields[fKey];
24
- const columnName = this.resolveColumnName(fKey, field);
25
- return this.escapeId(columnName);
26
- });
27
- const tableName = this.resolveTableName(entity, meta);
28
- ctx.append(`${this.escapeId(tableName)} MATCH {${fields.join(' ')}} : `);
29
- ctx.addValue(search.$value);
30
- return;
31
- }
32
- super.compare(ctx, entity, key, val, opts);
100
+ }
101
+ let _Company;
102
+ _dec7 = Entity(), _dec8 = Field(), _dec9 = Field(), _dec10 = Field({
103
+ type: 'jsonb'
104
+ });
105
+ class Company extends (_BaseEntity = BaseEntity) {
106
+ static{
107
+ ({ e: [_init_name, _init_description, _init_kind], c: [_Company, _initClass] } = _apply_decs_2203_r(this, [
108
+ [
109
+ _dec8,
110
+ 0,
111
+ "name"
112
+ ],
113
+ [
114
+ _dec9,
115
+ 0,
116
+ "description"
117
+ ],
118
+ [
119
+ _dec10,
120
+ 0,
121
+ "kind"
122
+ ]
123
+ ], [
124
+ _dec7
125
+ ], _BaseEntity));
126
+ }
127
+ static{
128
+ _initClass();
33
129
  }
34
- compareFieldOperator(ctx, entity, key, op, val, opts = {}) {
35
- super.compareFieldOperator(ctx, entity, key, op, val, opts);
130
+ constructor(...args){
131
+ super(...args), this.name = _init_name(this), this.description = _init_description(this), this.kind = _init_kind(this);
36
132
  }
37
- upsert(ctx, entity, conflictPaths, payload) {
38
- const meta = getMeta(entity);
39
- const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, (name)=>`EXCLUDED.${name}`);
40
- const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
41
- const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
42
- this.insert(ctx, entity, payload);
43
- ctx.append(` ON CONFLICT (${keysStr}) ${onConflict}`);
133
+ }
134
+ let _Profile;
135
+ _dec11 = Entity({
136
+ name: 'user_profile'
137
+ }), _dec12 = Id(), _dec13 = Field({
138
+ name: 'image'
139
+ });
140
+ class Profile extends (_BaseEntity1 = BaseEntity) {
141
+ static{
142
+ ({ e: [_init_pk, _init_picture], c: [_Profile, _initClass1] } = _apply_decs_2203_r(this, [
143
+ [
144
+ _dec12,
145
+ 0,
146
+ "pk"
147
+ ],
148
+ [
149
+ _dec13,
150
+ 0,
151
+ "picture"
152
+ ]
153
+ ], [
154
+ _dec11
155
+ ], _BaseEntity1));
156
+ }
157
+ static{
158
+ _initClass1();
44
159
  }
45
- escape(value) {
46
- return sqlstring.escape(value);
160
+ constructor(...args){
161
+ super(...args), this.pk = _init_pk(this), this.picture = _init_picture(this);
47
162
  }
48
163
  }
49
-
50
- class SqliteQuerier extends AbstractSqlQuerier {
51
- constructor(db, extra){
52
- super(new SqliteDialect(extra?.namingStrategy)), this.db = db, this.extra = extra;
53
- }
54
- async internalAll(query, values) {
55
- this.extra?.logger?.(query, values);
56
- return this.db.prepare(query).all(values || []);
57
- }
58
- async internalRun(query, values) {
59
- this.extra?.logger?.(query, values);
60
- const { changes, lastInsertRowid } = this.db.prepare(query).run(values || []);
61
- const firstId = lastInsertRowid ? Number(lastInsertRowid) - (changes - 1) : undefined;
62
- const ids = firstId ? Array(changes).fill(firstId).map((i, index)=>i + index) : [];
63
- return {
64
- changes,
65
- ids,
66
- firstId
67
- };
164
+ let _User;
165
+ _dec14 = Entity(), _dec15 = Field(), _dec16 = Field({
166
+ updatable: false
167
+ }), _dec17 = Field({
168
+ eager: false
169
+ }), _dec18 = OneToOne({
170
+ entity: ()=>_Profile,
171
+ mappedBy: (profile)=>profile.creator,
172
+ cascade: true
173
+ }), _dec19 = OneToMany({
174
+ entity: ()=>_User,
175
+ mappedBy: 'creator'
176
+ });
177
+ class User extends (_BaseEntity2 = BaseEntity) {
178
+ static{
179
+ ({ e: [_init_name1, _init_email, _init_password, _init_profile, _init_users], c: [_User, _initClass2] } = _apply_decs_2203_r(this, [
180
+ [
181
+ _dec15,
182
+ 0,
183
+ "name"
184
+ ],
185
+ [
186
+ _dec16,
187
+ 0,
188
+ "email"
189
+ ],
190
+ [
191
+ _dec17,
192
+ 0,
193
+ "password"
194
+ ],
195
+ [
196
+ _dec18,
197
+ 0,
198
+ "profile"
199
+ ],
200
+ [
201
+ _dec19,
202
+ 0,
203
+ "users"
204
+ ]
205
+ ], [
206
+ _dec14
207
+ ], _BaseEntity2));
208
+ }
209
+ static{
210
+ _initClass2();
68
211
  }
69
- async internalRelease() {
70
- if (this.hasOpenTransaction) {
71
- throw TypeError('pending transaction');
72
- }
73
- // no-op
212
+ constructor(...args){
213
+ super(...args), this.name = _init_name1(this), this.email = _init_email(this), this.password = _init_password(this), this.profile = _init_profile(this), this.users = _init_users(this);
74
214
  }
75
215
  }
76
-
77
- class Sqlite3QuerierPool extends AbstractQuerierPool {
78
- constructor(filename, opts, extra){
79
- super('sqlite', extra), this.filename = filename, this.opts = opts;
216
+ let _UserWithNonUpdatableId;
217
+ _dec20 = Entity(), _dec21 = Id({
218
+ updatable: false
219
+ }), _dec22 = Field();
220
+ class UserWithNonUpdatableId {
221
+ static{
222
+ ({ e: [_init_id1, _init_name2], c: [_UserWithNonUpdatableId, _initClass3] } = _apply_decs_2203_r(this, [
223
+ [
224
+ _dec21,
225
+ 0,
226
+ "id"
227
+ ],
228
+ [
229
+ _dec22,
230
+ 0,
231
+ "name"
232
+ ]
233
+ ], [
234
+ _dec20
235
+ ]));
236
+ }
237
+ static{
238
+ _initClass3();
239
+ }
240
+ constructor(){
241
+ this.id = _init_id1(this);
242
+ this.name = _init_name2(this);
80
243
  }
81
- async getQuerier() {
82
- if (!this.querier) {
83
- let db;
84
- if (typeof Bun !== 'undefined') {
85
- const { Database } = await import('bun:sqlite');
86
- db = new Database(this.filename, this.opts);
87
- db.run('PRAGMA journal_mode = WAL');
88
- } else {
89
- const { default: Database } = await import('better-sqlite3');
90
- db = new Database(this.filename, this.opts);
91
- db.pragma('journal_mode = WAL');
92
- }
93
- this.querier = new SqliteQuerier(db, this.extra);
94
- }
95
- return this.querier;
244
+ }
245
+ let _LedgerAccount;
246
+ _dec23 = Entity(), _dec24 = Field(), _dec25 = Field(), _dec26 = Field({
247
+ reference: ()=>_LedgerAccount
248
+ }), _dec27 = ManyToOne();
249
+ class LedgerAccount extends (_BaseEntity3 = BaseEntity) {
250
+ static{
251
+ ({ e: [_init_name3, _init_description1, _init_parentLedgerId, _init_parentLedger], c: [_LedgerAccount, _initClass4] } = _apply_decs_2203_r(this, [
252
+ [
253
+ _dec24,
254
+ 0,
255
+ "name"
256
+ ],
257
+ [
258
+ _dec25,
259
+ 0,
260
+ "description"
261
+ ],
262
+ [
263
+ _dec26,
264
+ 0,
265
+ "parentLedgerId"
266
+ ],
267
+ [
268
+ _dec27,
269
+ 0,
270
+ "parentLedger"
271
+ ]
272
+ ], [
273
+ _dec23
274
+ ], _BaseEntity3));
275
+ }
276
+ static{
277
+ _initClass4();
96
278
  }
97
- async end() {
98
- await this.querier.db.close();
99
- delete this.querier;
279
+ constructor(...args){
280
+ super(...args), this.name = _init_name3(this), this.description = _init_description1(this), this.parentLedgerId = _init_parentLedgerId(this), this.parentLedger = _init_parentLedger(this);
100
281
  }
101
282
  }
102
-
103
- export { Sqlite3QuerierPool, SqliteDialect, SqliteQuerier };
104
- //# sourceMappingURL=uql-browser.min.js.map
105
- >this.escapeId(c)).join(', ');
106
- return `CREATE ${unique}INDEX ${this.escapeId(index.name)} ON ${this.escapeId(tableName)} (${columns});`;
283
+ let _TaxCategory;
284
+ _dec28 = Entity(), _dec29 = Id({
285
+ onInsert: randomUUID
286
+ }), _dec30 = Field(), _dec31 = Field();
287
+ class TaxCategory extends (_BaseEntity4 = BaseEntity) {
288
+ static{
289
+ ({ e: [_init_pk1, _init_name4, _init_description2], c: [_TaxCategory, _initClass5] } = _apply_decs_2203_r(this, [
290
+ [
291
+ _dec29,
292
+ 0,
293
+ "pk"
294
+ ],
295
+ [
296
+ _dec30,
297
+ 0,
298
+ "name"
299
+ ],
300
+ [
301
+ _dec31,
302
+ 0,
303
+ "description"
304
+ ]
305
+ ], [
306
+ _dec28
307
+ ], _BaseEntity4));
308
+ }
309
+ static{
310
+ _initClass5();
107
311
  }
108
- generateDropIndex(tableName, indexName) {
109
- return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
312
+ constructor(...args){
313
+ super(...args), this.pk = _init_pk1(this), this.name = _init_name4(this), this.description = _init_description2(this);
110
314
  }
111
- /**
112
- * Generate column definitions from entity metadata
113
- */ generateColumnDefinitions(meta) {
114
- const columns = [];
115
- const fieldKeys = getKeys(meta.fields);
116
- for (const key of fieldKeys){
117
- const field = meta.fields[key];
118
- if (field?.virtual) continue; // Skip virtual fields
119
- const colDef = this.generateColumnDefinition(key, field, meta);
120
- columns.push(colDef);
121
- }
122
- return columns;
315
+ }
316
+ let _Tax;
317
+ _dec32 = Entity(), _dec33 = Field(), _dec34 = Field(), _dec35 = Field({
318
+ reference: ()=>_TaxCategory
319
+ }), _dec36 = ManyToOne(), _dec37 = Field();
320
+ class Tax extends (_BaseEntity5 = BaseEntity) {
321
+ static{
322
+ ({ e: [_init_name5, _init_percentage, _init_categoryId, _init_category, _init_description3], c: [_Tax, _initClass6] } = _apply_decs_2203_r(this, [
323
+ [
324
+ _dec33,
325
+ 0,
326
+ "name"
327
+ ],
328
+ [
329
+ _dec34,
330
+ 0,
331
+ "percentage"
332
+ ],
333
+ [
334
+ _dec35,
335
+ 0,
336
+ "categoryId"
337
+ ],
338
+ [
339
+ _dec36,
340
+ 0,
341
+ "category"
342
+ ],
343
+ [
344
+ _dec37,
345
+ 0,
346
+ "description"
347
+ ]
348
+ ], [
349
+ _dec32
350
+ ], _BaseEntity5));
351
+ }
352
+ static{
353
+ _initClass6();
123
354
  }
124
- /**
125
- * Generate a single column definition
126
- */ generateColumnDefinition(fieldKey, field, meta) {
127
- const columnName = this.escapeId(this.resolveColumnName(fieldKey, field));
128
- const isId = field.isId === true;
129
- const isPrimaryKey = isId && meta.id === fieldKey;
130
- // Determine SQL type
131
- let sqlType;
132
- if (isPrimaryKey && field.autoIncrement !== false && !field.onInsert) {
133
- // Auto-increment primary key
134
- sqlType = this.serialPrimaryKeyType;
135
- } else {
136
- sqlType = this.getSqlType(field, field.type);
137
- }
138
- let definition = `${columnName} ${sqlType}`;
139
- // PRIMARY KEY constraint (for non-serial types)
140
- if (isPrimaryKey && !sqlType.includes('PRIMARY KEY')) {
141
- definition += ' PRIMARY KEY';
142
- }
143
- // NULL/NOT NULL
144
- if (!isPrimaryKey) {
145
- const nullable = field.nullable ?? true;
146
- if (!nullable) {
147
- definition += ' NOT NULL';
148
- }
149
- }
150
- // UNIQUE constraint
151
- if (field.unique && !isPrimaryKey) {
152
- definition += ' UNIQUE';
153
- }
154
- // DEFAULT value
155
- if (field.defaultValue !== undefined) {
156
- definition += ` DEFAULT ${this.formatDefaultValue(field.defaultValue)}`;
157
- }
158
- // COMMENT (if supported)
159
- if (field.comment) {
160
- definition += this.generateColumnComment(this.resolveTableName(meta.entity, meta), this.resolveColumnName(fieldKey, field), field.comment);
161
- }
162
- return definition;
355
+ constructor(...args){
356
+ super(...args), this.name = _init_name5(this), this.percentage = _init_percentage(this), this.categoryId = _init_categoryId(this), this.category = _init_category(this), this.description = _init_description3(this);
163
357
  }
164
- /**
165
- * Generate column definition from a ColumnSchema object
166
- */ generateColumnDefinitionFromSchema(column) {
167
- const columnName = this.escapeId(column.name);
168
- let type = column.type;
169
- if (column.length && !type.includes('(')) {
170
- type = `${type}(${column.length})`;
171
- } else if (column.precision !== undefined && !type.includes('(')) {
172
- if (column.scale !== undefined) {
173
- type = `${type}(${column.precision}, ${column.scale})`;
174
- } else {
175
- type = `${type}(${column.precision})`;
176
- }
177
- }
178
- let definition = `${columnName} ${type}`;
179
- if (column.isPrimaryKey) {
180
- definition += ' PRIMARY KEY';
181
- }
182
- if (!column.nullable && !column.isPrimaryKey) {
183
- definition += ' NOT NULL';
184
- }
185
- if (column.isUnique && !column.isPrimaryKey) {
186
- definition += ' UNIQUE';
187
- }
188
- if (column.defaultValue !== undefined) {
189
- definition += ` DEFAULT ${this.formatDefaultValue(column.defaultValue)}`;
190
- }
191
- return definition;
358
+ }
359
+ let _MeasureUnitCategory;
360
+ _dec38 = Entity({
361
+ softDelete: true
362
+ }), _dec39 = Field(), _dec40 = OneToMany({
363
+ entity: ()=>_MeasureUnit,
364
+ mappedBy: (measureUnit)=>measureUnit.categoryId
365
+ }), _dec41 = Field({
366
+ onDelete: Date.now
367
+ });
368
+ class MeasureUnitCategory extends (_BaseEntity6 = BaseEntity) {
369
+ static{
370
+ ({ e: [_init_name6, _init_measureUnits, _init_deletedAt], c: [_MeasureUnitCategory, _initClass7] } = _apply_decs_2203_r(this, [
371
+ [
372
+ _dec39,
373
+ 0,
374
+ "name"
375
+ ],
376
+ [
377
+ _dec40,
378
+ 0,
379
+ "measureUnits"
380
+ ],
381
+ [
382
+ _dec41,
383
+ 0,
384
+ "deletedAt"
385
+ ]
386
+ ], [
387
+ _dec38
388
+ ], _BaseEntity6));
389
+ }
390
+ static{
391
+ _initClass7();
192
392
  }
193
- /**
194
- * Generate table constraints (indexes, foreign keys, etc.)
195
- */ generateTableConstraints(meta) {
196
- const constraints = [];
197
- const fieldKeys = getKeys(meta.fields);
198
- const tableName = this.resolveTableName(meta.entity, meta);
199
- // Generate indexes from field options
200
- for (const key of fieldKeys){
201
- const field = meta.fields[key];
202
- if (field?.index) {
203
- const columnName = this.resolveColumnName(key, field);
204
- const indexName = typeof field.index === 'string' ? field.index : `idx_${tableName}_${columnName}`;
205
- constraints.push(`INDEX ${this.escapeId(indexName)} (${this.escapeId(columnName)})`);
206
- }
207
- }
208
- // Generate foreign key constraints from references
209
- for (const key of fieldKeys){
210
- const field = meta.fields[key];
211
- if (field?.reference) {
212
- const refEntity = field.reference();
213
- const refMeta = getMeta(refEntity);
214
- const refIdField = refMeta.fields[refMeta.id];
215
- const columnName = this.resolveColumnName(key, field);
216
- const refTableName = this.resolveTableName(refEntity, refMeta);
217
- const refColumnName = this.resolveColumnName(refMeta.id, refIdField);
218
- const fkName = `fk_${tableName}_${columnName}`;
219
- constraints.push(`CONSTRAINT ${this.escapeId(fkName)} FOREIGN KEY (${this.escapeId(columnName)}) ` + `REFERENCES ${this.escapeId(refTableName)} (${this.escapeId(refColumnName)})`);
220
- }
221
- }
222
- return constraints;
393
+ constructor(...args){
394
+ super(...args), this.name = _init_name6(this), this.measureUnits = _init_measureUnits(this), this.deletedAt = _init_deletedAt(this);
223
395
  }
224
- getSqlType(field, fieldType) {
225
- // Use explicit column type if specified
226
- if (field.columnType) {
227
- return this.mapColumnType(field.columnType, field);
228
- }
229
- // Handle special types
230
- if (field.type === 'json' || field.type === 'jsonb') {
231
- return this.mapColumnType(field.type, field);
232
- }
233
- if (field.type === 'vector') {
234
- return this.mapColumnType('vector', field);
235
- }
236
- // Infer from TypeScript type
237
- const type = fieldType ?? field.type;
238
- if (type === Number || type === 'number') {
239
- return field.precision ? this.mapColumnType('decimal', field) : 'BIGINT';
240
- }
241
- if (type === String || type === 'string') {
242
- const length = field.length ?? 255;
243
- return `VARCHAR(${length})`;
244
- }
245
- if (type === Boolean || type === 'boolean') {
246
- return this.getBooleanType();
247
- }
248
- if (type === Date || type === 'date') {
249
- return 'TIMESTAMP';
250
- }
251
- if (type === BigInt || type === 'bigint') {
252
- return 'BIGINT';
253
- }
254
- // Default to VARCHAR
255
- return `VARCHAR(${field.length ?? 255})`;
396
+ }
397
+ let _MeasureUnit;
398
+ _dec42 = Entity({
399
+ softDelete: true
400
+ }), _dec43 = Field(), _dec44 = Field({
401
+ reference: ()=>_MeasureUnitCategory
402
+ }), _dec45 = ManyToOne({
403
+ cascade: 'persist'
404
+ }), _dec46 = Field({
405
+ onDelete: Date.now
406
+ });
407
+ class MeasureUnit extends (_BaseEntity7 = BaseEntity) {
408
+ static{
409
+ ({ e: [_init_name7, _init_categoryId1, _init_category1, _init_deletedAt1], c: [_MeasureUnit, _initClass8] } = _apply_decs_2203_r(this, [
410
+ [
411
+ _dec43,
412
+ 0,
413
+ "name"
414
+ ],
415
+ [
416
+ _dec44,
417
+ 0,
418
+ "categoryId"
419
+ ],
420
+ [
421
+ _dec45,
422
+ 0,
423
+ "category"
424
+ ],
425
+ [
426
+ _dec46,
427
+ 0,
428
+ "deletedAt"
429
+ ]
430
+ ], [
431
+ _dec42
432
+ ], _BaseEntity7));
433
+ }
434
+ static{
435
+ _initClass8();
256
436
  }
257
- /**
258
- * Get table options (e.g., ENGINE for MySQL)
259
- */ getTableOptions(meta) {
260
- return '';
437
+ constructor(...args){
438
+ super(...args), this.name = _init_name7(this), this.categoryId = _init_categoryId1(this), this.category = _init_category1(this), this.deletedAt = _init_deletedAt1(this);
261
439
  }
262
- /**
263
- * Format a default value for SQL
264
- */ formatDefaultValue(value) {
265
- if (value === null) {
266
- return 'NULL';
267
- }
268
- if (typeof value === 'string') {
269
- return `'${value.replace(/'/g, "''")}'`;
270
- }
271
- if (typeof value === 'boolean') {
272
- return value ? 'TRUE' : 'FALSE';
273
- }
274
- if (typeof value === 'number' || typeof value === 'bigint') {
275
- return String(value);
276
- }
277
- if (value instanceof Date) {
278
- return `'${value.toISOString()}'`;
279
- }
280
- return String(value);
440
+ }
441
+ let _Storehouse;
442
+ _dec47 = Entity(), _dec48 = Field(), _dec49 = Field(), _dec50 = Field();
443
+ class Storehouse extends (_BaseEntity8 = BaseEntity) {
444
+ static{
445
+ ({ e: [_init_name8, _init_address, _init_description4], c: [_Storehouse, _initClass9] } = _apply_decs_2203_r(this, [
446
+ [
447
+ _dec48,
448
+ 0,
449
+ "name"
450
+ ],
451
+ [
452
+ _dec49,
453
+ 0,
454
+ "address"
455
+ ],
456
+ [
457
+ _dec50,
458
+ 0,
459
+ "description"
460
+ ]
461
+ ], [
462
+ _dec47
463
+ ], _BaseEntity8));
464
+ }
465
+ static{
466
+ _initClass9();
281
467
  }
282
- /**
283
- * Compare two schemas and return the differences
284
- */ diffSchema(entity, currentSchema) {
285
- const meta = getMeta(entity);
286
- if (!currentSchema) {
287
- // Table doesn't exist, need to create
288
- return {
289
- tableName: this.resolveTableName(entity, meta),
290
- type: 'create'
291
- };
292
- }
293
- const columnsToAdd = [];
294
- const columnsToAlter = [];
295
- const columnsToDrop = [];
296
- const currentColumns = new Map(currentSchema.columns.map((c)=>[
297
- c.name,
298
- c
299
- ]));
300
- const fieldKeys = getKeys(meta.fields);
301
- // Check for new or altered columns
302
- for (const key of fieldKeys){
303
- const field = meta.fields[key];
304
- if (field?.virtual) continue;
305
- const columnName = this.resolveColumnName(key, field);
306
- const currentColumn = currentColumns.get(columnName);
307
- if (!currentColumn) {
308
- // Column needs to be added
309
- columnsToAdd.push(this.fieldToColumnSchema(key, field, meta));
310
- } else {
311
- // Check if column needs alteration
312
- const desiredColumn = this.fieldToColumnSchema(key, field, meta);
313
- if (this.columnsNeedAlteration(currentColumn, desiredColumn)) {
314
- columnsToAlter.push({
315
- from: currentColumn,
316
- to: desiredColumn
317
- });
318
- }
319
- }
320
- currentColumns.delete(columnName);
321
- }
322
- // Remaining columns in currentColumns should be dropped
323
- for (const [name] of currentColumns){
324
- columnsToDrop.push(name);
325
- }
326
- if (columnsToAdd.length === 0 && columnsToAlter.length === 0 && columnsToDrop.length === 0) {
327
- return undefined; // No changes needed
328
- }
329
- return {
330
- tableName: this.resolveTableName(entity, meta),
331
- type: 'alter',
332
- columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
333
- columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
334
- columnsToDrop: columnsToDrop.length > 0 ? columnsToDrop : undefined
335
- };
468
+ constructor(...args){
469
+ super(...args), this.name = _init_name8(this), this.address = _init_address(this), this.description = _init_description4(this);
336
470
  }
471
+ }
472
+ let _Item;
473
+ _dec51 = Entity(), _dec52 = Field(), _dec53 = Field(), _dec54 = Field(), _dec55 = Field({
474
+ reference: ()=>_LedgerAccount
475
+ }), _dec56 = ManyToOne(), _dec57 = Field({
476
+ reference: ()=>_LedgerAccount
477
+ }), _dec58 = ManyToOne(), _dec59 = Field({
478
+ reference: ()=>_Tax
479
+ }), _dec60 = ManyToOne(), _dec61 = Field({
480
+ reference: ()=>_MeasureUnit
481
+ }), _dec62 = ManyToOne(), _dec63 = Field(), _dec64 = Field(), _dec65 = ManyToMany({
482
+ entity: ()=>_Tag,
483
+ through: ()=>_ItemTag,
484
+ cascade: true
485
+ }), _dec66 = Field({
337
486
  /**
338
- * Convert field options to ColumnSchema
339
- */ fieldToColumnSchema(fieldKey, field, meta) {
340
- const isId = field.isId === true;
341
- const isPrimaryKey = isId && meta.id === fieldKey;
342
- return {
343
- name: this.resolveColumnName(fieldKey, field),
344
- type: this.getSqlType(field, field.type),
345
- nullable: field.nullable ?? !isPrimaryKey,
346
- defaultValue: field.defaultValue,
347
- isPrimaryKey,
348
- isAutoIncrement: isPrimaryKey && field.autoIncrement !== false && !field.onInsert,
349
- isUnique: field.unique ?? false,
350
- length: field.length,
351
- precision: field.precision,
352
- scale: field.scale,
353
- comment: field.comment
354
- };
487
+ * `virtual` property allows defining the value for a non-persistent field,
488
+ * such value might be a scalar or a (`raw`) function. Virtual-fields can
489
+ * be used in `$select` and `$where` as a common field whose value is
490
+ * replaced is replaced at runtime.
491
+ */ virtual: raw(({ ctx, escapedPrefix, dialect })=>{
492
+ ctx.append('(');
493
+ dialect.count(ctx, _ItemTag, {
494
+ $where: {
495
+ itemId: raw(({ ctx })=>{
496
+ ctx.append(escapedPrefix + dialect.escapeId('id'));
497
+ })
498
+ }
499
+ }, {
500
+ autoPrefix: true
501
+ });
502
+ ctx.append(')');
503
+ })
504
+ });
505
+ class Item extends (_BaseEntity9 = BaseEntity) {
506
+ static{
507
+ ({ e: [_init_name9, _init_description5, _init_code, _init_buyLedgerAccountId, _init_buyLedgerAccount, _init_saleLedgerAccountId, _init_saleLedgerAccount, _init_taxId, _init_tax, _init_measureUnitId, _init_measureUnit, _init_salePrice, _init_inventoryable, _init_tags, _init_tagsCount], c: [_Item, _initClass10] } = _apply_decs_2203_r(this, [
508
+ [
509
+ _dec52,
510
+ 0,
511
+ "name"
512
+ ],
513
+ [
514
+ _dec53,
515
+ 0,
516
+ "description"
517
+ ],
518
+ [
519
+ _dec54,
520
+ 0,
521
+ "code"
522
+ ],
523
+ [
524
+ _dec55,
525
+ 0,
526
+ "buyLedgerAccountId"
527
+ ],
528
+ [
529
+ _dec56,
530
+ 0,
531
+ "buyLedgerAccount"
532
+ ],
533
+ [
534
+ _dec57,
535
+ 0,
536
+ "saleLedgerAccountId"
537
+ ],
538
+ [
539
+ _dec58,
540
+ 0,
541
+ "saleLedgerAccount"
542
+ ],
543
+ [
544
+ _dec59,
545
+ 0,
546
+ "taxId"
547
+ ],
548
+ [
549
+ _dec60,
550
+ 0,
551
+ "tax"
552
+ ],
553
+ [
554
+ _dec61,
555
+ 0,
556
+ "measureUnitId"
557
+ ],
558
+ [
559
+ _dec62,
560
+ 0,
561
+ "measureUnit"
562
+ ],
563
+ [
564
+ _dec63,
565
+ 0,
566
+ "salePrice"
567
+ ],
568
+ [
569
+ _dec64,
570
+ 0,
571
+ "inventoryable"
572
+ ],
573
+ [
574
+ _dec65,
575
+ 0,
576
+ "tags"
577
+ ],
578
+ [
579
+ _dec66,
580
+ 0,
581
+ "tagsCount"
582
+ ]
583
+ ], [
584
+ _dec51
585
+ ], _BaseEntity9));
586
+ }
587
+ static{
588
+ _initClass10();
355
589
  }
356
- /**
357
- * Check if two columns differ enough to require alteration
358
- */ columnsNeedAlteration(current, desired) {
359
- // Compare relevant properties
360
- return current.type.toLowerCase() !== desired.type.toLowerCase() || current.nullable !== desired.nullable || current.isUnique !== desired.isUnique || JSON.stringify(current.defaultValue) !== JSON.stringify(desired.defaultValue);
590
+ constructor(...args){
591
+ super(...args), this.name = _init_name9(this), this.description = _init_description5(this), this.code = _init_code(this), this.buyLedgerAccountId = _init_buyLedgerAccountId(this), this.buyLedgerAccount = _init_buyLedgerAccount(this), this.saleLedgerAccountId = _init_saleLedgerAccountId(this), this.saleLedgerAccount = _init_saleLedgerAccount(this), this.taxId = _init_taxId(this), this.tax = _init_tax(this), this.measureUnitId = _init_measureUnitId(this), this.measureUnit = _init_measureUnit(this), this.salePrice = _init_salePrice(this), this.inventoryable = _init_inventoryable(this), this.tags = _init_tags(this), this.tagsCount = _init_tagsCount(this);
361
592
  }
362
593
  }
363
-
364
- /**
365
- * MySQL/MariaDB-specific schema generator
366
- */ class MysqlSchemaGenerator extends AbstractSchemaGenerator {
367
- mapColumnType(columnType, field) {
368
- switch(columnType){
369
- case 'int':
370
- return 'INT';
371
- case 'smallint':
372
- return 'SMALLINT';
373
- case 'bigint':
374
- return 'BIGINT';
375
- case 'float':
376
- return 'FLOAT';
377
- case 'double':
378
- case 'real':
379
- return 'DOUBLE';
380
- case 'decimal':
381
- case 'numeric':
382
- if (field.precision !== undefined) {
383
- if (field.scale !== undefined) {
384
- return `DECIMAL(${field.precision}, ${field.scale})`;
385
- }
386
- return `DECIMAL(${field.precision})`;
387
- }
388
- return 'DECIMAL(10, 2)';
389
- case 'boolean':
390
- return 'TINYINT(1)';
391
- case 'char':
392
- return `CHAR(${field.length ?? 1})`;
393
- case 'varchar':
394
- return `VARCHAR(${field.length ?? 255})`;
395
- case 'text':
396
- return 'TEXT';
397
- case 'uuid':
398
- return 'CHAR(36)';
399
- case 'date':
400
- return 'DATE';
401
- case 'time':
402
- return 'TIME';
403
- case 'timestamp':
404
- case 'timestamptz':
405
- return 'TIMESTAMP';
406
- case 'json':
407
- case 'jsonb':
408
- return 'JSON';
409
- case 'blob':
410
- case 'bytea':
411
- return 'BLOB';
412
- case 'vector':
413
- // MySQL doesn't have native vector support, use JSON
414
- return 'JSON';
415
- case 'serial':
416
- return 'INT AUTO_INCREMENT';
417
- case 'bigserial':
418
- return 'BIGINT AUTO_INCREMENT';
419
- default:
420
- return 'TEXT';
421
- }
594
+ let _Tag;
595
+ _dec67 = Entity(), _dec68 = Field(), _dec69 = ManyToMany({
596
+ entity: ()=>_Item,
597
+ mappedBy: (item)=>item.tags
598
+ }), _dec70 = Field({
599
+ virtual: raw(({ ctx, escapedPrefix, dialect })=>{
600
+ ctx.append('(');
601
+ dialect.count(ctx, _ItemTag, {
602
+ $where: {
603
+ tagId: raw(({ ctx })=>{
604
+ ctx.append(escapedPrefix + dialect.escapeId('id'));
605
+ })
606
+ }
607
+ }, {
608
+ autoPrefix: true
609
+ });
610
+ ctx.append(')');
611
+ })
612
+ });
613
+ class Tag extends (_BaseEntity10 = BaseEntity) {
614
+ static{
615
+ ({ e: [_init_name10, _init_items, _init_itemsCount], c: [_Tag, _initClass11] } = _apply_decs_2203_r(this, [
616
+ [
617
+ _dec68,
618
+ 0,
619
+ "name"
620
+ ],
621
+ [
622
+ _dec69,
623
+ 0,
624
+ "items"
625
+ ],
626
+ [
627
+ _dec70,
628
+ 0,
629
+ "itemsCount"
630
+ ]
631
+ ], [
632
+ _dec67
633
+ ], _BaseEntity10));
634
+ }
635
+ static{
636
+ _initClass11();
422
637
  }
423
- getBooleanType() {
424
- return 'TINYINT(1)';
638
+ constructor(...args){
639
+ super(...args), this.name = _init_name10(this), this.items = _init_items(this), this.itemsCount = _init_itemsCount(this);
425
640
  }
426
- generateAlterColumnStatements(tableName, column, newDefinition) {
427
- return [
428
- `ALTER TABLE ${this.escapeId(tableName)} MODIFY COLUMN ${newDefinition};`
429
- ];
641
+ }
642
+ let _ItemTag;
643
+ _dec71 = Entity(), _dec72 = Id(), _dec73 = Field({
644
+ reference: ()=>_Item
645
+ }), _dec74 = Field({
646
+ reference: ()=>_Tag
647
+ });
648
+ class ItemTag {
649
+ static{
650
+ ({ e: [_init_id2, _init_itemId, _init_tagId], c: [_ItemTag, _initClass12] } = _apply_decs_2203_r(this, [
651
+ [
652
+ _dec72,
653
+ 0,
654
+ "id"
655
+ ],
656
+ [
657
+ _dec73,
658
+ 0,
659
+ "itemId"
660
+ ],
661
+ [
662
+ _dec74,
663
+ 0,
664
+ "tagId"
665
+ ]
666
+ ], [
667
+ _dec71
668
+ ]));
669
+ }
670
+ static{
671
+ _initClass12();
672
+ }
673
+ constructor(){
674
+ this.id = _init_id2(this);
675
+ this.itemId = _init_itemId(this);
676
+ this.tagId = _init_tagId(this);
430
677
  }
431
- generateColumnComment(tableName, columnName, comment) {
432
- const escapedComment = comment.replace(/'/g, "''");
433
- return ` COMMENT '${escapedComment}'`;
678
+ }
679
+ let _InventoryAdjustment;
680
+ _dec75 = Entity(), _dec76 = OneToMany({
681
+ entity: ()=>_ItemAdjustment,
682
+ mappedBy: (rel)=>rel.inventoryAdjustment,
683
+ cascade: true
684
+ }), _dec77 = Field(), _dec78 = Field();
685
+ class InventoryAdjustment extends (_BaseEntity11 = BaseEntity) {
686
+ static{
687
+ ({ e: [_init_itemAdjustments, _init_date, _init_description6], c: [_InventoryAdjustment, _initClass13] } = _apply_decs_2203_r(this, [
688
+ [
689
+ _dec76,
690
+ 0,
691
+ "itemAdjustments"
692
+ ],
693
+ [
694
+ _dec77,
695
+ 0,
696
+ "date"
697
+ ],
698
+ [
699
+ _dec78,
700
+ 0,
701
+ "description"
702
+ ]
703
+ ], [
704
+ _dec75
705
+ ], _BaseEntity11));
706
+ }
707
+ static{
708
+ _initClass13();
434
709
  }
435
- getTableOptions() {
436
- return ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';
710
+ constructor(...args){
711
+ super(...args), this.itemAdjustments = _init_itemAdjustments(this), this.date = _init_date(this), this.description = _init_description6(this);
437
712
  }
438
- generateDropIndex(tableName, indexName) {
439
- // MySQL requires table name in DROP INDEX
440
- return `DROP INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)};`;
713
+ }
714
+ let _ItemAdjustment;
715
+ _dec79 = Entity(), _dec80 = Field({
716
+ reference: ()=>_Item
717
+ }), _dec81 = ManyToOne(), _dec82 = Field(), _dec83 = Field(), _dec84 = Field({
718
+ reference: ()=>_Storehouse
719
+ }), _dec85 = ManyToOne(), _dec86 = Field({
720
+ reference: ()=>_InventoryAdjustment
721
+ }), _dec87 = ManyToOne();
722
+ class ItemAdjustment extends (_BaseEntity12 = BaseEntity) {
723
+ static{
724
+ ({ e: [_init_itemId1, _init_item, _init_number, _init_buyPrice, _init_storehouseId, _init_storehouse, _init_inventoryAdjustmentId, _init_inventoryAdjustment], c: [_ItemAdjustment, _initClass14] } = _apply_decs_2203_r(this, [
725
+ [
726
+ _dec80,
727
+ 0,
728
+ "itemId"
729
+ ],
730
+ [
731
+ _dec81,
732
+ 0,
733
+ "item"
734
+ ],
735
+ [
736
+ _dec82,
737
+ 0,
738
+ "number"
739
+ ],
740
+ [
741
+ _dec83,
742
+ 0,
743
+ "buyPrice"
744
+ ],
745
+ [
746
+ _dec84,
747
+ 0,
748
+ "storehouseId"
749
+ ],
750
+ [
751
+ _dec85,
752
+ 0,
753
+ "storehouse"
754
+ ],
755
+ [
756
+ _dec86,
757
+ 0,
758
+ "inventoryAdjustmentId"
759
+ ],
760
+ [
761
+ _dec87,
762
+ 0,
763
+ "inventoryAdjustment"
764
+ ]
765
+ ], [
766
+ _dec79
767
+ ], _BaseEntity12));
768
+ }
769
+ static{
770
+ _initClass14();
441
771
  }
442
772
  constructor(...args){
443
- super(...args), this.serialPrimaryKeyType = 'INT AUTO_INCREMENT PRIMARY KEY';
773
+ super(...args), this.itemId = _init_itemId1(this), this.item = _init_item(this), this.number = _init_number(this), this.buyPrice = _init_buyPrice(this), this.storehouseId = _init_storehouseId(this), this.storehouse = _init_storehouse(this), this.inventoryAdjustmentId = _init_inventoryAdjustmentId(this), this.inventoryAdjustment = _init_inventoryAdjustment(this);
444
774
  }
445
775
  }
446
776
 
447
- /**
448
- * PostgreSQL-specific schema generator
449
- */ class PostgresSchemaGenerator extends AbstractSchemaGenerator {
450
- constructor(namingStrategy){
451
- super(namingStrategy, '"'), this.serialPrimaryKeyType = 'SERIAL PRIMARY KEY';
452
- }
453
- mapColumnType(columnType, field) {
454
- switch(columnType){
455
- case 'int':
456
- return 'INTEGER';
457
- case 'smallint':
458
- return 'SMALLINT';
459
- case 'bigint':
460
- return 'BIGINT';
461
- case 'float':
462
- case 'double':
463
- case 'real':
464
- return 'DOUBLE PRECISION';
465
- case 'decimal':
466
- case 'numeric':
467
- if (field.precision !== undefined) {
468
- if (field.scale !== undefined) {
469
- return `NUMERIC(${field.precision}, ${field.scale})`;
470
- }
471
- return `NUMERIC(${field.precision})`;
472
- }
473
- return 'NUMERIC';
474
- case 'boolean':
475
- return 'BOOLEAN';
476
- case 'char':
477
- return `CHAR(${field.length ?? 1})`;
478
- case 'varchar':
479
- return `VARCHAR(${field.length ?? 255})`;
480
- case 'text':
481
- return 'TEXT';
482
- case 'uuid':
483
- return 'UUID';
484
- case 'date':
485
- return 'DATE';
486
- case 'time':
487
- return 'TIME';
488
- case 'timestamp':
489
- return 'TIMESTAMP';
490
- case 'timestamptz':
491
- return 'TIMESTAMPTZ';
492
- case 'json':
493
- return 'JSON';
494
- case 'jsonb':
495
- return 'JSONB';
496
- case 'blob':
497
- case 'bytea':
498
- return 'BYTEA';
499
- case 'vector':
500
- if (field.length) {
501
- return `VECTOR(${field.length})`;
502
- }
503
- return 'VECTOR';
504
- case 'serial':
505
- return 'SERIAL';
506
- case 'bigserial':
507
- return 'BIGSERIAL';
508
- default:
509
- return 'TEXT';
510
- }
777
+ const holder = globalThis;
778
+ const metaKey = '@uql/core/entity/decorator';
779
+ const metas = holder[metaKey] ?? new Map();
780
+ holder[metaKey] = metas;
781
+ function getEntities() {
782
+ return [
783
+ ...metas.entries()
784
+ ].reduce((acc, [key, val])=>{
785
+ if (val.id) {
786
+ acc.push(key);
787
+ }
788
+ return acc;
789
+ }, []);
790
+ }
791
+ function ensureMeta(entity) {
792
+ let meta = metas.get(entity);
793
+ if (meta) {
794
+ return meta;
795
+ }
796
+ meta = {
797
+ entity,
798
+ fields: {},
799
+ relations: {}
800
+ };
801
+ metas.set(entity, meta);
802
+ return meta;
803
+ }
804
+ function getMeta(entity) {
805
+ const meta = metas.get(entity);
806
+ if (!meta) {
807
+ throw TypeError(`'${entity.name}' is not an entity`);
511
808
  }
512
- getBooleanType() {
513
- return 'BOOLEAN';
809
+ if (meta.processed) {
810
+ return meta;
514
811
  }
515
- generateAlterColumnStatements(tableName, column, newDefinition) {
516
- const statements = [];
517
- const escapedTableName = this.escapeId(tableName);
518
- const escapedColumnName = this.escapeId(column.name);
519
- // PostgreSQL uses separate ALTER COLUMN clauses for different changes
520
- // 1. Change type
521
- statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} TYPE ${column.type};`);
522
- // 2. Change nullability
523
- if (column.nullable) {
524
- statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} DROP NOT NULL;`);
812
+ meta.processed = true;
813
+ return fillRelations(meta);
814
+ }
815
+ function fillRelations(meta) {
816
+ for(const relKey in meta.relations){
817
+ const relOpts = meta.relations[relKey];
818
+ if (relOpts.references) {
819
+ continue;
820
+ }
821
+ if (relOpts.mappedBy) {
822
+ fillInverseSideRelations(relOpts);
823
+ continue;
824
+ }
825
+ const relEntity = relOpts.entity();
826
+ const relMeta = ensureMeta(relEntity);
827
+ if (relOpts.cardinality === 'mm') {
828
+ const idName = meta.fields[meta.id].name;
829
+ const relIdName = relMeta.fields[relMeta.id].name;
830
+ const source = lowerFirst(meta.name) + upperFirst(idName);
831
+ const target = lowerFirst(relMeta.name) + upperFirst(relIdName);
832
+ relOpts.references = [
833
+ {
834
+ local: source,
835
+ foreign: meta.id
836
+ },
837
+ {
838
+ local: target,
839
+ foreign: relMeta.id
840
+ }
841
+ ];
525
842
  } else {
526
- statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} SET NOT NULL;`);
843
+ relOpts.references = [
844
+ {
845
+ local: `${relKey}Id`,
846
+ foreign: relMeta.id
847
+ }
848
+ ];
527
849
  }
528
- // 3. Change default value
529
- if (column.defaultValue !== undefined) {
530
- statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} SET DEFAULT ${this.formatDefaultValue(column.defaultValue)};`);
531
- } else {
532
- statements.push(`ALTER TABLE ${escapedTableName} ALTER COLUMN ${escapedColumnName} DROP DEFAULT;`);
850
+ if (relOpts.through) {
851
+ fillThroughRelations(relOpts.through());
533
852
  }
534
- return statements;
535
- }
536
- generateColumnComment(tableName, columnName, comment) {
537
- // PostgreSQL handles comments separately via COMMENT ON COLUMN
538
- return '';
539
- }
540
- /**
541
- * Generate COMMENT ON COLUMN statement for PostgreSQL
542
- */ generateColumnCommentStatement(tableName, columnName, comment) {
543
- const escapedComment = comment.replace(/'/g, "''");
544
- return `COMMENT ON COLUMN ${this.escapeId(tableName)}.${this.escapeId(columnName)} IS '${escapedComment}';`;
545
- }
546
- generateDropIndex(tableName, indexName) {
547
- // PostgreSQL doesn't require table name in DROP INDEX
548
- return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
549
853
  }
854
+ return meta;
550
855
  }
551
-
552
- /**
553
- * SQLite-specific schema generator
554
- */ class SqliteSchemaGenerator extends AbstractSchemaGenerator {
555
- mapColumnType(columnType, field) {
556
- // SQLite has dynamic typing, but we use type affinity
557
- switch(columnType){
558
- case 'int':
559
- case 'smallint':
560
- case 'bigint':
561
- case 'serial':
562
- case 'bigserial':
563
- return 'INTEGER';
564
- case 'float':
565
- case 'double':
566
- case 'real':
567
- case 'decimal':
568
- case 'numeric':
569
- return 'REAL';
570
- case 'boolean':
571
- return 'INTEGER'; // SQLite uses 0/1 for booleans
572
- case 'char':
573
- case 'varchar':
574
- case 'text':
575
- case 'uuid':
576
- return 'TEXT';
577
- case 'date':
578
- case 'time':
579
- case 'timestamp':
580
- case 'timestamptz':
581
- return 'TEXT'; // SQLite stores dates as TEXT or INTEGER
582
- case 'json':
583
- case 'jsonb':
584
- return 'TEXT'; // SQLite stores JSON as TEXT
585
- case 'blob':
586
- case 'bytea':
587
- return 'BLOB';
588
- case 'vector':
589
- return 'TEXT'; // Store as JSON array
590
- default:
591
- return 'TEXT';
592
- }
593
- }
594
- getBooleanType() {
595
- return 'INTEGER';
596
- }
597
- generateAlterColumnStatements(tableName, column, newDefinition) {
598
- // SQLite has very limited ALTER TABLE support
599
- // Column type changes require recreating the table
600
- throw new Error(`SQLite does not support altering column '${column.name}' in table '${tableName}'. ` + 'You need to recreate the table to change column types.');
601
- }
602
- generateColumnComment(tableName, columnName, comment) {
603
- return '';
604
- }
605
- generateDropIndex(tableName, indexName) {
606
- return `DROP INDEX IF EXISTS ${this.escapeId(indexName)};`;
607
- }
608
- formatDefaultValue(value) {
609
- if (typeof value === 'boolean') {
610
- return value ? '1' : '0';
856
+ function fillInverseSideRelations(relOpts) {
857
+ const relEntity = relOpts.entity();
858
+ const relMeta = getMeta(relEntity);
859
+ relOpts.mappedBy = getMappedByRelationKey(relOpts);
860
+ if (relMeta.fields[relOpts.mappedBy]) {
861
+ relOpts.references = [
862
+ {
863
+ local: relMeta.id,
864
+ foreign: relOpts.mappedBy
865
+ }
866
+ ];
867
+ return;
868
+ }
869
+ const mappedByRelation = relMeta.relations[relOpts.mappedBy];
870
+ if (relOpts.cardinality === 'm1' || relOpts.cardinality === 'mm') {
871
+ relOpts.references = mappedByRelation.references.slice().reverse();
872
+ relOpts.through = mappedByRelation.through;
873
+ return;
874
+ }
875
+ relOpts.references = mappedByRelation.references.map(({ local, foreign })=>({
876
+ local: foreign,
877
+ foreign: local
878
+ }));
879
+ }
880
+ function fillThroughRelations(entity) {
881
+ const meta = ensureMeta(entity);
882
+ meta.relations = getKeys(meta.fields).reduce((relations, key)=>{
883
+ const { reference } = meta.fields[key];
884
+ if (reference) {
885
+ const relEntity = reference();
886
+ const relMeta = ensureMeta(relEntity);
887
+ const relKey = key.slice(0, -relMeta.id.length);
888
+ const relOpts = {
889
+ entity: reference,
890
+ cardinality: 'm1',
891
+ references: [
892
+ {
893
+ local: key,
894
+ foreign: relMeta.id
895
+ }
896
+ ]
897
+ };
898
+ relations[relKey] = relOpts;
611
899
  }
612
- return super.formatDefaultValue(value);
613
- }
614
- constructor(...args){
615
- super(...args), this.serialPrimaryKeyType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
616
- }
900
+ return relations;
901
+ }, {});
902
+ }
903
+ function getMappedByRelationKey(relOpts) {
904
+ if (typeof relOpts.mappedBy === 'function') {
905
+ const relEntity = relOpts.entity();
906
+ const relMeta = ensureMeta(relEntity);
907
+ const keyMap = getRelationKeyMap(relMeta);
908
+ return relOpts.mappedBy(keyMap);
909
+ }
910
+ return relOpts.mappedBy;
911
+ }
912
+ function getRelationKeyMap(meta) {
913
+ return getKeys(meta.fields).concat(getKeys(meta.relations)).reduce((acc, key)=>{
914
+ acc[key] = key;
915
+ return acc;
916
+ }, {});
617
917
  }
618
918
 
619
- /**
620
- * MySQL/MariaDB schema introspector.
621
- * Works with both MySQL and MariaDB as they share the same information_schema structure.
622
- */ class MysqlSchemaIntrospector {
623
- constructor(pool){
624
- this.pool = pool;
625
- }
626
- async getTableSchema(tableName) {
627
- const querier = await this.getQuerier();
628
- try {
629
- const exists = await this.tableExistsInternal(querier, tableName);
630
- if (!exists) {
631
- return undefined;
919
+ async function createTables(querier, primaryKeyType) {
920
+ const entities = getEntities();
921
+ await Promise.all(entities.map((entity)=>{
922
+ const sql = getDdlForTable(entity, querier, primaryKeyType);
923
+ return querier.run(sql);
924
+ }));
925
+ }
926
+ async function dropTables(querier) {
927
+ const entities = getEntities();
928
+ await Promise.all(entities.map((entity)=>{
929
+ const meta = getMeta(entity);
930
+ const sql = `DROP TABLE IF EXISTS ${querier.dialect.escapeId(meta.name)}`;
931
+ return querier.run(sql);
932
+ }));
933
+ }
934
+ async function clearTables(querier) {
935
+ const entities = getEntities();
936
+ await Promise.all(entities.map((entity)=>{
937
+ const ctx = querier.dialect.createContext();
938
+ querier.dialect.delete(ctx, entity, {});
939
+ return querier.run(ctx.sql, ctx.values);
940
+ }));
941
+ }
942
+ function getDdlForTable(entity, querier, primaryKeyType) {
943
+ const meta = getMeta(entity);
944
+ let sql = `CREATE TABLE ${querier.dialect.escapeId(meta.name)} (\n\t`;
945
+ const insertableIdType = 'VARCHAR(36)';
946
+ const defaultType = 'VARCHAR(255)';
947
+ const columns = getKeys(meta.fields).map((key)=>{
948
+ const field = meta.fields[key];
949
+ let propSql = querier.dialect.escapeId(field.name) + ' ';
950
+ if (field.isId) {
951
+ propSql += field.onInsert ? `${insertableIdType} PRIMARY KEY` : primaryKeyType;
952
+ } else {
953
+ if (field.type === Number) {
954
+ propSql += 'BIGINT';
955
+ } else if (field.type === Date) {
956
+ propSql += 'TIMESTAMP';
957
+ } else {
958
+ propSql += defaultType;
632
959
  }
633
- const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
634
- this.getColumns(querier, tableName),
635
- this.getIndexes(querier, tableName),
636
- this.getForeignKeys(querier, tableName),
637
- this.getPrimaryKey(querier, tableName)
638
- ]);
639
- return {
640
- name: tableName,
641
- columns,
642
- primaryKey,
643
- indexes,
644
- foreignKeys
645
- };
646
- } finally{
647
- await querier.release();
648
- }
649
- }
650
- async getTableNames() {
651
- const querier = await this.getQuerier();
652
- try {
653
- const sql = `
654
- SELECT TABLE_NAME as table_name
655
- FROM information_schema.TABLES
656
- WHERE TABLE_SCHEMA = DATABASE()
657
- AND TABLE_TYPE = 'BASE TABLE'
658
- ORDER BY TABLE_NAME
659
- `;
660
- const results = await querier.all(sql);
661
- return results.map((r)=>r.table_name);
662
- } finally{
663
- await querier.release();
664
- }
665
- }
666
- async tableExists(tableName) {
667
- const querier = await this.getQuerier();
668
- try {
669
- return this.tableExistsInternal(querier, tableName);
670
- } finally{
671
- await querier.release();
672
- }
673
- }
674
- async tableExistsInternal(querier, tableName) {
675
- const sql = `
676
- SELECT COUNT(*) as count
677
- FROM information_schema.TABLES
678
- WHERE TABLE_SCHEMA = DATABASE()
679
- AND TABLE_NAME = ?
680
- `;
681
- const results = await querier.all(sql, [
682
- tableName
683
- ]);
684
- return (results[0]?.count ?? 0) > 0;
685
- }
686
- async getQuerier() {
687
- const querier = await this.pool.getQuerier();
688
- if (!isSqlQuerier(querier)) {
689
- await querier.release();
690
- throw new Error('MysqlSchemaIntrospector requires a SQL-based querier');
691
960
  }
692
- return querier;
693
- }
694
- async getColumns(querier, tableName) {
695
- const sql = `
696
- SELECT
697
- COLUMN_NAME as column_name,
698
- DATA_TYPE as data_type,
699
- COLUMN_TYPE as column_type,
700
- IS_NULLABLE as is_nullable,
701
- COLUMN_DEFAULT as column_default,
702
- CHARACTER_MAXIMUM_LENGTH as character_maximum_length,
703
- NUMERIC_PRECISION as numeric_precision,
704
- NUMERIC_SCALE as numeric_scale,
705
- COLUMN_KEY as column_key,
706
- EXTRA as extra,
707
- COLUMN_COMMENT as column_comment
708
- FROM information_schema.COLUMNS
709
- WHERE TABLE_SCHEMA = DATABASE()
710
- AND TABLE_NAME = ?
711
- ORDER BY ORDINAL_POSITION
712
- `;
713
- const results = await querier.all(sql, [
714
- tableName
715
- ]);
716
- return results.map((row)=>({
717
- name: row.column_name,
718
- type: row.data_type.toUpperCase(),
719
- nullable: row.is_nullable === 'YES',
720
- defaultValue: this.parseDefaultValue(row.column_default),
721
- isPrimaryKey: row.column_key === 'PRI',
722
- isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
723
- isUnique: row.column_key === 'UNI',
724
- length: row.character_maximum_length ?? undefined,
725
- precision: row.numeric_precision ?? undefined,
726
- scale: row.numeric_scale ?? undefined,
727
- comment: row.column_comment || undefined
728
- }));
729
- }
730
- async getIndexes(querier, tableName) {
731
- const sql = `
732
- SELECT
733
- INDEX_NAME as index_name,
734
- GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
735
- NOT NON_UNIQUE as is_unique
736
- FROM information_schema.STATISTICS
737
- WHERE TABLE_SCHEMA = DATABASE()
738
- AND TABLE_NAME = ?
739
- AND INDEX_NAME != 'PRIMARY'
740
- GROUP BY INDEX_NAME, NON_UNIQUE
741
- ORDER BY INDEX_NAME
742
- `;
743
- const results = await querier.all(sql, [
744
- tableName
745
- ]);
746
- return results.map((row)=>({
747
- name: row.index_name,
748
- columns: row.columns.split(','),
749
- unique: Boolean(row.is_unique)
750
- }));
751
- }
752
- async getForeignKeys(querier, tableName) {
753
- const sql = `
754
- SELECT
755
- kcu.CONSTRAINT_NAME as constraint_name,
756
- GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as columns,
757
- kcu.REFERENCED_TABLE_NAME as referenced_table,
758
- GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) as referenced_columns,
759
- rc.DELETE_RULE as delete_rule,
760
- rc.UPDATE_RULE as update_rule
761
- FROM information_schema.KEY_COLUMN_USAGE kcu
762
- JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
763
- ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
764
- AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
765
- WHERE kcu.TABLE_SCHEMA = DATABASE()
766
- AND kcu.TABLE_NAME = ?
767
- AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
768
- GROUP BY kcu.CONSTRAINT_NAME, kcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE, rc.UPDATE_RULE
769
- ORDER BY kcu.CONSTRAINT_NAME
770
- `;
771
- const results = await querier.all(sql, [
772
- tableName
773
- ]);
774
- return results.map((row)=>({
775
- name: row.constraint_name,
776
- columns: row.columns.split(','),
777
- referencedTable: row.referenced_table,
778
- referencedColumns: row.referenced_columns.split(','),
779
- onDelete: this.normalizeReferentialAction(row.delete_rule),
780
- onUpdate: this.normalizeReferentialAction(row.update_rule)
781
- }));
782
- }
783
- async getPrimaryKey(querier, tableName) {
784
- const sql = `
785
- SELECT COLUMN_NAME as column_name
786
- FROM information_schema.KEY_COLUMN_USAGE
787
- WHERE TABLE_SCHEMA = DATABASE()
788
- AND TABLE_NAME = ?
789
- AND CONSTRAINT_NAME = 'PRIMARY'
790
- ORDER BY ORDINAL_POSITION
791
- `;
792
- const results = await querier.all(sql, [
793
- tableName
794
- ]);
795
- if (results.length === 0) {
796
- return undefined;
797
- }
798
- return results.map((r)=>r.column_name);
799
- }
800
- parseDefaultValue(defaultValue) {
801
- if (defaultValue === null) {
802
- return undefined;
803
- }
804
- // Check for common patterns
805
- if (defaultValue === 'NULL') {
806
- return null;
807
- }
808
- if (defaultValue === 'CURRENT_TIMESTAMP') {
809
- return defaultValue;
810
- }
811
- if (/^'.*'$/.test(defaultValue)) {
812
- return defaultValue.slice(1, -1);
813
- }
814
- if (/^-?\d+$/.test(defaultValue)) {
815
- return Number.parseInt(defaultValue, 10);
816
- }
817
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
818
- return Number.parseFloat(defaultValue);
819
- }
820
- return defaultValue;
821
- }
822
- normalizeReferentialAction(action) {
823
- switch(action.toUpperCase()){
824
- case 'CASCADE':
825
- return 'CASCADE';
826
- case 'SET NULL':
827
- return 'SET NULL';
828
- case 'RESTRICT':
829
- return 'RESTRICT';
830
- case 'NO ACTION':
831
- return 'NO ACTION';
832
- default:
833
- return undefined;
834
- }
835
- }
961
+ return propSql;
962
+ });
963
+ sql += columns.join(',\n\t');
964
+ sql += `\n);`;
965
+ // log('sql', sql);
966
+ return sql;
836
967
  }
837
- /**
838
- * Alias for MysqlSchemaIntrospector.
839
- * MariaDB uses the same information_schema structure as MySQL.
840
- */ const MariadbSchemaIntrospector = MysqlSchemaIntrospector;
841
968
 
842
- /**
843
- * PostgreSQL schema introspector
844
- */ class PostgresSchemaIntrospector {
845
- constructor(pool){
846
- this.pool = pool;
847
- }
848
- async getTableSchema(tableName) {
849
- const querier = await this.getQuerier();
850
- try {
851
- const exists = await this.tableExistsInternal(querier, tableName);
852
- if (!exists) {
853
- return undefined;
969
+ function createSpec(spec) {
970
+ const proto = Object.getPrototypeOf(spec);
971
+ let describeFn;
972
+ const specName = proto.constructor.name;
973
+ if (specName.startsWith('fff')) {
974
+ describeFn = describe.only;
975
+ } else if (specName.startsWith('xxx')) {
976
+ describeFn = describe.skip;
977
+ } else {
978
+ describeFn = describe;
979
+ }
980
+ describeFn(specName, ()=>createTestCases(spec));
981
+ }
982
+ function createTestCases(spec) {
983
+ let proto = Object.getPrototypeOf(spec);
984
+ const processedMethodsMap = {};
985
+ while(proto.constructor !== Object){
986
+ for (const key of Object.getOwnPropertyNames(proto)){
987
+ const isProcessed = processedMethodsMap[key];
988
+ processedMethodsMap[key] = true;
989
+ if (isProcessed || key === 'constructor' || typeof spec[key] !== 'function') {
990
+ continue;
991
+ }
992
+ const callback = spec[key].bind(spec);
993
+ if (hooks[key]) {
994
+ hooks[key](callback);
995
+ } else if (key.startsWith('should')) {
996
+ it(key, callback);
997
+ } else if (key.startsWith('fffShould')) {
998
+ it.only(key, callback);
999
+ } else if (key.startsWith('xxxShould')) {
1000
+ it.skip(key, callback);
854
1001
  }
855
- const [columns, indexes, foreignKeys, primaryKey] = await Promise.all([
856
- this.getColumns(querier, tableName),
857
- this.getIndexes(querier, tableName),
858
- this.getForeignKeys(querier, tableName),
859
- this.getPrimaryKey(querier, tableName)
860
- ]);
861
- return {
862
- name: tableName,
863
- columns,
864
- primaryKey,
865
- indexes,
866
- foreignKeys
867
- };
868
- } finally{
869
- await querier.release();
870
1002
  }
1003
+ proto = Object.getPrototypeOf(proto);
871
1004
  }
872
- async getTableNames() {
873
- const querier = await this.getQuerier();
1005
+ }
1006
+ const hooks = {
1007
+ beforeAll,
1008
+ beforeEach,
1009
+ afterEach,
1010
+ afterAll
1011
+ };
1012
+
1013
+ export { BaseEntity, _Company as Company, _InventoryAdjustment as InventoryAdjustment, _Item as Item, _ItemAdjustment as ItemAdjustment, _ItemTag as ItemTag, _LedgerAccount as LedgerAccount, _MeasureUnit as MeasureUnit, _MeasureUnitCategory as MeasureUnitCategory, _Profile as Profile, _Storehouse as Storehouse, _Tag as Tag, _Tax as Tax, _TaxCategory as TaxCategory, _User as User, _UserWithNonUpdatableId as UserWithNonUpdatableId, clearTables, createSpec, createTables, dropTables };
1014
+ //# sourceMappingURL=uql-browser.min.js.map
1015
+ nst querier = await this.getQuerier();
874
1016
  try {
875
1017
  const sql = `
876
1018
  SELECT table_name