@veap/database 0.1.0 → 0.1.1

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.
Files changed (49) hide show
  1. package/README.md +3 -2
  2. package/dist/casts.js +1 -1
  3. package/dist/composition.d.ts +15 -0
  4. package/dist/composition.js +26 -0
  5. package/dist/connection.d.ts +1 -1
  6. package/dist/connection.js +9 -5
  7. package/dist/factory.d.ts +1 -1
  8. package/dist/factory.js +1 -1
  9. package/dist/index.d.ts +18 -16
  10. package/dist/index.js +18 -16
  11. package/dist/migration-provider.d.ts +2 -0
  12. package/dist/migration-provider.js +27 -6
  13. package/dist/migrations/index.d.ts +3 -3
  14. package/dist/migrations/index.js +3 -3
  15. package/dist/migrations/runner.d.ts +1 -1
  16. package/dist/migrations/runner.js +20 -19
  17. package/dist/migrations/schema-builder.js +1 -1
  18. package/dist/model.d.ts +4 -4
  19. package/dist/model.js +25 -11
  20. package/dist/provider.d.ts +2 -1
  21. package/dist/provider.js +62 -8
  22. package/dist/query-builder.d.ts +6 -6
  23. package/dist/query-builder.js +40 -6
  24. package/dist/relations/belongs-to-many.d.ts +3 -3
  25. package/dist/relations/belongs-to-many.js +2 -2
  26. package/dist/relations/belongs-to.d.ts +3 -3
  27. package/dist/relations/belongs-to.js +1 -1
  28. package/dist/relations/eager-loader.d.ts +1 -1
  29. package/dist/relations/eager-loader.js +3 -1
  30. package/dist/relations/has-many.d.ts +3 -3
  31. package/dist/relations/has-many.js +1 -1
  32. package/dist/relations/has-one.d.ts +3 -3
  33. package/dist/relations/has-one.js +1 -1
  34. package/dist/relations/index.d.ts +11 -11
  35. package/dist/relations/index.js +11 -11
  36. package/dist/relations/morph-many.d.ts +3 -3
  37. package/dist/relations/morph-many.js +2 -2
  38. package/dist/relations/morph-one.d.ts +3 -3
  39. package/dist/relations/morph-one.js +2 -2
  40. package/dist/relations/morph-to-many.d.ts +3 -3
  41. package/dist/relations/morph-to-many.js +3 -3
  42. package/dist/relations/morph-to.d.ts +3 -3
  43. package/dist/relations/morph-to.js +3 -3
  44. package/dist/relations/relation.d.ts +2 -2
  45. package/dist/scopes.d.ts +2 -2
  46. package/dist/seeder.js +2 -2
  47. package/dist/tokens.d.ts +10 -0
  48. package/dist/tokens.js +8 -0
  49. package/package.json +10 -2
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
- [![CI](https://github.com/veapjs/veap-database/actions/workflows/ci.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/ci.yml)
2
- [![Release](https://github.com/veapjs/veap-database/actions/workflows/release.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/release.yml)
1
+ [![CI](https://github.com/veapjs/veap-database/actions/workflows/CI.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/CI.yml)
2
+ [![Release](https://github.com/veapjs/veap-database/actions/workflows/RELEASE.yml/badge.svg)](https://github.com/veapjs/veap-database/actions/workflows/RELEASE.yml)
3
+ [![Version](https://img.shields.io/npm/v/@veap/database?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/@veap/database)
3
4
 
4
5
  # @veap/database
5
6
 
package/dist/casts.js CHANGED
@@ -57,7 +57,7 @@ export function serializeForStorage(value, type, clientType) {
57
57
  }
58
58
  switch (type) {
59
59
  case "json":
60
- return JSON.stringify(value);
60
+ return typeof value === "string" ? value : JSON.stringify(value);
61
61
  case "boolean":
62
62
  if (clientType?.includes("sqlite")) {
63
63
  return value ? 1 : 0;
@@ -0,0 +1,15 @@
1
+ import { ServiceProvider } from "@veap/kernel";
2
+ declare module "@veap/kernel" {
3
+ interface ApplicationBuilder {
4
+ /**
5
+ * Registers host application migrations with IoC and configures MigrationServiceProvider.
6
+ */
7
+ withMigrations(migrations: any[]): this;
8
+ }
9
+ }
10
+ declare class MigrationsConfigProvider extends ServiceProvider {
11
+ private migrations;
12
+ constructor(container: any, migrations?: any[]);
13
+ register(): void;
14
+ }
15
+ export { MigrationsConfigProvider };
@@ -0,0 +1,26 @@
1
+ import { ApplicationBuilder, ServiceProvider } from "@veap/kernel";
2
+ import { MigrationServiceProvider } from "./migration-provider.js";
3
+ import { APP_MIGRATIONS } from "./tokens.js";
4
+ class MigrationsConfigProvider extends ServiceProvider {
5
+ migrations;
6
+ constructor(container, migrations = []) {
7
+ super(container);
8
+ this.migrations = migrations;
9
+ }
10
+ register() {
11
+ this.container.register({
12
+ token: APP_MIGRATIONS,
13
+ useValue: this.migrations,
14
+ });
15
+ this.container.register({
16
+ token: "AppMigrations",
17
+ useValue: this.migrations,
18
+ });
19
+ }
20
+ }
21
+ if (!ApplicationBuilder.prototype.withMigrations) {
22
+ ApplicationBuilder.prototype.withMigrations = function (migrations) {
23
+ return this.use((c) => new MigrationsConfigProvider(c, migrations)).use(MigrationServiceProvider);
24
+ };
25
+ }
26
+ export { MigrationsConfigProvider };
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { type Knex } from "knex";
3
- import type { DatabaseConfig, PostgresSslConfig, PostgresSslOptions, TransactionOptions } from "./types";
3
+ import type { DatabaseConfig, PostgresSslConfig, PostgresSslOptions, TransactionOptions } from "./types.js";
4
4
  /**
5
5
  * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
6
6
  */
@@ -2,8 +2,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import knex from "knex";
5
- import { DatabaseError } from "./errors";
6
- import { debug, warn } from "./logger";
5
+ import { DatabaseError } from "./errors.js";
6
+ import { debug, warn } from "./logger.js";
7
7
  const globalForDb = globalThis;
8
8
  /**
9
9
  * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
@@ -90,9 +90,13 @@ export function resolveSqliteFilename(filename) {
90
90
  }
91
91
  if (!clean.startsWith(":memory:")) {
92
92
  try {
93
- fs.mkdirSync(path.dirname(path.resolve(clean)), {
94
- recursive: true,
95
- });
93
+ if (typeof process !== "undefined" && process.versions?.node) {
94
+ /* turbopackIgnore: true */
95
+ const resolvedPath = path.resolve(clean);
96
+ fs.mkdirSync(path.dirname(resolvedPath), {
97
+ recursive: true,
98
+ });
99
+ }
96
100
  }
97
101
  catch {
98
102
  // Read-only filesystem etc. Let the driver surface its own error.
package/dist/factory.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Model } from "./model";
1
+ import type { Model } from "./model.js";
2
2
  export declare abstract class Factory<M extends Model = any> {
3
3
  private static get registry();
4
4
  /**
package/dist/factory.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DatabaseError } from "./errors";
1
+ import { DatabaseError } from "./errors.js";
2
2
  export class Factory {
3
3
  static get registry() {
4
4
  const g = globalThis;
package/dist/index.d.ts CHANGED
@@ -1,17 +1,19 @@
1
1
  export { default as knex, type Knex } from "knex";
2
- export * from "./casts";
3
- export * from "./connection";
4
- export * from "./errors";
5
- export * from "./events";
6
- export * from "./factory";
7
- export * from "./logger";
8
- export * from "./migration-provider";
9
- export * from "./migrations/index";
10
- export * from "./model";
11
- export * from "./provider";
12
- export * from "./query-builder";
13
- export * from "./relations/index";
14
- export * from "./scopes";
15
- export * from "./seeder";
16
- export * from "./types";
17
- export * from "./utils";
2
+ export * from "./casts.js";
3
+ export * from "./composition.js";
4
+ export * from "./connection.js";
5
+ export * from "./errors.js";
6
+ export * from "./events.js";
7
+ export * from "./factory.js";
8
+ export * from "./logger.js";
9
+ export * from "./migration-provider.js";
10
+ export * from "./migrations/index.js";
11
+ export * from "./model.js";
12
+ export * from "./provider.js";
13
+ export * from "./query-builder.js";
14
+ export * from "./relations/index.js";
15
+ export * from "./scopes.js";
16
+ export * from "./seeder.js";
17
+ export * from "./tokens.js";
18
+ export * from "./types.js";
19
+ export * from "./utils.js";
package/dist/index.js CHANGED
@@ -1,17 +1,19 @@
1
1
  export { default as knex } from "knex";
2
- export * from "./casts";
3
- export * from "./connection";
4
- export * from "./errors";
5
- export * from "./events";
6
- export * from "./factory";
7
- export * from "./logger";
8
- export * from "./migration-provider";
9
- export * from "./migrations/index";
10
- export * from "./model";
11
- export * from "./provider";
12
- export * from "./query-builder";
13
- export * from "./relations/index";
14
- export * from "./scopes";
15
- export * from "./seeder";
16
- export * from "./types";
17
- export * from "./utils";
2
+ export * from "./casts.js";
3
+ export * from "./composition.js";
4
+ export * from "./connection.js";
5
+ export * from "./errors.js";
6
+ export * from "./events.js";
7
+ export * from "./factory.js";
8
+ export * from "./logger.js";
9
+ export * from "./migration-provider.js";
10
+ export * from "./migrations/index.js";
11
+ export * from "./model.js";
12
+ export * from "./provider.js";
13
+ export * from "./query-builder.js";
14
+ export * from "./relations/index.js";
15
+ export * from "./scopes.js";
16
+ export * from "./seeder.js";
17
+ export * from "./tokens.js";
18
+ export * from "./types.js";
19
+ export * from "./utils.js";
@@ -1,5 +1,7 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
2
  export declare class MigrationServiceProvider extends ServiceProvider {
3
+ private migrations?;
4
+ constructor(container: any, migrations?: any[]);
3
5
  register(): void;
4
6
  boot(): Promise<void>;
5
7
  }
@@ -1,15 +1,36 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
- import { runMigrations } from "./migrations/runner";
2
+ import { runMigrations } from "./migrations/runner.js";
3
+ import { APP_MIGRATIONS } from "./tokens.js";
3
4
  export class MigrationServiceProvider extends ServiceProvider {
5
+ migrations;
6
+ constructor(container, migrations) {
7
+ super(container);
8
+ this.migrations = migrations;
9
+ }
4
10
  register() {
5
- // No bindings needed
11
+ if (this.migrations?.length) {
12
+ this.container.register({
13
+ token: APP_MIGRATIONS,
14
+ useValue: this.migrations,
15
+ });
16
+ this.container.register({
17
+ token: "AppMigrations",
18
+ useValue: this.migrations,
19
+ });
20
+ }
6
21
  }
7
22
  async boot() {
8
- if (this.container.has("AppMigrations")) {
9
- const appMigrations = await this.container.resolve("AppMigrations");
10
- if (appMigrations?.length) {
11
- await runMigrations("app", appMigrations);
23
+ let appMigrations = this.migrations;
24
+ if (!appMigrations?.length) {
25
+ if (this.container.has(APP_MIGRATIONS)) {
26
+ appMigrations = await this.container.resolve(APP_MIGRATIONS);
12
27
  }
28
+ else if (this.container.has("AppMigrations")) {
29
+ appMigrations = await this.container.resolve("AppMigrations");
30
+ }
31
+ }
32
+ if (appMigrations?.length) {
33
+ await runMigrations("app", appMigrations);
13
34
  }
14
35
  }
15
36
  }
@@ -1,3 +1,3 @@
1
- export * from "./generator";
2
- export * from "./runner";
3
- export * from "./schema-builder";
1
+ export * from "./generator.js";
2
+ export * from "./runner.js";
3
+ export * from "./schema-builder.js";
@@ -1,3 +1,3 @@
1
- export * from "./generator";
2
- export * from "./runner";
3
- export * from "./schema-builder";
1
+ export * from "./generator.js";
2
+ export * from "./runner.js";
3
+ export * from "./schema-builder.js";
@@ -1,4 +1,4 @@
1
- import { Schema } from "./schema-builder";
1
+ import { Schema } from "./schema-builder.js";
2
2
  export interface Migration {
3
3
  name: string;
4
4
  up: (db: any, schema: Schema) => Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
- import { getKnex } from "../connection";
3
- import { debug, warn } from "../logger";
4
- import { Schema } from "./schema-builder";
2
+ import { getKnex } from "../connection.js";
3
+ import { debug, warn } from "../logger.js";
4
+ import { Schema } from "./schema-builder.js";
5
5
  async function ensureMigrationsTable(knex = getKnex()) {
6
6
  try {
7
7
  const hasTable = await knex.schema.hasTable("migrations");
@@ -27,6 +27,19 @@ async function ensureMigrationsTable(knex = getKnex()) {
27
27
  warn("veap:migration", "Failed to ensure migrations table:", error);
28
28
  }
29
29
  }
30
+ async function acquireAdvisoryLockIfPostgres(tx, knex) {
31
+ const clientName = knex?.client?.config?.client || "";
32
+ if (typeof clientName === "string" &&
33
+ (clientName.includes("pg") || clientName.includes("postgres"))) {
34
+ const lockId = 123456;
35
+ try {
36
+ await tx.raw("SELECT pg_advisory_xact_lock(?)", [lockId]);
37
+ }
38
+ catch (_e) {
39
+ debug("veap:migration", "Could not acquire advisory lock, continuing...");
40
+ }
41
+ }
42
+ }
30
43
  /**
31
44
  * Creates a compatibility DB adapter for migrations that provides `.execute(query)`
32
45
  * wrapping `trx.raw(query)`, while also being directly callable as a Knex query builder
@@ -67,14 +80,8 @@ export async function runMigrations(scope, migrations) {
67
80
  return;
68
81
  await ensureMigrationsTable(knex);
69
82
  await knex.transaction(async (tx) => {
70
- // Acquire advisory lock (unique ID for migrations in Postgres)
71
- const lockId = 123456;
72
- try {
73
- await tx.raw("SELECT pg_advisory_xact_lock(?)", [lockId]);
74
- }
75
- catch (_e) {
76
- debug("veap:migration", "Could not acquire advisory lock, continuing...");
77
- }
83
+ // Acquire advisory lock only if running on PostgreSQL
84
+ await acquireAdvisoryLockIfPostgres(tx, knex);
78
85
  debug("veap:migration", `Running migrations for scope: ${scope}`);
79
86
  // 1. Get executed migrations for this scope
80
87
  const executed = await tx("migrations")
@@ -131,14 +138,8 @@ export async function rollbackMigrations(scope, migrations) {
131
138
  return;
132
139
  await ensureMigrationsTable(knex);
133
140
  await knex.transaction(async (tx) => {
134
- // Acquire advisory lock
135
- const lockId = 123456;
136
- try {
137
- await tx.raw("SELECT pg_advisory_xact_lock(?)", [lockId]);
138
- }
139
- catch (_e) {
140
- debug("veap:migration", "Could not acquire advisory lock, continuing...");
141
- }
141
+ // Acquire advisory lock only if running on PostgreSQL
142
+ await acquireAdvisoryLockIfPostgres(tx, knex);
142
143
  // 1. Get executed migrations for this scope ordered by batch desc
143
144
  const executed = await tx("migrations")
144
145
  .select("name", "batch")
@@ -1,4 +1,4 @@
1
- import { getKnex } from "../connection";
1
+ import { getKnex } from "../connection.js";
2
2
  /**
3
3
  * Lightweight SQL helper for migrations, compatible with sql`...` template strings.
4
4
  */
package/dist/model.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type CastType } from "./casts";
2
- import { ModelQueryBuilder } from "./query-builder";
3
- import { BelongsTo, BelongsToMany, HasMany, HasOne, MorphedByMany, MorphMany, MorphOne, MorphTo, MorphToMany } from "./relations";
4
- import type { GlobalScope } from "./scopes";
1
+ import { type CastType } from "./casts.js";
2
+ import { ModelQueryBuilder } from "./query-builder.js";
3
+ import { BelongsTo, BelongsToMany, HasMany, HasOne, MorphedByMany, MorphMany, MorphOne, MorphTo, MorphToMany } from "./relations/index.js";
4
+ import type { GlobalScope } from "./scopes.js";
5
5
  export declare abstract class Model<Attributes extends Record<string, any> = Record<string, any>> {
6
6
  [key: string]: any;
7
7
  /**
package/dist/model.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /** biome-ignore-all lint/correctness/noConstructorReturn: <ignore> */
2
2
  import crypto from "node:crypto";
3
- import { castAttribute, serializeForStorage } from "./casts";
4
- import { getKnex } from "./connection";
5
- import { DatabaseError } from "./errors";
6
- import { getDatabaseEventDispatcher } from "./events";
7
- import { Factory } from "./factory";
8
- import { ModelQueryBuilder } from "./query-builder";
9
- import { BelongsTo, BelongsToMany, HasMany, HasOne, MorphedByMany, MorphMany, MorphMap, MorphOne, MorphTo, MorphToMany, } from "./relations";
10
- import { toCamelCase, toSnakeCase } from "./utils";
3
+ import { castAttribute, serializeForStorage } from "./casts.js";
4
+ import { getKnex } from "./connection.js";
5
+ import { DatabaseError } from "./errors.js";
6
+ import { getDatabaseEventDispatcher } from "./events.js";
7
+ import { Factory } from "./factory.js";
8
+ import { ModelQueryBuilder } from "./query-builder.js";
9
+ import { BelongsTo, BelongsToMany, HasMany, HasOne, MorphedByMany, MorphMany, MorphMap, MorphOne, MorphTo, MorphToMany, } from "./relations/index.js";
10
+ import { toCamelCase, toSnakeCase } from "./utils.js";
11
11
  export class Model {
12
12
  /**
13
13
  * Database table name.
@@ -285,7 +285,7 @@ export class Model {
285
285
  * Executes a database transaction.
286
286
  */
287
287
  static async transaction(callback) {
288
- const { transaction: runTrx } = await import("./connection");
288
+ const { transaction: runTrx } = await import("./connection.js");
289
289
  return await runTrx(callback);
290
290
  }
291
291
  /**
@@ -336,12 +336,15 @@ export class Model {
336
336
  */
337
337
  static async destroy(id) {
338
338
  const knex = getKnex();
339
+ const clientType = knex.client?.config?.client;
339
340
  if (this.softDeletes) {
340
341
  const col = toSnakeCase(this.deletedAtColumn || "deleted_at");
341
342
  const count = await knex(this.table)
342
343
  .where(this.primaryKey, id)
343
344
  .whereNull(col)
344
- .update({ [col]: new Date() });
345
+ .update({
346
+ [col]: serializeForStorage(new Date(), "datetime", clientType),
347
+ });
345
348
  return count > 0;
346
349
  }
347
350
  const count = await knex(this.table).where(this.primaryKey, id).delete();
@@ -607,7 +610,18 @@ export class Model {
607
610
  }
608
611
  insertData[col] = serializeForStorage(v, castType, clientType);
609
612
  }
610
- await knex(modelClass.table).insert(insertData);
613
+ const insertResult = await knex(modelClass.table)
614
+ .insert(insertData)
615
+ .returning(pk);
616
+ if (!this.getAttribute(pk)) {
617
+ if (Array.isArray(insertResult) && insertResult.length > 0) {
618
+ const first = insertResult[0];
619
+ const returnedId = typeof first === "object" && first !== null ? first[pk] : first;
620
+ if (returnedId !== undefined && returnedId !== null) {
621
+ this.setAttribute(pk, returnedId);
622
+ }
623
+ }
624
+ }
611
625
  this.isNew = false;
612
626
  this._original = { ...this._attributes };
613
627
  await this.fireModelEvent("created");
@@ -1,5 +1,5 @@
1
1
  import { ServiceProvider } from "@veap/kernel";
2
- import type { DatabaseConfig } from "./types";
2
+ import type { DatabaseConfig } from "./types.js";
3
3
  export interface DatabaseProviderConfig {
4
4
  databaseUrl?: string;
5
5
  config?: DatabaseConfig;
@@ -9,4 +9,5 @@ export declare class DatabaseServiceProvider extends ServiceProvider {
9
9
  constructor(container: any, config?: DatabaseProviderConfig);
10
10
  register(): void;
11
11
  boot(): Promise<void>;
12
+ shutdown(): Promise<void>;
12
13
  }
package/dist/provider.js CHANGED
@@ -1,7 +1,9 @@
1
- import { ConfigService, DATABASE, ServiceProvider } from "@veap/kernel";
1
+ import { ConfigService, ServiceProvider } from "@veap/kernel";
2
2
  import knex from "knex";
3
- import { initDatabase, isSqliteDatabase, resolvePostgresSslConfig, resolveSqliteFilename, setKnex, } from "./connection";
4
- import { debug } from "./logger";
3
+ import { getKnex, initDatabase, isSqliteDatabase, resolvePostgresSslConfig, resolveSqliteFilename, setKnex, } from "./connection.js";
4
+ import { setDatabaseEventDispatcher } from "./events.js";
5
+ import { debug, setDatabaseLogger } from "./logger.js";
6
+ import { DATABASE } from "./tokens.js";
5
7
  export class DatabaseServiceProvider extends ServiceProvider {
6
8
  config;
7
9
  constructor(container, config) {
@@ -12,13 +14,54 @@ export class DatabaseServiceProvider extends ServiceProvider {
12
14
  // Port token registration
13
15
  }
14
16
  async boot() {
15
- const configService = await this.container.resolve(ConfigService);
17
+ let configService = null;
18
+ if (this.container.has(ConfigService)) {
19
+ configService = await this.container.resolve(ConfigService);
20
+ }
21
+ else if (this.container.has("ConfigService")) {
22
+ configService = await this.container.resolve("ConfigService");
23
+ }
16
24
  const databaseUrl = this.config?.databaseUrl ||
17
- configService.get("DATABASE_URL") ||
25
+ configService?.get?.("DATABASE_URL") ||
18
26
  process.env.DATABASE_URL ||
19
27
  process.env.DATABASE_FILE;
20
- const isProd = configService.get("NODE_ENV") === "production" ||
28
+ const isProd = configService?.get?.("NODE_ENV") === "production" ||
21
29
  process.env.NODE_ENV === "production";
30
+ // Auto-wire EventDispatcher if bound to container
31
+ if (this.container.has("EventDispatcher")) {
32
+ try {
33
+ const dispatcher = (await this.container.resolve("EventDispatcher"));
34
+ if (dispatcher && typeof dispatcher.publish === "function") {
35
+ setDatabaseEventDispatcher(dispatcher);
36
+ }
37
+ }
38
+ catch {
39
+ // Continue without event dispatcher
40
+ }
41
+ }
42
+ // Auto-wire Logger if bound to container
43
+ if (this.container.has("LOGGER")) {
44
+ try {
45
+ const logger = (await this.container.resolve("LOGGER"));
46
+ if (logger && typeof logger.info === "function") {
47
+ setDatabaseLogger({
48
+ debug: (ch, msg, ...args) => logger.debug
49
+ ? logger.debug(`[${ch}] ${msg}`, ...args)
50
+ : undefined,
51
+ info: (ch, msg, ...args) => logger.info(`[${ch}] ${msg}`, ...args),
52
+ warn: (ch, msg, ...args) => logger.warn
53
+ ? logger.warn(`[${ch}] ${msg}`, ...args)
54
+ : console.warn(`[${ch}] ${msg}`, ...args),
55
+ error: (ch, msg, ...args) => logger.error
56
+ ? logger.error(`[${ch}] ${msg}`, ...args)
57
+ : console.error(`[${ch}] ${msg}`, ...args),
58
+ });
59
+ }
60
+ }
61
+ catch {
62
+ // Continue with default logger
63
+ }
64
+ }
22
65
  if (this.config?.config) {
23
66
  const instance = initDatabase(this.config.config);
24
67
  this.container.register({ token: DATABASE, useValue: instance });
@@ -32,8 +75,8 @@ export class DatabaseServiceProvider extends ServiceProvider {
32
75
  const ssl = resolvePostgresSslConfig({
33
76
  databaseUrl: resolvedUrl,
34
77
  isProd,
35
- rejectUnauthorizedEnv: configService.get("DATABASE_SSL_REJECT_UNAUTHORIZED"),
36
- caCert: configService.get("DATABASE_SSL_CA"),
78
+ rejectUnauthorizedEnv: configService?.get?.("DATABASE_SSL_REJECT_UNAUTHORIZED"),
79
+ caCert: configService?.get?.("DATABASE_SSL_CA"),
37
80
  });
38
81
  const instance = knex({
39
82
  client,
@@ -55,4 +98,15 @@ export class DatabaseServiceProvider extends ServiceProvider {
55
98
  useValue: instance,
56
99
  });
57
100
  }
101
+ async shutdown() {
102
+ try {
103
+ const k = getKnex();
104
+ if (k && typeof k.destroy === "function") {
105
+ await k.destroy();
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore if not initialized
110
+ }
111
+ }
58
112
  }
@@ -1,5 +1,6 @@
1
1
  import type { Knex } from "knex";
2
- import type { Model } from "./model";
2
+ import type { Model } from "./model.js";
3
+ import type { PaginationResult } from "./types.js";
3
4
  export declare class ModelQueryBuilder<M extends Model = any> {
4
5
  modelClass: any;
5
6
  [key: string]: any;
@@ -65,6 +66,9 @@ export declare class ModelQueryBuilder<M extends Model = any> {
65
66
  take(count: number): this;
66
67
  offset(count: number): this;
67
68
  skip(count: number): this;
69
+ groupBy(...columns: any[]): this;
70
+ having(column: any, operatorOrValue?: any, value?: any): this;
71
+ distinct(...columns: any[]): this;
68
72
  join(table: string, first: string, operatorOrSecond: string, second?: string): this;
69
73
  leftJoin(table: string, first: string, operatorOrSecond: string, second?: string): this;
70
74
  /**
@@ -162,12 +166,8 @@ export declare class ModelQueryBuilder<M extends Model = any> {
162
166
  /**
163
167
  * Paginates results.
164
168
  */
165
- paginate(page?: number, perPage?: number): Promise<{
166
- data: M[];
167
- total: number;
169
+ paginate(page?: number, perPage?: number): Promise<PaginationResult<M> & {
168
170
  page: number;
169
- perPage: number;
170
- lastPage: number;
171
171
  }>;
172
172
  /**
173
173
  * Bulk updates matching records.
@@ -1,9 +1,9 @@
1
- import { serializeForStorage } from "./casts";
2
- import { getKnex } from "./connection";
3
- import { DatabaseError } from "./errors";
4
- import { BelongsTo, BelongsToMany, MorphedByMany, MorphMany, MorphOne, MorphToMany, } from "./relations";
5
- import { eagerLoadRelations } from "./relations/eager-loader";
6
- import { toCamelCase, toSnakeCase } from "./utils";
1
+ import { serializeForStorage } from "./casts.js";
2
+ import { getKnex } from "./connection.js";
3
+ import { DatabaseError } from "./errors.js";
4
+ import { eagerLoadRelations } from "./relations/eager-loader.js";
5
+ import { BelongsTo, BelongsToMany, MorphedByMany, MorphMany, MorphOne, MorphToMany, } from "./relations/index.js";
6
+ import { toCamelCase, toSnakeCase } from "./utils.js";
7
7
  export class ModelQueryBuilder {
8
8
  modelClass;
9
9
  knexBuilder;
@@ -47,6 +47,14 @@ export class ModelQueryBuilder {
47
47
  : result;
48
48
  };
49
49
  }
50
+ // 3. Delegate to Knex query builder if method exists (e.g. groupBy, having, distinct, joinRaw)
51
+ if (target.knexBuilder &&
52
+ typeof target.knexBuilder[prop] === "function") {
53
+ return (...args) => {
54
+ const result = target.knexBuilder[prop](...args);
55
+ return result === target.knexBuilder ? receiver : result;
56
+ };
57
+ }
50
58
  }
51
59
  return Reflect.get(target, prop, receiver);
52
60
  },
@@ -270,6 +278,30 @@ export class ModelQueryBuilder {
270
278
  skip(count) {
271
279
  return this.offset(count);
272
280
  }
281
+ groupBy(...columns) {
282
+ const converted = columns.map((col) => typeof col === "string" ? toSnakeCase(col) : col);
283
+ this.knexBuilder.groupBy(...converted);
284
+ return this;
285
+ }
286
+ having(column, operatorOrValue, value) {
287
+ if (value === undefined) {
288
+ if (operatorOrValue === undefined) {
289
+ this.knexBuilder.having(column);
290
+ }
291
+ else {
292
+ this.knexBuilder.having(column, "=", operatorOrValue);
293
+ }
294
+ }
295
+ else {
296
+ this.knexBuilder.having(column, operatorOrValue, value);
297
+ }
298
+ return this;
299
+ }
300
+ distinct(...columns) {
301
+ const converted = columns.map((col) => typeof col === "string" ? toSnakeCase(col) : col);
302
+ this.knexBuilder.distinct(...converted);
303
+ return this;
304
+ }
273
305
  join(table, first, operatorOrSecond, second) {
274
306
  if (second === undefined) {
275
307
  this.knexBuilder.join(table, first, operatorOrSecond);
@@ -646,6 +678,7 @@ export class ModelQueryBuilder {
646
678
  data,
647
679
  total,
648
680
  page,
681
+ currentPage: page,
649
682
  perPage,
650
683
  lastPage,
651
684
  };
@@ -654,6 +687,7 @@ export class ModelQueryBuilder {
654
687
  * Bulk updates matching records.
655
688
  */
656
689
  async update(attributes) {
690
+ this.applyGlobalScopes();
657
691
  const converted = {};
658
692
  const casts = this.modelClass?.casts || {};
659
693
  const knex = getKnex();
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class BelongsToMany<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  pivotTable: string;
6
6
  foreignPivotKey: string;
@@ -1,5 +1,5 @@
1
- import { getKnex } from "../connection";
2
- import { Relation } from "./relation";
1
+ import { getKnex } from "../connection.js";
2
+ import { Relation } from "./relation.js";
3
3
  export class BelongsToMany extends Relation {
4
4
  pivotTable;
5
5
  foreignPivotKey;
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class BelongsTo<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  query(): ModelQueryBuilder<Related>;
6
6
  get(): Promise<Related | null>;
@@ -1,4 +1,4 @@
1
- import { Relation } from "./relation";
1
+ import { Relation } from "./relation.js";
2
2
  export class BelongsTo extends Relation {
3
3
  query() {
4
4
  const foreignKeyValue = this.parent.getAttribute(this.foreignKey);
@@ -1,4 +1,4 @@
1
- import type { Model } from "../model";
1
+ import type { Model } from "../model.js";
2
2
  /**
3
3
  * Loads relations onto an array of model instances in bulk queries, avoiding N+1.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { DatabaseError } from "../errors";
1
+ import { DatabaseError } from "../errors.js";
2
2
  /**
3
3
  * Loads relations onto an array of model instances in bulk queries, avoiding N+1.
4
4
  */
@@ -11,6 +11,8 @@ export async function eagerLoadRelations(models, relations) {
11
11
  for (const rel of relations) {
12
12
  const parts = rel.split(".");
13
13
  const head = parts[0];
14
+ if (!head)
15
+ continue;
14
16
  const tail = parts.slice(1).join(".");
15
17
  if (!tree[head]) {
16
18
  tree[head] = [];
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class HasMany<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  query(): ModelQueryBuilder<Related>;
6
6
  get(): Promise<Related[]>;
@@ -1,4 +1,4 @@
1
- import { Relation } from "./relation";
1
+ import { Relation } from "./relation.js";
2
2
  export class HasMany extends Relation {
3
3
  query() {
4
4
  const localKeyValue = this.parent.getAttribute(this.localKey);
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class HasOne<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  query(): ModelQueryBuilder<Related>;
6
6
  get(): Promise<Related | null>;
@@ -1,4 +1,4 @@
1
- import { Relation } from "./relation";
1
+ import { Relation } from "./relation.js";
2
2
  export class HasOne extends Relation {
3
3
  query() {
4
4
  const localKeyValue = this.parent.getAttribute(this.localKey);
@@ -1,11 +1,11 @@
1
- export * from "./belongs-to";
2
- export * from "./belongs-to-many";
3
- export * from "./eager-loader";
4
- export * from "./has-many";
5
- export * from "./has-one";
6
- export * from "./morph-many";
7
- export * from "./morph-map";
8
- export * from "./morph-one";
9
- export * from "./morph-to";
10
- export * from "./morph-to-many";
11
- export * from "./relation";
1
+ export * from "./belongs-to.js";
2
+ export * from "./belongs-to-many.js";
3
+ export * from "./eager-loader.js";
4
+ export * from "./has-many.js";
5
+ export * from "./has-one.js";
6
+ export * from "./morph-many.js";
7
+ export * from "./morph-map.js";
8
+ export * from "./morph-one.js";
9
+ export * from "./morph-to.js";
10
+ export * from "./morph-to-many.js";
11
+ export * from "./relation.js";
@@ -1,11 +1,11 @@
1
- export * from "./belongs-to";
2
- export * from "./belongs-to-many";
3
- export * from "./eager-loader";
4
- export * from "./has-many";
5
- export * from "./has-one";
6
- export * from "./morph-many";
7
- export * from "./morph-map";
8
- export * from "./morph-one";
9
- export * from "./morph-to";
10
- export * from "./morph-to-many";
11
- export * from "./relation";
1
+ export * from "./belongs-to.js";
2
+ export * from "./belongs-to-many.js";
3
+ export * from "./eager-loader.js";
4
+ export * from "./has-many.js";
5
+ export * from "./has-one.js";
6
+ export * from "./morph-many.js";
7
+ export * from "./morph-map.js";
8
+ export * from "./morph-one.js";
9
+ export * from "./morph-to.js";
10
+ export * from "./morph-to-many.js";
11
+ export * from "./relation.js";
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class MorphMany<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  morphName: string;
6
6
  typeColumn: string;
@@ -1,5 +1,5 @@
1
- import { MorphMap } from "./morph-map";
2
- import { Relation } from "./relation";
1
+ import { MorphMap } from "./morph-map.js";
2
+ import { Relation } from "./relation.js";
3
3
  export class MorphMany extends Relation {
4
4
  morphName;
5
5
  typeColumn;
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class MorphOne<Parent extends Model = any, Related extends Model = any> extends Relation<Parent, Related> {
5
5
  morphName: string;
6
6
  typeColumn: string;
@@ -1,5 +1,5 @@
1
- import { MorphMap } from "./morph-map";
2
- import { Relation } from "./relation";
1
+ import { MorphMap } from "./morph-map.js";
2
+ import { Relation } from "./relation.js";
3
3
  export class MorphOne extends Relation {
4
4
  morphName;
5
5
  typeColumn;
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  /**
5
5
  * MorphToMany represents a many-to-many polymorphic relation where the parent
6
6
  * owns the polymorphic side of the pivot table (e.g. BlogPost -> tags via taggables).
@@ -1,6 +1,6 @@
1
- import { getKnex } from "../connection";
2
- import { MorphMap } from "./morph-map";
3
- import { Relation } from "./relation";
1
+ import { getKnex } from "../connection.js";
2
+ import { MorphMap } from "./morph-map.js";
3
+ import { Relation } from "./relation.js";
4
4
  /**
5
5
  * MorphToMany represents a many-to-many polymorphic relation where the parent
6
6
  * owns the polymorphic side of the pivot table (e.g. BlogPost -> tags via taggables).
@@ -1,6 +1,6 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
3
- import { Relation } from "./relation";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
+ import { Relation } from "./relation.js";
4
4
  export declare class MorphTo<Child extends Model = any, Parent extends Model = any> extends Relation<Child, Parent> {
5
5
  morphName: string;
6
6
  typeColumn: string;
@@ -1,6 +1,6 @@
1
- import { DatabaseError } from "../errors";
2
- import { MorphMap } from "./morph-map";
3
- import { Relation } from "./relation";
1
+ import { DatabaseError } from "../errors.js";
2
+ import { MorphMap } from "./morph-map.js";
3
+ import { Relation } from "./relation.js";
4
4
  export class MorphTo extends Relation {
5
5
  morphName;
6
6
  typeColumn;
@@ -1,5 +1,5 @@
1
- import type { Model } from "../model";
2
- import type { ModelQueryBuilder } from "../query-builder";
1
+ import type { Model } from "../model.js";
2
+ import type { ModelQueryBuilder } from "../query-builder.js";
3
3
  export declare abstract class Relation<Parent extends Model = any, Related extends Model = any> {
4
4
  parent: Parent;
5
5
  related: any;
package/dist/scopes.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Model } from "./model";
2
- import type { ModelQueryBuilder } from "./query-builder";
1
+ import type { Model } from "./model.js";
2
+ import type { ModelQueryBuilder } from "./query-builder.js";
3
3
  /**
4
4
  * Callback function for applying a global scope.
5
5
  */
package/dist/seeder.js CHANGED
@@ -1,5 +1,5 @@
1
- import { transaction } from "./connection";
2
- import { debug, info, warn } from "./logger";
1
+ import { transaction } from "./connection.js";
2
+ import { debug, info, warn } from "./logger.js";
3
3
  /**
4
4
  * Base Database Seeder class.
5
5
  */
@@ -0,0 +1,10 @@
1
+ import type { Token } from "@veap/kernel";
2
+ import type { Knex } from "knex";
3
+ /**
4
+ * IoC Token for primary Knex database connection instance.
5
+ */
6
+ export declare const DATABASE: Token<Knex>;
7
+ /**
8
+ * IoC Token for application database migrations array.
9
+ */
10
+ export declare const APP_MIGRATIONS: Token<any[]>;
package/dist/tokens.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * IoC Token for primary Knex database connection instance.
3
+ */
4
+ export const DATABASE = Symbol.for("veap:kernel:database");
5
+ /**
6
+ * IoC Token for application database migrations array.
7
+ */
8
+ export const APP_MIGRATIONS = Symbol.for("veap:kernel:app-migrations");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veap/database",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Eloquent-like Active Record ORM powered by Knex with AsyncLocalStorage transactions",
5
5
  "bugs": "https://github.com/veapjs/veap-database/issues",
6
6
  "repository": {
@@ -16,6 +16,14 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "default": "./dist/index.js"
18
18
  },
19
+ "./migrations": {
20
+ "types": "./dist/migrations/index.d.ts",
21
+ "default": "./dist/migrations/index.js"
22
+ },
23
+ "./relations": {
24
+ "types": "./dist/relations/index.d.ts",
25
+ "default": "./dist/relations/index.js"
26
+ },
19
27
  "./package.json": "./package.json"
20
28
  },
21
29
  "files": [
@@ -28,7 +36,7 @@
28
36
  "test": "vitest run"
29
37
  },
30
38
  "dependencies": {
31
- "@veap/kernel": "^0.1.0",
39
+ "@veap/kernel": "^0.1.1",
32
40
  "better-sqlite3": "^13.0.3",
33
41
  "knex": "^3.3.0"
34
42
  },