alepha 0.9.4 → 0.10.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.
package/postgres.d.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  import * as _alepha_core1 from "alepha";
2
- import { Alepha, AlephaError, Descriptor, KIND, Service, Static, TObject, TSchema as TSchema$1 } from "alepha";
3
- import * as drizzle_orm7 from "drizzle-orm";
2
+ import { Alepha, AlephaError, Descriptor, KIND, Service, Static, TArray, TBigInt, TBoolean, TInteger, TKeysToIndexer, TNull, TNumber, TNumberOptions, TObject, TObjectOptions, TOptional, TOptionalAdd, TPick, TRecord, TSchema as TSchema$1, TString, TStringOptions, TUnion } from "alepha";
3
+ import * as drizzle_orm6 from "drizzle-orm";
4
4
  import { BuildColumns, BuildExtraConfigColumns, SQL, SQLWrapper, TableConfig, sql } from "drizzle-orm";
5
5
  import * as pg$1 from "drizzle-orm/pg-core";
6
6
  import { AnyPgColumn, LockConfig, LockStrength, PgColumn, PgColumnBuilderBase, PgDatabase, PgInsertValue, PgSequenceOptions, PgTableExtraConfigValue, PgTableWithColumns, PgTransaction, PgTransactionConfig, SelectedFields, TableConfig as TableConfig$1, UpdateDeleteAction } from "drizzle-orm/pg-core";
7
- import { DateTimeProvider } from "alepha/datetime";
7
+ import { DateTime, DateTimeProvider } from "alepha/datetime";
8
8
  import * as _alepha_logger1 from "alepha/logger";
9
9
  import * as _alepha_lock0 from "alepha/lock";
10
10
  import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
11
11
  import postgres from "postgres";
12
12
  import * as _alepha_retry0 from "alepha/retry";
13
- import * as _sinclair_typebox1 from "@sinclair/typebox";
14
- import { Evaluate, IntegerOptions, Kind, NumberOptions, ObjectOptions, OptionalKind, Static as Static$1, StringOptions, TAdditionalProperties, TArray, TBoolean, TInteger, TIntersect, TNumber, TObject as TObject$1, TOptional, TOptionalWithFlag, TPick, TProperties, TReadonly, TRecord, TSchema as TSchema$2, TString } from "@sinclair/typebox";
13
+ import * as typebox1 from "typebox";
15
14
  import { PgTransactionConfig as PgTransactionConfig$1 } from "drizzle-orm/pg-core/session";
16
15
  import * as DrizzleKit from "drizzle-kit/api";
17
16
  import { MigrationConfig } from "drizzle-orm/migrator";
@@ -65,28 +64,33 @@ interface PgRefOptions {
65
64
  };
66
65
  }
67
66
  //#endregion
68
- //#region src/interfaces/TInsertObject.d.ts
67
+ //#region src/schemas/insertSchema.d.ts
69
68
  /**
70
- * Fork of the original typebox schema "TObject".
69
+ * Transforms a TObject schema for insert operations.
70
+ * All default properties at the root level are made optional.
71
+ *
72
+ * @example
73
+ * Before: { name: string; age: number(default=0); }
74
+ * After: { name: string; age?: number; }
71
75
  */
72
- interface TInsertObject<T extends TObject$1> extends TSchema$2, ObjectOptions {
73
- [Kind]: "Object";
74
- static: ObjectStatic<{ [K in keyof T["properties"] as T["properties"][K] extends {
75
- [PG_DEFAULT]: any;
76
- } ? never : K]: T["properties"][K] }, this["params"]>;
77
- additionalProperties?: TAdditionalProperties;
78
- type: "object";
79
- required?: string[];
80
- properties: { [K in keyof T["properties"] as T["properties"][K] extends {
81
- [PG_DEFAULT]: any;
82
- } ? never : K]: T["properties"][K] };
83
- }
84
- type ReadonlyOptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonly<TSchema$2> ? T[K] extends TOptional<T[K]> ? K : never : never }[keyof T];
85
- type ReadonlyPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonly<TSchema$2> ? T[K] extends TOptional<T[K]> ? never : K : never }[keyof T];
86
- type OptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TOptional<TSchema$2> ? T[K] extends TReadonly<T[K]> ? never : K : never }[keyof T];
87
- type RequiredPropertyKeys<T extends TProperties> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>;
88
- type ObjectStaticProperties<T extends TProperties, R extends Record<keyof any, unknown>> = Evaluate<Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> & Readonly<Pick<R, ReadonlyPropertyKeys<T>>> & Partial<Pick<R, OptionalPropertyKeys<T>>> & Required<Pick<R, RequiredPropertyKeys<T>>>>;
89
- type ObjectStatic<T extends TProperties, P extends unknown[]> = ObjectStaticProperties<T, { [K in keyof T]: Static$1<T[K], P> }>;
76
+ type TObjectInsert<T extends TObject> = TObject<{ [K in keyof T["properties"]]: T["properties"][K] extends {
77
+ [PG_DEFAULT]: any;
78
+ } | {
79
+ "~optional": true;
80
+ } ? TOptional<T["properties"][K]> : T["properties"][K] }>;
81
+ declare const insertSchema: <T extends TObject>(obj: T) => TObjectInsert<T>;
82
+ //#endregion
83
+ //#region src/schemas/updateSchema.d.ts
84
+ /**
85
+ * Transforms a TObject schema for update operations.
86
+ * All optional properties at the root level are made nullable (i.e., `T | null`).
87
+ * This allows an API endpoint to explicitly accept `null` to clear an optional field in the database.
88
+ *
89
+ * @example
90
+ * Before: { name?: string; age: number; }
91
+ * After: { name?: string | null; age: number; }
92
+ */
93
+ type TObjectUpdate<T extends TObject> = TObject<{ [K in keyof T["properties"]]: T["properties"][K] extends TOptional<infer U> ? TOptional<TUnion<[U, TNull]>> : T["properties"][K] }>;
90
94
  //#endregion
91
95
  //#region src/helpers/schemaToPgColumns.d.ts
92
96
  /**
@@ -100,34 +104,29 @@ declare const schemaToPgColumns: <T extends TObject>(schema: T) => FromSchema<T>
100
104
  * @param value The value of the field.
101
105
  * @returns The PG column.
102
106
  */
103
- declare const mapFieldToColumn: (name: string, value: TSchema$1) => pg$1.PgSerialBuilderInitial<string> | pg$1.PgIntegerBuilderInitial<string> | pg$1.PgBigInt53BuilderInitial<string> | pg$1.PgNumericBuilderInitial<string> | pg$1.PgTimestampBuilderInitial<string> | pg$1.PgUUIDBuilderInitial<string> | pg$1.PgCustomColumnBuilder<{
107
+ declare const mapFieldToColumn: (name: string, value: TSchema$1) => pg$1.PgSerialBuilderInitial<string> | pg$1.PgIntegerBuilderInitial<string> | drizzle_orm6.IsIdentity<pg$1.PgBigInt64BuilderInitial<"">, "byDefault"> | drizzle_orm6.IsIdentity<pg$1.PgBigInt64BuilderInitial<"">, "always"> | pg$1.PgBigInt53BuilderInitial<string> | pg$1.PgNumericBuilderInitial<string> | pg$1.PgTimestampBuilderInitial<string> | pg$1.PgUUIDBuilderInitial<string> | pg$1.PgCustomColumnBuilder<{
104
108
  name: string;
105
109
  dataType: "custom";
106
110
  columnType: "PgCustomColumn";
107
111
  data: Buffer<ArrayBufferLike>;
108
112
  driverParam: unknown;
109
113
  enumValues: undefined;
110
- }> | pg$1.PgTimestampStringBuilderInitial<string> | pg$1.PgDateStringBuilderInitial<string> | pg$1.PgTextBuilderInitial<string, [string, ...string[]]> | pg$1.PgBooleanBuilderInitial<string> | drizzle_orm7.$Type<pg$1.PgCustomColumnBuilder<{
114
+ }> | pg$1.PgTimestampStringBuilderInitial<string> | pg$1.PgDateStringBuilderInitial<string> | pg$1.PgTextBuilderInitial<string, [string, ...string[]]> | pg$1.PgBooleanBuilderInitial<string> | drizzle_orm6.$Type<pg$1.PgCustomColumnBuilder<{
111
115
  name: string;
112
116
  dataType: "custom";
113
117
  columnType: "PgCustomColumn";
114
118
  data: {
115
119
  [x: string]: unknown;
116
120
  [x: number]: unknown;
121
+ [x: symbol]: unknown;
117
122
  };
118
123
  driverParam: string;
119
124
  enumValues: undefined;
120
125
  }>, {
121
126
  [x: string]: unknown;
122
127
  [x: number]: unknown;
123
- }> | drizzle_orm7.$Type<pg$1.PgCustomColumnBuilder<{
124
- name: string;
125
- dataType: "custom";
126
- columnType: "PgCustomColumn";
127
- data: {};
128
- driverParam: string;
129
- enumValues: undefined;
130
- }>, {}> | drizzle_orm7.$Type<pg$1.PgCustomColumnBuilder<{
128
+ [x: symbol]: unknown;
129
+ }> | drizzle_orm6.$Type<pg$1.PgCustomColumnBuilder<{
131
130
  name: string;
132
131
  dataType: "custom";
133
132
  columnType: "PgCustomColumn";
@@ -252,93 +251,490 @@ type FromSchema<T extends TObject> = { [key in keyof T["properties"]]: PgColumnB
252
251
  type PgTableWithColumnsAndSchema<T extends TableConfig, R extends TObject> = PgTableWithColumns<T> & {
253
252
  get $table(): PgTableWithColumns<T>;
254
253
  get $schema(): R;
255
- get $insertSchema(): TInsertObject<R>;
254
+ get $insertSchema(): TObjectInsert<R>;
255
+ get $updateSchema(): TObjectUpdate<R>;
256
256
  };
257
- interface TableLike<T extends TObject = TObject> {
258
- $schema: T;
259
- }
260
257
  //#endregion
261
258
  //#region src/descriptors/$entity.d.ts
262
259
  /**
263
- * Declare a new entity in the database.
264
- * This descriptor alone does not create the table, it only describes it.
265
- * It must be used with `$repository` to create the table and perform operations on it.
260
+ * Creates a database entity descriptor that defines table structure using TypeBox schemas.
261
+ *
262
+ * This descriptor provides a type-safe way to define database tables using JSON Schema
263
+ * syntax while generating the necessary database metadata for migrations and operations.
264
+ * It integrates with Drizzle ORM under the hood and works seamlessly with the $repository
265
+ * descriptor for complete database functionality.
266
+ *
267
+ * **Key Features**
268
+ *
269
+ * - **Type-Safe Schema Definition**: Uses TypeBox for full TypeScript type inference
270
+ * - **Automatic Table Generation**: Creates Drizzle ORM table structures automatically
271
+ * - **Index Management**: Supports single-column, multi-column, and unique indexes
272
+ * - **Constraint Support**: Foreign keys, unique constraints, and check constraints
273
+ * - **Audit Fields**: Built-in support for created_at, updated_at, deleted_at, and version fields
274
+ * - **Schema Validation**: Automatic insert/update schema generation with validation
275
+ *
276
+ * **Important Note**:
277
+ * This descriptor only defines the table structure - it does not create the physical
278
+ * database table. Use it with $repository to perform actual database operations,
279
+ * and run migrations to create the tables in your database.
266
280
  *
267
- * This is a convenience function to create a table with a json schema.
268
- * For now, it creates a drizzle-orm table under the hood.
281
+ * **Use Cases**
282
+ *
283
+ * Essential for defining database schema in type-safe applications:
284
+ * - User management and authentication tables
285
+ * - Business domain entities (products, orders, customers)
286
+ * - Audit and logging tables
287
+ * - Junction tables for many-to-many relationships
288
+ * - Configuration and settings tables
289
+ *
290
+ * @example
291
+ * **Basic entity with indexes:**
269
292
  * ```ts
270
293
  * import { $entity } from "alepha/postgres";
294
+ * import { pg, t } from "alepha";
271
295
  *
272
296
  * const User = $entity({
273
- * name: "user",
297
+ * name: "users",
274
298
  * schema: t.object({
275
299
  * id: pg.primaryKey(t.uuid()),
276
- * name: t.string(),
277
- * email: t.string(),
300
+ * email: t.string({ format: "email" }),
301
+ * username: t.string({ minLength: 3, maxLength: 30 }),
302
+ * firstName: t.string(),
303
+ * lastName: t.string(),
304
+ * isActive: t.boolean({ default: true }),
305
+ * createdAt: pg.createdAt(),
306
+ * updatedAt: pg.updatedAt(),
307
+ * deletedAt: pg.deletedAt()
308
+ * }),
309
+ * indexes: [
310
+ * "email", // Simple index on email
311
+ * "username", // Simple index on username
312
+ * { column: "email", unique: true }, // Unique constraint on email
313
+ * { columns: ["firstName", "lastName"] } // Composite index
314
+ * ]
315
+ * });
316
+ * ```
317
+ *
318
+ * @example
319
+ * **E-commerce product entity with relationships:**
320
+ * ```ts
321
+ * const Product = $entity({
322
+ * name: "products",
323
+ * schema: t.object({
324
+ * id: pg.primaryKey(t.uuid()),
325
+ * sku: t.string({ minLength: 3 }),
326
+ * name: t.string({ minLength: 1, maxLength: 200 }),
327
+ * description: t.optional(t.string()),
328
+ * price: t.number({ minimum: 0 }),
329
+ * categoryId: t.string({ format: "uuid" }),
330
+ * inStock: t.boolean({ default: true }),
331
+ * stockQuantity: t.integer({ minimum: 0, default: 0 }),
332
+ * tags: t.optional(t.array(t.string())), // PostgreSQL array column
333
+ * metadata: t.optional(t.record(t.string(), t.any())), // JSONB column
334
+ * version: pg.version(),
335
+ * createdAt: pg.createdAt(),
336
+ * updatedAt: pg.updatedAt()
337
+ * }),
338
+ * indexes: [
339
+ * { column: "sku", unique: true }, // Unique SKU
340
+ * "categoryId", // Foreign key index
341
+ * "inStock", // Filter frequently by stock status
342
+ * { columns: ["categoryId", "inStock"] }, // Composite for category + stock queries
343
+ * "createdAt" // For date-based queries
344
+ * ],
345
+ * foreignKeys: [
346
+ * {
347
+ * name: "fk_product_category",
348
+ * columns: ["categoryId"],
349
+ * foreignColumns: [Category.id] // Reference to Category entity
350
+ * }
351
+ * ]
352
+ * });
353
+ * ```
354
+ *
355
+ * @example
356
+ * **Audit log entity with constraints:**
357
+ * ```ts
358
+ * const AuditLog = $entity({
359
+ * name: "audit_logs",
360
+ * schema: t.object({
361
+ * id: pg.primaryKey(t.uuid()),
362
+ * tableName: t.string(),
363
+ * recordId: t.string(),
364
+ * action: t.enum(["CREATE", "UPDATE", "DELETE"]),
365
+ * userId: t.optional(t.string({ format: "uuid" })),
366
+ * oldValues: t.optional(t.record(t.string(), t.any())),
367
+ * newValues: t.optional(t.record(t.string(), t.any())),
368
+ * timestamp: pg.createdAt(),
369
+ * ipAddress: t.optional(t.string()),
370
+ * userAgent: t.optional(t.string())
371
+ * }),
372
+ * indexes: [
373
+ * "tableName",
374
+ * "recordId",
375
+ * "userId",
376
+ * "action",
377
+ * { columns: ["tableName", "recordId"] }, // Find all changes to a record
378
+ * { columns: ["userId", "timestamp"] }, // User activity timeline
379
+ * "timestamp" // Time-based queries
380
+ * ],
381
+ * constraints: [
382
+ * {
383
+ * name: "valid_action_values",
384
+ * columns: ["action"],
385
+ * check: sql`action IN ('CREATE', 'UPDATE', 'DELETE')`
386
+ * }
387
+ * ]
388
+ * });
389
+ * ```
390
+ *
391
+ * @example
392
+ * **Many-to-many junction table:**
393
+ * ```ts
394
+ * const UserRole = $entity({
395
+ * name: "user_roles",
396
+ * schema: t.object({
397
+ * id: pg.primaryKey(t.uuid()),
398
+ * userId: t.string({ format: "uuid" }),
399
+ * roleId: t.string({ format: "uuid" }),
400
+ * assignedBy: t.string({ format: "uuid" }),
401
+ * assignedAt: pg.createdAt(),
402
+ * expiresAt: t.optional(t.datetime())
403
+ * }),
404
+ * indexes: [
405
+ * "userId",
406
+ * "roleId",
407
+ * "assignedBy",
408
+ * { columns: ["userId", "roleId"], unique: true }, // Prevent duplicate assignments
409
+ * "expiresAt" // For cleanup of expired roles
410
+ * ],
411
+ * foreignKeys: [
412
+ * {
413
+ * columns: ["userId"],
414
+ * foreignColumns: [User.id]
415
+ * },
416
+ * {
417
+ * columns: ["roleId"],
418
+ * foreignColumns: [Role.id]
419
+ * },
420
+ * {
421
+ * columns: ["assignedBy"],
422
+ * foreignColumns: [User.id]
423
+ * }
424
+ * ]
425
+ * });
426
+ * ```
427
+ *
428
+ * @example
429
+ * **Entity with custom Drizzle configuration:**
430
+ * ```ts
431
+ * const Order = $entity({
432
+ * name: "orders",
433
+ * schema: t.object({
434
+ * id: pg.primaryKey(t.uuid()),
435
+ * orderNumber: t.string(),
436
+ * customerId: t.string({ format: "uuid" }),
437
+ * status: t.enum(["pending", "processing", "shipped", "delivered"]),
438
+ * totalAmount: t.number({ minimum: 0 }),
439
+ * currency: t.string({ default: "USD" }),
440
+ * notes: t.optional(t.string()),
441
+ * createdAt: pg.createdAt(),
442
+ * updatedAt: pg.updatedAt(),
443
+ * version: pg.version()
278
444
  * }),
279
- * indexes: ["email"],
445
+ * indexes: [
446
+ * { column: "orderNumber", unique: true },
447
+ * "customerId",
448
+ * "status",
449
+ * "createdAt",
450
+ * { columns: ["customerId", "status"] }
451
+ * ],
452
+ * // Advanced Drizzle ORM configuration
453
+ * config: (table) => [
454
+ * // Custom index with specific options
455
+ * index("idx_orders_amount_status")
456
+ * .on(table.totalAmount, table.status)
457
+ * .where(sql`status != 'cancelled'`), // Partial index
458
+ *
459
+ * // Full-text search index (PostgreSQL specific)
460
+ * index("idx_orders_search")
461
+ * .using("gin", table.notes)
462
+ * ]
280
463
  * });
281
464
  * ```
282
465
  *
283
466
  * @stability 2
284
467
  */
285
468
  declare const $entity: {
286
- <TTableName extends string, TSchema extends TObject$1, TColumnsMap extends FromSchema<TSchema>>(options: EntityDescriptorOptions<TTableName, TSchema>): PgTableWithColumnsAndSchema<PgTableConfig<TTableName, TSchema, TColumnsMap>, TSchema>;
469
+ <TTableName extends string, TSchema extends TObject, TColumnsMap extends FromSchema<TSchema>>(options: EntityDescriptorOptions<TTableName, TSchema>): PgTableWithColumnsAndSchema<PgTableConfig<TTableName, TSchema, TColumnsMap>, TSchema>;
287
470
  [KIND]: string;
288
471
  };
289
- interface EntityDescriptorOptions<TTableName extends string, T extends TObject$1, Keys = keyof Static$1<T>> {
472
+ interface EntityDescriptorOptions<TTableName extends string, T extends TObject, Keys = keyof Static<T>> {
290
473
  /**
291
- * The name of the table. This is the name that will be used in the database.
292
- * @example
293
- * name: "user"
474
+ * The database table name that will be created for this entity.
475
+ *
476
+ * This name:
477
+ * - Must be unique within your database schema
478
+ * - Should follow your database naming conventions (typically snake_case)
479
+ * - Will be used in generated SQL queries and migrations
480
+ * - Should be descriptive of the entity's purpose
481
+ *
482
+ * **Naming Guidelines**:
483
+ * - Use plural nouns for table names ("users", "products", "orders")
484
+ * - Use snake_case for multi-word names ("user_profiles", "order_items")
485
+ * - Keep names concise but descriptive
486
+ * - Avoid SQL reserved words
487
+ *
488
+ * @example "users"
489
+ * @example "product_categories"
490
+ * @example "user_roles"
491
+ * @example "audit_logs"
294
492
  */
295
493
  name: TTableName;
296
494
  /**
297
- * The schema of the table. This is a TypeBox schema that describes the columns and their types.
495
+ * TypeBox schema defining the table structure and column types.
496
+ *
497
+ * This schema:
498
+ * - Defines all table columns with their types and constraints
499
+ * - Provides full TypeScript type inference for the entity
500
+ * - Supports validation rules and default values
501
+ * - Enables automatic insert/update schema generation
502
+ * - Must include exactly one primary key field marked with `pg.primaryKey()`
503
+ *
504
+ * **Supported PostgreSQL Types**:
505
+ * - `pg.primaryKey(t.uuid())` - UUID primary key
506
+ * - `t.string()` - VARCHAR column
507
+ * - `t.integer()`, `t.number()` - Numeric columns
508
+ * - `t.boolean()` - Boolean column
509
+ * - `t.array(t.string())` - PostgreSQL array column
510
+ * - `t.record(t.string(), t.any())` - JSONB column
511
+ * - `pg.createdAt()`, `pg.updatedAt()`, `pg.deletedAt()` - Audit timestamps
512
+ * - `pg.version()` - Optimistic locking version field
513
+ *
514
+ * **Schema Best Practices**:
515
+ * - Always include a primary key
516
+ * - Use appropriate TypeBox constraints (minLength, format, etc.)
517
+ * - Add audit fields for trackability
518
+ * - Use optional fields for nullable columns
519
+ * - Include foreign key columns for relationships
520
+ *
298
521
  * @example
299
- * schema: t.object({
300
- * id: t.uuid(),
301
- * name: t.string(),
302
- * email: t.string(),
303
- * phoneNumber: t.string(),
522
+ * ```ts
523
+ * t.object({
524
+ * id: pg.primaryKey(t.uuid()),
525
+ * email: t.string({ format: "email" }),
526
+ * firstName: t.string({ minLength: 1, maxLength: 100 }),
527
+ * lastName: t.string({ minLength: 1, maxLength: 100 }),
528
+ * age: t.optional(t.integer({ minimum: 0, maximum: 150 })),
529
+ * isActive: t.boolean({ default: true }),
530
+ * preferences: t.optional(t.record(t.string(), t.any())),
531
+ * tags: t.optional(t.array(t.string())),
532
+ * createdAt: pg.createdAt(),
533
+ * updatedAt: pg.updatedAt(),
534
+ * version: pg.version()
304
535
  * })
536
+ * ```
305
537
  */
306
538
  schema: T;
307
539
  /**
308
- * The indexes to create for the table. This can be a string or an object with the column name and options.
309
- * @example
310
- * indexes: ["name", { column: "email", unique: true }]
540
+ * Database indexes to create for query optimization.
541
+ *
542
+ * Indexes improve query performance but consume disk space and slow down writes.
543
+ * Choose indexes based on your actual query patterns and performance requirements.
544
+ *
545
+ * **Index Types**:
546
+ * - **Simple string**: Creates a single-column index
547
+ * - **Single column object**: Creates index on one column with options
548
+ * - **Multi-column object**: Creates composite index on multiple columns
549
+ *
550
+ * **Index Guidelines**:
551
+ * - Index frequently queried columns (WHERE, ORDER BY, JOIN conditions)
552
+ * - Create unique indexes for business constraints
553
+ * - Use composite indexes for multi-column queries
554
+ * - Index foreign key columns for join performance
555
+ * - Monitor index usage and remove unused indexes
556
+ *
557
+ * **Performance Considerations**:
558
+ * - Each index increases storage requirements
559
+ * - Indexes slow down INSERT/UPDATE/DELETE operations
560
+ * - PostgreSQL can use multiple indexes in complex queries
561
+ * - Partial indexes can be more efficient for filtered queries
562
+ *
563
+ * @example ["email", "createdAt", { column: "username", unique: true }]
564
+ * @example [{ columns: ["userId", "status"], name: "idx_user_status" }]
565
+ * @example ["categoryId", { columns: ["price", "inStock"] }]
311
566
  */
312
567
  indexes?: (Keys | {
568
+ /**
569
+ * Single column to index.
570
+ */
313
571
  column: Keys;
572
+ /**
573
+ * Whether this should be a unique index (enforces uniqueness constraint).
574
+ */
314
575
  unique?: boolean;
576
+ /**
577
+ * Custom name for the index. If not provided, generates name automatically.
578
+ */
315
579
  name?: string;
316
580
  } | {
581
+ /**
582
+ * Multiple columns for composite index (order matters for query optimization).
583
+ */
317
584
  columns: Keys[];
585
+ /**
586
+ * Whether this should be a unique index (enforces uniqueness constraint).
587
+ */
318
588
  unique?: boolean;
589
+ /**
590
+ * Custom name for the index. If not provided, generates name automatically.
591
+ */
319
592
  name?: string;
320
593
  })[];
594
+ /**
595
+ * Foreign key constraints to maintain referential integrity.
596
+ *
597
+ * Foreign keys ensure that values in specified columns must exist in the referenced table.
598
+ * They prevent orphaned records and maintain database consistency.
599
+ *
600
+ * **Foreign Key Benefits**:
601
+ * - Prevents invalid references to non-existent records
602
+ * - Maintains data integrity automatically
603
+ * - Provides clear schema documentation of relationships
604
+ * - Enables cascade operations (DELETE, UPDATE)
605
+ *
606
+ * **Considerations**:
607
+ * - Foreign keys can impact performance on large tables
608
+ * - They prevent deletion of referenced records
609
+ * - Consider cascade options for related data cleanup
610
+ *
611
+ * @example
612
+ * ```ts
613
+ * foreignKeys: [
614
+ * {
615
+ * name: "fk_user_role",
616
+ * columns: ["roleId"],
617
+ * foreignColumns: [Role.id]
618
+ * },
619
+ * {
620
+ * columns: ["createdBy"],
621
+ * foreignColumns: [User.id]
622
+ * }
623
+ * ]
624
+ * ```
625
+ */
321
626
  foreignKeys?: Array<{
627
+ /**
628
+ * Optional name for the foreign key constraint.
629
+ */
322
630
  name?: string;
323
- columns: Array<keyof Static$1<T>>;
631
+ /**
632
+ * Local columns that reference the foreign table.
633
+ */
634
+ columns: Array<keyof Static<T>>;
635
+ /**
636
+ * Referenced columns in the foreign table.
637
+ */
324
638
  foreignColumns: Array<AnyPgColumn>;
325
639
  }>;
640
+ /**
641
+ * Additional table constraints for data validation.
642
+ *
643
+ * Constraints enforce business rules at the database level, providing
644
+ * an additional layer of data integrity beyond application validation.
645
+ *
646
+ * **Constraint Types**:
647
+ * - **Unique constraints**: Prevent duplicate values across columns
648
+ * - **Check constraints**: Enforce custom validation rules with SQL expressions
649
+ *
650
+ * **Use Cases**:
651
+ * - Enforce unique combinations of columns
652
+ * - Validate value ranges or patterns
653
+ * - Ensure consistent data states
654
+ * - Implement business rule validation
655
+ *
656
+ * @example
657
+ * ```ts
658
+ * constraints: [
659
+ * {
660
+ * name: "unique_user_email",
661
+ * columns: ["email"],
662
+ * unique: true
663
+ * },
664
+ * {
665
+ * name: "valid_age_range",
666
+ * columns: ["age"],
667
+ * check: sql`age >= 0 AND age <= 150`
668
+ * },
669
+ * {
670
+ * name: "unique_user_username_per_tenant",
671
+ * columns: ["tenantId", "username"],
672
+ * unique: true
673
+ * }
674
+ * ]
675
+ * ```
676
+ */
326
677
  constraints?: Array<{
327
- columns: Array<keyof Static$1<T>>;
678
+ /**
679
+ * Columns involved in this constraint.
680
+ */
681
+ columns: Array<keyof Static<T>>;
682
+ /**
683
+ * Optional name for the constraint.
684
+ */
328
685
  name?: string;
686
+ /**
687
+ * Whether this is a unique constraint.
688
+ */
329
689
  unique?: boolean | {};
690
+ /**
691
+ * SQL expression for check constraint validation.
692
+ */
330
693
  check?: SQL;
331
694
  }>;
332
695
  /**
333
- * Extra configuration for the table. See drizzle-orm documentation for more details.
696
+ * Advanced Drizzle ORM configuration for complex table setups.
697
+ *
698
+ * This allows you to use advanced Drizzle ORM features that aren't covered
699
+ * by the simplified options above. Use this for:
700
+ * - Custom index types (GIN, GIST, etc.)
701
+ * - Partial indexes with WHERE clauses
702
+ * - Advanced constraint configurations
703
+ * - PostgreSQL-specific features
704
+ *
705
+ * **When to Use**:
706
+ * - Need PostgreSQL-specific index types
707
+ * - Require partial indexes for performance
708
+ * - Want fine-grained control over table creation
709
+ * - Using advanced PostgreSQL features
710
+ *
711
+ * See Drizzle ORM documentation for complete configuration options.
712
+ *
713
+ * @param self - The table columns available for configuration
714
+ * @returns Array of Drizzle table configuration objects
334
715
  *
335
- * @param self The table descriptor.
336
- * @returns The extra configuration for the table.
716
+ * @example
717
+ * ```ts
718
+ * config: (table) => [
719
+ * // Partial index for active users only
720
+ * index("idx_active_users_email")
721
+ * .on(table.email)
722
+ * .where(sql`is_active = true`),
723
+ *
724
+ * // GIN index for full-text search
725
+ * index("idx_content_search")
726
+ * .using("gin", table.searchVector),
727
+ *
728
+ * // Unique constraint with custom options
729
+ * uniqueIndex("idx_unique_slug_per_tenant")
730
+ * .on(table.tenantId, table.slug)
731
+ * ]
732
+ * ```
337
733
  */
338
734
  config?: (self: BuildExtraConfigColumns<string, FromSchema<T>, "pg">) => PgTableExtraConfigValue[];
339
735
  }
340
- type Entity<T extends TObject$1> = PgTableWithColumnsAndSchema<PgTableConfig<string, T, FromSchema<T>>, T>;
341
- type PgTableConfig<TTableName extends string, TSchema extends TObject$1, TColumnsMap extends FromSchema<TSchema>> = {
736
+ type Entity<T extends TObject> = PgTableWithColumnsAndSchema<PgTableConfig<string, T, FromSchema<T>>, T>;
737
+ type PgTableConfig<TTableName extends string, TSchema extends TObject, TColumnsMap extends FromSchema<TSchema>> = {
342
738
  name: TTableName;
343
739
  schema: any;
344
740
  columns: BuildColumns<TTableName, TColumnsMap, "pg">;
@@ -351,21 +747,6 @@ declare class PgError extends AlephaError {
351
747
  constructor(message: string, cause?: unknown);
352
748
  }
353
749
  //#endregion
354
- //#region src/helpers/nullToUndefined.d.ts
355
- /**
356
- * Replaces all null values in an object with undefined.
357
- * We need this for converting all nulls from Drizzle outputs.
358
- *
359
- * @param value - The object to be processed.
360
- * @return A new object with all null values replaced with undefined.
361
- */
362
- declare const nullToUndefined: <T extends object>(value: T) => NullToUndefined<T>;
363
- /**
364
- * Replaces all null values in an object with undefined.
365
- */
366
- type NullToUndefined<T> = T extends null ? undefined : T extends null | undefined | string | number | boolean | symbol | bigint | Function | Date | RegExp ? T : T extends Array<infer U> ? Array<NullToUndefined<U>> : T extends Map<infer K, infer V> ? Map<K, NullToUndefined<V>> : T extends Set<infer U> ? Set<NullToUndefined<U>> : T extends object ? { [K in keyof T]: NullToUndefined<T[K]> } : unknown;
367
- type NullifyIfOptional<T> = { [K in keyof T]: undefined extends T[K] ? T[K] | null : T[K] };
368
- //#endregion
369
750
  //#region src/helpers/pgAttr.d.ts
370
751
  /**
371
752
  * Type representation.
@@ -734,22 +1115,6 @@ interface FilterOperators<TValue> {
734
1115
  arrayOverlaps?: TValue;
735
1116
  }
736
1117
  //#endregion
737
- //#region src/interfaces/InferInsert.d.ts
738
- /**
739
- * Enhance Typebox with a support of "Default" (PG_DEFAULT).
740
- */
741
- type InferInsert<T extends TObject$1> = StaticEntry<T> & StaticDefaultEntry<T>;
742
- type StaticDefaultEntry<T extends TObject$1> = { [K in keyof T["properties"] as T["properties"][K] extends {
743
- [PG_DEFAULT]: any;
744
- } | {
745
- [OptionalKind]: "Optional";
746
- } ? K : never]?: Static$1<T["properties"][K]> };
747
- type StaticEntry<T extends TObject$1> = { [K in keyof T["properties"] as T["properties"][K] extends {
748
- [PG_DEFAULT]: any;
749
- } | {
750
- [OptionalKind]: "Optional";
751
- } ? never : K]: Static$1<T["properties"][K]> };
752
- //#endregion
753
1118
  //#region src/interfaces/PgQueryWhere.d.ts
754
1119
  type PgQueryWhereOrSQL<T extends object> = SQLWrapper | PgQueryWhere<T>;
755
1120
  type PgQueryWhere<T extends object> = { [Key in keyof T]?: FilterOperators<T[Key]> | T[Key] } & {
@@ -823,16 +1188,15 @@ type PgQueryWhere<T extends object> = { [Key in keyof T]?: FilterOperators<T[Key
823
1188
  };
824
1189
  //#endregion
825
1190
  //#region src/interfaces/PgQuery.d.ts
826
- interface PgQuery<T extends TObject$1, Select extends (keyof Static$1<T>)[] = []> {
827
- columns?: Select;
1191
+ interface PgQuery<T extends TObject> {
828
1192
  distinct?: boolean;
829
- where?: PgQueryWhereOrSQL<Static$1<T>>;
1193
+ where?: PgQueryWhereOrSQL<Static<T>>;
830
1194
  limit?: number;
831
1195
  offset?: number;
832
- sort?: { [key in keyof Static$1<T>]?: "asc" | "desc" };
833
- groupBy?: (keyof Static$1<T>)[];
1196
+ sort?: { [key in keyof Static<T>]?: "asc" | "desc" };
1197
+ groupBy?: (keyof Static<T>)[];
834
1198
  }
835
- type PgQueryResult<T extends TObject$1, Select extends (keyof Static$1<T>)[]> = TPick<T, Select>;
1199
+ type PgQueryResult<T extends TObject, Select extends (keyof Static<T>)[]> = TPick<T, TKeysToIndexer<Select>>;
836
1200
  //#endregion
837
1201
  //#region src/providers/drivers/PostgresProvider.d.ts
838
1202
  type SQLLike = SQLWrapper | string;
@@ -841,37 +1205,38 @@ declare abstract class PostgresProvider {
841
1205
  abstract get db(): PgDatabase<any>;
842
1206
  abstract get schema(): string;
843
1207
  abstract get dialect(): string;
844
- abstract execute<T extends TObject$1 = any>(query: SQLLike, schema?: T): Promise<Array<T extends TObject$1 ? Static$1<T> : any>>;
1208
+ abstract execute<T extends TObject | undefined = undefined>(query: SQLLike, schema?: T): Promise<Array<T extends TObject ? Static<T> : any>>;
845
1209
  }
846
1210
  //#endregion
847
1211
  //#region src/schemas/pageQuerySchema.d.ts
848
- declare const pageQuerySchema: _sinclair_typebox1.TObject<{
849
- page: _sinclair_typebox1.TOptional<_sinclair_typebox1.TNumber>;
850
- size: _sinclair_typebox1.TOptional<_sinclair_typebox1.TNumber>;
851
- sort: _sinclair_typebox1.TOptional<_sinclair_typebox1.TString>;
1212
+ declare const pageQuerySchema: typebox1.TObject<{
1213
+ page: typebox1.TOptional<typebox1.TInteger>;
1214
+ size: typebox1.TOptional<typebox1.TInteger>;
1215
+ sort: typebox1.TOptional<typebox1.TString>;
852
1216
  }>;
853
1217
  type PageQuery = Static<typeof pageQuerySchema>;
854
1218
  //#endregion
855
1219
  //#region src/schemas/pageSchema.d.ts
856
1220
  /**
857
- * Page Schema
1221
+ * Create a pagination schema for the given object schema.
1222
+ *
1223
+ * @example
1224
+ * const userSchema = t.object({ id: t.int(), name: t.string() });
1225
+ * const pagedUserSchema = pageSchema(userSchema);
858
1226
  *
859
- * @param objectSchema
860
- * @param options
1227
+ * @see {@link $repository#paginate}
861
1228
  */
862
- declare const pageSchema: <T extends TObject$1 | TIntersect | TRecord>(objectSchema: T, options?: ObjectOptions) => TPage<T>;
863
- type TPage<T extends TObject$1 | TIntersect | TRecord> = TObject$1<{
1229
+ declare const pageSchema: <T extends TObject | TRecord>(objectSchema: T, options?: TObjectOptions) => TPage<T>;
1230
+ type TPage<T extends TObject | TRecord> = TObject<{
864
1231
  content: TArray<T>;
865
- can: TObject$1<{
1232
+ can: TObject<{
866
1233
  next: TBoolean;
867
1234
  previous: TBoolean;
868
1235
  }>;
869
- page: TObject$1<{
1236
+ page: TObject<{
870
1237
  number: TInteger;
871
1238
  size: TInteger;
872
- totalElements: TOptionalWithFlag<TInteger, true>;
873
- queryDuration: TOptionalWithFlag<TInteger, true>;
874
- countDuration: TOptionalWithFlag<TInteger, true>;
1239
+ totalElements: TOptionalAdd<TInteger>;
875
1240
  }>;
876
1241
  }>;
877
1242
  type Page<T> = {
@@ -884,35 +1249,393 @@ type Page<T> = {
884
1249
  number: number;
885
1250
  size: number;
886
1251
  totalElements?: number;
887
- queryDuration?: number;
888
- countDuration?: number;
889
1252
  };
890
1253
  };
891
1254
  //#endregion
892
1255
  //#region src/descriptors/$repository.d.ts
893
1256
  /**
1257
+ * Creates a repository descriptor for database operations on a defined entity.
1258
+ *
1259
+ * This descriptor provides a comprehensive, type-safe interface for performing all
1260
+ * database operations on entities defined with $entity. It offers a rich set of
1261
+ * CRUD operations, advanced querying capabilities, pagination, transactions, and
1262
+ * built-in support for audit trails and soft deletes.
1263
+ *
1264
+ * **Key Features**
1265
+ *
1266
+ * - **Complete CRUD Operations**: Create, read, update, delete with full type safety
1267
+ * - **Advanced Querying**: Complex WHERE conditions, sorting, pagination, and aggregations
1268
+ * - **Transaction Support**: Database transactions for consistency and atomicity
1269
+ * - **Soft Delete Support**: Built-in soft delete functionality with `pg.deletedAt()` fields
1270
+ * - **Optimistic Locking**: Version-based conflict resolution with `pg.version()` fields
1271
+ * - **Audit Trail Integration**: Automatic handling of `createdAt`, `updatedAt` timestamps
1272
+ * - **Raw SQL Support**: Execute custom SQL queries when needed
1273
+ * - **Pagination**: Built-in pagination with metadata and navigation
1274
+ *
1275
+ * **Important Requirements**
1276
+ * - Must be used with an entity created by $entity
1277
+ * - Entity schema must include exactly one primary key field
1278
+ * - Database tables must be created via migrations before use
1279
+ *
1280
+ * **Use Cases**
1281
+ *
1282
+ * Essential for all database-driven applications:
1283
+ * - User management and authentication systems
1284
+ * - E-commerce product and order management
1285
+ * - Content management and blogging platforms
1286
+ * - Financial and accounting applications
1287
+ * - Any application requiring persistent data storage
1288
+ *
1289
+ * @example
1290
+ * **Basic repository with CRUD operations:**
1291
+ * ```ts
1292
+ * import { $entity, $repository } from "alepha/postgres";
1293
+ * import { pg, t } from "alepha";
1294
+ *
1295
+ * // First, define the entity
1296
+ * const User = $entity({
1297
+ * name: "users",
1298
+ * schema: t.object({
1299
+ * id: pg.primaryKey(t.uuid()),
1300
+ * email: t.string({ format: "email" }),
1301
+ * firstName: t.string(),
1302
+ * lastName: t.string(),
1303
+ * isActive: t.boolean({ default: true }),
1304
+ * createdAt: pg.createdAt(),
1305
+ * updatedAt: pg.updatedAt()
1306
+ * }),
1307
+ * indexes: [{ column: "email", unique: true }]
1308
+ * });
1309
+ *
1310
+ * class UserService {
1311
+ * users = $repository({ table: User });
1312
+ *
1313
+ * async createUser(userData: { email: string; firstName: string; lastName: string }) {
1314
+ * return await this.users.create({
1315
+ * id: generateUUID(),
1316
+ * email: userData.email,
1317
+ * firstName: userData.firstName,
1318
+ * lastName: userData.lastName,
1319
+ * isActive: true
1320
+ * });
1321
+ * }
1322
+ *
1323
+ * async getUserByEmail(email: string) {
1324
+ * return await this.users.findOne({ email });
1325
+ * }
1326
+ *
1327
+ * async updateUser(id: string, updates: { firstName?: string; lastName?: string }) {
1328
+ * return await this.users.updateById(id, updates);
1329
+ * }
1330
+ *
1331
+ * async deactivateUser(id: string) {
1332
+ * return await this.users.updateById(id, { isActive: false });
1333
+ * }
1334
+ * }
1335
+ * ```
1336
+ *
1337
+ * @example
1338
+ * **Advanced querying and filtering:**
1339
+ * ```ts
1340
+ * const Product = $entity({
1341
+ * name: "products",
1342
+ * schema: t.object({
1343
+ * id: pg.primaryKey(t.uuid()),
1344
+ * name: t.string(),
1345
+ * price: t.number({ minimum: 0 }),
1346
+ * categoryId: t.string({ format: "uuid" }),
1347
+ * inStock: t.boolean(),
1348
+ * tags: t.optional(t.array(t.string())),
1349
+ * createdAt: pg.createdAt(),
1350
+ * updatedAt: pg.updatedAt()
1351
+ * }),
1352
+ * indexes: ["categoryId", "inStock", "price"]
1353
+ * });
1354
+ *
1355
+ * class ProductService {
1356
+ * products = $repository({ table: Product });
1357
+ *
1358
+ * async searchProducts(filters: {
1359
+ * categoryId?: string;
1360
+ * minPrice?: number;
1361
+ * maxPrice?: number;
1362
+ * inStock?: boolean;
1363
+ * searchTerm?: string;
1364
+ * }, page: number = 0, size: number = 20) {
1365
+ * const query = this.products.createQuery({
1366
+ * where: {
1367
+ * and: [
1368
+ * filters.categoryId ? { categoryId: filters.categoryId } : {},
1369
+ * filters.inStock !== undefined ? { inStock: filters.inStock } : {},
1370
+ * filters.minPrice ? { price: { gte: filters.minPrice } } : {},
1371
+ * filters.maxPrice ? { price: { lte: filters.maxPrice } } : {},
1372
+ * filters.searchTerm ? { name: { ilike: `%${filters.searchTerm}%` } } : {}
1373
+ * ]
1374
+ * },
1375
+ * sort: { createdAt: "desc" }
1376
+ * });
1377
+ *
1378
+ * return await this.products.paginate({ page, size }, query, { count: true });
1379
+ * }
1380
+ *
1381
+ * async getTopSellingProducts(limit: number = 10) {
1382
+ * // Custom SQL query for complex analytics
1383
+ * return await this.products.query(
1384
+ * (table, db) => db
1385
+ * .select({
1386
+ * id: table.id,
1387
+ * name: table.name,
1388
+ * price: table.price,
1389
+ * salesCount: sql<number>`COALESCE(sales.count, 0)`
1390
+ * })
1391
+ * .from(table)
1392
+ * .leftJoin(
1393
+ * sql`(
1394
+ * SELECT product_id, COUNT(*) as count
1395
+ * FROM order_items
1396
+ * WHERE created_at > NOW() - INTERVAL '30 days'
1397
+ * GROUP BY product_id
1398
+ * ) sales`,
1399
+ * sql`sales.product_id = ${table.id}`
1400
+ * )
1401
+ * .orderBy(sql`sales.count DESC NULLS LAST`)
1402
+ * .limit(limit)
1403
+ * );
1404
+ * }
1405
+ * }
1406
+ * ```
1407
+ *
1408
+ * @example
1409
+ * **Transaction handling and data consistency:**
1410
+ * ```ts
1411
+ * class OrderService {
1412
+ * orders = $repository({ table: Order });
1413
+ * orderItems = $repository({ table: OrderItem });
1414
+ * products = $repository({ table: Product });
1415
+ *
1416
+ * async createOrderWithItems(orderData: {
1417
+ * customerId: string;
1418
+ * items: Array<{ productId: string; quantity: number; price: number }>;
1419
+ * }) {
1420
+ * return await this.orders.transaction(async (tx) => {
1421
+ * // Create the order
1422
+ * const order = await this.orders.create({
1423
+ * id: generateUUID(),
1424
+ * customerId: orderData.customerId,
1425
+ * status: 'pending',
1426
+ * totalAmount: orderData.items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
1427
+ * }, { tx });
1428
+ *
1429
+ * // Create order items and update product inventory
1430
+ * for (const itemData of orderData.items) {
1431
+ * await this.orderItems.create({
1432
+ * id: generateUUID(),
1433
+ * orderId: order.id,
1434
+ * productId: itemData.productId,
1435
+ * quantity: itemData.quantity,
1436
+ * unitPrice: itemData.price
1437
+ * }, { tx });
1438
+ *
1439
+ * // Update product inventory using optimistic locking
1440
+ * const product = await this.products.findById(itemData.productId, { tx });
1441
+ * if (product.stockQuantity < itemData.quantity) {
1442
+ * throw new Error(`Insufficient stock for product ${itemData.productId}`);
1443
+ * }
1444
+ *
1445
+ * await this.products.save({
1446
+ * ...product,
1447
+ * stockQuantity: product.stockQuantity - itemData.quantity
1448
+ * }, { tx });
1449
+ * }
1450
+ *
1451
+ * return order;
1452
+ * });
1453
+ * }
1454
+ * }
1455
+ * ```
1456
+ *
1457
+ * @example
1458
+ * **Soft delete and audit trail:**
1459
+ * ```ts
1460
+ * const Document = $entity({
1461
+ * name: "documents",
1462
+ * schema: t.object({
1463
+ * id: pg.primaryKey(t.uuid()),
1464
+ * title: t.string(),
1465
+ * content: t.string(),
1466
+ * authorId: t.string({ format: "uuid" }),
1467
+ * version: pg.version(),
1468
+ * createdAt: pg.createdAt(),
1469
+ * updatedAt: pg.updatedAt(),
1470
+ * deletedAt: pg.deletedAt() // Enables soft delete
1471
+ * })
1472
+ * });
1473
+ *
1474
+ * class DocumentService {
1475
+ * documents = $repository({ table: Document });
1476
+ *
1477
+ * async updateDocument(id: string, updates: { title?: string; content?: string }) {
1478
+ * // This uses optimistic locking via the version field
1479
+ * const document = await this.documents.findById(id);
1480
+ * return await this.documents.save({
1481
+ * ...document,
1482
+ * ...updates // updatedAt will be set automatically
1483
+ * });
1484
+ * }
1485
+ *
1486
+ * async softDeleteDocument(id: string) {
1487
+ * // Soft delete - sets deletedAt timestamp
1488
+ * await this.documents.deleteById(id);
1489
+ * }
1490
+ *
1491
+ * async permanentDeleteDocument(id: string) {
1492
+ * // Hard delete - actually removes from database
1493
+ * await this.documents.deleteById(id, { force: true });
1494
+ * }
1495
+ *
1496
+ * async getActiveDocuments() {
1497
+ * // Automatically excludes soft-deleted records
1498
+ * return await this.documents.find({
1499
+ * where: { authorId: { isNotNull: true } },
1500
+ * sort: { updatedAt: "desc" }
1501
+ * });
1502
+ * }
1503
+ *
1504
+ * async getAllDocumentsIncludingDeleted() {
1505
+ * // Include soft-deleted records
1506
+ * return await this.documents.find({}, { force: true });
1507
+ * }
1508
+ * }
1509
+ * ```
1510
+ *
1511
+ * @example
1512
+ * **Complex filtering and aggregation:**
1513
+ * ```ts
1514
+ * class AnalyticsService {
1515
+ * users = $repository({ table: User });
1516
+ * orders = $repository({ table: Order });
1517
+ *
1518
+ * async getUserStatistics(filters: {
1519
+ * startDate?: string;
1520
+ * endDate?: string;
1521
+ * isActive?: boolean;
1522
+ * }) {
1523
+ * const whereConditions = [];
1524
+ *
1525
+ * if (filters.startDate) {
1526
+ * whereConditions.push({ createdAt: { gte: filters.startDate } });
1527
+ * }
1528
+ * if (filters.endDate) {
1529
+ * whereConditions.push({ createdAt: { lte: filters.endDate } });
1530
+ * }
1531
+ * if (filters.isActive !== undefined) {
1532
+ * whereConditions.push({ isActive: filters.isActive });
1533
+ * }
1534
+ *
1535
+ * const totalUsers = await this.users.count({
1536
+ * and: whereConditions
1537
+ * });
1538
+ *
1539
+ * const activeUsers = await this.users.count({
1540
+ * and: [...whereConditions, { isActive: true }]
1541
+ * });
1542
+ *
1543
+ * // Complex aggregation query
1544
+ * const recentActivity = await this.users.query(
1545
+ * sql`
1546
+ * SELECT
1547
+ * DATE_TRUNC('day', created_at) as date,
1548
+ * COUNT(*) as new_users,
1549
+ * COUNT(*) FILTER (WHERE is_active = true) as active_users
1550
+ * FROM users
1551
+ * WHERE created_at >= NOW() - INTERVAL '30 days'
1552
+ * GROUP BY DATE_TRUNC('day', created_at)
1553
+ * ORDER BY date DESC
1554
+ * `
1555
+ * );
1556
+ *
1557
+ * return {
1558
+ * totalUsers,
1559
+ * activeUsers,
1560
+ * inactiveUsers: totalUsers - activeUsers,
1561
+ * recentActivity
1562
+ * };
1563
+ * }
1564
+ * }
1565
+ * ```
1566
+ *
894
1567
  * @stability 3
895
1568
  */
896
1569
  declare const $repository: {
897
- <EntityTableConfig extends TableConfig, EntitySchema extends TObject$1>(optionsOrTable: RepositoryDescriptorOptions<EntityTableConfig, EntitySchema> | PgTableWithColumnsAndSchema<EntityTableConfig, EntitySchema>): RepositoryDescriptor<EntityTableConfig, EntitySchema>;
1570
+ <EntityTableConfig extends TableConfig, EntitySchema extends TObject>(optionsOrTable: RepositoryDescriptorOptions<EntityTableConfig, EntitySchema> | PgTableWithColumnsAndSchema<EntityTableConfig, EntitySchema>): RepositoryDescriptor<EntityTableConfig, EntitySchema>;
898
1571
  [KIND]: typeof RepositoryDescriptor;
899
1572
  };
900
- interface RepositoryDescriptorOptions<EntityTableConfig extends TableConfig, EntitySchema extends TObject$1> {
1573
+ interface RepositoryDescriptorOptions<EntityTableConfig extends TableConfig, EntitySchema extends TObject> {
901
1574
  /**
902
- * The table to create the repository for.
1575
+ * The entity table definition created with $entity.
1576
+ *
1577
+ * This table:
1578
+ * - Must be created using the $entity descriptor
1579
+ * - Defines the schema, indexes, and constraints for the repository
1580
+ * - Provides type information for all repository operations
1581
+ * - Must include exactly one primary key field
1582
+ *
1583
+ * The repository will automatically:
1584
+ * - Generate typed CRUD operations based on the entity schema
1585
+ * - Handle audit fields like createdAt, updatedAt, deletedAt
1586
+ * - Support optimistic locking if version field is present
1587
+ * - Provide soft delete functionality if deletedAt field exists
1588
+ *
1589
+ * **Entity Requirements**:
1590
+ * - Must have been created with $entity descriptor
1591
+ * - Schema must include a primary key field marked with `pg.primaryKey()`
1592
+ * - Corresponding database table must exist (created via migrations)
1593
+ *
1594
+ * @example
1595
+ * ```ts
1596
+ * const User = $entity({
1597
+ * name: "users",
1598
+ * schema: t.object({
1599
+ * id: pg.primaryKey(t.uuid()),
1600
+ * email: t.string({ format: "email" }),
1601
+ * name: t.string()
1602
+ * })
1603
+ * });
1604
+ *
1605
+ * const userRepository = $repository({ table: User });
1606
+ * ```
903
1607
  */
904
1608
  table: PgTableWithColumnsAndSchema<EntityTableConfig, EntitySchema>;
905
1609
  /**
906
- * Override default provider.
1610
+ * Override the default PostgreSQL database provider.
1611
+ *
1612
+ * By default, the repository will use the injected PostgresProvider from the
1613
+ * dependency injection container. Use this option to:
1614
+ * - Connect to a different database
1615
+ * - Use a specific connection pool
1616
+ * - Implement custom database behavior
1617
+ * - Support multi-tenant architectures with database per tenant
1618
+ *
1619
+ * **Common Use Cases**:
1620
+ * - Multi-database applications
1621
+ * - Read replicas for query optimization
1622
+ * - Different databases for different entity types
1623
+ * - Testing with separate test databases
1624
+ *
1625
+ * @default Uses injected PostgresProvider
1626
+ *
1627
+ * @example ReadOnlyPostgresProvider
1628
+ * @example TenantSpecificPostgresProvider
1629
+ * @example TestDatabaseProvider
907
1630
  */
908
1631
  provider?: Service<PostgresProvider>;
909
1632
  }
910
- declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, EntitySchema extends TObject$1> extends Descriptor<RepositoryDescriptorOptions<EntityTableConfig, EntitySchema>> {
1633
+ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, EntitySchema extends TObject> extends Descriptor<RepositoryDescriptorOptions<EntityTableConfig, EntitySchema>> {
911
1634
  protected readonly dateTimeProvider: DateTimeProvider;
912
- readonly provider: PostgresProvider;
913
1635
  protected readonly alepha: Alepha;
1636
+ readonly provider: PostgresProvider;
914
1637
  readonly schema: EntitySchema;
915
- readonly schemaInsert: TInsertObject<EntitySchema>;
1638
+ readonly schemaInsert: TObjectInsert<EntitySchema>;
916
1639
  /**
917
1640
  * Represents the primary key of the table.
918
1641
  * - Key is the name of the primary key column.
@@ -921,7 +1644,7 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
921
1644
  * ID is mandatory. If the table does not have a primary key, it will throw an error.
922
1645
  */
923
1646
  readonly id: {
924
- type: TSchema$2;
1647
+ type: TSchema$1;
925
1648
  key: keyof EntitySchema["properties"];
926
1649
  col: PgColumn;
927
1650
  };
@@ -936,11 +1659,12 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
936
1659
  /**
937
1660
  * Getter for the database connection from the database provider.
938
1661
  */
939
- protected get db(): PgDatabase<any, Record<string, never>, drizzle_orm7.ExtractTablesWithRelations<Record<string, never>>>;
1662
+ protected get db(): PgDatabase<any, Record<string, never>, drizzle_orm6.ExtractTablesWithRelations<Record<string, never>>>;
940
1663
  /**
941
1664
  * Execute a SQL query.
942
1665
  */
943
- query<T extends TObject$1 = EntitySchema>(query: SQLLike | ((table: PgTableWithColumns<EntityTableConfig>, db: PgDatabase<any>) => SQLLike), schema?: T): Promise<Static$1<T>[]>;
1666
+ query<T extends TObject = EntitySchema>(query: SQLLike | ((table: PgTableWithColumns<EntityTableConfig>, db: PgDatabase<any>) => SQLLike), schema?: T): Promise<Static<T>[]>;
1667
+ protected mapRawFieldsToEntity(row: any[]): any;
944
1668
  /**
945
1669
  * Get a Drizzle column from the table by his name.
946
1670
  *
@@ -960,10 +1684,10 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
960
1684
  *
961
1685
  * @returns The SELECT query builder.
962
1686
  */
963
- protected select(opts?: StatementOptions): pg$1.PgSelectBase<string, Record<string, PgColumn<drizzle_orm7.ColumnBaseConfig<drizzle_orm7.ColumnDataType, string>, {}, {}>>, "single", Record<string, "not-null">, false, never, {
1687
+ protected select(opts?: StatementOptions): pg$1.PgSelectBase<string, Record<string, PgColumn<drizzle_orm6.ColumnBaseConfig<drizzle_orm6.ColumnDataType, string>, {}, {}>>, "single", Record<string, "not-null">, false, never, {
964
1688
  [x: string]: unknown;
965
1689
  }[], {
966
- [x: string]: PgColumn<drizzle_orm7.ColumnBaseConfig<drizzle_orm7.ColumnDataType, string>, {}, {}>;
1690
+ [x: string]: PgColumn<drizzle_orm6.ColumnBaseConfig<drizzle_orm6.ColumnDataType, string>, {}, {}>;
967
1691
  }>;
968
1692
  protected selectDistinct(opts: StatementOptions | undefined, fields: SelectedFields): pg$1.PgSelectBase<string, SelectedFields, "partial", Record<string, "not-null">, false, never, {
969
1693
  [x: string]: unknown;
@@ -995,7 +1719,7 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
995
1719
  * @param opts The statement options.
996
1720
  * @returns The found entities.
997
1721
  */
998
- find<Select extends (keyof Static$1<EntitySchema>)[]>(query?: PgQuery<EntitySchema, Select>, opts?: StatementOptions): Promise<Static$1<PgQueryResult<EntitySchema, Select>>[]>;
1722
+ find(query?: PgQuery<EntitySchema>, opts?: StatementOptions): Promise<Static<EntitySchema>[]>;
999
1723
  /**
1000
1724
  * Find a single entity.
1001
1725
  *
@@ -1003,19 +1727,19 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1003
1727
  * @param opts The statement options.
1004
1728
  * @returns The found entity.
1005
1729
  */
1006
- findOne<T extends Static$1<EntitySchema> = Static$1<EntitySchema>>(where: PgQueryWhere<T>, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1730
+ findOne<T extends Static<EntitySchema> = Static<EntitySchema>>(where: PgQueryWhere<T>, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1007
1731
  /**
1008
1732
  * Find an entity by ID.
1009
1733
  */
1010
- findById(id: string | number, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1734
+ findById(id: string | number, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1011
1735
  /**
1012
1736
  * Paginate entities.
1013
1737
  */
1014
1738
  paginate(pagination?: PageQuery, query?: PgQuery<EntitySchema>, opts?: StatementOptions & {
1015
- skipCount?: boolean;
1016
- }): Promise<Page<Static$1<EntitySchema>>>;
1739
+ count?: boolean;
1740
+ }): Promise<Page<Static<EntitySchema>>>;
1017
1741
  createQuery(query?: PgQuery<EntitySchema>): PgQuery<EntitySchema>;
1018
- createQueryWhere(where?: PgQueryWhere<Static$1<EntitySchema>>): PgQueryWhere<Static$1<EntitySchema>>;
1742
+ createQueryWhere(where?: PgQueryWhere<Static<EntitySchema>>): PgQueryWhere<Static<EntitySchema>>;
1019
1743
  /**
1020
1744
  * Create an entity.
1021
1745
  *
@@ -1023,7 +1747,7 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1023
1747
  * @param opts The options for creating the entity.
1024
1748
  * @returns The ID of the created entity.
1025
1749
  */
1026
- create(data: InferInsert<EntitySchema>, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1750
+ create(data: Static<TObjectInsert<EntitySchema>>, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1027
1751
  /**
1028
1752
  * Create many entities.
1029
1753
  *
@@ -1031,43 +1755,45 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1031
1755
  * @param opts The statement options.
1032
1756
  * @returns The created entities.
1033
1757
  */
1034
- createMany(values: Array<InferInsert<EntitySchema>>, opts?: StatementOptions): Promise<Static$1<EntitySchema>[]>;
1758
+ createMany(values: Array<Static<TObjectInsert<EntitySchema>>>, opts?: StatementOptions): Promise<Static<EntitySchema>[]>;
1035
1759
  /**
1036
1760
  * Find an entity and update it.
1037
1761
  */
1038
- updateOne(where: PgQueryWhereOrSQL<Static$1<EntitySchema>>, data: Partial<NullifyIfOptional<Static$1<EntitySchema>>>, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1762
+ updateOne(where: PgQueryWhereOrSQL<Static<EntitySchema>>, data: Partial<Static<TObjectUpdate<EntitySchema>>>, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1039
1763
  /**
1040
1764
  * Save a given entity.
1041
1765
  *
1042
1766
  * @example
1043
1767
  * ```ts
1044
1768
  * const entity = await repository.findById(1);
1045
- * entity.name = "New Name";
1769
+ * entity.name = "New Name"; // update a field
1770
+ * delete entity.description; // delete a field
1046
1771
  * await repository.save(entity);
1047
1772
  * ```
1048
1773
  *
1049
1774
  * Difference with `updateById/updateOne`:
1050
1775
  *
1051
- * - requires the entity to be fetched first
1052
- * - check pg.version() if present - optimistic locking
1776
+ * - requires the entity to be fetched first (whole object is expected)
1777
+ * - check pg.version() if present -> optimistic locking
1053
1778
  * - validate entity against schema
1779
+ * - undefined values will be set to null, not ignored!
1054
1780
  *
1055
1781
  * @see {@link PostgresTypeProvider#version}
1056
1782
  * @see {@link PgVersionMismatchError}
1057
1783
  */
1058
- save(entity: Static$1<EntitySchema>, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1784
+ save(entity: Static<EntitySchema>, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1059
1785
  /**
1060
1786
  * Find an entity by ID and update it.
1061
1787
  */
1062
- updateById(id: string | number, data: Partial<NullifyIfOptional<Static$1<EntitySchema>>>, opts?: StatementOptions): Promise<Static$1<EntitySchema>>;
1788
+ updateById(id: string | number, data: Partial<Static<TObjectUpdate<EntitySchema>>>, opts?: StatementOptions): Promise<Static<EntitySchema>>;
1063
1789
  /**
1064
1790
  * Find many entities and update all of them.
1065
1791
  */
1066
- updateMany(where: PgQueryWhereOrSQL<Static$1<EntitySchema>>, data: Partial<NullifyIfOptional<Static$1<EntitySchema>>>, opts?: StatementOptions): Promise<void>;
1792
+ updateMany(where: PgQueryWhereOrSQL<Static<EntitySchema>>, data: Partial<Static<TObjectUpdate<EntitySchema>>>, opts?: StatementOptions): Promise<void>;
1067
1793
  /**
1068
1794
  * Find many and delete all of them.
1069
1795
  */
1070
- deleteMany(where?: PgQueryWhere<Static$1<EntitySchema>>, opts?: StatementOptions): Promise<void>;
1796
+ deleteMany(where?: PgQueryWhere<Static<EntitySchema>>, opts?: StatementOptions): Promise<void>;
1071
1797
  /**
1072
1798
  * Delete all entities.
1073
1799
  */
@@ -1077,11 +1803,11 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1077
1803
  *
1078
1804
  * You must fetch the entity first in order to delete it.
1079
1805
  */
1080
- destroy(entity: Static$1<EntitySchema>, opts?: StatementOptions): Promise<void>;
1806
+ destroy(entity: Static<EntitySchema>, opts?: StatementOptions): Promise<void>;
1081
1807
  /**
1082
1808
  * Find an entity and delete it.
1083
1809
  */
1084
- deleteOne(where?: PgQueryWhere<Static$1<EntitySchema>>, opts?: StatementOptions): Promise<void>;
1810
+ deleteOne(where?: PgQueryWhere<Static<EntitySchema>>, opts?: StatementOptions): Promise<void>;
1085
1811
  /**
1086
1812
  * Find an entity by ID and delete it.
1087
1813
  */
@@ -1089,14 +1815,12 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1089
1815
  /**
1090
1816
  * Count entities.
1091
1817
  */
1092
- count(where?: PgQueryWhereOrSQL<Static$1<EntitySchema>>, opts?: StatementOptions): Promise<number>;
1818
+ count(where?: PgQueryWhereOrSQL<Static<EntitySchema>>, opts?: StatementOptions): Promise<number>;
1093
1819
  protected conflictMessagePattern: string;
1094
1820
  protected handleError(error: unknown, message: string): PgError;
1095
- protected withDeletedAt(where: PgQueryWhereOrSQL<Static$1<EntitySchema>>, opts?: {
1821
+ protected withDeletedAt(where: PgQueryWhereOrSQL<Static<EntitySchema>>, opts?: {
1096
1822
  force?: boolean;
1097
- }): PgQueryWhereOrSQL<(EntitySchema & {
1098
- params: [];
1099
- })["static"]>;
1823
+ }): PgQueryWhereOrSQL<Static<EntitySchema>>;
1100
1824
  protected get organization(): undefined;
1101
1825
  protected deletedAt(): PgAttrField | undefined;
1102
1826
  /**
@@ -1106,7 +1830,7 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1106
1830
  * @param schema The schema to use.
1107
1831
  * @param col The column to use.
1108
1832
  */
1109
- protected jsonQueryToSql(query: PgQueryWhereOrSQL<Static$1<EntitySchema>>, schema?: TObject$1, col?: (key: string) => PgColumn): SQL | undefined;
1833
+ protected jsonQueryToSql(query: PgQueryWhereOrSQL<Static<EntitySchema>>, schema?: TObject, col?: (key: string) => PgColumn): SQL | undefined;
1110
1834
  /**
1111
1835
  * Map a filter operator to a SQL query.
1112
1836
  *
@@ -1122,7 +1846,7 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1122
1846
  * @param limit The limit of the pagination.
1123
1847
  * @param offset The offset of the pagination.
1124
1848
  */
1125
- protected createPagination(entities: Static$1<EntitySchema>[], limit?: number, offset?: number): Page<Static$1<EntitySchema>>;
1849
+ protected createPagination(entities: Static<EntitySchema>[], limit?: number, offset?: number): Page<Static<EntitySchema>>;
1126
1850
  /**
1127
1851
  * Convert something to valid Pg Insert Value.
1128
1852
  */
@@ -1134,24 +1858,24 @@ declare class RepositoryDescriptor<EntityTableConfig extends TableConfig, Entity
1134
1858
  * @param schema The schema to use.
1135
1859
  * @returns The cleaned row.
1136
1860
  */
1137
- protected clean<T extends TObject$1 = EntitySchema>(row: any, schema?: T): Static$1<T>;
1861
+ protected clean<T extends TObject = EntitySchema>(row: any, schema?: T): Static<T>;
1138
1862
  /**
1139
1863
  * Get the where clause for an ID.
1140
1864
  *
1141
1865
  * @param id The ID to get the where clause for.
1142
1866
  * @returns The where clause for the ID.
1143
1867
  */
1144
- protected getWhereId(id: string | number): PgQueryWhere<Static$1<EntitySchema>>;
1868
+ protected getWhereId(id: string | number): PgQueryWhere<Static<EntitySchema>>;
1145
1869
  /**
1146
1870
  * Find a primary key in the schema.
1147
1871
  *
1148
1872
  * @param schema
1149
1873
  * @protected
1150
1874
  */
1151
- protected getPrimaryKey(schema: TObject$1): {
1875
+ protected getPrimaryKey(schema: TObject): {
1152
1876
  key: string;
1153
- col: PgColumn<drizzle_orm7.ColumnBaseConfig<drizzle_orm7.ColumnDataType, string>, {}, {}>;
1154
- type: TSchema$2;
1877
+ col: PgColumn<drizzle_orm6.ColumnBaseConfig<drizzle_orm6.ColumnDataType, string>, {}, {}>;
1878
+ type: TSchema$1;
1155
1879
  };
1156
1880
  }
1157
1881
  /**
@@ -1173,10 +1897,238 @@ interface StatementOptions {
1173
1897
  * If true, ignore soft delete.
1174
1898
  */
1175
1899
  force?: boolean;
1900
+ /**
1901
+ * Force the current time.
1902
+ */
1903
+ now?: DateTime;
1176
1904
  }
1177
1905
  //#endregion
1178
1906
  //#region src/descriptors/$sequence.d.ts
1179
1907
  /**
1908
+ * Creates a PostgreSQL sequence descriptor for generating unique numeric values.
1909
+ *
1910
+ * This descriptor provides a type-safe interface to PostgreSQL sequences, which are
1911
+ * database objects that generate unique numeric identifiers. Sequences are commonly
1912
+ * used for primary keys, order numbers, invoice numbers, and other cases where
1913
+ * guaranteed unique, incrementing values are needed across concurrent operations.
1914
+ *
1915
+ * **Key Features**
1916
+ *
1917
+ * - **Thread-Safe**: PostgreSQL sequences are inherently thread-safe and handle concurrency
1918
+ * - **Configurable Parameters**: Start value, increment, min/max bounds, and cycling behavior
1919
+ * - **Automatic Creation**: Sequences are created automatically when first used
1920
+ * - **Type Safety**: Full TypeScript support with numeric return types
1921
+ * - **Performance**: Optimized for high-throughput ID generation
1922
+ * - **Schema Support**: Works with PostgreSQL schemas for organization
1923
+ *
1924
+ * **Use Cases**
1925
+ *
1926
+ * Perfect for generating unique identifiers in concurrent environments:
1927
+ * - Primary key generation (alternative to UUIDs)
1928
+ * - Order numbers and invoice sequences
1929
+ * - Ticket numbers and reference IDs
1930
+ * - Version numbers and revision tracking
1931
+ * - Batch numbers for processing workflows
1932
+ * - Any scenario requiring guaranteed unique incrementing numbers
1933
+ *
1934
+ * @example
1935
+ * **Basic sequence for order numbers:**
1936
+ * ```ts
1937
+ * import { $sequence } from "alepha/postgres";
1938
+ *
1939
+ * class OrderService {
1940
+ * orderNumbers = $sequence({
1941
+ * name: "order_numbers",
1942
+ * start: 1000, // Start from order #1000
1943
+ * increment: 1 // Increment by 1 each time
1944
+ * });
1945
+ *
1946
+ * async createOrder(orderData: OrderData) {
1947
+ * const orderNumber = await this.orderNumbers.next();
1948
+ *
1949
+ * return await this.orders.create({
1950
+ * id: generateUUID(),
1951
+ * orderNumber,
1952
+ * ...orderData
1953
+ * });
1954
+ * }
1955
+ *
1956
+ * async getCurrentOrderNumber() {
1957
+ * // Get the last generated number without incrementing
1958
+ * return await this.orderNumbers.current();
1959
+ * }
1960
+ * }
1961
+ * ```
1962
+ *
1963
+ * @example
1964
+ * **Invoice numbering with yearly reset:**
1965
+ * ```ts
1966
+ * class InvoiceService {
1967
+ * // Separate sequence for each year
1968
+ * getInvoiceSequence(year: number) {
1969
+ * return $sequence({
1970
+ * name: `invoice_numbers_${year}`,
1971
+ * start: 1,
1972
+ * increment: 1
1973
+ * });
1974
+ * }
1975
+ *
1976
+ * async generateInvoiceNumber(): Promise<string> {
1977
+ * const year = new Date().getFullYear();
1978
+ * const sequence = this.getInvoiceSequence(year);
1979
+ * const number = await sequence.next();
1980
+ *
1981
+ * // Format as INV-2024-001, INV-2024-002, etc.
1982
+ * return `INV-${year}-${number.toString().padStart(3, '0')}`;
1983
+ * }
1984
+ * }
1985
+ * ```
1986
+ *
1987
+ * @example
1988
+ * **High-performance ID generation with custom increments:**
1989
+ * ```ts
1990
+ * class TicketService {
1991
+ * // Generate ticket numbers in increments of 10 for better distribution
1992
+ * ticketSequence = $sequence({
1993
+ * name: "ticket_numbers",
1994
+ * start: 1000,
1995
+ * increment: 10,
1996
+ * min: 1000,
1997
+ * max: 999999,
1998
+ * cycle: false // Don't cycle when max is reached
1999
+ * });
2000
+ *
2001
+ * priorityTicketSequence = $sequence({
2002
+ * name: "priority_ticket_numbers",
2003
+ * start: 1,
2004
+ * increment: 1,
2005
+ * min: 1,
2006
+ * max: 999,
2007
+ * cycle: true // Cycle when reaching max
2008
+ * });
2009
+ *
2010
+ * async generateTicketNumber(isPriority: boolean = false): Promise<number> {
2011
+ * if (isPriority) {
2012
+ * return await this.priorityTicketSequence.next();
2013
+ * }
2014
+ * return await this.ticketSequence.next();
2015
+ * }
2016
+ *
2017
+ * async getSequenceStatus() {
2018
+ * return {
2019
+ * currentTicketNumber: await this.ticketSequence.current(),
2020
+ * currentPriorityNumber: await this.priorityTicketSequence.current()
2021
+ * };
2022
+ * }
2023
+ * }
2024
+ * ```
2025
+ *
2026
+ * @example
2027
+ * **Batch processing with sequence-based coordination:**
2028
+ * ```ts
2029
+ * class BatchProcessor {
2030
+ * batchSequence = $sequence({
2031
+ * name: "batch_numbers",
2032
+ * start: 1,
2033
+ * increment: 1
2034
+ * });
2035
+ *
2036
+ * async processBatch(items: any[]) {
2037
+ * const batchNumber = await this.batchSequence.next();
2038
+ *
2039
+ * console.log(`Starting batch processing #${batchNumber} with ${items.length} items`);
2040
+ *
2041
+ * try {
2042
+ * // Process items with batch number for tracking
2043
+ * for (const item of items) {
2044
+ * await this.processItem(item, batchNumber);
2045
+ * }
2046
+ *
2047
+ * await this.auditLogger.log({
2048
+ * event: 'batch_completed',
2049
+ * batchNumber,
2050
+ * itemCount: items.length,
2051
+ * timestamp: new Date()
2052
+ * });
2053
+ *
2054
+ * return { batchNumber, processedCount: items.length };
2055
+ *
2056
+ * } catch (error) {
2057
+ * await this.auditLogger.log({
2058
+ * event: 'batch_failed',
2059
+ * batchNumber,
2060
+ * error: error.message,
2061
+ * timestamp: new Date()
2062
+ * });
2063
+ * throw error;
2064
+ * }
2065
+ * }
2066
+ *
2067
+ * async processItem(item: any, batchNumber: number) {
2068
+ * // Associate item processing with batch number
2069
+ * await this.items.update(item.id, {
2070
+ * ...item.updates,
2071
+ * batchNumber,
2072
+ * processedAt: new Date()
2073
+ * });
2074
+ * }
2075
+ * }
2076
+ * ```
2077
+ *
2078
+ * @example
2079
+ * **Multi-tenant sequence management:**
2080
+ * ```ts
2081
+ * class TenantSequenceService {
2082
+ * // Create tenant-specific sequences
2083
+ * getTenantSequence(tenantId: string, sequenceType: string) {
2084
+ * return $sequence({
2085
+ * name: `${tenantId}_${sequenceType}_seq`,
2086
+ * start: 1,
2087
+ * increment: 1
2088
+ * });
2089
+ * }
2090
+ *
2091
+ * async generateTenantOrderNumber(tenantId: string): Promise<string> {
2092
+ * const sequence = this.getTenantSequence(tenantId, 'orders');
2093
+ * const number = await sequence.next();
2094
+ *
2095
+ * return `${tenantId.toUpperCase()}-ORD-${number.toString().padStart(6, '0')}`;
2096
+ * }
2097
+ *
2098
+ * async generateTenantInvoiceNumber(tenantId: string): Promise<string> {
2099
+ * const sequence = this.getTenantSequence(tenantId, 'invoices');
2100
+ * const number = await sequence.next();
2101
+ *
2102
+ * return `${tenantId.toUpperCase()}-INV-${number.toString().padStart(6, '0')}`;
2103
+ * }
2104
+ *
2105
+ * async getTenantSequenceStatus(tenantId: string) {
2106
+ * const orderSeq = this.getTenantSequence(tenantId, 'orders');
2107
+ * const invoiceSeq = this.getTenantSequence(tenantId, 'invoices');
2108
+ *
2109
+ * return {
2110
+ * tenant: tenantId,
2111
+ * sequences: {
2112
+ * orders: {
2113
+ * current: await orderSeq.current(),
2114
+ * next: await orderSeq.next()
2115
+ * },
2116
+ * invoices: {
2117
+ * current: await invoiceSeq.current()
2118
+ * }
2119
+ * }
2120
+ * };
2121
+ * }
2122
+ * }
2123
+ * ```
2124
+ *
2125
+ * **Important Notes**:
2126
+ * - Sequences are created automatically when first used
2127
+ * - PostgreSQL sequences are atomic and handle high concurrency
2128
+ * - Sequence values are not rolled back in failed transactions
2129
+ * - Consider the impact of max values and cycling behavior
2130
+ * - Sequences are schema-scoped in PostgreSQL
2131
+ *
1180
2132
  * @stability 1
1181
2133
  */
1182
2134
  declare const $sequence: {
@@ -1184,11 +2136,130 @@ declare const $sequence: {
1184
2136
  [KIND]: typeof SequenceDescriptor;
1185
2137
  };
1186
2138
  interface SequenceDescriptorOptions {
2139
+ /**
2140
+ * Name of the PostgreSQL sequence to create.
2141
+ *
2142
+ * This name:
2143
+ * - Must be unique within the database schema
2144
+ * - Should follow PostgreSQL identifier conventions
2145
+ * - Will be used in generated SQL for sequence operations
2146
+ * - Should be descriptive of the sequence's purpose
2147
+ *
2148
+ * If not provided, defaults to the property key where the sequence is declared.
2149
+ *
2150
+ * **Naming Guidelines**:
2151
+ * - Use descriptive names like "order_numbers", "invoice_seq"
2152
+ * - Include the purpose or entity type in the name
2153
+ * - Consider adding "_seq" suffix for clarity
2154
+ * - Use snake_case for consistency with PostgreSQL conventions
2155
+ *
2156
+ * @example "order_numbers"
2157
+ * @example "invoice_sequence"
2158
+ * @example "ticket_numbers_seq"
2159
+ */
1187
2160
  name?: string;
2161
+ /**
2162
+ * The starting value for the sequence.
2163
+ *
2164
+ * This value:
2165
+ * - Determines the first number that will be generated
2166
+ * - Can be any integer within the sequence's range
2167
+ * - Is useful for avoiding conflicts with existing data
2168
+ * - Can be set higher for business reasons (e.g., starting invoices at 1000)
2169
+ *
2170
+ * **Common Patterns**:
2171
+ * - Start at 1 for simple counters
2172
+ * - Start at 1000+ for professional-looking numbers
2173
+ * - Start at current max + 1 when migrating existing data
2174
+ *
2175
+ * @default 1
2176
+ * @example 1 // Simple counter starting at 1
2177
+ * @example 1000 // Professional numbering starting at 1000
2178
+ * @example 10000 // Large starting number for established businesses
2179
+ */
1188
2180
  start?: number;
2181
+ /**
2182
+ * The increment value for each call to next().
2183
+ *
2184
+ * This value:
2185
+ * - Determines how much the sequence increases each time
2186
+ * - Can be any positive or negative integer
2187
+ * - Affects the gaps between generated numbers
2188
+ * - Can be used for number distribution strategies
2189
+ *
2190
+ * **Use Cases**:
2191
+ * - increment: 1 for consecutive numbering
2192
+ * - increment: 10 for distributed numbering (leaves gaps for manual entries)
2193
+ * - increment: -1 for countdown sequences
2194
+ *
2195
+ * @default 1
2196
+ * @example 1 // Standard consecutive numbering
2197
+ * @example 10 // Leave gaps between numbers
2198
+ * @example 100 // Large gaps for special numbering schemes
2199
+ */
1189
2200
  increment?: number;
2201
+ /**
2202
+ * The minimum value the sequence can generate.
2203
+ *
2204
+ * When the sequence reaches this value:
2205
+ * - It cannot generate values below this minimum
2206
+ * - Helps prevent negative numbers in contexts where they don't make sense
2207
+ * - Works with cycling behavior to define the lower bound
2208
+ *
2209
+ * **Considerations**:
2210
+ * - Set to 1 for positive-only sequences
2211
+ * - Set to 0 if zero values are acceptable
2212
+ * - Consider business rules about minimum valid numbers
2213
+ *
2214
+ * @example 1 // No zero or negative values
2215
+ * @example 0 // Allow zero values
2216
+ * @example 1000 // Maintain minimum professional appearance
2217
+ */
1190
2218
  min?: number;
2219
+ /**
2220
+ * The maximum value the sequence can generate.
2221
+ *
2222
+ * When the sequence reaches this value:
2223
+ * - It cannot generate values above this maximum
2224
+ * - Behavior depends on the cycle option
2225
+ * - Useful for preventing overflow or limiting number ranges
2226
+ *
2227
+ * **Planning Considerations**:
2228
+ * - Consider the expected volume of your application
2229
+ * - Account for business growth over time
2230
+ * - Factor in any formatting constraints (e.g., fixed-width displays)
2231
+ * - Remember that PostgreSQL sequences can handle very large numbers
2232
+ *
2233
+ * @example 999999 // Six-digit limit
2234
+ * @example 2147483647 // Maximum 32-bit signed integer
2235
+ * @example 9999 // Four-digit limit for display purposes
2236
+ */
1191
2237
  max?: number;
2238
+ /**
2239
+ * Whether the sequence should cycle back to the minimum when it reaches the maximum.
2240
+ *
2241
+ * **cycle: true**:
2242
+ * - When max is reached, next value will be the minimum value
2243
+ * - Useful for scenarios where number reuse is acceptable
2244
+ * - Common for temporary identifiers or rotating references
2245
+ *
2246
+ * **cycle: false (default)**:
2247
+ * - When max is reached, further calls will fail with an error
2248
+ * - Prevents unexpected number reuse
2249
+ * - Better for permanent identifiers where uniqueness is critical
2250
+ *
2251
+ * **Use Cases for Cycling**:
2252
+ * - Temporary ticket numbers that can be reused
2253
+ * - Session IDs with limited lifetime
2254
+ * - Batch numbers in rotating systems
2255
+ *
2256
+ * **Avoid Cycling For**:
2257
+ * - Primary keys and permanent identifiers
2258
+ * - Invoice numbers and financial references
2259
+ * - Audit logs and compliance records
2260
+ *
2261
+ * @default false
2262
+ */
1192
2263
  cycle?: boolean;
1193
2264
  }
1194
2265
  declare class SequenceDescriptor extends Descriptor<SequenceDescriptorOptions> {
@@ -1202,11 +2273,482 @@ declare class SequenceDescriptor extends Descriptor<SequenceDescriptorOptions> {
1202
2273
  //#endregion
1203
2274
  //#region src/descriptors/$transaction.d.ts
1204
2275
  /**
2276
+ * Creates a transaction descriptor for database operations requiring atomicity and consistency.
2277
+ *
2278
+ * This descriptor provides a convenient way to wrap database operations in PostgreSQL
2279
+ * transactions, ensuring ACID properties and automatic retry logic for version conflicts.
2280
+ * It integrates seamlessly with the repository pattern and provides built-in handling
2281
+ * for optimistic locking scenarios with automatic retry on version mismatches.
2282
+ *
2283
+ * **Key Features**
2284
+ *
2285
+ * - **ACID Compliance**: Full transaction support with commit/rollback functionality
2286
+ * - **Automatic Retry Logic**: Built-in retry for optimistic locking conflicts
2287
+ * - **Type Safety**: Full TypeScript support with generic parameters
2288
+ * - **Isolation Levels**: Configurable transaction isolation levels
2289
+ * - **Error Handling**: Automatic rollback on errors with proper error propagation
2290
+ * - **Repository Integration**: Seamless integration with $repository operations
2291
+ * - **Performance**: Efficient transaction management with connection reuse
2292
+ *
2293
+ * **Use Cases**
2294
+ *
2295
+ * Essential for operations requiring atomicity and consistency:
2296
+ * - Financial transactions and accounting operations
2297
+ * - Complex business workflows with multiple database operations
2298
+ * - Data migrations and bulk operations
2299
+ * - E-commerce order processing with inventory updates
2300
+ * - User registration with related data creation
2301
+ * - Audit trail creation with business operations
2302
+ *
2303
+ * @example
2304
+ * **Basic transaction for financial operations:**
2305
+ * ```ts
2306
+ * import { $transaction } from "alepha/postgres";
2307
+ *
2308
+ * class BankingService {
2309
+ * transfer = $transaction({
2310
+ * handler: async (tx, fromAccountId: string, toAccountId: string, amount: number) => {
2311
+ * // All operations within this transaction are atomic
2312
+ * console.log(`Processing transfer: $${amount} from ${fromAccountId} to ${toAccountId}`);
2313
+ *
2314
+ * // Get current account balances
2315
+ * const fromAccount = await this.accounts.findById(fromAccountId, { tx });
2316
+ * const toAccount = await this.accounts.findById(toAccountId, { tx });
2317
+ *
2318
+ * // Validate sufficient balance
2319
+ * if (fromAccount.balance < amount) {
2320
+ * throw new Error(`Insufficient funds. Balance: $${fromAccount.balance}, Required: $${amount}`);
2321
+ * }
2322
+ *
2323
+ * // Update account balances atomically
2324
+ * const updatedFromAccount = await this.accounts.updateById(
2325
+ * fromAccountId,
2326
+ * { balance: fromAccount.balance - amount },
2327
+ * { tx }
2328
+ * );
2329
+ *
2330
+ * const updatedToAccount = await this.accounts.updateById(
2331
+ * toAccountId,
2332
+ * { balance: toAccount.balance + amount },
2333
+ * { tx }
2334
+ * );
2335
+ *
2336
+ * // Create transaction record
2337
+ * const transactionRecord = await this.transactions.create({
2338
+ * id: generateUUID(),
2339
+ * fromAccountId,
2340
+ * toAccountId,
2341
+ * amount,
2342
+ * type: 'transfer',
2343
+ * status: 'completed',
2344
+ * processedAt: new Date().toISOString()
2345
+ * }, { tx });
2346
+ *
2347
+ * console.log(`Transfer completed successfully: ${transactionRecord.id}`);
2348
+ *
2349
+ * return {
2350
+ * transactionId: transactionRecord.id,
2351
+ * fromBalance: updatedFromAccount.balance,
2352
+ * toBalance: updatedToAccount.balance
2353
+ * };
2354
+ * }
2355
+ * });
2356
+ *
2357
+ * async transferFunds(fromAccountId: string, toAccountId: string, amount: number) {
2358
+ * // This will automatically retry if there's a version mismatch (optimistic locking)
2359
+ * return await this.transfer.run(fromAccountId, toAccountId, amount);
2360
+ * }
2361
+ * }
2362
+ * ```
2363
+ *
2364
+ * @example
2365
+ * **E-commerce order processing with inventory management:**
2366
+ * ```ts
2367
+ * class OrderService {
2368
+ * processOrder = $transaction({
2369
+ * config: {
2370
+ * isolationLevel: 'serializable' // Highest isolation for critical operations
2371
+ * },
2372
+ * handler: async (tx, orderData: {
2373
+ * customerId: string;
2374
+ * items: Array<{ productId: string; quantity: number; price: number }>;
2375
+ * shippingAddress: Address;
2376
+ * paymentMethodId: string;
2377
+ * }) => {
2378
+ * console.log(`Processing order for customer ${orderData.customerId}`);
2379
+ *
2380
+ * let totalAmount = 0;
2381
+ * const orderItems = [];
2382
+ *
2383
+ * // Process each item and update inventory atomically
2384
+ * for (const itemData of orderData.items) {
2385
+ * const product = await this.products.findById(itemData.productId, { tx });
2386
+ *
2387
+ * // Check inventory availability
2388
+ * if (product.stockQuantity < itemData.quantity) {
2389
+ * throw new Error(`Insufficient stock for ${product.name}. Available: ${product.stockQuantity}, Requested: ${itemData.quantity}`);
2390
+ * }
2391
+ *
2392
+ * // Update product inventory with optimistic locking
2393
+ * await this.products.save({
2394
+ * ...product,
2395
+ * stockQuantity: product.stockQuantity - itemData.quantity
2396
+ * }, { tx });
2397
+ *
2398
+ * // Calculate totals
2399
+ * const lineTotal = itemData.price * itemData.quantity;
2400
+ * totalAmount += lineTotal;
2401
+ *
2402
+ * orderItems.push({
2403
+ * id: generateUUID(),
2404
+ * productId: itemData.productId,
2405
+ * quantity: itemData.quantity,
2406
+ * unitPrice: itemData.price,
2407
+ * lineTotal
2408
+ * });
2409
+ * }
2410
+ *
2411
+ * // Create the main order record
2412
+ * const order = await this.orders.create({
2413
+ * id: generateUUID(),
2414
+ * customerId: orderData.customerId,
2415
+ * status: 'pending',
2416
+ * totalAmount,
2417
+ * shippingAddress: orderData.shippingAddress,
2418
+ * createdAt: new Date().toISOString()
2419
+ * }, { tx });
2420
+ *
2421
+ * // Create order items
2422
+ * for (const itemData of orderItems) {
2423
+ * await this.orderItems.create({
2424
+ * ...itemData,
2425
+ * orderId: order.id
2426
+ * }, { tx });
2427
+ * }
2428
+ *
2429
+ * // Process payment
2430
+ * const paymentResult = await this.paymentService.processPayment({
2431
+ * orderId: order.id,
2432
+ * amount: totalAmount,
2433
+ * paymentMethodId: orderData.paymentMethodId,
2434
+ * customerId: orderData.customerId
2435
+ * }, { tx });
2436
+ *
2437
+ * if (!paymentResult.success) {
2438
+ * throw new Error(`Payment failed: ${paymentResult.error}`);
2439
+ * }
2440
+ *
2441
+ * // Update order status
2442
+ * const completedOrder = await this.orders.updateById(
2443
+ * order.id,
2444
+ * {
2445
+ * status: 'paid',
2446
+ * paymentId: paymentResult.paymentId,
2447
+ * paidAt: new Date().toISOString()
2448
+ * },
2449
+ * { tx }
2450
+ * );
2451
+ *
2452
+ * console.log(`Order processed successfully: ${order.id}`);
2453
+ *
2454
+ * return {
2455
+ * orderId: order.id,
2456
+ * totalAmount,
2457
+ * paymentId: paymentResult.paymentId,
2458
+ * itemCount: orderItems.length
2459
+ * };
2460
+ * }
2461
+ * });
2462
+ * }
2463
+ * ```
2464
+ *
2465
+ * @example
2466
+ * **User registration with related data creation:**
2467
+ * ```ts
2468
+ * class UserService {
2469
+ * registerUser = $transaction({
2470
+ * handler: async (tx, registrationData: {
2471
+ * email: string;
2472
+ * password: string;
2473
+ * profile: {
2474
+ * firstName: string;
2475
+ * lastName: string;
2476
+ * dateOfBirth: string;
2477
+ * };
2478
+ * preferences: {
2479
+ * notifications: boolean;
2480
+ * newsletter: boolean;
2481
+ * };
2482
+ * }) => {
2483
+ * console.log(`Registering new user: ${registrationData.email}`);
2484
+ *
2485
+ * // Check if email already exists
2486
+ * const existingUser = await this.users.find(
2487
+ * { where: { email: registrationData.email } },
2488
+ * { tx }
2489
+ * );
2490
+ *
2491
+ * if (existingUser.length > 0) {
2492
+ * throw new Error(`User with email ${registrationData.email} already exists`);
2493
+ * }
2494
+ *
2495
+ * // Hash password
2496
+ * const hashedPassword = await this.hashPassword(registrationData.password);
2497
+ *
2498
+ * // Create user record
2499
+ * const user = await this.users.create({
2500
+ * id: generateUUID(),
2501
+ * email: registrationData.email,
2502
+ * passwordHash: hashedPassword,
2503
+ * isActive: true,
2504
+ * emailVerified: false
2505
+ * }, { tx });
2506
+ *
2507
+ * // Create user profile
2508
+ * const profile = await this.userProfiles.create({
2509
+ * id: generateUUID(),
2510
+ * userId: user.id,
2511
+ * firstName: registrationData.profile.firstName,
2512
+ * lastName: registrationData.profile.lastName,
2513
+ * dateOfBirth: registrationData.profile.dateOfBirth
2514
+ * }, { tx });
2515
+ *
2516
+ * // Create user preferences
2517
+ * const preferences = await this.userPreferences.create({
2518
+ * id: generateUUID(),
2519
+ * userId: user.id,
2520
+ * notifications: registrationData.preferences.notifications,
2521
+ * newsletter: registrationData.preferences.newsletter
2522
+ * }, { tx });
2523
+ *
2524
+ * // Create audit log entry
2525
+ * await this.auditLogs.create({
2526
+ * id: generateUUID(),
2527
+ * userId: user.id,
2528
+ * action: 'user_registered',
2529
+ * details: { email: user.email },
2530
+ * timestamp: new Date().toISOString()
2531
+ * }, { tx });
2532
+ *
2533
+ * console.log(`User registration completed: ${user.id}`);
2534
+ *
2535
+ * return {
2536
+ * userId: user.id,
2537
+ * email: user.email,
2538
+ * profile: {
2539
+ * firstName: profile.firstName,
2540
+ * lastName: profile.lastName
2541
+ * }
2542
+ * };
2543
+ * }
2544
+ * });
2545
+ * }
2546
+ * ```
2547
+ *
2548
+ * @example
2549
+ * **Data migration with progress tracking:**
2550
+ * ```ts
2551
+ * class MigrationService {
2552
+ * migrateUserData = $transaction({
2553
+ * config: {
2554
+ * isolationLevel: 'read_committed',
2555
+ * accessMode: 'read_write'
2556
+ * },
2557
+ * handler: async (tx, batchSize: number = 1000) => {
2558
+ * console.log(`Starting data migration with batch size ${batchSize}`);
2559
+ *
2560
+ * let totalMigrated = 0;
2561
+ * let hasMore = true;
2562
+ * let offset = 0;
2563
+ *
2564
+ * while (hasMore) {
2565
+ * // Get batch of users to migrate
2566
+ * const users = await this.legacyUsers.find({
2567
+ * limit: batchSize,
2568
+ * offset,
2569
+ * sort: { id: 'asc' }
2570
+ * }, { tx });
2571
+ *
2572
+ * if (users.length === 0) {
2573
+ * hasMore = false;
2574
+ * break;
2575
+ * }
2576
+ *
2577
+ * // Process each user in the batch
2578
+ * for (const legacyUser of users) {
2579
+ * try {
2580
+ * // Transform legacy data to new format
2581
+ * const newUser = {
2582
+ * id: generateUUID(),
2583
+ * email: legacyUser.email_address,
2584
+ * firstName: legacyUser.first_name,
2585
+ * lastName: legacyUser.last_name,
2586
+ * createdAt: legacyUser.created_date,
2587
+ * isActive: legacyUser.status === 'active'
2588
+ * };
2589
+ *
2590
+ * // Create new user record
2591
+ * await this.users.create(newUser, { tx });
2592
+ *
2593
+ * // Mark legacy user as migrated
2594
+ * await this.legacyUsers.updateById(
2595
+ * legacyUser.id,
2596
+ * {
2597
+ * migrated: true,
2598
+ * migratedAt: new Date().toISOString(),
2599
+ * newUserId: newUser.id
2600
+ * },
2601
+ * { tx }
2602
+ * );
2603
+ *
2604
+ * totalMigrated++;
2605
+ *
2606
+ * } catch (error) {
2607
+ * console.error(`Failed to migrate user ${legacyUser.id}:`, error.message);
2608
+ *
2609
+ * // Log failed migration
2610
+ * await this.migrationErrors.create({
2611
+ * id: generateUUID(),
2612
+ * legacyUserId: legacyUser.id,
2613
+ * error: error.message,
2614
+ * attemptedAt: new Date().toISOString()
2615
+ * }, { tx });
2616
+ * }
2617
+ * }
2618
+ *
2619
+ * offset += batchSize;
2620
+ * console.log(`Migrated ${totalMigrated} users so far...`);
2621
+ * }
2622
+ *
2623
+ * // Update migration status
2624
+ * await this.migrationStatus.updateById(
2625
+ * 'user_migration',
2626
+ * {
2627
+ * totalMigrated,
2628
+ * completedAt: new Date().toISOString(),
2629
+ * status: 'completed'
2630
+ * },
2631
+ * { tx }
2632
+ * );
2633
+ *
2634
+ * console.log(`Migration completed. Total migrated: ${totalMigrated}`);
2635
+ *
2636
+ * return { totalMigrated };
2637
+ * }
2638
+ * });
2639
+ * }
2640
+ * ```
2641
+ *
2642
+ * **Important Notes**:
2643
+ * - All operations within the transaction handler are atomic
2644
+ * - Automatic retry on `PgVersionMismatchError` for optimistic locking
2645
+ * - Pass `{ tx }` option to all repository operations within the transaction
2646
+ * - Transactions are automatically rolled back on any unhandled error
2647
+ * - Use appropriate isolation levels based on your consistency requirements
2648
+ *
1205
2649
  * @stability 2
1206
2650
  */
1207
2651
  declare const $transaction: <T extends any[], R>(opts: TransactionDescriptorOptions<T, R>) => _alepha_retry0.RetryDescriptorFn<(...args: T) => Promise<R>>;
1208
2652
  interface TransactionDescriptorOptions<T extends any[], R> {
2653
+ /**
2654
+ * Transaction handler function that contains all database operations to be executed atomically.
2655
+ *
2656
+ * This function:
2657
+ * - Receives a transaction object as the first parameter
2658
+ * - Should pass the transaction to all repository operations via `{ tx }` option
2659
+ * - All operations within are automatically rolled back if any error occurs
2660
+ * - Has access to the full Alepha dependency injection container
2661
+ * - Will be automatically retried if a `PgVersionMismatchError` occurs
2662
+ *
2663
+ * **Transaction Guidelines**:
2664
+ * - Keep transactions as short as possible to minimize lock contention
2665
+ * - Always pass the `tx` parameter to repository operations
2666
+ * - Handle expected business errors gracefully
2667
+ * - Log important operations for debugging and audit trails
2668
+ * - Consider the impact of long-running transactions on performance
2669
+ *
2670
+ * **Error Handling**:
2671
+ * - Throwing any error will automatically roll back the transaction
2672
+ * - `PgVersionMismatchError` triggers automatic retry logic
2673
+ * - Other database errors will be propagated after rollback
2674
+ * - Use try-catch within the handler for business-specific error handling
2675
+ *
2676
+ * @param tx - The PostgreSQL transaction object to use for all database operations
2677
+ * @param ...args - Additional arguments passed to the transaction function
2678
+ * @returns Promise resolving to the transaction result
2679
+ *
2680
+ * @example
2681
+ * ```ts
2682
+ * handler: async (tx, orderId: string, newStatus: string) => {
2683
+ * // Get the current order (with transaction)
2684
+ * const order = await this.orders.findById(orderId, { tx });
2685
+ *
2686
+ * // Validate business rules
2687
+ * if (!this.isValidStatusTransition(order.status, newStatus)) {
2688
+ * throw new Error(`Invalid status transition: ${order.status} -> ${newStatus}`);
2689
+ * }
2690
+ *
2691
+ * // Update order status (with transaction)
2692
+ * const updatedOrder = await this.orders.updateById(
2693
+ * orderId,
2694
+ * { status: newStatus },
2695
+ * { tx }
2696
+ * );
2697
+ *
2698
+ * // Create audit log (with transaction)
2699
+ * await this.auditLogs.create({
2700
+ * id: generateUUID(),
2701
+ * entityId: orderId,
2702
+ * action: 'status_change',
2703
+ * oldValue: order.status,
2704
+ * newValue: newStatus,
2705
+ * timestamp: new Date().toISOString()
2706
+ * }, { tx });
2707
+ *
2708
+ * return updatedOrder;
2709
+ * }
2710
+ * ```
2711
+ */
1209
2712
  handler: (tx: PgTransaction<any, any, any>, ...args: T) => Promise<R>;
2713
+ /**
2714
+ * PostgreSQL transaction configuration options.
2715
+ *
2716
+ * This allows you to customize transaction behavior including:
2717
+ * - **Isolation Level**: Controls visibility of concurrent transaction changes
2718
+ * - **Access Mode**: Whether the transaction is read-only or read-write
2719
+ * - **Deferrable**: For serializable transactions, allows deferring to avoid conflicts
2720
+ *
2721
+ * **Isolation Levels**:
2722
+ * - **read_uncommitted**: Lowest isolation, allows dirty reads (rarely used)
2723
+ * - **read_committed**: Default level, prevents dirty reads
2724
+ * - **repeatable_read**: Prevents dirty and non-repeatable reads
2725
+ * - **serializable**: Highest isolation, full ACID compliance
2726
+ *
2727
+ * **Access Modes**:
2728
+ * - **read_write**: Default, allows both read and write operations
2729
+ * - **read_only**: Only allows read operations, can provide performance benefits
2730
+ *
2731
+ * **When to Use Different Isolation Levels**:
2732
+ * - **read_committed**: Most common operations, good balance of consistency and performance
2733
+ * - **repeatable_read**: When you need consistent reads throughout the transaction
2734
+ * - **serializable**: Critical financial operations, when absolute consistency is required
2735
+ *
2736
+ * @example
2737
+ * ```ts
2738
+ * config: {
2739
+ * isolationLevel: 'serializable', // Highest consistency for financial operations
2740
+ * accessMode: 'read_write'
2741
+ * }
2742
+ * ```
2743
+ *
2744
+ * @example
2745
+ * ```ts
2746
+ * config: {
2747
+ * isolationLevel: 'read_committed', // Default level for most operations
2748
+ * accessMode: 'read_only' // Performance optimization for read-only operations
2749
+ * }
2750
+ * ```
2751
+ */
1210
2752
  config?: PgTransactionConfig$1;
1211
2753
  }
1212
2754
  type TransactionContext = PgTransaction<any, any, any>;
@@ -1222,13 +2764,13 @@ declare class PgEntityNotFoundError extends PgError {
1222
2764
  declare class RepositoryProvider {
1223
2765
  protected readonly log: _alepha_logger1.Logger;
1224
2766
  protected readonly alepha: Alepha;
1225
- protected get repositories(): RepositoryDescriptor<TableConfig$1, TObject$1>[];
2767
+ protected get repositories(): RepositoryDescriptor<TableConfig$1, TObject>[];
1226
2768
  /**
1227
2769
  * Get all repositories.
1228
2770
  *
1229
2771
  * @param provider - Filter by provider.
1230
2772
  */
1231
- getRepositories(provider?: PostgresProvider): RepositoryDescriptor<TableConfig$1, TObject$1>[];
2773
+ getRepositories(provider?: PostgresProvider): RepositoryDescriptor<TableConfig$1, TObject>[];
1232
2774
  /**
1233
2775
  * Get all tables from the repositories.
1234
2776
  *
@@ -1281,7 +2823,7 @@ declare class DrizzleKitProvider {
1281
2823
  declare module "alepha" {
1282
2824
  interface Env extends Partial<Static<typeof envSchema>> {}
1283
2825
  }
1284
- declare const envSchema: TObject$1<{
2826
+ declare const envSchema: TObject<{
1285
2827
  /**
1286
2828
  * Main configuration for database connection.
1287
2829
  * Accept a string in the format of a Postgres connection URL.
@@ -1345,7 +2887,7 @@ declare class NodePostgresProvider extends PostgresProvider {
1345
2887
  get db(): PostgresJsDatabase;
1346
2888
  protected readonly configure: _alepha_core1.HookDescriptor<"start">;
1347
2889
  protected readonly stop: _alepha_core1.HookDescriptor<"stop">;
1348
- execute<T extends TObject$1 = any>(query: SQLLike, schema?: T): Promise<Array<T extends TObject$1 ? Static<T> : any>>;
2890
+ execute<T extends TObject | undefined = undefined>(query: SQLLike, schema?: T): Promise<Array<T extends TObject ? Static<T> : any>>;
1349
2891
  connect(): Promise<void>;
1350
2892
  close(): Promise<void>;
1351
2893
  protected migrate: _alepha_lock0.LockDescriptor<() => Promise<void>>;
@@ -1373,25 +2915,20 @@ declare class NodePostgresProvider extends PostgresProvider {
1373
2915
  * TODO: options to skip deletion on failure, in order to inspect the schema?
1374
2916
  */
1375
2917
  protected generateTestSchemaName(): string;
1376
- protected mapResult<T extends TObject$1 = any>(result: Array<any>, schema?: T): Array<T extends TObject$1 ? Static<T> : any>;
2918
+ protected mapResult<T extends TObject = any>(result: Array<any>, schema?: T): Array<T extends TObject ? Static<T> : any>;
1377
2919
  }
1378
2920
  //#endregion
1379
2921
  //#region src/providers/PostgresTypeProvider.d.ts
1380
- declare module "alepha" {
1381
- interface TypeProvider {
1382
- pg: PostgresTypeProvider;
1383
- }
1384
- }
1385
2922
  declare class PostgresTypeProvider {
1386
- readonly attr: <T extends TSchema$2, Attr extends PgSymbolKeys>(type: T, attr: Attr, value?: PgSymbols[Attr]) => PgAttr<T, Attr>;
2923
+ readonly attr: <T extends TSchema$1, Attr extends PgSymbolKeys>(type: T, attr: Attr, value?: PgSymbols[Attr]) => PgAttr<T, Attr>;
1387
2924
  /**
1388
2925
  * Creates a primary key with an identity column.
1389
2926
  */
1390
- readonly identityPrimaryKey: (identity?: PgIdentityOptions, options?: IntegerOptions) => PgAttr<PgAttr<PgAttr<TInteger, typeof PG_PRIMARY_KEY>, typeof PG_IDENTITY>, typeof PG_DEFAULT>;
2927
+ readonly identityPrimaryKey: (identity?: PgIdentityOptions, options?: TNumberOptions) => PgAttr<PgAttr<PgAttr<TInteger, typeof PG_PRIMARY_KEY>, typeof PG_IDENTITY>, typeof PG_DEFAULT>;
1391
2928
  /**
1392
2929
  * Creates a primary key with a big identity column. (default)
1393
2930
  */
1394
- readonly bigIdentityPrimaryKey: (identity?: PgIdentityOptions, options?: NumberOptions) => PgAttr<PgAttr<PgAttr<TNumber, typeof PG_PRIMARY_KEY>, typeof PG_IDENTITY>, typeof PG_DEFAULT>;
2931
+ readonly bigIdentityPrimaryKey: (identity?: PgIdentityOptions, options?: TNumberOptions) => PgAttr<PgAttr<PgAttr<TNumber, typeof PG_PRIMARY_KEY>, typeof PG_IDENTITY>, typeof PG_DEFAULT>;
1395
2932
  /**
1396
2933
  * Creates a primary key with a UUID column.
1397
2934
  */
@@ -1403,14 +2940,15 @@ declare class PostgresTypeProvider {
1403
2940
  * - `t.uuid()` -> PG UUID
1404
2941
  */
1405
2942
  primaryKey(): PgAttr<PgAttr<TInteger, PgPrimaryKey>, PgDefault>;
1406
- primaryKey(type: TString, options?: StringOptions): PgAttr<PgAttr<TString, PgPrimaryKey>, PgDefault>;
1407
- primaryKey(type: TInteger, options?: IntegerOptions, identity?: PgIdentityOptions): PgAttr<PgAttr<TInteger, PgPrimaryKey>, PgDefault>;
1408
- primaryKey(type: TNumber, options?: NumberOptions, identity?: PgIdentityOptions): PgAttr<PgAttr<TNumber, PgPrimaryKey>, PgDefault>;
2943
+ primaryKey(type: TString, options?: TStringOptions): PgAttr<PgAttr<TString, PgPrimaryKey>, PgDefault>;
2944
+ primaryKey(type: TInteger, options?: TNumberOptions, identity?: PgIdentityOptions): PgAttr<PgAttr<TInteger, PgPrimaryKey>, PgDefault>;
2945
+ primaryKey(type: TNumber, options?: TNumberOptions, identity?: PgIdentityOptions): PgAttr<PgAttr<TNumber, PgPrimaryKey>, PgDefault>;
2946
+ primaryKey(type: TBigInt, options?: TNumberOptions, identity?: PgIdentityOptions): PgAttr<PgAttr<TBigInt, PgPrimaryKey>, PgDefault>;
1409
2947
  /**
1410
2948
  * Wrap a schema with "default" attribute.
1411
2949
  * This is used to set a default value for a column in the database.
1412
2950
  */
1413
- readonly default: <T extends TSchema$2>(type: T, value?: Static$1<T>) => PgAttr<T, PgDefault>;
2951
+ readonly default: <T extends TSchema$1>(type: T, value?: Static<T>) => PgAttr<T, PgDefault>;
1414
2952
  /**
1415
2953
  * Creates a column 'version'.
1416
2954
  *
@@ -1421,41 +2959,33 @@ declare class PostgresTypeProvider {
1421
2959
  * @see {@link RepositoryDescriptor#save}
1422
2960
  * @see {@link PgVersionMismatchError}
1423
2961
  */
1424
- readonly version: (options?: IntegerOptions) => PgAttr<PgAttr<TInteger, typeof PG_VERSION>, typeof PG_DEFAULT>;
2962
+ readonly version: (options?: TNumberOptions) => PgAttr<PgAttr<TInteger, typeof PG_VERSION>, typeof PG_DEFAULT>;
1425
2963
  /**
1426
2964
  * Creates a column Created At. So just a datetime column with a default value of the current timestamp.
1427
2965
  */
1428
- readonly createdAt: (options?: StringOptions) => PgAttr<PgAttr<TString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
2966
+ readonly createdAt: (options?: TStringOptions) => PgAttr<PgAttr<TString, typeof PG_CREATED_AT>, typeof PG_DEFAULT>;
1429
2967
  /**
1430
2968
  * Creates a column Updated At. Like createdAt, but it is updated on every update of the row.
1431
2969
  */
1432
- readonly updatedAt: (options?: StringOptions) => PgAttr<PgAttr<TString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
2970
+ readonly updatedAt: (options?: TStringOptions) => PgAttr<PgAttr<TString, typeof PG_UPDATED_AT>, typeof PG_DEFAULT>;
1433
2971
  /**
1434
2972
  * Creates a column Deleted At for soft delete functionality.
1435
2973
  * This is used to mark rows as deleted without actually removing them from the database.
1436
2974
  * The column is nullable - NULL means not deleted, timestamp means deleted.
1437
2975
  */
1438
- readonly deletedAt: (options?: StringOptions) => PgAttr<_sinclair_typebox1.TOptional<TString>, typeof PG_DELETED_AT>;
2976
+ readonly deletedAt: (options?: TStringOptions) => PgAttr<typebox1.TOptional<TString>, typeof PG_DELETED_AT>;
1439
2977
  /**
1440
2978
  * Creates a reference to another table or schema. Basically a foreign key.
1441
2979
  */
1442
- readonly ref: <T extends TSchema$2>(type: T, ref: () => any, actions?: {
2980
+ readonly ref: <T extends TSchema$1>(type: T, ref: () => any, actions?: {
1443
2981
  onUpdate?: UpdateDeleteAction$1;
1444
2982
  onDelete?: UpdateDeleteAction$1;
1445
2983
  }) => PgAttr<T, PgRef>;
1446
- /**
1447
- * Convert a schema to a schema for INSERT operations.
1448
- * It means that:
1449
- * - All pg.default() will be optional
1450
- *
1451
- * @internal
1452
- */
1453
- readonly insert: <T extends TObject$1>(obj: T) => TInsertObject<T>;
1454
2984
  /**
1455
2985
  * Creates a page schema for a given object schema.
1456
2986
  * It's used by {@link RepositoryDescriptor#paginate} method.
1457
2987
  */
1458
- readonly page: <T extends TObject$1>(resource: T, options?: ObjectOptions) => TPage<T>;
2988
+ readonly page: <T extends TObject>(resource: T, options?: TObjectOptions) => TPage<T>;
1459
2989
  }
1460
2990
  declare const pg: PostgresTypeProvider;
1461
2991
  //#endregion
@@ -1463,24 +2993,20 @@ declare const pg: PostgresTypeProvider;
1463
2993
  /**
1464
2994
  * @deprecated Use `pg.primaryKey()` instead.
1465
2995
  */
1466
- declare const legacyIdSchema: PgAttr<PgAttr<PgAttr<_sinclair_typebox1.TInteger, typeof PG_PRIMARY_KEY>, typeof PG_SERIAL>, typeof PG_DEFAULT>;
2996
+ declare const legacyIdSchema: PgAttr<PgAttr<PgAttr<typebox1.TInteger, typeof PG_PRIMARY_KEY>, typeof PG_SERIAL>, typeof PG_DEFAULT>;
1467
2997
  //#endregion
1468
2998
  //#region src/types/schema.d.ts
1469
2999
  /**
1470
3000
  * Postgres schema type.
1471
3001
  */
1472
- declare const schema: <TDocument extends TSchema$2>(name: string, document: TDocument) => drizzle_orm7.$Type<pg$1.PgCustomColumnBuilder<{
3002
+ declare const schema: <TDocument extends TSchema$1>(name: string, document: TDocument) => drizzle_orm6.$Type<pg$1.PgCustomColumnBuilder<{
1473
3003
  name: string;
1474
3004
  dataType: "custom";
1475
3005
  columnType: "PgCustomColumn";
1476
- data: (TDocument & {
1477
- params: [];
1478
- })["static"];
3006
+ data: Static<TDocument>;
1479
3007
  driverParam: string;
1480
3008
  enumValues: undefined;
1481
- }>, (TDocument & {
1482
- params: [];
1483
- })["static"]>;
3009
+ }>, Static<TDocument>>;
1484
3010
  //#endregion
1485
3011
  //#region src/index.d.ts
1486
3012
  /**
@@ -1525,5 +3051,5 @@ declare const schema: <TDocument extends TSchema$2>(name: string, document: TDoc
1525
3051
  */
1526
3052
  declare const AlephaPostgres: _alepha_core1.Service<_alepha_core1.Module>;
1527
3053
  //#endregion
1528
- export { $entity, $repository, $sequence, $transaction, AlephaPostgres, DrizzleKitProvider, Entity, EntityDescriptorOptions, FilterOperators, FromSchema, NodePostgresProvider, NodePostgresProviderOptions, NullToUndefined, NullifyIfOptional, PG_CREATED_AT, PG_DEFAULT, PG_DELETED_AT, PG_IDENTITY, PG_PRIMARY_KEY, PG_REF, PG_SCHEMA, PG_SERIAL, PG_UPDATED_AT, PG_VERSION, Page, PageQuery, PgDefault, PgEntityNotFoundError, PgIdentityOptions, PgPrimaryKey, PgQuery, PgQueryResult, PgQueryWhere, PgQueryWhereOrSQL, PgRef, PgRefOptions, PgSymbolKeys, PgSymbols, PgTableConfig, PgTableWithColumnsAndSchema, PostgresProvider, PostgresTypeProvider, RepositoryDescriptor, RepositoryDescriptorOptions, RepositoryProvider, SQLLike, SequenceDescriptor, SequenceDescriptorOptions, StatementOptions, TInsertObject, TPage, TableLike, TransactionContext, TransactionDescriptorOptions, camelToSnakeCase, drizzle_orm7 as drizzle, legacyIdSchema, mapFieldToColumn, mapStringToColumn, nullToUndefined, pageQuerySchema, pageSchema, pg, schema, schemaToPgColumns, sql };
3054
+ export { $entity, $repository, $sequence, $transaction, AlephaPostgres, DrizzleKitProvider, Entity, EntityDescriptorOptions, FilterOperators, FromSchema, NodePostgresProvider, NodePostgresProviderOptions, PG_CREATED_AT, PG_DEFAULT, PG_DELETED_AT, PG_IDENTITY, PG_PRIMARY_KEY, PG_REF, PG_SCHEMA, PG_SERIAL, PG_UPDATED_AT, PG_VERSION, Page, PageQuery, PgDefault, PgEntityNotFoundError, PgIdentityOptions, PgPrimaryKey, PgQuery, PgQueryResult, PgQueryWhere, PgQueryWhereOrSQL, PgRef, PgRefOptions, PgSymbolKeys, PgSymbols, PgTableConfig, PgTableWithColumnsAndSchema, PostgresProvider, PostgresTypeProvider, RepositoryDescriptor, RepositoryDescriptorOptions, RepositoryProvider, SQLLike, SequenceDescriptor, SequenceDescriptorOptions, StatementOptions, TObjectInsert, TPage, TransactionContext, TransactionDescriptorOptions, camelToSnakeCase, drizzle_orm6 as drizzle, insertSchema, legacyIdSchema, mapFieldToColumn, mapStringToColumn, pageQuerySchema, pageSchema, pg, schema, schemaToPgColumns, sql };
1529
3055
  //# sourceMappingURL=index.d.ts.map