@veap/database 0.0.0-stage → 0.1.0

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -2
  3. package/dist/casts.d.ts +9 -0
  4. package/dist/casts.js +78 -0
  5. package/dist/connection.d.ts +49 -0
  6. package/dist/connection.js +189 -0
  7. package/dist/errors.d.ts +7 -0
  8. package/dist/errors.js +17 -0
  9. package/dist/events.d.ts +5 -0
  10. package/dist/events.js +7 -0
  11. package/dist/factory.d.ts +68 -0
  12. package/dist/factory.js +161 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.js +17 -0
  15. package/dist/logger.d.ts +11 -0
  16. package/dist/logger.js +21 -0
  17. package/dist/migration-provider.d.ts +5 -0
  18. package/dist/migration-provider.js +15 -0
  19. package/dist/migrations/generator.d.ts +4 -0
  20. package/dist/migrations/generator.js +58 -0
  21. package/dist/migrations/index.d.ts +3 -0
  22. package/dist/migrations/index.js +3 -0
  23. package/dist/migrations/runner.d.ts +12 -0
  24. package/dist/migrations/runner.js +178 -0
  25. package/dist/migrations/schema-builder.d.ts +84 -0
  26. package/dist/migrations/schema-builder.js +409 -0
  27. package/dist/model.d.ts +379 -0
  28. package/dist/model.js +899 -0
  29. package/dist/provider.d.ts +12 -0
  30. package/dist/provider.js +58 -0
  31. package/dist/query-builder.d.ts +197 -0
  32. package/dist/query-builder.js +715 -0
  33. package/dist/relations/belongs-to-many.d.ts +15 -0
  34. package/dist/relations/belongs-to-many.js +80 -0
  35. package/dist/relations/belongs-to.d.ts +8 -0
  36. package/dist/relations/belongs-to.js +37 -0
  37. package/dist/relations/eager-loader.d.ts +5 -0
  38. package/dist/relations/eager-loader.js +66 -0
  39. package/dist/relations/has-many.d.ts +9 -0
  40. package/dist/relations/has-many.js +48 -0
  41. package/dist/relations/has-one.d.ts +8 -0
  42. package/dist/relations/has-one.js +37 -0
  43. package/dist/relations/index.d.ts +11 -0
  44. package/dist/relations/index.js +11 -0
  45. package/dist/relations/morph-many.d.ts +14 -0
  46. package/dist/relations/morph-many.js +65 -0
  47. package/dist/relations/morph-map.d.ts +32 -0
  48. package/dist/relations/morph-map.js +73 -0
  49. package/dist/relations/morph-one.d.ts +14 -0
  50. package/dist/relations/morph-one.js +65 -0
  51. package/dist/relations/morph-to-many.d.ts +44 -0
  52. package/dist/relations/morph-to-many.js +215 -0
  53. package/dist/relations/morph-to.d.ts +19 -0
  54. package/dist/relations/morph-to.js +90 -0
  55. package/dist/relations/relation.d.ts +22 -0
  56. package/dist/relations/relation.js +13 -0
  57. package/dist/scopes.d.ts +16 -0
  58. package/dist/scopes.js +1 -0
  59. package/dist/seeder.d.ts +25 -0
  60. package/dist/seeder.js +49 -0
  61. package/dist/types.d.ts +31 -0
  62. package/dist/types.js +1 -0
  63. package/dist/utils.d.ts +9 -0
  64. package/dist/utils.js +20 -0
  65. package/package.json +50 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Damian Miller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,177 @@
1
- # Temporary Holding Version
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)
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ # @veap/database
5
+
6
+ Eloquent-inspired Active Record ORM powered by Knex with AsyncLocalStorage
7
+ transaction scoping and automatic migrations for PostgreSQL, MySQL, and SQLite.
8
+
9
+ ## Overview
10
+
11
+ `@veap/database` brings expressive, developer-friendly Active Record modeling to
12
+ TypeScript. It combines the ergonomic query building of Laravel Eloquent with
13
+ the battle-tested reliability of Knex. With built-in `AsyncLocalStorage`
14
+ context propagation, database transactions flow seamlessly across function and
15
+ module boundaries without manual transaction object passing.
16
+
17
+ ## Features
18
+
19
+ - **Active Record models**: Clean declarative models with timestamps, UUID
20
+ primary keys, attribute casting (JSON, dates, booleans), and local scopes.
21
+ - **AsyncLocalStorage transactions**: Zero-boilerplate `transaction()` blocks
22
+ that bind nested queries automatically to the current transaction.
23
+ - **Expressive relations**: Full relation graph support including `belongsTo`,
24
+ `hasMany`, `hasOne`, `belongsToMany`, and polymorphic variants (`morphTo`,
25
+ `morphMany`, `morphOne`, `morphToMany`).
26
+ - **Eager loading & query builder**: Fluid chainable queries with `.with()`,
27
+ `.where()`, `.orderBy()`, `.paginate()`, and `.first()`.
28
+ - **Knex proxy & raw queries**: Direct access to Knex via `getKnex()` and the
29
+ convenient dynamic proxy `dbClient`.
30
+ - **Migration & Schema runner**: Declarative `Schema` builder and automated
31
+ migration runner supporting versioned schema updates.
32
+ - **IoC integration**: Ships with `DatabaseServiceProvider` and
33
+ `MigrationServiceProvider` for modular Veap applications.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ # Using bun
39
+ bun add @veap/database better-sqlite3 # or pg / mysql2
40
+
41
+ # Using pnpm
42
+ pnpm add @veap/database better-sqlite3
43
+
44
+ # Using npm
45
+ npm install @veap/database better-sqlite3
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Define Active Record Models
51
+
52
+ Create models extending the base `Model` class:
53
+
54
+ ```ts
55
+ import { Model, belongsTo, hasMany } from "@veap/database";
56
+ import type { BelongsTo, HasMany } from "@veap/database";
57
+
58
+ export class Author extends Model {
59
+ static override tableName = "authors";
60
+
61
+ declare id: string;
62
+ declare name: string;
63
+ declare email: string;
64
+
65
+ public posts(): HasMany<Post> {
66
+ return hasMany(this, Post, "author_id");
67
+ }
68
+ }
69
+
70
+ export class Post extends Model {
71
+ static override tableName = "posts";
72
+
73
+ declare id: string;
74
+ declare title: string;
75
+ declare content: string;
76
+ declare author_id: string;
77
+
78
+ public author(): BelongsTo<Author> {
79
+ return belongsTo(this, Author, "author_id");
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Querying and CRUD Operations
85
+
86
+ Perform common queries and persistence operations:
87
+
88
+ ```ts
89
+ // Create
90
+ const author = await Author.create({
91
+ name: "Ada Lovelace",
92
+ email: "ada@example.com",
93
+ });
94
+
95
+ // Find and update
96
+ const post = await Post.query().where("slug", "first-post").first();
97
+ if (post) {
98
+ post.title = "Updated Title";
99
+ await post.save();
100
+ }
101
+
102
+ // Eager loading relations
103
+ const postsWithAuthors = await Post.query()
104
+ .with("author")
105
+ .where("status", "published")
106
+ .orderBy("created_at", "desc")
107
+ .get();
108
+ ```
109
+
110
+ ### Seamless Transactions with AsyncLocalStorage
111
+
112
+ Run operations inside a transaction without passing around connection objects:
113
+
114
+ ```ts
115
+ import { transaction } from "@veap/database";
116
+
117
+ await transaction(async () => {
118
+ const author = await Author.create({
119
+ name: "Grace Hopper",
120
+ email: "grace@example.com",
121
+ });
122
+
123
+ await Post.create({
124
+ title: "Compilers and Nanoseconds",
125
+ content: "...",
126
+ author_id: author.id,
127
+ });
128
+ // If any error is thrown, the entire block rolls back automatically.
129
+ });
130
+ ```
131
+
132
+ ### Standalone Database Initialization
133
+
134
+ When using outside the Veap application builder:
135
+
136
+ ```ts
137
+ import { initDatabase } from "@veap/database";
138
+
139
+ initDatabase({
140
+ client: "better-sqlite3",
141
+ connection: {
142
+ filename: "./data/app.sqlite",
143
+ },
144
+ useNullAsDefault: true,
145
+ });
146
+ ```
147
+
148
+ ### IoC Service Provider Setup
149
+
150
+ When composing inside a Veap application:
151
+
152
+ ```ts
153
+ import { Application } from "@veap/kernel";
154
+ import { DatabaseServiceProvider } from "@veap/database";
155
+
156
+ export const app = Application.configure()
157
+ .use(DatabaseServiceProvider)
158
+ .create();
159
+ ```
160
+
161
+ ## Subpath Exports
162
+
163
+ | Export Path | Content |
164
+ | ---------------- | ----------------------------------------------------------------------- |
165
+ | `@veap/database` | Core ORM, Model, relations, transactions, schema builder, and providers |
166
+
167
+ ## Documentation
168
+
169
+ For full guides and relational modeling patterns, visit:
170
+
171
+ - **Official Website**: [https://veap.pl](https://veap.pl)
172
+ - **Documentation**: [https://veap.pl/docs](https://veap.pl/docs)
173
+ - **GitHub Repository**: [https://github.com/veapjs/veap-database](https://github.com/veapjs/veap-database)
174
+
175
+ ## License
176
+
177
+ MIT
@@ -0,0 +1,9 @@
1
+ export type CastType = "string" | "number" | "boolean" | "json" | "datetime" | "date" | (string & {});
2
+ /**
3
+ * Casts raw database value to application/model representation.
4
+ */
5
+ export declare function castAttribute(value: any, type: CastType): any;
6
+ /**
7
+ * Prepares model attribute value for database storage based on dialect.
8
+ */
9
+ export declare function serializeForStorage(value: any, type: CastType | undefined, clientType?: string): any;
package/dist/casts.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Casts raw database value to application/model representation.
3
+ */
4
+ export function castAttribute(value, type) {
5
+ if (value === null || value === undefined) {
6
+ return value;
7
+ }
8
+ switch (type) {
9
+ case "string":
10
+ return String(value);
11
+ case "number": {
12
+ const num = Number(value);
13
+ return Number.isNaN(num) ? value : num;
14
+ }
15
+ case "boolean":
16
+ return value === 1 || value === "1" || value === true || value === "true";
17
+ case "json":
18
+ if (typeof value === "string") {
19
+ try {
20
+ return JSON.parse(value);
21
+ }
22
+ catch {
23
+ return value;
24
+ }
25
+ }
26
+ return value;
27
+ case "datetime":
28
+ case "date":
29
+ if (value instanceof Date) {
30
+ return value;
31
+ }
32
+ return new Date(value);
33
+ default:
34
+ return value;
35
+ }
36
+ }
37
+ /**
38
+ * Prepares model attribute value for database storage based on dialect.
39
+ */
40
+ export function serializeForStorage(value, type, clientType) {
41
+ if (value === null || value === undefined) {
42
+ return null;
43
+ }
44
+ if (!type) {
45
+ if (value instanceof Date) {
46
+ if (clientType?.includes("sqlite")) {
47
+ return value.toISOString();
48
+ }
49
+ return value;
50
+ }
51
+ if (typeof value === "object" && !(value instanceof Date)) {
52
+ if (clientType?.includes("sqlite")) {
53
+ return JSON.stringify(value);
54
+ }
55
+ }
56
+ return value;
57
+ }
58
+ switch (type) {
59
+ case "json":
60
+ return JSON.stringify(value);
61
+ case "boolean":
62
+ if (clientType?.includes("sqlite")) {
63
+ return value ? 1 : 0;
64
+ }
65
+ return Boolean(value);
66
+ case "datetime":
67
+ case "date":
68
+ if (value instanceof Date) {
69
+ if (clientType?.includes("sqlite")) {
70
+ return value.toISOString();
71
+ }
72
+ return value;
73
+ }
74
+ return value;
75
+ default:
76
+ return value;
77
+ }
78
+ }
@@ -0,0 +1,49 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { type Knex } from "knex";
3
+ import type { DatabaseConfig, PostgresSslConfig, PostgresSslOptions, TransactionOptions } from "./types";
4
+ /**
5
+ * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
6
+ */
7
+ export declare const transactionStorage: AsyncLocalStorage<Knex.Transaction>;
8
+ /**
9
+ * Returns the currently active transaction if inside a transaction() callback.
10
+ */
11
+ export declare function getActiveTransaction(): Knex.Transaction | undefined;
12
+ /**
13
+ * Injects or overrides the global Knex database instance.
14
+ */
15
+ export declare function setKnex(instance: Knex): Knex;
16
+ export declare function isSqliteDatabase(url?: string): boolean;
17
+ /**
18
+ * Resolves PostgreSQL TLS/SSL configuration based on environment, connection string parameters,
19
+ * and security settings.
20
+ */
21
+ export declare function resolvePostgresSslConfig(options: PostgresSslOptions): PostgresSslConfig;
22
+ /**
23
+ * Resolves SQLite database filename, automatically redirecting to /tmp on serverless (Vercel/Lambda)
24
+ * where the root deployment filesystem is read-only.
25
+ */
26
+ export declare function resolveSqliteFilename(filename: string): string;
27
+ /**
28
+ * Initializes a new Knex instance from a configuration object.
29
+ */
30
+ export declare function initDatabase(config: DatabaseConfig): Knex;
31
+ /**
32
+ * Returns the active Knex database instance or active transaction if inside a transaction context.
33
+ */
34
+ /**
35
+ * Checks whether the global Knex database instance is currently initialized.
36
+ */
37
+ export declare function isDatabaseInitialized(): boolean;
38
+ export declare function getKnex(): Knex;
39
+ /**
40
+ * Dynamic proxy for knex queries.
41
+ * Can be called as a function: dbClient('users').where(...)
42
+ * or accessed via properties: dbClient.schema, dbClient.raw, dbClient.transaction
43
+ */
44
+ export declare const dbClient: Knex;
45
+ /**
46
+ * Executes a callback within a database transaction managed via AsyncLocalStorage.
47
+ * Automatically commits if callback succeeds, and rolls back if an error is thrown.
48
+ */
49
+ export declare function transaction<T>(callback: (trx: Knex.Transaction) => Promise<T>, options?: TransactionOptions): Promise<T>;
@@ -0,0 +1,189 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import knex from "knex";
5
+ import { DatabaseError } from "./errors";
6
+ import { debug, warn } from "./logger";
7
+ const globalForDb = globalThis;
8
+ /**
9
+ * AsyncLocalStorage holding the currently active Knex transaction for the asynchronous execution context.
10
+ */
11
+ export const transactionStorage = globalForDb.__VEAP_TRANSACTION_STORAGE__ ||
12
+ (globalForDb.__VEAP_TRANSACTION_STORAGE__ =
13
+ new AsyncLocalStorage());
14
+ /**
15
+ * Returns the currently active transaction if inside a transaction() callback.
16
+ */
17
+ export function getActiveTransaction() {
18
+ return transactionStorage.getStore();
19
+ }
20
+ /**
21
+ * Injects or overrides the global Knex database instance.
22
+ */
23
+ export function setKnex(instance) {
24
+ globalForDb.__VEAP_KNEX__ = instance;
25
+ debug("veap:database", "Knex database instance registered.");
26
+ return instance;
27
+ }
28
+ export function isSqliteDatabase(url) {
29
+ if (!url)
30
+ return false;
31
+ return (url.startsWith("sqlite:") ||
32
+ url.startsWith("file:") ||
33
+ url.endsWith(".sqlite") ||
34
+ url.endsWith(".db"));
35
+ }
36
+ /**
37
+ * Resolves PostgreSQL TLS/SSL configuration based on environment, connection string parameters,
38
+ * and security settings.
39
+ */
40
+ export function resolvePostgresSslConfig(options) {
41
+ const { databaseUrl, isProd, rejectUnauthorizedEnv, caCert } = options;
42
+ if (databaseUrl.includes("sslmode=disable")) {
43
+ return false;
44
+ }
45
+ const isSslRequested = isProd ||
46
+ databaseUrl.includes("sslmode=require") ||
47
+ databaseUrl.includes("ssl=true") ||
48
+ databaseUrl.includes("sslmode=no-verify");
49
+ if (!isSslRequested) {
50
+ return false;
51
+ }
52
+ let rejectUnauthorized = true;
53
+ if (rejectUnauthorizedEnv !== undefined) {
54
+ rejectUnauthorized =
55
+ rejectUnauthorizedEnv !== "false" && rejectUnauthorizedEnv !== "0";
56
+ }
57
+ else if (databaseUrl.includes("sslmode=no-verify") ||
58
+ databaseUrl.includes("rejectUnauthorized=false")) {
59
+ rejectUnauthorized = false;
60
+ }
61
+ const sslConfig = {
62
+ rejectUnauthorized,
63
+ };
64
+ if (caCert) {
65
+ sslConfig.ca = caCert;
66
+ }
67
+ return sslConfig;
68
+ }
69
+ /**
70
+ * Resolves SQLite database filename, automatically redirecting to /tmp on serverless (Vercel/Lambda)
71
+ * where the root deployment filesystem is read-only.
72
+ */
73
+ export function resolveSqliteFilename(filename) {
74
+ let clean = filename.replace(/^(?:sqlite|file):(\/\/)?/, "");
75
+ if (!clean)
76
+ clean = "veap.sqlite";
77
+ const isServerless = process.env.VERCEL === "1" ||
78
+ !!process.env.AWS_LAMBDA_FUNCTION_NAME ||
79
+ !!process.env.LAMBDA_TASK_ROOT;
80
+ if (isServerless &&
81
+ !clean.startsWith("/tmp") &&
82
+ !clean.startsWith(":memory:")) {
83
+ const base = clean.split(/[\/\\]/).pop() || "dev.sqlite";
84
+ const redirected = `/tmp/${base}`;
85
+ warn("veap:database", `Running SQLite on Vercel/Serverless environment where filesystem (/var/task) is read-only. ` +
86
+ `Redirecting SQLite database file from "${clean}" to writable temporary path "${redirected}". ` +
87
+ `WARNING: SQLite storage on serverless functions is ephemeral and wiped across function cold starts. ` +
88
+ `For persistent production deployments on Vercel, set a PostgreSQL DATABASE_URL in Vercel Environment Variables.`);
89
+ return redirected;
90
+ }
91
+ if (!clean.startsWith(":memory:")) {
92
+ try {
93
+ fs.mkdirSync(path.dirname(path.resolve(clean)), {
94
+ recursive: true,
95
+ });
96
+ }
97
+ catch {
98
+ // Read-only filesystem etc. Let the driver surface its own error.
99
+ }
100
+ }
101
+ return clean;
102
+ }
103
+ /**
104
+ * Initializes a new Knex instance from a configuration object.
105
+ */
106
+ export function initDatabase(config) {
107
+ let client = config.client;
108
+ if (!client && config.driver) {
109
+ if (config.driver === "postgres")
110
+ client = "pg";
111
+ else if (config.driver === "sqlite")
112
+ client = "better-sqlite3";
113
+ else if (config.driver === "mysql")
114
+ client = "mysql2";
115
+ }
116
+ if ((client === "better-sqlite3" || config.driver === "sqlite") &&
117
+ typeof config.connection === "object" &&
118
+ config.connection !== null) {
119
+ const conn = config.connection;
120
+ if (conn.filename) {
121
+ conn.filename = resolveSqliteFilename(conn.filename);
122
+ }
123
+ }
124
+ const finalConfig = {
125
+ ...config,
126
+ client: client || "pg",
127
+ useNullAsDefault: config.useNullAsDefault ?? true,
128
+ };
129
+ const instance = knex(finalConfig);
130
+ return setKnex(instance);
131
+ }
132
+ /**
133
+ * Returns the active Knex database instance or active transaction if inside a transaction context.
134
+ */
135
+ /**
136
+ * Checks whether the global Knex database instance is currently initialized.
137
+ */
138
+ export function isDatabaseInitialized() {
139
+ return Boolean(getActiveTransaction() || globalForDb.__VEAP_KNEX__);
140
+ }
141
+ export function getKnex() {
142
+ const activeTrx = getActiveTransaction();
143
+ if (activeTrx) {
144
+ return activeTrx;
145
+ }
146
+ if (globalForDb.__VEAP_KNEX__) {
147
+ return globalForDb.__VEAP_KNEX__;
148
+ }
149
+ throw DatabaseError.Internal("[veap:ORM] Database not initialized. Please call initDatabase() or register DatabaseServiceProvider.");
150
+ }
151
+ /**
152
+ * Dynamic proxy for knex queries.
153
+ * Can be called as a function: dbClient('users').where(...)
154
+ * or accessed via properties: dbClient.schema, dbClient.raw, dbClient.transaction
155
+ */
156
+ export const dbClient = new Proxy((() => { }), {
157
+ apply(_target, _thisArg, argArray) {
158
+ const k = getKnex();
159
+ return k(...argArray);
160
+ },
161
+ get(_target, prop) {
162
+ const k = getKnex();
163
+ const value = k[prop];
164
+ return typeof value === "function" ? value.bind(k) : value;
165
+ },
166
+ });
167
+ /**
168
+ * Executes a callback within a database transaction managed via AsyncLocalStorage.
169
+ * Automatically commits if callback succeeds, and rolls back if an error is thrown.
170
+ */
171
+ export async function transaction(callback, options) {
172
+ const currentTrx = getActiveTransaction();
173
+ if (currentTrx) {
174
+ if (options?.savepoint) {
175
+ return await currentTrx.transaction(async (spTrx) => {
176
+ return await transactionStorage.run(spTrx, async () => {
177
+ return await callback(spTrx);
178
+ });
179
+ });
180
+ }
181
+ return await callback(currentTrx);
182
+ }
183
+ const k = getKnex();
184
+ return await k.transaction(async (trx) => {
185
+ return await transactionStorage.run(trx, async () => {
186
+ return await callback(trx);
187
+ });
188
+ });
189
+ }
@@ -0,0 +1,7 @@
1
+ export declare class DatabaseError extends Error {
2
+ readonly code: string;
3
+ constructor(message: string, code?: string);
4
+ static NotFound(message?: string): DatabaseError;
5
+ static Internal(message?: string): DatabaseError;
6
+ static QueryFailed(message: string): DatabaseError;
7
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,17 @@
1
+ export class DatabaseError extends Error {
2
+ code;
3
+ constructor(message, code = "DATABASE_ERROR") {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "DatabaseError";
7
+ }
8
+ static NotFound(message = "Record not found") {
9
+ return new DatabaseError(message, "NOT_FOUND");
10
+ }
11
+ static Internal(message = "Internal database error") {
12
+ return new DatabaseError(message, "INTERNAL_ERROR");
13
+ }
14
+ static QueryFailed(message) {
15
+ return new DatabaseError(message, "QUERY_FAILED");
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ export interface DatabaseEventDispatcher {
2
+ publish(event: string, payload: any): Promise<void> | void;
3
+ }
4
+ export declare function setDatabaseEventDispatcher(dispatcher: DatabaseEventDispatcher | null): void;
5
+ export declare function getDatabaseEventDispatcher(): DatabaseEventDispatcher | null;
package/dist/events.js ADDED
@@ -0,0 +1,7 @@
1
+ let activeDispatcher = null;
2
+ export function setDatabaseEventDispatcher(dispatcher) {
3
+ activeDispatcher = dispatcher;
4
+ }
5
+ export function getDatabaseEventDispatcher() {
6
+ return activeDispatcher;
7
+ }
@@ -0,0 +1,68 @@
1
+ import type { Model } from "./model";
2
+ export declare abstract class Factory<M extends Model = any> {
3
+ private static get registry();
4
+ /**
5
+ * Registers a factory class/instance for a model.
6
+ */
7
+ static register(modelClass: any, factoryOrFactoryFn: any): void;
8
+ /**
9
+ * Gets the registered factory for a model if one exists.
10
+ */
11
+ static getForModel(modelClass: any): any;
12
+ protected modelClass: any;
13
+ protected countNumber: number;
14
+ protected states: Array<Record<string, any> | ((attributes: Record<string, any>) => Record<string, any>)>;
15
+ protected sequenceItems: Array<Record<string, any>>;
16
+ constructor(modelClass?: any);
17
+ /**
18
+ * Defines the default model attributes.
19
+ */
20
+ abstract definition(): Record<string, any> | Promise<Record<string, any>>;
21
+ /**
22
+ * Sets the number of models that should be generated.
23
+ */
24
+ count(amount: number): this;
25
+ /**
26
+ * Applies an attribute state transformation to the factory.
27
+ */
28
+ state(override: Record<string, any> | ((attributes: Record<string, any>) => Record<string, any>)): this;
29
+ /**
30
+ * Applies sequential states rotating through the given items.
31
+ */
32
+ sequence(...items: Record<string, any>[]): this;
33
+ /**
34
+ * Generates the raw attributes dictionary for a single model instance.
35
+ */
36
+ raw(override?: Record<string, any>, index?: number): Promise<Record<string, any>>;
37
+ /**
38
+ * Creates a single model instance in memory without saving to database.
39
+ */
40
+ makeOne(override?: Record<string, any>, index?: number): Promise<M>;
41
+ /**
42
+ * Creates model instance(s) in memory without saving to database.
43
+ */
44
+ make(override?: Record<string, any>): Promise<M | M[]>;
45
+ /**
46
+ * Creates and persists a single model instance to the database.
47
+ */
48
+ createOne(override?: Record<string, any>, index?: number): Promise<M>;
49
+ /**
50
+ * Creates and persists model instance(s) to the database.
51
+ */
52
+ create(override?: Record<string, any>): Promise<M | M[]>;
53
+ }
54
+ export interface FactoryConstructor<M extends Model = any> {
55
+ new (): Factory<M>;
56
+ (count?: number): Factory<M>;
57
+ count(amount: number): Factory<M>;
58
+ state(override: Record<string, any> | ((attrs: any) => Record<string, any>)): Factory<M>;
59
+ sequence(...items: Record<string, any>[]): Factory<M>;
60
+ make(override?: Record<string, any>): Promise<M | M[]>;
61
+ makeOne(override?: Record<string, any>): Promise<M>;
62
+ create(override?: Record<string, any>): Promise<M | M[]>;
63
+ createOne(override?: Record<string, any>): Promise<M>;
64
+ }
65
+ /**
66
+ * Convenient helper to define a Model Factory in a single concise call.
67
+ */
68
+ export declare function defineFactory<M extends Model = any>(modelClass: new (...args: any[]) => M, definitionFn: () => Record<string, any> | Promise<Record<string, any>>): FactoryConstructor<M>;