orchid-orm 1.17.36 → 1.17.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -337,7 +337,10 @@ type MapRelations<T extends Table> = T extends {
337
337
  [K in keyof T['relations']]: MapRelation<T, T['relations'], K>;
338
338
  } : EmptyObject;
339
339
 
340
- type TableClass<T extends Table = Table> = new () => T;
340
+ type TableClass<T extends Table = Table> = {
341
+ new (): T;
342
+ instance(): T;
343
+ };
341
344
  type TableClasses = Record<string, TableClass>;
342
345
  type TableToDb<T extends Table, RelationQueries extends Record<string, RelationQueryBase>> = Db<T['table'], T['columns'], RelationQueries, T['types'], T['computed'] extends ComputedColumnsBase<never> ? T['columns'] & {
343
346
  [K in keyof T['computed']]: ReturnType<T['computed'][K]>['_type'];
@@ -370,151 +373,153 @@ type BeforeHookMethod = <T extends Table>(cb: QueryBeforeHook) => T;
370
373
  type AfterHookMethod = <T extends Table>(cb: QueryAfterHook) => T;
371
374
  type AfterSelectableHookMethod = <T extends Table, S extends (keyof T['columns'])[]>(this: T, select: S, cb: AfterHook<S, T['columns']>) => T;
372
375
  type SchemaProviderBase = any;
376
+ interface BaseTableInstance<ColumnTypes> {
377
+ table: string;
378
+ columns: ColumnsShapeBase;
379
+ schema?: string;
380
+ noPrimaryKey?: boolean;
381
+ snakeCase?: boolean;
382
+ types: ColumnTypes;
383
+ q: QueryData;
384
+ language?: string;
385
+ filePath: string;
386
+ result: ColumnsShapeBase;
387
+ clone<T extends QueryBase>(this: T): T;
388
+ getFilePath(): string;
389
+ setColumns<T extends ColumnsShapeBase>(fn: (t: ColumnTypes) => T): T;
390
+ /**
391
+ * You can add a generated column in the migration (see [generated](/guide/migration-column-methods.html#generated-column)),
392
+ * such column will persist in the database, it can be indexed.
393
+ *
394
+ * Or you can add a computed column on the ORM level, without adding it to the database, in such a way:
395
+ *
396
+ * ```ts
397
+ * import { BaseTable } from './baseTable';
398
+ *
399
+ * export class UserTable extends BaseTable {
400
+ * readonly table = 'user';
401
+ * columns = this.setColumns((t) => ({
402
+ * id: t.identity().primaryKey(),
403
+ * firstName: t.string(),
404
+ * lastName: t.string(),
405
+ * }));
406
+ *
407
+ * computed = this.setComputed({
408
+ * fullName: (q) =>
409
+ * q.sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}`.type(
410
+ * (t) => t.string(),
411
+ * ),
412
+ * });
413
+ * }
414
+ * ```
415
+ *
416
+ * `setComputed` takes an object where keys are computed column names, and values are functions returning raw SQL.
417
+ *
418
+ * Use `q.column` as shown above to reference a table column, it will be prefixed with a correct table name even if the table is joined under a different name.
419
+ *
420
+ * Computed columns are not selected by default, only on demand:
421
+ *
422
+ * ```ts
423
+ * const a = await db.user.take();
424
+ * a.fullName; // not selected
425
+ *
426
+ * const b = await db.user.select('*', 'fullName');
427
+ * b.fullName; // selected
428
+ *
429
+ * // Table post belongs to user as an author.
430
+ * // it's possible to select joined computed column:
431
+ * const posts = await db.post
432
+ * .join('author')
433
+ * .select('post.title', 'author.fullName');
434
+ * ```
435
+ *
436
+ * SQL query can be generated dynamically based on the current request context.
437
+ *
438
+ * Imagine we are using [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking)
439
+ * to keep track of current user's language.
440
+ *
441
+ * And we have articles translated to different languages, each article has `title_en`, `title_uk`, `title_be` and so on.
442
+ *
443
+ * We can define a computed `title` by passing a function into `sql` method:
444
+ *
445
+ * ```ts
446
+ * type Locale = 'en' | 'uk' | 'be';
447
+ * const asyncLanguageStorage = new AsyncLocalStorage<Locale>();
448
+ * const defaultLocale: Locale = 'en';
449
+ *
450
+ * export class ArticleTable extends BaseTable {
451
+ * readonly table = 'article';
452
+ * columns = this.setColumns((t) => ({
453
+ * id: t.identity().primaryKey(),
454
+ * title_en: t.text(),
455
+ * title_uk: t.text().nullable(),
456
+ * title_be: t.text().nullable(),
457
+ * }));
458
+ *
459
+ * computed = this.setComputed({
460
+ * title: (q) =>
461
+ * q
462
+ * // .sql can take a function that accepts `sql` argument and must return SQL
463
+ * .sql((sql) => {
464
+ * // get locale dynamically based on current storage value
465
+ * const locale = asyncLanguageStorage.getStore() || defaultLocale;
466
+ *
467
+ * // use COALESCE in case when localized title is NULL, use title_en
468
+ * return sql`COALESCE(
469
+ * ${q.column(`title_${locale}`)},
470
+ * ${q.column(`title_${defaultLocale}`)}
471
+ * )`;
472
+ * })
473
+ * .type((t) => t.text()),
474
+ * });
475
+ * }
476
+ * ```
477
+ *
478
+ * @param computed - object where keys are column names and values are functions returning raw SQL
479
+ */
480
+ setComputed<Table extends string, Shape extends ColumnsShapeBase, Computed extends ComputedColumnsBase<Db<Table, Shape, EmptyObject, ColumnTypes>>>(computed: Computed): Computed;
481
+ belongsTo<Self extends Table, Related extends TableClass, Scope extends Query, Options extends BelongsToOptions<Self, Related, Scope>>(this: Self, fn: () => Related, options: Options): {
482
+ type: 'belongsTo';
483
+ fn: () => Related;
484
+ options: Options;
485
+ };
486
+ hasOne<Self extends Table, Related extends TableClass, Scope extends Query, Through extends string, Source extends string, Options extends HasOneOptions<Self, Related, Scope, Through, Source>>(this: Self, fn: () => Related, options: Options): {
487
+ type: 'hasOne';
488
+ fn: () => Related;
489
+ options: Options;
490
+ };
491
+ hasMany<Self extends Table, Related extends TableClass, Scope extends Query, Through extends string, Source extends string, Options extends HasManyOptions<Self, Related, Scope, Through, Source>>(this: Self, fn: () => Related, options: Options): {
492
+ type: 'hasMany';
493
+ fn: () => Related;
494
+ options: Options;
495
+ };
496
+ hasAndBelongsToMany<Self extends Table, Related extends TableClass, Scope extends Query, Options extends HasAndBelongsToManyOptions<Self, Related, Scope>>(this: Self, fn: () => Related, options: Options): {
497
+ type: 'hasAndBelongsToMany';
498
+ fn: () => Related;
499
+ options: Options;
500
+ };
501
+ beforeQuery: BeforeHookMethod;
502
+ afterQuery: AfterHookMethod;
503
+ beforeCreate: BeforeHookMethod;
504
+ afterCreate: AfterSelectableHookMethod;
505
+ afterCreateCommit: AfterSelectableHookMethod;
506
+ beforeUpdate: BeforeHookMethod;
507
+ afterUpdate: AfterSelectableHookMethod;
508
+ afterUpdateCommit: AfterSelectableHookMethod;
509
+ beforeSave: BeforeHookMethod;
510
+ afterSave: AfterSelectableHookMethod;
511
+ afterSaveCommit: AfterSelectableHookMethod;
512
+ beforeDelete: BeforeHookMethod;
513
+ afterDelete: AfterSelectableHookMethod;
514
+ afterDeleteCommit: AfterSelectableHookMethod;
515
+ }
373
516
  interface BaseTableClass<ColumnTypes, SchemaProvider extends SchemaProviderBase> {
374
517
  nowSQL: string | undefined;
375
518
  exportAs: string;
376
519
  schema: SchemaProvider;
377
520
  getFilePath(): string;
378
- new (): {
379
- table: string;
380
- columns: ColumnsShapeBase;
381
- schema?: string;
382
- noPrimaryKey?: boolean;
383
- snakeCase?: boolean;
384
- types: ColumnTypes;
385
- q: QueryData;
386
- language?: string;
387
- filePath: string;
388
- result: ColumnsShapeBase;
389
- clone<T extends QueryBase>(this: T): T;
390
- getFilePath(): string;
391
- setColumns<T extends ColumnsShapeBase>(fn: (t: ColumnTypes) => T): T;
392
- /**
393
- * You can add a generated column in the migration (see [generated](/guide/migration-column-methods.html#generated-column)),
394
- * such column will persist in the database, it can be indexed.
395
- *
396
- * Or you can add a computed column on the ORM level, without adding it to the database, in such a way:
397
- *
398
- * ```ts
399
- * import { BaseTable } from './baseTable';
400
- *
401
- * export class UserTable extends BaseTable {
402
- * readonly table = 'user';
403
- * columns = this.setColumns((t) => ({
404
- * id: t.identity().primaryKey(),
405
- * firstName: t.string(),
406
- * lastName: t.string(),
407
- * }));
408
- *
409
- * computed = this.setComputed({
410
- * fullName: (q) =>
411
- * q.sql`${q.column('firstName')} || ' ' || ${q.column('lastName')}`.type(
412
- * (t) => t.string(),
413
- * ),
414
- * });
415
- * }
416
- * ```
417
- *
418
- * `setComputed` takes an object where keys are computed column names, and values are functions returning raw SQL.
419
- *
420
- * Use `q.column` as shown above to reference a table column, it will be prefixed with a correct table name even if the table is joined under a different name.
421
- *
422
- * Computed columns are not selected by default, only on demand:
423
- *
424
- * ```ts
425
- * const a = await db.user.take();
426
- * a.fullName; // not selected
427
- *
428
- * const b = await db.user.select('*', 'fullName');
429
- * b.fullName; // selected
430
- *
431
- * // Table post belongs to user as an author.
432
- * // it's possible to select joined computed column:
433
- * const posts = await db.post
434
- * .join('author')
435
- * .select('post.title', 'author.fullName');
436
- * ```
437
- *
438
- * SQL query can be generated dynamically based on the current request context.
439
- *
440
- * Imagine we are using [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking)
441
- * to keep track of current user's language.
442
- *
443
- * And we have articles translated to different languages, each article has `title_en`, `title_uk`, `title_be` and so on.
444
- *
445
- * We can define a computed `title` by passing a function into `sql` method:
446
- *
447
- * ```ts
448
- * type Locale = 'en' | 'uk' | 'be';
449
- * const asyncLanguageStorage = new AsyncLocalStorage<Locale>();
450
- * const defaultLocale: Locale = 'en';
451
- *
452
- * export class ArticleTable extends BaseTable {
453
- * readonly table = 'article';
454
- * columns = this.setColumns((t) => ({
455
- * id: t.identity().primaryKey(),
456
- * title_en: t.text(),
457
- * title_uk: t.text().nullable(),
458
- * title_be: t.text().nullable(),
459
- * }));
460
- *
461
- * computed = this.setComputed({
462
- * title: (q) =>
463
- * q
464
- * // .sql can take a function that accepts `sql` argument and must return SQL
465
- * .sql((sql) => {
466
- * // get locale dynamically based on current storage value
467
- * const locale = asyncLanguageStorage.getStore() || defaultLocale;
468
- *
469
- * // use COALESCE in case when localized title is NULL, use title_en
470
- * return sql`COALESCE(
471
- * ${q.column(`title_${locale}`)},
472
- * ${q.column(`title_${defaultLocale}`)}
473
- * )`;
474
- * })
475
- * .type((t) => t.text()),
476
- * });
477
- * }
478
- * ```
479
- *
480
- * @param computed - object where keys are column names and values are functions returning raw SQL
481
- */
482
- setComputed<Table extends string, Shape extends ColumnsShapeBase, Computed extends ComputedColumnsBase<Db<Table, Shape, EmptyObject, ColumnTypes>>>(computed: Computed): Computed;
483
- belongsTo<Self extends Table, Related extends TableClass, Scope extends Query, Options extends BelongsToOptions<Self, Related, Scope>>(this: Self, fn: () => Related, options: Options): {
484
- type: 'belongsTo';
485
- fn: () => Related;
486
- options: Options;
487
- };
488
- hasOne<Self extends Table, Related extends TableClass, Scope extends Query, Through extends string, Source extends string, Options extends HasOneOptions<Self, Related, Scope, Through, Source>>(this: Self, fn: () => Related, options: Options): {
489
- type: 'hasOne';
490
- fn: () => Related;
491
- options: Options;
492
- };
493
- hasMany<Self extends Table, Related extends TableClass, Scope extends Query, Through extends string, Source extends string, Options extends HasManyOptions<Self, Related, Scope, Through, Source>>(this: Self, fn: () => Related, options: Options): {
494
- type: 'hasMany';
495
- fn: () => Related;
496
- options: Options;
497
- };
498
- hasAndBelongsToMany<Self extends Table, Related extends TableClass, Scope extends Query, Options extends HasAndBelongsToManyOptions<Self, Related, Scope>>(this: Self, fn: () => Related, options: Options): {
499
- type: 'hasAndBelongsToMany';
500
- fn: () => Related;
501
- options: Options;
502
- };
503
- beforeQuery: BeforeHookMethod;
504
- afterQuery: AfterHookMethod;
505
- beforeCreate: BeforeHookMethod;
506
- afterCreate: AfterSelectableHookMethod;
507
- afterCreateCommit: AfterSelectableHookMethod;
508
- beforeUpdate: BeforeHookMethod;
509
- afterUpdate: AfterSelectableHookMethod;
510
- afterUpdateCommit: AfterSelectableHookMethod;
511
- beforeSave: BeforeHookMethod;
512
- afterSave: AfterSelectableHookMethod;
513
- afterSaveCommit: AfterSelectableHookMethod;
514
- beforeDelete: BeforeHookMethod;
515
- afterDelete: AfterSelectableHookMethod;
516
- afterDeleteCommit: AfterSelectableHookMethod;
517
- };
521
+ new (): BaseTableInstance<ColumnTypes>;
522
+ instance(): BaseTableInstance<ColumnTypes>;
518
523
  }
519
524
  declare function createBaseTable<SchemaProvider extends SchemaProviderBase, ColumnTypes = DefaultColumnTypes>({ columnTypes: columnTypesArg, snakeCase, filePath: filePathArg, nowSQL, exportAs, language, schemaProvider: schemaProviderArg, }?: {
520
525
  columnTypes?: ColumnTypes | ((t: DefaultColumnTypes) => ColumnTypes);
@@ -545,4 +550,4 @@ type Repo<T extends Query, Methods extends MethodsBase<T>, Mapped = MapMethods<T
545
550
  }>(q: Q) => Q & Mapped) & T & Mapped;
546
551
  declare const createRepo: <T extends Query, Methods extends MethodsBase<T>>(table: T, methods: Methods) => Repo<T, Methods, MapMethods<T, Methods>>;
547
552
 
548
- export { BaseTableClass, DbTable, Insertable, MapMethods, MapQueryMethods, MethodsBase, OrchidORM, QueryMethods, Queryable, Repo, ScopeFn, Selectable, Table, TableClass, TableClasses, TableToDb, Updateable, createBaseTable, createRepo, orchidORM };
553
+ export { BaseTableClass, BaseTableInstance, DbTable, Insertable, MapMethods, MapQueryMethods, MethodsBase, OrchidORM, QueryMethods, Queryable, Repo, ScopeFn, Selectable, Table, TableClass, TableClasses, TableToDb, Updateable, createBaseTable, createRepo, orchidORM };
package/dist/index.js CHANGED
@@ -44,6 +44,10 @@ function createBaseTable({
44
44
  `Failed to determine file path of a base table. Please set the \`filePath\` option of \`createBaseTable\` manually.`
45
45
  );
46
46
  }
47
+ static instance() {
48
+ var _a2;
49
+ return (_a2 = this._instance) != null ? _a2 : this._instance = new this();
50
+ }
47
51
  clone() {
48
52
  return this;
49
53
  }
@@ -73,7 +77,7 @@ function createBaseTable({
73
77
  }
74
78
  }
75
79
  }
76
- return this.constructor.prototype.columns = shape;
80
+ return shape;
77
81
  }
78
82
  setComputed(computed) {
79
83
  return computed;
@@ -1696,7 +1700,7 @@ const orchidORM = (_a, tables) => {
1696
1700
  if (key[0] === "$") {
1697
1701
  throw new Error(`Table class name must not start with $`);
1698
1702
  }
1699
- const table = new tables[key]();
1703
+ const table = tables[key].instance();
1700
1704
  tableInstances[key] = table;
1701
1705
  const options2 = __spreadProps(__spreadValues$1({}, commonOptions), {
1702
1706
  schema: table.schema,