@bunnykit/orm 0.1.5 → 0.1.6

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/bin/bunny.js CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Connection } from "../src/connection/Connection.js";
3
+ import { ConnectionManager } from "../src/connection/ConnectionManager.js";
4
+ import { TenantContext } from "../src/connection/TenantContext.js";
5
+ import { configureBunny } from "../src/config/BunnyConfig.js";
3
6
  import { Migrator } from "../src/migration/Migrator.js";
4
7
  import { MigrationCreator } from "../src/migration/MigrationCreator.js";
5
8
  import { TypeGenerator } from "../src/typegen/TypeGenerator.js";
@@ -18,6 +21,126 @@ function parseEnvPathSetting(value) {
18
21
  return undefined;
19
22
  return paths.length === 1 ? paths[0] : paths;
20
23
  }
24
+ function getDefaultMigrationsPath(config) {
25
+ return config.migrationsPath || config.migrations?.landlord || "./database/migrations";
26
+ }
27
+ function getFirstMigrationPath(path) {
28
+ return normalizePathList(path).filter(Boolean)[0];
29
+ }
30
+ function parseMigrationTarget(args) {
31
+ if (args.includes("--landlord"))
32
+ return { scope: "landlord" };
33
+ if (args.includes("--tenants"))
34
+ return { scope: "tenants" };
35
+ const tenantFlagIndex = args.indexOf("--tenant");
36
+ if (tenantFlagIndex >= 0) {
37
+ const tenantId = args[tenantFlagIndex + 1];
38
+ if (!tenantId) {
39
+ throw new Error("Usage: bun run bunny migrate --tenant <tenantId>");
40
+ }
41
+ return { scope: "tenant", tenantId };
42
+ }
43
+ return { scope: "default" };
44
+ }
45
+ function createTypeGeneratorOptions(config) {
46
+ const modelRoots = normalizePathList(config.modelsPath || config.typeDeclarationModelsDir);
47
+ return {
48
+ declarations: !config.typeStubs,
49
+ stubs: config.typeStubs,
50
+ modelDeclarations: config.typeDeclarations,
51
+ modelDirectory: modelRoots[0],
52
+ modelDirectories: modelRoots.length > 1 ? modelRoots : undefined,
53
+ modelImportPrefix: config.typeDeclarationImportPrefix,
54
+ singularModels: config.typeDeclarationSingularModels,
55
+ declarationDirName: "types",
56
+ };
57
+ }
58
+ async function runMigratorCommand(command, migrator, statusLabel) {
59
+ if (command === "migrate") {
60
+ await migrator.run();
61
+ return;
62
+ }
63
+ if (command === "migrate:rollback") {
64
+ await migrator.rollback();
65
+ return;
66
+ }
67
+ const status = await migrator.status();
68
+ if (statusLabel) {
69
+ console.log(statusLabel);
70
+ }
71
+ console.table(status);
72
+ }
73
+ async function getTenantIds(config) {
74
+ if (!config.tenancy?.listTenants) {
75
+ throw new Error("Tenant migrations require tenancy.listTenants() in bunny.config.ts.");
76
+ }
77
+ const tenantIds = await config.tenancy.listTenants();
78
+ return tenantIds.map((tenantId) => String(tenantId));
79
+ }
80
+ async function runTenantMigrationCommand(command, config, tenantPath, tenantId, typesOutDir) {
81
+ await TenantContext.run(tenantId, async () => {
82
+ const context = TenantContext.current();
83
+ if (!context) {
84
+ throw new Error(`Tenant "${tenantId}" did not resolve to an active context.`);
85
+ }
86
+ console.log(`Tenant: ${tenantId}`);
87
+ const migrator = new Migrator(context.connection, tenantPath, typesOutDir, createTypeGeneratorOptions(config));
88
+ await runMigratorCommand(command, migrator);
89
+ });
90
+ }
91
+ async function runConfiguredMigrationCommand(command, config, connection, target) {
92
+ if (!config.migrations) {
93
+ const migrator = new Migrator(connection, getDefaultMigrationsPath(config), config.typesOutDir, createTypeGeneratorOptions(config));
94
+ await runMigratorCommand(command, migrator);
95
+ return;
96
+ }
97
+ const landlordPath = config.migrations.landlord;
98
+ const tenantPath = config.migrations.tenant;
99
+ const runLandlord = async () => {
100
+ if (!landlordPath)
101
+ return;
102
+ console.log("Landlord migrations");
103
+ const migrator = new Migrator(connection, landlordPath, config.typesOutDir, createTypeGeneratorOptions(config));
104
+ await runMigratorCommand(command, migrator);
105
+ };
106
+ const runAllTenants = async () => {
107
+ if (!tenantPath)
108
+ return;
109
+ if (!config.tenancy?.resolveTenant) {
110
+ throw new Error("Tenant migrations require tenancy.resolveTenant() in bunny.config.ts.");
111
+ }
112
+ ConnectionManager.setTenantResolver(config.tenancy.resolveTenant);
113
+ const tenantIds = await getTenantIds(config);
114
+ for (const tenantId of tenantIds) {
115
+ await runTenantMigrationCommand(command, config, tenantPath, tenantId);
116
+ }
117
+ };
118
+ if (target.scope === "landlord") {
119
+ await runLandlord();
120
+ return;
121
+ }
122
+ if (target.scope === "tenant") {
123
+ if (!tenantPath)
124
+ return;
125
+ if (!config.tenancy?.resolveTenant) {
126
+ throw new Error("Tenant migrations require tenancy.resolveTenant() in bunny.config.ts.");
127
+ }
128
+ ConnectionManager.setTenantResolver(config.tenancy.resolveTenant);
129
+ await runTenantMigrationCommand(command, config, tenantPath, target.tenantId);
130
+ return;
131
+ }
132
+ if (target.scope === "tenants") {
133
+ await runAllTenants();
134
+ return;
135
+ }
136
+ if (command === "migrate:rollback") {
137
+ await runAllTenants();
138
+ await runLandlord();
139
+ return;
140
+ }
141
+ await runLandlord();
142
+ await runAllTenants();
143
+ }
21
144
  async function createReplBootstrap(config) {
22
145
  const tmpRoot = process.env.BUNNY_REPL_TMPDIR || "/private/tmp";
23
146
  const dir = join(tmpRoot, "bunny-repl");
@@ -254,8 +377,8 @@ async function main() {
254
377
  }
255
378
  const config = await loadConfig();
256
379
  const creator = new MigrationCreator();
257
- const migrationRoots = normalizePathList(config.migrationsPath);
258
- const targetPath = args[2] || migrationRoots[0] || "./database/migrations";
380
+ const migrationRoots = normalizePathList(config.migrationsPath || config.migrations?.landlord);
381
+ const targetPath = args[2] || migrationRoots[0] || getFirstMigrationPath(config.migrations?.landlord) || "./database/migrations";
259
382
  const path = await creator.create(name, targetPath);
260
383
  console.log(`Created migration: ${path}`);
261
384
  return;
@@ -290,31 +413,22 @@ async function main() {
290
413
  process.exit(exitCode);
291
414
  }
292
415
  const config = await loadConfig();
293
- const connection = new Connection(config.connection);
294
- const modelRoots = normalizePathList(config.modelsPath || config.typeDeclarationModelsDir);
295
- const migrator = new Migrator(connection, config.migrationsPath, config.typesOutDir, {
296
- declarations: !config.typeStubs,
297
- stubs: config.typeStubs,
298
- modelDeclarations: config.typeDeclarations,
299
- modelDirectory: modelRoots[0],
300
- modelDirectories: modelRoots.length > 1 ? modelRoots : undefined,
301
- modelImportPrefix: config.typeDeclarationImportPrefix,
302
- singularModels: config.typeDeclarationSingularModels,
303
- declarationDirName: "types",
304
- });
416
+ const { connection } = configureBunny(config);
305
417
  if (command === "migrate") {
306
- await migrator.run();
418
+ await runConfiguredMigrationCommand(command, config, connection, parseMigrationTarget(args.slice(1)));
307
419
  }
308
420
  else if (command === "migrate:rollback") {
309
- await migrator.rollback();
421
+ await runConfiguredMigrationCommand(command, config, connection, parseMigrationTarget(args.slice(1)));
310
422
  }
311
423
  else if (command === "migrate:status") {
312
- const status = await migrator.status();
313
- console.table(status);
424
+ await runConfiguredMigrationCommand(command, config, connection, parseMigrationTarget(args.slice(1)));
314
425
  }
315
426
  else {
316
427
  console.log("Usage:");
317
- console.log(" bun run bunny migrate Run pending migrations");
428
+ console.log(" bun run bunny migrate Run landlord migrations, then all tenant migrations when configured");
429
+ console.log(" bun run bunny migrate --landlord Run landlord migrations only");
430
+ console.log(" bun run bunny migrate --tenants Run all tenant migrations only");
431
+ console.log(" bun run bunny migrate --tenant <id> Run one tenant's migrations only");
318
432
  console.log(" bun run bunny migrate:make <name> [dir] Create a new migration");
319
433
  console.log(" bun run bunny migrate:rollback Rollback the last batch");
320
434
  console.log(" bun run bunny migrate:status Show migration status");
@@ -0,0 +1,28 @@
1
+ import { Connection } from "../connection/Connection.js";
2
+ import type { TenantResolver } from "../connection/ConnectionManager.js";
3
+ import type { ModelDeclaration } from "../typegen/TypeGenerator.js";
4
+ import type { ConnectionConfig } from "../types/index.js";
5
+ export interface BunnyConfig {
6
+ connection: ConnectionConfig;
7
+ migrationsPath?: string | string[];
8
+ migrations?: {
9
+ landlord?: string | string[];
10
+ tenant?: string | string[];
11
+ };
12
+ tenancy?: {
13
+ resolveTenant?: TenantResolver;
14
+ listTenants?: () => string[] | Promise<string[]>;
15
+ };
16
+ modelsPath?: string | string[];
17
+ typesOutDir?: string;
18
+ typeDeclarations?: Record<string, string | ModelDeclaration>;
19
+ typeDeclarationModelsDir?: string;
20
+ typeDeclarationImportPrefix?: string;
21
+ typeDeclarationSingularModels?: boolean;
22
+ typeStubs?: boolean;
23
+ }
24
+ export interface ConfiguredBunny {
25
+ config: BunnyConfig;
26
+ connection: Connection;
27
+ }
28
+ export declare function configureBunny(config: BunnyConfig): ConfiguredBunny;
@@ -0,0 +1,14 @@
1
+ import { Connection } from "../connection/Connection.js";
2
+ import { ConnectionManager } from "../connection/ConnectionManager.js";
3
+ import { Model } from "../model/Model.js";
4
+ import { Schema } from "../schema/Schema.js";
5
+ export function configureBunny(config) {
6
+ const connection = new Connection(config.connection);
7
+ ConnectionManager.setDefault(connection);
8
+ Model.setConnection(connection);
9
+ Schema.setConnection(connection);
10
+ if (config.tenancy?.resolveTenant) {
11
+ ConnectionManager.setTenantResolver(config.tenancy.resolveTenant);
12
+ }
13
+ return { config, connection };
14
+ }
@@ -1,13 +1,32 @@
1
1
  import { SQL } from "bun";
2
2
  import type { ConnectionConfig } from "../types/index.js";
3
+ import { Grammar } from "../query/grammars/Grammar.js";
3
4
  export declare class Connection {
4
5
  readonly driver: SQL;
5
6
  private driverName;
6
- constructor(config: ConnectionConfig);
7
+ private grammar;
8
+ private config;
9
+ private schema?;
10
+ private ownsDriver;
11
+ constructor(config: ConnectionConfig, options?: {
12
+ driver?: SQL;
13
+ schema?: string;
14
+ ownsDriver?: boolean;
15
+ });
7
16
  getDriverName(): "sqlite" | "mysql" | "postgres";
17
+ getGrammar(): Grammar;
18
+ getSchema(): string | undefined;
19
+ withSchema(schema: string): Connection;
20
+ withoutSchema(): Connection;
21
+ qualifyTable(table: string): string;
22
+ private quoteIdentifier;
8
23
  query(sqlString: string): Promise<any[]>;
9
24
  run(sqlString: string): Promise<any>;
10
25
  beginTransaction(): Promise<void>;
11
26
  commit(): Promise<void>;
12
27
  rollback(): Promise<void>;
28
+ transaction<T>(callback: (connection: Connection) => T | Promise<T>): Promise<T>;
29
+ withTenant<T>(tenantId: string, callback: (connection: Connection) => T | Promise<T>, setting?: string): Promise<T>;
30
+ withSearchPath<T>(schema: string, callback: (connection: Connection) => T | Promise<T>): Promise<T>;
31
+ close(): Promise<void>;
13
32
  }
@@ -1,8 +1,18 @@
1
1
  import { SQL } from "bun";
2
+ import { SQLiteGrammar } from "../query/grammars/SQLiteGrammar.js";
3
+ import { MySqlGrammar } from "../query/grammars/MySqlGrammar.js";
4
+ import { PostgresGrammar } from "../query/grammars/PostgresGrammar.js";
2
5
  export class Connection {
3
6
  driver;
4
7
  driverName;
5
- constructor(config) {
8
+ grammar;
9
+ config;
10
+ schema;
11
+ ownsDriver;
12
+ constructor(config, options = {}) {
13
+ this.config = config;
14
+ this.schema = options.schema || ("schema" in config ? config.schema : undefined);
15
+ this.ownsDriver = options.ownsDriver ?? !options.driver;
6
16
  let url;
7
17
  if ("url" in config && config.url) {
8
18
  url = config.url;
@@ -20,16 +30,51 @@ export class Connection {
20
30
  else {
21
31
  throw new Error("Invalid connection configuration. Provide a url or driver config.");
22
32
  }
23
- this.driver = new SQL(url);
33
+ this.driver = options.driver || new SQL(url);
24
34
  this.driverName = url.startsWith("sqlite")
25
35
  ? "sqlite"
26
36
  : url.startsWith("mysql")
27
37
  ? "mysql"
28
38
  : "postgres";
39
+ switch (this.driverName) {
40
+ case "sqlite":
41
+ this.grammar = new SQLiteGrammar();
42
+ break;
43
+ case "mysql":
44
+ this.grammar = new MySqlGrammar();
45
+ break;
46
+ case "postgres":
47
+ this.grammar = new PostgresGrammar();
48
+ break;
49
+ }
29
50
  }
30
51
  getDriverName() {
31
52
  return this.driverName;
32
53
  }
54
+ getGrammar() {
55
+ return this.grammar;
56
+ }
57
+ getSchema() {
58
+ return this.schema;
59
+ }
60
+ withSchema(schema) {
61
+ if (this.schema === schema)
62
+ return this;
63
+ return new Connection(this.config, { driver: this.driver, schema, ownsDriver: false });
64
+ }
65
+ withoutSchema() {
66
+ if (!this.schema)
67
+ return this;
68
+ return new Connection(this.config, { driver: this.driver, ownsDriver: false });
69
+ }
70
+ qualifyTable(table) {
71
+ if (!this.schema || this.driverName === "sqlite" || table.includes("."))
72
+ return table;
73
+ return `${this.schema}.${table}`;
74
+ }
75
+ quoteIdentifier(value) {
76
+ return `"${value.replace(/"/g, '""')}"`;
77
+ }
33
78
  async query(sqlString) {
34
79
  // Use unsafe for generated SQL strings
35
80
  return (await this.driver.unsafe(sqlString));
@@ -46,4 +91,37 @@ export class Connection {
46
91
  async rollback() {
47
92
  await this.driver.unsafe("ROLLBACK");
48
93
  }
94
+ async transaction(callback) {
95
+ return await this.driver.begin(async (sql) => {
96
+ const connection = new Connection(this.config, {
97
+ driver: sql,
98
+ schema: this.schema,
99
+ ownsDriver: false,
100
+ });
101
+ return await callback(connection);
102
+ });
103
+ }
104
+ async withTenant(tenantId, callback, setting = "app.tenant_id") {
105
+ if (this.driverName !== "postgres") {
106
+ return await this.transaction(callback);
107
+ }
108
+ return await this.transaction(async (connection) => {
109
+ await connection.run(`SET LOCAL ${setting} = ${connection.getGrammar().escape(tenantId)}`);
110
+ return await callback(connection);
111
+ });
112
+ }
113
+ async withSearchPath(schema, callback) {
114
+ if (this.driverName !== "postgres") {
115
+ throw new Error("search_path schema switching is only supported for PostgreSQL connections.");
116
+ }
117
+ return await this.transaction(async (connection) => {
118
+ await connection.run(`SET LOCAL search_path TO ${connection.quoteIdentifier(schema)}`);
119
+ return await callback(connection.withoutSchema());
120
+ });
121
+ }
122
+ async close() {
123
+ if (this.ownsDriver) {
124
+ await this.driver.close();
125
+ }
126
+ }
49
127
  }
@@ -0,0 +1,40 @@
1
+ import { Connection } from "./Connection.js";
2
+ import type { ConnectionConfig } from "../types/index.js";
3
+ import type { ActiveTenantContext } from "./TenantContext.js";
4
+ export type TenantResolution = {
5
+ strategy: "database";
6
+ name: string;
7
+ config: ConnectionConfig;
8
+ } | {
9
+ strategy: "schema";
10
+ name: string;
11
+ config?: ConnectionConfig;
12
+ connection?: string | Connection;
13
+ schema: string;
14
+ mode?: "qualify" | "search_path";
15
+ } | {
16
+ strategy: "rls";
17
+ name: string;
18
+ config?: ConnectionConfig;
19
+ connection?: string | Connection;
20
+ tenantId?: string;
21
+ setting?: string;
22
+ };
23
+ export type TenantResolver = (tenantId: string) => TenantResolution | Promise<TenantResolution>;
24
+ export declare class ConnectionManager {
25
+ private static defaultConnection?;
26
+ private static connections;
27
+ private static tenantResolver?;
28
+ private static tenantCache;
29
+ static setDefault(connection: Connection): void;
30
+ static getDefault(): Connection | undefined;
31
+ static add(name: string, connection: Connection | ConnectionConfig): Connection;
32
+ static get(name: string): Connection | undefined;
33
+ static require(name: string): Connection;
34
+ static setTenantResolver(resolver: TenantResolver): void;
35
+ static resolveTenant(tenantId: string): Promise<ActiveTenantContext>;
36
+ static getResolvedTenant(tenantId: string): ActiveTenantContext | undefined;
37
+ static purgeTenant(tenantId: string): void;
38
+ static closeTenant(tenantId: string): Promise<void>;
39
+ static closeAll(): Promise<void>;
40
+ }
@@ -0,0 +1,104 @@
1
+ import { Connection } from "./Connection.js";
2
+ export class ConnectionManager {
3
+ static defaultConnection;
4
+ static connections = new Map();
5
+ static tenantResolver;
6
+ static tenantCache = new Map();
7
+ static setDefault(connection) {
8
+ this.defaultConnection = connection;
9
+ }
10
+ static getDefault() {
11
+ return this.defaultConnection;
12
+ }
13
+ static add(name, connection) {
14
+ const resolved = connection instanceof Connection ? connection : new Connection(connection);
15
+ this.connections.set(name, resolved);
16
+ return resolved;
17
+ }
18
+ static get(name) {
19
+ return this.connections.get(name);
20
+ }
21
+ static require(name) {
22
+ const connection = this.get(name);
23
+ if (!connection) {
24
+ throw new Error(`No connection registered for "${name}".`);
25
+ }
26
+ return connection;
27
+ }
28
+ static setTenantResolver(resolver) {
29
+ this.tenantResolver = resolver;
30
+ this.tenantCache.clear();
31
+ }
32
+ static async resolveTenant(tenantId) {
33
+ const cached = this.tenantCache.get(tenantId);
34
+ if (cached)
35
+ return cached;
36
+ if (!this.tenantResolver) {
37
+ throw new Error("No tenant resolver configured.");
38
+ }
39
+ const resolution = await this.tenantResolver(tenantId);
40
+ const schema = resolution.strategy === "schema" ? resolution.schema : undefined;
41
+ const schemaMode = resolution.strategy === "schema" ? resolution.mode || "qualify" : undefined;
42
+ let connection = (resolution.strategy === "schema" || resolution.strategy === "rls") && resolution.connection instanceof Connection
43
+ ? resolution.connection
44
+ : (resolution.strategy === "schema" || resolution.strategy === "rls") && typeof resolution.connection === "string"
45
+ ? this.require(resolution.connection)
46
+ : this.connections.get(resolution.name);
47
+ if (!connection) {
48
+ if ((resolution.strategy === "schema" || resolution.strategy === "rls") && !resolution.config) {
49
+ connection = this.defaultConnection;
50
+ }
51
+ if (!connection && !resolution.config) {
52
+ throw new Error(`No connection config or registered connection found for tenant "${tenantId}".`);
53
+ }
54
+ }
55
+ if (!connection) {
56
+ const config = resolution.config;
57
+ if (!config) {
58
+ throw new Error(`No connection config or registered connection found for tenant "${tenantId}".`);
59
+ }
60
+ connection = new Connection(config, { schema });
61
+ this.connections.set(resolution.name, connection);
62
+ }
63
+ else if (schema && schemaMode === "qualify") {
64
+ connection = connection.withSchema(schema);
65
+ }
66
+ const context = {
67
+ tenantId,
68
+ connection,
69
+ connectionName: resolution.name,
70
+ strategy: resolution.strategy,
71
+ schema,
72
+ schemaMode,
73
+ rlsTenantId: resolution.strategy === "rls" ? resolution.tenantId || tenantId : undefined,
74
+ rlsSetting: resolution.strategy === "rls" ? resolution.setting || "app.tenant_id" : undefined,
75
+ };
76
+ this.tenantCache.set(tenantId, context);
77
+ return context;
78
+ }
79
+ static getResolvedTenant(tenantId) {
80
+ return this.tenantCache.get(tenantId);
81
+ }
82
+ static purgeTenant(tenantId) {
83
+ this.tenantCache.delete(tenantId);
84
+ }
85
+ static async closeTenant(tenantId) {
86
+ const context = this.tenantCache.get(tenantId);
87
+ if (!context)
88
+ return;
89
+ this.tenantCache.delete(tenantId);
90
+ const connection = this.connections.get(context.connectionName);
91
+ this.connections.delete(context.connectionName);
92
+ await connection?.close();
93
+ }
94
+ static async closeAll() {
95
+ const connections = new Set(this.connections.values());
96
+ if (this.defaultConnection)
97
+ connections.add(this.defaultConnection);
98
+ this.connections.clear();
99
+ this.tenantCache.clear();
100
+ for (const connection of connections) {
101
+ await connection.close();
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,15 @@
1
+ import type { Connection } from "./Connection.js";
2
+ export interface ActiveTenantContext {
3
+ tenantId: string;
4
+ connection: Connection;
5
+ connectionName: string;
6
+ strategy: "database" | "schema" | "rls";
7
+ schema?: string;
8
+ schemaMode?: "qualify" | "search_path";
9
+ rlsTenantId?: string;
10
+ rlsSetting?: string;
11
+ }
12
+ export declare class TenantContext {
13
+ static current(): ActiveTenantContext | undefined;
14
+ static run<T>(tenantId: string, callback: () => T | Promise<T>): Promise<T>;
15
+ }
@@ -0,0 +1,22 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { ConnectionManager } from "./ConnectionManager.js";
3
+ const storage = new AsyncLocalStorage();
4
+ export class TenantContext {
5
+ static current() {
6
+ return storage.getStore();
7
+ }
8
+ static async run(tenantId, callback) {
9
+ const context = await ConnectionManager.resolveTenant(tenantId);
10
+ if (context.strategy === "schema" && context.schema && context.schemaMode === "search_path") {
11
+ return await context.connection.withSearchPath(context.schema, async (connection) => {
12
+ return await storage.run({ ...context, connection }, callback);
13
+ });
14
+ }
15
+ if (context.strategy === "rls") {
16
+ return await context.connection.withTenant(context.rlsTenantId || context.tenantId, async (connection) => {
17
+ return await storage.run({ ...context, connection }, callback);
18
+ }, context.rlsSetting);
19
+ }
20
+ return await storage.run(context, callback);
21
+ }
22
+ }
@@ -1,4 +1,10 @@
1
1
  export { Connection } from "./connection/Connection.js";
2
+ export { ConnectionManager } from "./connection/ConnectionManager.js";
3
+ export type { TenantResolution, TenantResolver } from "./connection/ConnectionManager.js";
4
+ export { TenantContext } from "./connection/TenantContext.js";
5
+ export type { ActiveTenantContext } from "./connection/TenantContext.js";
6
+ export { configureBunny } from "./config/BunnyConfig.js";
7
+ export type { BunnyConfig, ConfiguredBunny } from "./config/BunnyConfig.js";
2
8
  export type { ConnectionConfig } from "./types/index.js";
3
9
  export { Schema } from "./schema/Schema.js";
4
10
  export { Blueprint } from "./schema/Blueprint.js";
package/dist/src/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export { Connection } from "./connection/Connection.js";
2
+ export { ConnectionManager } from "./connection/ConnectionManager.js";
3
+ export { TenantContext } from "./connection/TenantContext.js";
4
+ export { configureBunny } from "./config/BunnyConfig.js";
2
5
  export { Schema } from "./schema/Schema.js";
3
6
  export { Blueprint } from "./schema/Blueprint.js";
4
7
  export { Grammar } from "./schema/grammars/Grammar.js";
@@ -21,7 +21,7 @@ export class BelongsToMany {
21
21
  this.relatedKey = relatedKey || related.primaryKey;
22
22
  this.foreignPivotKey = foreignPivotKey || `${snakeCase(parent.constructor.name)}_id`;
23
23
  this.relatedPivotKey = relatedPivotKey || `${snakeCase(related.name)}_id`;
24
- this.builder = related.query();
24
+ this.builder = related.on(parent.getConnection());
25
25
  this.addConstraints();
26
26
  }
27
27
  addConstraints() {
@@ -35,7 +35,7 @@ export class BelongsToMany {
35
35
  }
36
36
  addEagerConstraints(models) {
37
37
  const keys = models.map((m) => m.getAttribute(this.parentKey));
38
- this.builder = this.related.query();
38
+ this.builder = this.related.on(this.parent.getConnection());
39
39
  const relatedTable = this.related.getTable();
40
40
  this.builder.select(`${relatedTable}.*`, `${this.table}.${this.foreignPivotKey}`);
41
41
  this.builder.join(this.table, `${this.table}.${this.relatedPivotKey}`, "=", `${relatedTable}.${this.relatedKey}`);
@@ -66,7 +66,7 @@ export class BelongsToMany {
66
66
  }
67
67
  newExistenceQuery(parentTable, aggregate, callback) {
68
68
  const relatedTable = this.related.getTable();
69
- const query = this.related.query().select(aggregate);
69
+ const query = this.related.on(this.parent.getConnection()).select(aggregate);
70
70
  query.join(this.table, `${this.table}.${this.relatedPivotKey}`, "=", `${relatedTable}.${this.relatedKey}`);
71
71
  query.whereColumn(`${this.table}.${this.foreignPivotKey}`, "=", `${parentTable}.${this.parentKey}`);
72
72
  if (callback)
@@ -89,10 +89,12 @@ export class BelongsToMany {
89
89
  [this.relatedPivotKey]: id,
90
90
  ...attributes,
91
91
  }));
92
- await new Builder(this.related.getConnection(), this.table).insert(records);
92
+ const connection = this.parent.getConnection();
93
+ await new Builder(connection, connection.qualifyTable(this.table)).insert(records);
93
94
  }
94
95
  async detach(ids) {
95
- const builder = new Builder(this.related.getConnection(), this.table)
96
+ const connection = this.parent.getConnection();
97
+ const builder = new Builder(connection, connection.qualifyTable(this.table))
96
98
  .where(this.foreignPivotKey, this.parent.getAttribute(this.parentKey));
97
99
  if (ids !== undefined) {
98
100
  builder.whereIn(this.relatedPivotKey, Array.isArray(ids) ? ids : [ids]);
@@ -101,7 +103,8 @@ export class BelongsToMany {
101
103
  }
102
104
  async sync(ids, detachMissing = true) {
103
105
  const idList = Array.isArray(ids) ? ids : [ids];
104
- const current = await new Builder(this.related.getConnection(), this.table)
106
+ const connection = this.parent.getConnection();
107
+ const current = await new Builder(connection, connection.qualifyTable(this.table))
105
108
  .where(this.foreignPivotKey, this.parent.getAttribute(this.parentKey))
106
109
  .pluck(this.relatedPivotKey);
107
110
  const currentSet = new Set(current);
@@ -97,10 +97,13 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
97
97
  $exists: boolean;
98
98
  $relations: Record<string, any>;
99
99
  $casts: Record<string, CastDefinition>;
100
+ $connection?: Connection;
100
101
  constructor(attributes?: Partial<T>);
101
102
  static getTable(): string;
102
103
  static getConnection(): Connection;
103
104
  static setConnection(connection: Connection): void;
105
+ static on<M extends typeof Model>(this: M, connection: string | Connection): Builder<InstanceType<M>>;
106
+ static forTenant<M extends typeof Model>(this: M, tenantId: string): Builder<InstanceType<M>>;
104
107
  static query<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
105
108
  static addGlobalScope(name: string, scope: GlobalScope): void;
106
109
  static removeGlobalScope(name: string): void;
@@ -165,6 +168,8 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
165
168
  static eagerLoadRelations(models: Model[], relations: string[]): Promise<void>;
166
169
  static eagerLoadRelation(models: Model[], relationName: string): Promise<void>;
167
170
  fill(attributes: Partial<T>): this;
171
+ setConnection(connection: Connection): this;
172
+ getConnection(): Connection;
168
173
  isFillable(key: string): boolean;
169
174
  getAttribute<K extends keyof T>(key: K): T[K];
170
175
  getAttribute(key: string): any;