@bunnykit/orm 0.1.14 → 0.1.15

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.
@@ -76,7 +76,6 @@ export class Connection {
76
76
  return `"${value.replace(/"/g, '""')}"`;
77
77
  }
78
78
  async query(sqlString) {
79
- // Use unsafe for generated SQL strings
80
79
  return (await this.driver.unsafe(sqlString));
81
80
  }
82
81
  async run(sqlString) {
@@ -21,15 +21,28 @@ export type TenantResolution = {
21
21
  setting?: string;
22
22
  };
23
23
  export type TenantResolver = (tenantId: string) => TenantResolution | Promise<TenantResolution>;
24
+ export interface PoolConfig {
25
+ maxConnections?: number;
26
+ minConnections?: number;
27
+ idleTimeout?: number;
28
+ }
24
29
  export declare class ConnectionManager {
25
30
  private static defaultConnection?;
26
31
  private static connections;
32
+ private static pools;
33
+ private static poolConfigs;
27
34
  private static tenantResolver?;
28
35
  private static tenantCache;
29
36
  static setDefault(connection: Connection): void;
30
37
  static getDefault(): Connection | undefined;
38
+ static setPoolConfig(name: string, config: PoolConfig): void;
39
+ static getPoolConfig(name: string): PoolConfig | undefined;
40
+ private static getPooledConnection;
41
+ private static releasePooledConnection;
31
42
  static add(name: string, connection: Connection | ConnectionConfig): Connection;
32
43
  static get(name: string): Connection | undefined;
44
+ static getPooled(name: string, config?: ConnectionConfig): Promise<Connection>;
45
+ static release(name: string, connection: Connection): void;
33
46
  static require(name: string): Connection;
34
47
  static setTenantResolver(resolver: TenantResolver): void;
35
48
  static resolveTenant(tenantId: string): Promise<ActiveTenantContext>;
@@ -2,6 +2,8 @@ import { Connection } from "./Connection.js";
2
2
  export class ConnectionManager {
3
3
  static defaultConnection;
4
4
  static connections = new Map();
5
+ static pools = new Map();
6
+ static poolConfigs = new Map();
5
7
  static tenantResolver;
6
8
  static tenantCache = new Map();
7
9
  static setDefault(connection) {
@@ -10,6 +12,67 @@ export class ConnectionManager {
10
12
  static getDefault() {
11
13
  return this.defaultConnection;
12
14
  }
15
+ static setPoolConfig(name, config) {
16
+ this.poolConfigs.set(name, { maxConnections: 10, minConnections: 1, idleTimeout: 30000, ...config });
17
+ }
18
+ static getPoolConfig(name) {
19
+ return this.poolConfigs.get(name);
20
+ }
21
+ static async getPooledConnection(name, config) {
22
+ const poolConfig = this.poolConfigs.get(name) || { maxConnections: 10, minConnections: 1, idleTimeout: 30000 };
23
+ let pool = this.pools.get(name);
24
+ if (!pool) {
25
+ pool = [];
26
+ this.pools.set(name, pool);
27
+ }
28
+ const now = Date.now();
29
+ const idleTimeout = poolConfig.idleTimeout || 30000;
30
+ while (pool.length > 0) {
31
+ const idx = pool.findIndex((c) => !c.inUse && (now - c.lastUsed) < idleTimeout);
32
+ if (idx === -1)
33
+ break;
34
+ const pooled = pool[idx];
35
+ pool.splice(idx, 1);
36
+ try {
37
+ pooled.connection.query("SELECT 1").catch(() => null);
38
+ pooled.inUse = true;
39
+ return pooled.connection;
40
+ }
41
+ catch {
42
+ await pooled.connection.close().catch(() => null);
43
+ }
44
+ }
45
+ if (pool.length < (poolConfig.maxConnections || 10)) {
46
+ const connection = new Connection(config);
47
+ pool.push({ connection, lastUsed: Date.now(), inUse: true });
48
+ return connection;
49
+ }
50
+ return new Promise((resolve, reject) => {
51
+ const checkInterval = setInterval(() => {
52
+ const available = pool.find((c) => !c.inUse);
53
+ if (available) {
54
+ clearInterval(checkInterval);
55
+ available.inUse = true;
56
+ available.lastUsed = Date.now();
57
+ resolve(available.connection);
58
+ }
59
+ }, 50);
60
+ setTimeout(() => {
61
+ clearInterval(checkInterval);
62
+ reject(new Error(`Connection pool exhausted for "${name}"`));
63
+ }, 30000);
64
+ });
65
+ }
66
+ static releasePooledConnection(name, connection) {
67
+ const pool = this.pools.get(name);
68
+ if (!pool)
69
+ return;
70
+ const pooled = pool.find((p) => p.connection === connection);
71
+ if (pooled) {
72
+ pooled.inUse = false;
73
+ pooled.lastUsed = Date.now();
74
+ }
75
+ }
13
76
  static add(name, connection) {
14
77
  const resolved = connection instanceof Connection ? connection : new Connection(connection);
15
78
  this.connections.set(name, resolved);
@@ -18,6 +81,18 @@ export class ConnectionManager {
18
81
  static get(name) {
19
82
  return this.connections.get(name);
20
83
  }
84
+ static async getPooled(name, config) {
85
+ if (config) {
86
+ return this.getPooledConnection(name, config);
87
+ }
88
+ const existing = this.connections.get(name);
89
+ if (existing)
90
+ return existing;
91
+ throw new Error(`No connection registered for "${name}". Use add() first or provide config.`);
92
+ }
93
+ static release(name, connection) {
94
+ this.releasePooledConnection(name, connection);
95
+ }
21
96
  static require(name) {
22
97
  const connection = this.get(name);
23
98
  if (!connection) {
@@ -93,9 +168,15 @@ export class ConnectionManager {
93
168
  }
94
169
  static async closeAll() {
95
170
  const connections = new Set(this.connections.values());
171
+ for (const pool of this.pools.values()) {
172
+ for (const { connection } of pool) {
173
+ connections.add(connection);
174
+ }
175
+ }
96
176
  if (this.defaultConnection)
97
177
  connections.add(this.defaultConnection);
98
178
  this.connections.clear();
179
+ this.pools.clear();
99
180
  this.tenantCache.clear();
100
181
  for (const connection of connections) {
101
182
  await connection.close();
@@ -30,6 +30,7 @@ export declare class Builder<T = Record<string, any>> {
30
30
  unions: UnionClause[];
31
31
  fromRaw?: string;
32
32
  updateJoins: string[];
33
+ bindings: any[];
33
34
  constructor(connection: Connection, table: string);
34
35
  private get grammar();
35
36
  setModel(model: ModelConstructor): this;
@@ -157,7 +158,7 @@ export declare class Builder<T = Record<string, any>> {
157
158
  paginate(perPage?: number, page?: number): Promise<Paginator<T>>;
158
159
  chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
159
160
  each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
160
- cursor(): AsyncGenerator<T>;
161
+ cursor(keyset?: Record<string, any>): AsyncGenerator<T>;
161
162
  lazy(count?: number): AsyncGenerator<T>;
162
163
  insert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
163
164
  insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
@@ -18,6 +18,7 @@ export class Builder {
18
18
  unions = [];
19
19
  fromRaw;
20
20
  updateJoins = [];
21
+ bindings = [];
21
22
  constructor(connection, table) {
22
23
  this.connection = connection;
23
24
  this.tableName = table;
@@ -455,6 +456,7 @@ export class Builder {
455
456
  cloned.unions = [...this.unions];
456
457
  cloned.fromRaw = this.fromRaw;
457
458
  cloned.updateJoins = [...this.updateJoins];
459
+ cloned.bindings = [...this.bindings];
458
460
  return cloned;
459
461
  }
460
462
  wrapColumn(value) {
@@ -697,14 +699,34 @@ export class Builder {
697
699
  }
698
700
  });
699
701
  }
700
- async *cursor() {
701
- let offset = 0;
702
- while (true) {
703
- const items = await this.clone().offset(offset).limit(1).get();
704
- if (items.length === 0)
705
- break;
706
- yield items[0];
707
- offset++;
702
+ async *cursor(keyset) {
703
+ const model = this.model;
704
+ const primaryKey = model ? model.primaryKey || "id" : "id";
705
+ const orderColumn = this.orders[0]?.column || primaryKey;
706
+ const orderDirection = this.orders[0]?.direction || "asc";
707
+ const builder = this.clone();
708
+ builder.orders = [{ column: orderColumn, direction: orderDirection }];
709
+ builder.offsetValue = undefined;
710
+ if (keyset) {
711
+ const op = orderDirection === "asc" ? ">" : "<";
712
+ builder.wheres.push({
713
+ type: "basic",
714
+ column: orderColumn,
715
+ operator: op,
716
+ value: keyset[orderColumn],
717
+ boolean: "and",
718
+ scope: undefined,
719
+ });
720
+ }
721
+ const items = await builder.limit(1).get();
722
+ if (items.length === 0)
723
+ return;
724
+ yield items[0];
725
+ const nextKeyset = items[0] && typeof items[0] === "object"
726
+ ? { [orderColumn]: items[0][orderColumn] }
727
+ : undefined;
728
+ if (nextKeyset) {
729
+ yield* this.cursor(nextKeyset);
708
730
  }
709
731
  }
710
732
  async *lazy(count = 1000) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnykit/orm",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "An Eloquent-inspired ORM for Bun's native SQL client supporting SQLite, MySQL, and PostgreSQL.",
5
5
  "license": "MIT",
6
6
  "packageManager": "bun@1.3.12",