outlet-orm 5.5.3 → 6.5.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/types/index.d.ts CHANGED
@@ -153,6 +153,20 @@ declare module 'outlet-orm' {
153
153
  to: number;
154
154
  }
155
155
 
156
+ /** Observer interface for model lifecycle events */
157
+ export interface ModelObserver<T extends Model = Model> {
158
+ creating?(model: T): boolean | void | Promise<boolean | void>;
159
+ created?(model: T): void | Promise<void>;
160
+ updating?(model: T): boolean | void | Promise<boolean | void>;
161
+ updated?(model: T): void | Promise<void>;
162
+ saving?(model: T): boolean | void | Promise<boolean | void>;
163
+ saved?(model: T): void | Promise<void>;
164
+ deleting?(model: T): boolean | void | Promise<boolean | void>;
165
+ deleted?(model: T): void | Promise<void>;
166
+ restoring?(model: T): boolean | void | Promise<boolean | void>;
167
+ restored?(model: T): void | Promise<void>;
168
+ }
169
+
156
170
  export class QueryBuilder<T extends Model> {
157
171
  constructor(model: typeof Model);
158
172
 
@@ -204,6 +218,12 @@ declare module 'outlet-orm' {
204
218
  get(): Promise<T[]>;
205
219
  first(): Promise<T | null>;
206
220
  firstOrFail(): Promise<T>;
221
+ /** Get the first record matching current wheres or create a new one */
222
+ firstOrCreate(values?: Record<string, any>): Promise<T>;
223
+ /** Get the first record matching current wheres or return a new unsaved instance */
224
+ firstOrNew(values?: Record<string, any>): Promise<T>;
225
+ /** Find a record matching current wheres and update it, or create a new one */
226
+ updateOrCreate(values?: Record<string, any>): Promise<T>;
207
227
  paginate(page?: number, perPage?: number): Promise<PaginationResult<T>>;
208
228
  count(): Promise<number>;
209
229
  exists(): Promise<boolean>;
@@ -214,6 +234,8 @@ declare module 'outlet-orm' {
214
234
  delete(): Promise<any>;
215
235
  increment(column: string, amount?: number): Promise<any>;
216
236
  decrement(column: string, amount?: number): Promise<any>;
237
+ /** Lazily iterate over matching records using an async generator */
238
+ cursor(chunkSize?: number): AsyncGenerator<T, void, unknown>;
217
239
 
218
240
  clone(): QueryBuilder<T>;
219
241
  }
@@ -344,6 +366,25 @@ declare module 'outlet-orm' {
344
366
  static whereNull<T extends Model>(this: new () => T, column: string): QueryBuilder<T>;
345
367
  static whereNotNull<T extends Model>(this: new () => T, column: string): QueryBuilder<T>;
346
368
  static count(): Promise<number>;
369
+
370
+ // Convenience query methods
371
+ /** Find the first record matching conditions or create a new one */
372
+ static firstOrCreate<T extends Model>(this: new () => T, conditions: Record<string, any>, values?: Record<string, any>): Promise<T>;
373
+ /** Find the first record matching conditions or return a new unsaved instance */
374
+ static firstOrNew<T extends Model>(this: new () => T, conditions: Record<string, any>, values?: Record<string, any>): Promise<T>;
375
+ /** Find a record matching conditions and update it, or create a new one */
376
+ static updateOrCreate<T extends Model>(this: new () => T, conditions: Record<string, any>, values?: Record<string, any>): Promise<T>;
377
+ /** Insert or update multiple records in bulk (ON CONFLICT / ON DUPLICATE KEY) */
378
+ static upsert(rows: Record<string, any>[], uniqueBy: string | string[], update?: string[]): Promise<any>;
379
+
380
+ // Observer
381
+ /** Register an observer class that listens to model lifecycle events */
382
+ static observe(observer: ModelObserver | (new () => ModelObserver)): void;
383
+
384
+ // Cursor / Stream
385
+ /** Lazily iterate over all matching records using an async generator */
386
+ static cursor<T extends Model>(this: new () => T, chunkSize?: number): AsyncGenerator<T, void, unknown>;
387
+
347
388
  static with<T extends Model>(this: new () => T, ...relations: string[] | [Record<string, (qb: QueryBuilder<any>) => void> | string[]]): QueryBuilder<T>;
348
389
  /** Include hidden attributes in query results */
349
390
  static withHidden<T extends Model>(this: new () => T): QueryBuilder<T>;
@@ -671,4 +712,229 @@ declare module 'outlet-orm' {
671
712
  run(target?: string | null): Promise<void>;
672
713
  runSeeder(seederRef: string): Promise<void>;
673
714
  }
715
+
716
+ // ==================== Backup ====================
717
+
718
+ export type BackupFormat = 'sql' | 'json';
719
+ export type BackupType = 'full' | 'partial' | 'journal';
720
+ export type SaltLength = 4 | 5 | 6;
721
+
722
+ /** Encryption options for backup files */
723
+ export interface EncryptionOptions {
724
+ /** Enable AES-256-GCM encryption (default: false) */
725
+ encrypt?: boolean;
726
+ /** Password used for key derivation (required when encrypt=true) */
727
+ encryptionPassword?: string;
728
+ /**
729
+ * Grain de sable (salt) length in characters.
730
+ * Must be 4, 5, or 6. Default: 6.
731
+ */
732
+ saltLength?: SaltLength;
733
+ }
734
+
735
+ export interface BackupOptions extends EncryptionOptions {
736
+ /** Override the auto-generated filename */
737
+ filename?: string;
738
+ /** Output format – 'sql' (default) or 'json' */
739
+ format?: BackupFormat;
740
+ }
741
+
742
+ export interface JournalOptions extends EncryptionOptions {
743
+ /** Override the auto-generated filename */
744
+ filename?: string;
745
+ /** Clear the query log after writing the journal (default: false) */
746
+ flush?: boolean;
747
+ }
748
+
749
+ export interface RestoreOptions {
750
+ /**
751
+ * Password to decrypt an encrypted backup.
752
+ * Falls back to the BackupManager constructor password.
753
+ */
754
+ encryptionPassword?: string;
755
+ }
756
+
757
+ export interface RestoreResult {
758
+ /** Number of SQL statements executed */
759
+ statements: number;
760
+ }
761
+
762
+ export interface BackupManagerOptions extends EncryptionOptions {
763
+ /** Directory where backup files are written (default: './database/backups') */
764
+ backupPath?: string;
765
+ }
766
+
767
+ export interface ScheduleConfig extends EncryptionOptions {
768
+ /** Interval between backups in milliseconds (minimum: 1000) */
769
+ intervalMs: number;
770
+ /** Run once immediately when scheduled (default: false) */
771
+ runNow?: boolean;
772
+ /** Table names – required for 'partial' type */
773
+ tables?: string[];
774
+ /** Output format for full/partial backups */
775
+ format?: BackupFormat;
776
+ /** Auto-flush query log after each journal backup */
777
+ flush?: boolean;
778
+ /** Called with the file path after a successful backup */
779
+ onSuccess?: (filePath: string) => void;
780
+ /** Called with the error when a backup fails */
781
+ onError?: (error: Error) => void;
782
+ /** Optional unique job identifier (defaults to "<type>_<timestamp>") */
783
+ name?: string;
784
+ }
785
+
786
+ export class BackupManager {
787
+ constructor(connection: DatabaseConnection, options?: BackupManagerOptions);
788
+
789
+ /** Full backup – schema + data for every table. */
790
+ full(options?: BackupOptions): Promise<string>;
791
+ /** Partial backup – schema + data for the specified tables only. */
792
+ partial(tables: string[], options?: BackupOptions): Promise<string>;
793
+ /**
794
+ * Transaction-log backup – replayable DML statements from the query log.
795
+ * Requires DatabaseConnection.enableQueryLog() to be called beforehand.
796
+ */
797
+ journal(options?: JournalOptions): Promise<string>;
798
+ /** Restore a previously created SQL backup file (supports encrypted files). */
799
+ restore(filePath: string, options?: RestoreOptions): Promise<RestoreResult>;
800
+ }
801
+
802
+ export class BackupScheduler {
803
+ constructor(connection: DatabaseConnection, options?: BackupManagerOptions);
804
+
805
+ schedule(type: BackupType, config: ScheduleConfig): string;
806
+ stop(name: string): void;
807
+ stopAll(): void;
808
+ activeJobs(): string[];
809
+ }
810
+
811
+ // ==================== Backup Encryption ====================
812
+
813
+ export interface EncryptResult {
814
+ /** Full file content to write to disk */
815
+ encryptedContent: string;
816
+ /** The grain de sable (salt) that was generated */
817
+ salt: string;
818
+ }
819
+
820
+ export namespace BackupEncryption {
821
+ /**
822
+ * Encrypt a string payload with AES-256-GCM.
823
+ * @param plaintext Content to encrypt
824
+ * @param password Encryption password
825
+ * @param saltLength Grain de sable length (4–6, default 6)
826
+ */
827
+ function encrypt(plaintext: string, password: string, saltLength?: SaltLength): EncryptResult;
828
+
829
+ /**
830
+ * Decrypt a previously encrypted backup payload.
831
+ * @param encryptedContent Raw file content produced by encrypt()
832
+ * @param password The same password used during encryption
833
+ */
834
+ function decrypt(encryptedContent: string, password: string): string;
835
+
836
+ /** Return true if the content looks like an outlet-orm encrypted backup. */
837
+ function isEncrypted(content: string): boolean;
838
+
839
+ /**
840
+ * Generate a random alphanumeric salt (grain de sable).
841
+ * @param length 4, 5, or 6 characters
842
+ */
843
+ function generateSalt(length?: SaltLength): string;
844
+ }
845
+
846
+ // ==================== Backup Socket Server ====================
847
+
848
+ export interface ServerOptions extends BackupManagerOptions {
849
+ /** TCP port to listen on (default: 9119) */
850
+ port?: number;
851
+ /** Host / IP to bind (default: '127.0.0.1') */
852
+ host?: string;
853
+ }
854
+
855
+ export interface ServerStatus {
856
+ uptime: number;
857
+ jobs: string[];
858
+ clients: number;
859
+ }
860
+
861
+ export interface ServerAddress {
862
+ host: string;
863
+ port: number;
864
+ }
865
+
866
+ export class BackupSocketServer {
867
+ constructor(connection: DatabaseConnection, options?: ServerOptions);
868
+
869
+ /** Start the TCP daemon. */
870
+ listen(): Promise<void>;
871
+ /** Gracefully stop the daemon and all scheduled jobs. */
872
+ close(): Promise<void>;
873
+ /** Return the bound address (null before listen). */
874
+ address(): ServerAddress | null;
875
+
876
+ on(event: 'listening', listener: (addr: ServerAddress) => void): this;
877
+ on(event: 'close', listener: () => void): this;
878
+ on(event: 'error', listener: (err: Error) => void): this;
879
+ on(event: 'event', listener: (payload: Record<string, any>) => void): this;
880
+ on(event: string, listener: (...args: any[]) => void): this;
881
+ }
882
+
883
+ // ==================== Backup Socket Client ====================
884
+
885
+ export interface ClientOptions {
886
+ /** TCP port of the server (default: 9119) */
887
+ port?: number;
888
+ /** Host of the server (default: '127.0.0.1') */
889
+ host?: string;
890
+ /** Timeout in ms waiting for a server reply (default: 30000) */
891
+ timeout?: number;
892
+ }
893
+
894
+ export interface RunOptions extends EncryptionOptions {
895
+ format? : BackupFormat;
896
+ filename?: string;
897
+ flush? : boolean;
898
+ }
899
+
900
+ export class BackupSocketClient {
901
+ constructor(options?: ClientOptions);
902
+
903
+ readonly connected: boolean;
904
+
905
+ connect(): Promise<void>;
906
+ disconnect(): Promise<void>;
907
+
908
+ ping(): Promise<'pong'>;
909
+ status(): Promise<ServerStatus>;
910
+ jobs(): Promise<string[]>;
911
+
912
+ schedule(type: BackupType, config: ScheduleConfig): Promise<string>;
913
+ stop(name: string): Promise<boolean>;
914
+ stopAll(): Promise<boolean>;
915
+
916
+ /**
917
+ * Trigger a one-shot backup immediately.
918
+ * @param tables Required for 'partial' type
919
+ */
920
+ run(type: BackupType, options?: RunOptions): Promise<string>;
921
+ run(type: 'partial', tables: string[], options?: RunOptions): Promise<string>;
922
+
923
+ /**
924
+ * Restore a previously created backup file on the server.
925
+ * Supports plain SQL and encrypted .enc files.
926
+ * @param filePath Absolute path to the backup file (server-side)
927
+ * @param options Supply encryptionPassword when the file is encrypted
928
+ */
929
+ restore(filePath: string, options?: RestoreOptions): Promise<RestoreResult>;
930
+
931
+ on(event: 'connect', listener: () => void): this;
932
+ on(event: 'disconnect', listener: () => void): this;
933
+ on(event: 'error', listener: (err: Error) => void): this;
934
+ on(event: 'jobStart', listener: (payload: { name: string; type: string }) => void): this;
935
+ on(event: 'jobDone', listener: (payload: { name: string; type: string; filePath: string }) => void): this;
936
+ on(event: 'jobError', listener: (payload: { name: string; type: string; error: string }) => void): this;
937
+ on(event: 'serverEvent', listener: (payload: Record<string, any>) => void): this;
938
+ on(event: string, listener: (...args: any[]) => void): this;
939
+ }
674
940
  }