@cheetah.js/orm 0.1.141 → 0.1.143

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/README.md CHANGED
@@ -124,7 +124,7 @@ export class User {
124
124
  }
125
125
  ```
126
126
 
127
- For define a index for a multiple properties, add the @Index decorator. You can use it on a property or on the class. It accepts either an array of property names (legacy) or an object with a properties field (recommended):
127
+ For define a index for a multiple properties, add the @Index decorator. You can use it on a property or on the class. It accepts either an array of property names (legacy) or an object with a properties field (recommended):
128
128
 
129
129
  ```javascript
130
130
  @Entity()
@@ -156,12 +156,34 @@ export class User {
156
156
  email: string;
157
157
  }
158
158
 
159
- // Backward compatible usage (array):
160
- // @Index(['name', 'email'])
161
- ```
162
-
163
- #### Property options
164
- | Option | Type | Description |
159
+ // Backward compatible usage (array):
160
+ // @Index(['name', 'email'])
161
+ ```
162
+
163
+ Partial indexes (Postgres only) can be declared with `where`. You can provide a raw SQL string or a typed callback that receives the column map (with autocomplete based on your entity):
164
+
165
+ ```javascript
166
+ @Entity()
167
+ @Index<{ User }>({
168
+ properties: ['email'],
169
+ where: (columns) => `${columns.isActive} = true`,
170
+ })
171
+ export class User {
172
+ @PrimaryKey()
173
+ id: number;
174
+
175
+ @Property()
176
+ email: string;
177
+
178
+ @Property()
179
+ isActive: boolean;
180
+ }
181
+ ```
182
+
183
+ Note: MySQL does not support partial indexes; using `where` with the MySQL driver will throw.
184
+
185
+ #### Property options
186
+ | Option | Type | Description |
165
187
  | ------ | ---- |--------------------------------------------------------------------------------------------|
166
188
  | nullable | boolean | Defines if the property is nullable. |
167
189
  | unique | boolean | Defines if the property is unique. |
@@ -1,5 +1,15 @@
1
+ export type IndexColumnMap<T> = {
2
+ [K in keyof T as K extends symbol ? never : K]: string;
3
+ };
4
+ export type IndexPredicate<T> = string | ((columns: IndexColumnMap<T>) => string);
5
+ export type IndexDefinition = {
6
+ name: string;
7
+ properties: string[];
8
+ where?: IndexPredicate<any>;
9
+ };
1
10
  type IndexOptions<T> = {
2
11
  properties: (keyof T)[];
12
+ where?: IndexPredicate<T>;
3
13
  } | (keyof T)[] | undefined;
4
14
  export declare function Index<T>(options?: IndexOptions<T>): ClassDecorator & PropertyDecorator;
5
15
  export {};
@@ -10,7 +10,8 @@ function buildFromOptions(options) {
10
10
  if (!props || props.length === 0)
11
11
  return null;
12
12
  const keys = props;
13
- return { name: `${keys.join('_')}_index`, properties: keys };
13
+ const where = Array.isArray(options) ? undefined : options?.where;
14
+ return { name: `${keys.join('_')}_index`, properties: keys, where };
14
15
  }
15
16
  function buildFromProperty(propertyKey) {
16
17
  const name = String(propertyKey);
@@ -12,7 +12,63 @@ var EntityStorage_1;
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.EntityStorage = void 0;
14
14
  const core_1 = require("@cheetah.js/core");
15
+ const orm_session_context_1 = require("../orm-session-context");
15
16
  const utils_1 = require("../utils");
17
+ function buildIndexColumnMap(properties, relations) {
18
+ const map = mapPropertyColumns(properties);
19
+ addRelationColumns(map, relations);
20
+ return map;
21
+ }
22
+ function mapPropertyColumns(properties) {
23
+ const map = {};
24
+ Object.entries(properties).forEach(([key, value]) => {
25
+ map[key] = value.options.columnName;
26
+ });
27
+ return map;
28
+ }
29
+ function addRelationColumns(map, relations) {
30
+ relations.forEach((relation) => {
31
+ map[String(relation.propertyKey)] = relation.columnName;
32
+ });
33
+ }
34
+ function mapIndexDefinitions(indexes, entityName, columnMap) {
35
+ return indexes.map((index) => toSnapshotIndex(index, entityName, columnMap));
36
+ }
37
+ function toSnapshotIndex(index, entityName, columnMap) {
38
+ const columns = resolveIndexColumns(index, columnMap);
39
+ const indexName = resolveIndexName(index.name, entityName, columns);
40
+ return {
41
+ table: entityName,
42
+ indexName,
43
+ columnName: columns.join(","),
44
+ where: resolveIndexWhere(index.where, columnMap),
45
+ };
46
+ }
47
+ function resolveIndexColumns(index, columnMap) {
48
+ return index.properties.map((propName) => resolveIndexColumn(propName, columnMap));
49
+ }
50
+ function resolveIndexColumn(propName, columnMap) {
51
+ const mapped = columnMap[propName];
52
+ if (mapped) {
53
+ return mapped;
54
+ }
55
+ return (0, utils_1.toSnakeCase)(propName);
56
+ }
57
+ function resolveIndexName(name, entityName, columns) {
58
+ if (name.includes('_pkey') || name.includes('[TABLE]')) {
59
+ return name.replace("[TABLE]", entityName);
60
+ }
61
+ return `${columns.join("_")}_index`;
62
+ }
63
+ function resolveIndexWhere(where, columnMap) {
64
+ if (!where) {
65
+ return undefined;
66
+ }
67
+ if (typeof where === "string") {
68
+ return where;
69
+ }
70
+ return where(columnMap);
71
+ }
16
72
  let EntityStorage = EntityStorage_1 = class EntityStorage {
17
73
  constructor() {
18
74
  this.entities = new Map();
@@ -21,42 +77,14 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
21
77
  add(entity, properties, relations, hooks) {
22
78
  const entityName = entity.options?.tableName || (0, utils_1.toSnakeCase)(entity.target.name);
23
79
  const indexes = core_1.Metadata.get("indexes", entity.target) || [];
80
+ const columnMap = buildIndexColumnMap(properties, relations);
24
81
  this.entities.set(entity.target, {
25
82
  properties: properties,
26
83
  hideProperties: Object.entries(properties)
27
84
  .filter(([_key, value]) => value.options.hidden)
28
85
  .map(([key]) => key),
29
86
  relations,
30
- indexes: indexes.map((index) => {
31
- // Convert property names to database column names
32
- const columnNames = index.properties.map(propName => {
33
- // Check if it's a regular property
34
- if (properties[propName]) {
35
- return properties[propName].options.columnName;
36
- }
37
- // Check if it's a relation
38
- const relation = relations.find(rel => String(rel.propertyKey) === propName);
39
- if (relation) {
40
- return relation.columnName;
41
- }
42
- // Fallback to snake_case conversion if not found
43
- return (0, utils_1.toSnakeCase)(propName);
44
- });
45
- // Preserve the original index name if it's a primary key index (contains _pkey or [TABLE])
46
- // Otherwise, generate index name using column names
47
- let indexName;
48
- if (index.name.includes('_pkey') || index.name.includes('[TABLE]')) {
49
- indexName = index.name.replace("[TABLE]", entityName);
50
- }
51
- else {
52
- indexName = `${columnNames.join("_")}_index`;
53
- }
54
- return {
55
- table: entityName,
56
- indexName,
57
- columnName: columnNames.join(","),
58
- };
59
- }),
87
+ indexes: mapIndexDefinitions(indexes, entityName, columnMap),
60
88
  hooks,
61
89
  tableName: entityName,
62
90
  ...entity.options,
@@ -69,6 +97,10 @@ let EntityStorage = EntityStorage_1 = class EntityStorage {
69
97
  return this.entities.entries();
70
98
  }
71
99
  static getInstance() {
100
+ const scoped = orm_session_context_1.ormSessionContext.getStorage();
101
+ if (scoped) {
102
+ return scoped;
103
+ }
72
104
  return EntityStorage_1.instance;
73
105
  }
74
106
  async snapshot(values) {
@@ -1,4 +1,4 @@
1
- import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
1
+ import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, IndexStatement, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
2
2
  import { BunDriverBase } from './bun-driver.base';
3
3
  export declare class BunMysqlDriver extends BunDriverBase implements DriverInterface {
4
4
  readonly dbType: "mysql";
@@ -19,10 +19,7 @@ export declare class BunMysqlDriver extends BunDriverBase implements DriverInter
19
19
  protected buildLimitAndOffsetClause(statement: Statement<any>): string;
20
20
  getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
21
21
  getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
22
- getCreateIndex(index: {
23
- name: string;
24
- properties: string[];
25
- }, schema: string | undefined, tableName: string): string;
22
+ getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
26
23
  getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
27
24
  getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
28
25
  getDropIndex(index: {
@@ -95,7 +95,15 @@ class BunMysqlDriver extends bun_driver_base_1.BunDriverBase {
95
95
  return `ALTER TABLE \`${schema}\`.\`${tableName}\` ADD CONSTRAINT \`${tableName}_${colDiff.colName}_fk\` FOREIGN KEY (\`${colDiff.colName}\`) REFERENCES \`${fk.referencedTableName}\` (\`${fk.referencedColumnName}\`);`;
96
96
  }
97
97
  getCreateIndex(index, schema, tableName) {
98
- return `CREATE INDEX \`${index.name}\` ON \`${schema}\`.\`${tableName}\` (${index.properties.map((prop) => `\`${prop}\``).join(', ')});`;
98
+ const properties = index.properties || [];
99
+ if (properties.length === 0) {
100
+ throw new Error("Index properties are required.");
101
+ }
102
+ if (index.where) {
103
+ throw new Error("Partial indexes are only supported on postgres.");
104
+ }
105
+ const columns = properties.map((prop) => `\`${prop}\``).join(', ');
106
+ return `CREATE INDEX \`${index.name}\` ON \`${schema}\`.\`${tableName}\` (${columns});`;
99
107
  }
100
108
  getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions) {
101
109
  let sql = ``;
@@ -1,4 +1,4 @@
1
- import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
1
+ import { ColDiff, ConnectionSettings, DriverInterface, ForeignKeyInfo, IndexStatement, SnapshotConstraintInfo, SnapshotIndexInfo, SnapshotTable, Statement } from './driver.interface';
2
2
  import { BunDriverBase } from './bun-driver.base';
3
3
  export declare class BunPgDriver extends BunDriverBase implements DriverInterface {
4
4
  readonly dbType: "postgres";
@@ -18,10 +18,7 @@ export declare class BunPgDriver extends BunDriverBase implements DriverInterfac
18
18
  }>;
19
19
  getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
20
20
  getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
21
- getCreateIndex(index: {
22
- name: string;
23
- properties: string[];
24
- }, schema: string | undefined, tableName: string): string;
21
+ getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
25
22
  getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
26
23
  getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
27
24
  getDropIndex(index: {
@@ -70,7 +70,13 @@ class BunPgDriver extends bun_driver_base_1.BunDriverBase {
70
70
  return `ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colDiff.colName}_fk" FOREIGN KEY ("${colDiff.colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`;
71
71
  }
72
72
  getCreateIndex(index, schema, tableName) {
73
- return `CREATE INDEX "${index.name}" ON "${schema}"."${tableName}" (${index.properties.map((prop) => `"${prop}"`).join(', ')});`;
73
+ const properties = index.properties || [];
74
+ if (properties.length === 0) {
75
+ throw new Error("Index properties are required.");
76
+ }
77
+ const columns = properties.map((prop) => `"${prop}"`).join(', ');
78
+ const where = this.buildWhereClause(index.where);
79
+ return `CREATE INDEX "${index.name}" ON "${schema}"."${tableName}" (${columns})${where};`;
74
80
  }
75
81
  getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions) {
76
82
  let beforeSql = ``;
@@ -16,16 +16,10 @@ export interface DriverInterface {
16
16
  snapshot(tableName: string, options: any): Promise<SnapshotTable | undefined>;
17
17
  getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]): string;
18
18
  getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo): string;
19
- getCreateIndex(index: {
20
- name: string;
21
- properties?: string[];
22
- }, schema: string | undefined, tableName: string): string;
19
+ getCreateIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
23
20
  getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void;
24
21
  getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string): void;
25
- getDropIndex(index: {
26
- name: string;
27
- properties?: string[];
28
- }, schema: string | undefined, tableName: string): string;
22
+ getDropIndex(index: IndexStatement, schema: string | undefined, tableName: string): string;
29
23
  getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
30
24
  getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
31
25
  getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string;
@@ -148,6 +142,12 @@ export type SnapshotIndexInfo = {
148
142
  table: string;
149
143
  indexName: string;
150
144
  columnName: string;
145
+ where?: string;
146
+ };
147
+ export type IndexStatement = {
148
+ name: string;
149
+ properties?: string[];
150
+ where?: string;
151
151
  };
152
152
  export type ForeignKeyInfo = {
153
153
  referencedTableName: string;
@@ -0,0 +1,16 @@
1
+ import type { Orm } from './orm';
2
+ import type { EntityStorage } from './domain/entities';
3
+ type OrmSession = {
4
+ orm: Orm<any>;
5
+ storage: EntityStorage;
6
+ };
7
+ declare class OrmSessionContext {
8
+ private storage;
9
+ constructor();
10
+ run<T>(session: OrmSession, routine: () => Promise<T>): Promise<T>;
11
+ getOrm(): Orm<any> | undefined;
12
+ getStorage(): EntityStorage | undefined;
13
+ hasContext(): boolean;
14
+ }
15
+ export declare const ormSessionContext: OrmSessionContext;
16
+ export type { OrmSession };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ormSessionContext = void 0;
4
+ const async_hooks_1 = require("async_hooks");
5
+ class OrmSessionContext {
6
+ constructor() {
7
+ this.storage = new async_hooks_1.AsyncLocalStorage();
8
+ }
9
+ run(session, routine) {
10
+ return this.storage.run(session, routine);
11
+ }
12
+ getOrm() {
13
+ return this.storage.getStore()?.orm;
14
+ }
15
+ getStorage() {
16
+ return this.storage.getStore()?.storage;
17
+ }
18
+ hasContext() {
19
+ return this.storage.getStore() !== undefined;
20
+ }
21
+ }
22
+ exports.ormSessionContext = new OrmSessionContext();
package/dist/orm.js CHANGED
@@ -15,6 +15,7 @@ const core_1 = require("@cheetah.js/core");
15
15
  const SqlBuilder_1 = require("./SqlBuilder");
16
16
  const query_cache_manager_1 = require("./cache/query-cache-manager");
17
17
  const transaction_context_1 = require("./transaction/transaction-context");
18
+ const orm_session_context_1 = require("./orm-session-context");
18
19
  let Orm = Orm_1 = class Orm {
19
20
  constructor(logger, cacheService) {
20
21
  this.logger = logger;
@@ -28,6 +29,10 @@ let Orm = Orm_1 = class Orm {
28
29
  }
29
30
  }
30
31
  static getInstance() {
32
+ const scoped = orm_session_context_1.ormSessionContext.getOrm();
33
+ if (scoped) {
34
+ return scoped;
35
+ }
31
36
  return Orm_1.instance;
32
37
  }
33
38
  setConnection(connection) {
@@ -45,6 +45,8 @@ const entities_1 = require("../domain/entities");
45
45
  const orm_1 = require("../orm");
46
46
  const orm_service_1 = require("../orm.service");
47
47
  const bun_pg_driver_1 = require("../driver/bun-pg.driver");
48
+ const orm_session_context_1 = require("../orm-session-context");
49
+ const constants_1 = require("../constants");
48
50
  const DEFAULT_SCHEMA = 'public';
49
51
  const DEFAULT_CONNECTION = {
50
52
  host: 'localhost',
@@ -57,6 +59,7 @@ const DEFAULT_CONNECTION = {
57
59
  const sessionCache = new Map();
58
60
  function getCacheKey(options) {
59
61
  const connection = resolveConnection(options.connection);
62
+ const entitySignature = resolveEntitySignature();
60
63
  return JSON.stringify({
61
64
  host: connection.host,
62
65
  port: connection.port,
@@ -64,8 +67,20 @@ function getCacheKey(options) {
64
67
  schema: options.schema ?? DEFAULT_SCHEMA,
65
68
  entityFile: options.entityFile,
66
69
  migrationPath: connection.migrationPath,
70
+ entitySignature,
67
71
  });
68
72
  }
73
+ function resolveEntitySignature() {
74
+ const entities = core_1.Metadata.get(constants_1.ENTITIES, Reflect) || [];
75
+ return buildEntitySignature(entities);
76
+ }
77
+ function buildEntitySignature(entities) {
78
+ if (entities.length < 1) {
79
+ return 'none';
80
+ }
81
+ const names = entities.map((entity) => entity.target?.name ?? 'unknown');
82
+ return names.sort().join('|');
83
+ }
69
84
  async function withDatabase(arg1, arg2, arg3) {
70
85
  const { routine: targetRoutine, options: targetOptions, statements } = await normalizeArgs(arg1, arg2, arg3);
71
86
  const cacheKey = getCacheKey(targetOptions);
@@ -80,12 +95,13 @@ async function withDatabase(arg1, arg2, arg3) {
80
95
  };
81
96
  sessionCache.set(cacheKey, cachedSession);
82
97
  }
83
- activateSession(cachedSession);
84
- const context = buildContext(cachedSession.orm);
85
- await dropAndRecreateSchema(context, cachedSession.schema);
86
- await prepareSchema(context, cachedSession.schema);
87
- await createTables(context, schemaStatements);
88
- await targetRoutine(context);
98
+ await runWithSession(cachedSession, async () => {
99
+ const context = buildContext(cachedSession.orm);
100
+ await dropAndRecreateSchema(context, cachedSession.schema);
101
+ await prepareSchema(context, cachedSession.schema);
102
+ await createTables(context, schemaStatements);
103
+ await targetRoutine(context);
104
+ });
89
105
  }
90
106
  async function createSession(options) {
91
107
  const logger = selectLogger(options);
@@ -112,9 +128,8 @@ async function initializeOrm(orm, storage, options) {
112
128
  const connection = resolveConnection(options.connection);
113
129
  await service.onInit(connection);
114
130
  }
115
- function activateSession(session) {
116
- orm_1.Orm.instance = session.orm;
117
- entities_1.EntityStorage.instance = session.storage;
131
+ async function runWithSession(session, routine) {
132
+ await orm_session_context_1.ormSessionContext.run({ orm: session.orm, storage: session.storage }, routine);
118
133
  }
119
134
  function resolveConnection(overrides) {
120
135
  if (!overrides) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheetah.js/orm",
3
- "version": "0.1.141",
3
+ "version": "0.1.143",
4
4
  "description": "A simple ORM for Cheetah.js.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -55,5 +55,5 @@
55
55
  "bun",
56
56
  "value-object"
57
57
  ],
58
- "gitHead": "f3f3004bde2d552db43db3ac56b4d286bbfbc7f6"
58
+ "gitHead": "698043086053a80b0e1fe53adb89ac494ef92f9f"
59
59
  }