@@ -21,16 +21,24 @@ import type {
2121} from '@objectstack/spec/system' ;
2222import { SysMetadataObject } from '../objects/sys-metadata.object.js' ;
2323import { SysMetadataHistoryObject } from '../objects/sys-metadata-history.object.js' ;
24- import type { IDataDriver } from '@objectstack/spec/contracts' ;
24+ import type { IDataDriver , IDataEngine } from '@objectstack/spec/contracts' ;
2525import type { MetadataLoader } from './loader-interface.js' ;
2626import { calculateChecksum } from '../utils/metadata-history-utils.js' ;
2727
2828/**
2929 * Configuration for the DatabaseLoader.
30+ *
31+ * Accepts either a raw `IDataDriver` or an `IDataEngine` (ObjectQL).
32+ * When `engine` is provided, all CRUD operations route through the engine
33+ * which handles datasource mapping automatically — no manual driver
34+ * resolution needed. Schema sync is also skipped (the engine handles it).
3035 */
3136export interface DatabaseLoaderOptions {
3237 /** The IDataDriver instance to use for database operations */
33- driver : IDataDriver ;
38+ driver ?: IDataDriver ;
39+
40+ /** The IDataEngine (ObjectQL) instance — preferred over raw driver */
41+ engine ?: IDataEngine ;
3442
3543 /** The table name to store metadata records (default: 'sys_metadata') */
3644 tableName ?: string ;
@@ -64,7 +72,8 @@ export class DatabaseLoader implements MetadataLoader {
6472 } ,
6573 } ;
6674
67- private driver : IDataDriver ;
75+ private driver ?: IDataDriver ;
76+ private engine ?: IDataEngine ;
6877 private tableName : string ;
6978 private historyTableName : string ;
7079 private tenantId ?: string ;
@@ -73,13 +82,63 @@ export class DatabaseLoader implements MetadataLoader {
7382 private historySchemaReady = false ;
7483
7584 constructor ( options : DatabaseLoaderOptions ) {
85+ if ( ! options . driver && ! options . engine ) {
86+ throw new Error ( 'DatabaseLoader requires either a driver or engine' ) ;
87+ }
7688 this . driver = options . driver ;
89+ this . engine = options . engine ;
7790 this . tableName = options . tableName ?? 'sys_metadata' ;
7891 this . historyTableName = options . historyTableName ?? 'sys_metadata_history' ;
7992 this . tenantId = options . tenantId ;
8093 this . trackHistory = options . trackHistory !== false ; // Default to true
8194 }
8295
96+ // ==========================================
97+ // Internal CRUD helpers (driver vs engine)
98+ // ==========================================
99+
100+ private async _find ( table : string , query : Record < string , unknown > ) : Promise < Record < string , unknown > [ ] > {
101+ if ( this . engine ) {
102+ return this . engine . find ( table , query as any ) ;
103+ }
104+ return this . driver ! . find ( table , { object : table , ...query } as any ) ;
105+ }
106+
107+ private async _findOne ( table : string , query : Record < string , unknown > ) : Promise < Record < string , unknown > | null > {
108+ if ( this . engine ) {
109+ return this . engine . findOne ( table , query as any ) ;
110+ }
111+ return this . driver ! . findOne ( table , { object : table , ...query } as any ) ;
112+ }
113+
114+ private async _count ( table : string , query : Record < string , unknown > ) : Promise < number > {
115+ if ( this . engine ) {
116+ return this . engine . count ( table , query as any ) ;
117+ }
118+ return this . driver ! . count ( table , { object : table , ...query } as any ) ;
119+ }
120+
121+ private async _create ( table : string , data : Record < string , unknown > ) : Promise < Record < string , unknown > > {
122+ if ( this . engine ) {
123+ return this . engine . insert ( table , data ) ;
124+ }
125+ return this . driver ! . create ( table , data ) ;
126+ }
127+
128+ private async _update ( table : string , id : string , data : Record < string , unknown > ) : Promise < Record < string , unknown > > {
129+ if ( this . engine ) {
130+ return this . engine . update ( table , { id, ...data } ) ;
131+ }
132+ return this . driver ! . update ( table , id , data ) ;
133+ }
134+
135+ private async _delete ( table : string , id : string ) : Promise < any > {
136+ if ( this . engine ) {
137+ return this . engine . delete ( table , { where : { id } } as any ) ;
138+ }
139+ return this . driver ! . delete ( table , id ) ;
140+ }
141+
83142 /**
84143 * Ensure the metadata table exists.
85144 * Uses IDataDriver.syncSchema with the SysMetadataObject definition
@@ -88,8 +147,14 @@ export class DatabaseLoader implements MetadataLoader {
88147 private async ensureSchema ( ) : Promise < void > {
89148 if ( this . schemaReady ) return ;
90149
150+ // When using engine, schema sync is handled by ObjectQL startup
151+ if ( this . engine ) {
152+ this . schemaReady = true ;
153+ return ;
154+ }
155+
91156 try {
92- await this . driver . syncSchema ( this . tableName , {
157+ await this . driver ! . syncSchema ( this . tableName , {
93158 ...SysMetadataObject ,
94159 name : this . tableName ,
95160 } ) ;
@@ -107,8 +172,14 @@ export class DatabaseLoader implements MetadataLoader {
107172 private async ensureHistorySchema ( ) : Promise < void > {
108173 if ( ! this . trackHistory || this . historySchemaReady ) return ;
109174
175+ // When using engine, schema sync is handled by ObjectQL startup
176+ if ( this . engine ) {
177+ this . historySchemaReady = true ;
178+ return ;
179+ }
180+
110181 try {
111- await this . driver . syncSchema ( this . historyTableName , {
182+ await this . driver ! . syncSchema ( this . historyTableName , {
112183 ...SysMetadataHistoryObject ,
113184 name : this . historyTableName ,
114185 } ) ;
@@ -191,7 +262,7 @@ export class DatabaseLoader implements MetadataLoader {
191262 } ;
192263
193264 try {
194- await this . driver . create ( this . historyTableName , {
265+ await this . _create ( this . historyTableName , {
195266 id : historyRecord . id ,
196267 metadata_id : historyRecord . metadataId ,
197268 name : historyRecord . name ,
@@ -269,8 +340,7 @@ export class DatabaseLoader implements MetadataLoader {
269340 await this . ensureSchema ( ) ;
270341
271342 try {
272- const row = await this . driver . findOne ( this . tableName , {
273- object : this . tableName ,
343+ const row = await this . _findOne ( this . tableName , {
274344 where : this . baseFilter ( type , name ) ,
275345 } ) ;
276346
@@ -306,8 +376,7 @@ export class DatabaseLoader implements MetadataLoader {
306376 await this . ensureSchema ( ) ;
307377
308378 try {
309- const rows = await this . driver . find ( this . tableName , {
310- object : this . tableName ,
379+ const rows = await this . _find ( this . tableName , {
311380 where : this . baseFilter ( type ) ,
312381 } ) ;
313382
@@ -323,8 +392,7 @@ export class DatabaseLoader implements MetadataLoader {
323392 await this . ensureSchema ( ) ;
324393
325394 try {
326- const count = await this . driver . count ( this . tableName , {
327- object : this . tableName ,
395+ const count = await this . _count ( this . tableName , {
328396 where : this . baseFilter ( type , name ) ,
329397 } ) ;
330398
@@ -338,8 +406,7 @@ export class DatabaseLoader implements MetadataLoader {
338406 await this . ensureSchema ( ) ;
339407
340408 try {
341- const row = await this . driver . findOne ( this . tableName , {
342- object : this . tableName ,
409+ const row = await this . _findOne ( this . tableName , {
343410 where : this . baseFilter ( type , name ) ,
344411 } ) ;
345412
@@ -365,8 +432,7 @@ export class DatabaseLoader implements MetadataLoader {
365432 await this . ensureSchema ( ) ;
366433
367434 try {
368- const rows = await this . driver . find ( this . tableName , {
369- object : this . tableName ,
435+ const rows = await this . _find ( this . tableName , {
370436 where : this . baseFilter ( type ) ,
371437 fields : [ 'name' ] ,
372438 } ) ;
@@ -393,8 +459,7 @@ export class DatabaseLoader implements MetadataLoader {
393459 await this . ensureHistorySchema ( ) ;
394460
395461 // Resolve the parent metadata record ID
396- const metadataRow = await this . driver . findOne ( this . tableName , {
397- object : this . tableName ,
462+ const metadataRow = await this . _findOne ( this . tableName , {
398463 where : this . baseFilter ( type , name ) ,
399464 } ) ;
400465 if ( ! metadataRow ) return null ;
@@ -407,8 +472,7 @@ export class DatabaseLoader implements MetadataLoader {
407472 filter . tenant_id = this . tenantId ;
408473 }
409474
410- const row = await this . driver . findOne ( this . historyTableName , {
411- object : this . historyTableName ,
475+ const row = await this . _findOne ( this . historyTableName , {
412476 where : filter ,
413477 } ) ;
414478 if ( ! row ) return null ;
@@ -430,6 +494,95 @@ export class DatabaseLoader implements MetadataLoader {
430494 } ;
431495 }
432496
497+ /**
498+ * Query history records with pagination and filtering.
499+ * Encapsulates history table queries so MetadataManager doesn't need
500+ * direct driver access.
501+ */
502+ async queryHistory (
503+ type : string ,
504+ name : string ,
505+ options ?: {
506+ operationType ?: string ;
507+ since ?: string ;
508+ until ?: string ;
509+ limit ?: number ;
510+ offset ?: number ;
511+ includeMetadata ?: boolean ;
512+ }
513+ ) : Promise < { records : any [ ] ; total : number ; hasMore : boolean } > {
514+ if ( ! this . trackHistory ) {
515+ return { records : [ ] , total : 0 , hasMore : false } ;
516+ }
517+
518+ await this . ensureSchema ( ) ;
519+ await this . ensureHistorySchema ( ) ;
520+
521+ // Find the metadata record
522+ const filter : Record < string , unknown > = { type, name } ;
523+ if ( this . tenantId ) filter . tenant_id = this . tenantId ;
524+
525+ const metadataRecord = await this . _findOne ( this . tableName , { where : filter } ) ;
526+ if ( ! metadataRecord ) {
527+ return { records : [ ] , total : 0 , hasMore : false } ;
528+ }
529+
530+ // Build history query
531+ const historyFilter : Record < string , unknown > = {
532+ metadata_id : metadataRecord . id ,
533+ } ;
534+ if ( this . tenantId ) historyFilter . tenant_id = this . tenantId ;
535+ if ( options ?. operationType ) historyFilter . operation_type = options . operationType ;
536+ if ( options ?. since ) historyFilter . recorded_at = { $gte : options . since } ;
537+ if ( options ?. until ) {
538+ if ( historyFilter . recorded_at ) {
539+ ( historyFilter . recorded_at as Record < string , unknown > ) . $lte = options . until ;
540+ } else {
541+ historyFilter . recorded_at = { $lte : options . until } ;
542+ }
543+ }
544+
545+ const limit = options ?. limit ?? 50 ;
546+ const offset = options ?. offset ?? 0 ;
547+
548+ const historyRecords = await this . _find ( this . historyTableName , {
549+ where : historyFilter ,
550+ orderBy : [ { field : 'recorded_at' , order : 'desc' as const } ] ,
551+ limit : limit + 1 ,
552+ offset,
553+ } ) ;
554+
555+ const hasMore = historyRecords . length > limit ;
556+ const records = historyRecords . slice ( 0 , limit ) ;
557+ const total = await this . _count ( this . historyTableName , { where : historyFilter } ) ;
558+
559+ const includeMetadata = options ?. includeMetadata !== false ;
560+ const result = records . map ( ( row : Record < string , unknown > ) => {
561+ const parsedMetadata =
562+ typeof row . metadata === 'string'
563+ ? JSON . parse ( row . metadata as string )
564+ : ( row . metadata as Record < string , unknown > | null | undefined ) ;
565+
566+ return {
567+ id : row . id as string ,
568+ metadataId : row . metadata_id as string ,
569+ name : row . name as string ,
570+ type : row . type as string ,
571+ version : row . version as number ,
572+ operationType : row . operation_type as string ,
573+ metadata : includeMetadata ? parsedMetadata : null ,
574+ checksum : row . checksum as string ,
575+ previousChecksum : row . previous_checksum as string | undefined ,
576+ changeNote : row . change_note as string | undefined ,
577+ tenantId : row . tenant_id as string | undefined ,
578+ recordedBy : row . recorded_by as string | undefined ,
579+ recordedAt : row . recorded_at as string ,
580+ } ;
581+ } ) ;
582+
583+ return { records : result , total, hasMore } ;
584+ }
585+
433586 /**
434587 * Perform a rollback: persist `restoredData` as the new current state and record a
435588 * single 'revert' history entry (instead of the usual 'update' entry that `save()`
@@ -451,8 +604,7 @@ export class DatabaseLoader implements MetadataLoader {
451604 const metadataJson = JSON . stringify ( restoredData ) ;
452605 const newChecksum = await calculateChecksum ( restoredData ) ;
453606
454- const existing = await this . driver . findOne ( this . tableName , {
455- object : this . tableName ,
607+ const existing = await this . _findOne ( this . tableName , {
456608 where : this . baseFilter ( type , name ) ,
457609 } ) ;
458610
@@ -463,7 +615,7 @@ export class DatabaseLoader implements MetadataLoader {
463615 const previousChecksum = existing . checksum as string | undefined ;
464616 const newVersion = ( ( existing . version as number ) ?? 0 ) + 1 ;
465617
466- await this . driver . update ( this . tableName , existing . id as string , {
618+ await this . _update ( this . tableName , existing . id as string , {
467619 metadata : metadataJson ,
468620 version : newVersion ,
469621 checksum : newChecksum ,
@@ -500,8 +652,7 @@ export class DatabaseLoader implements MetadataLoader {
500652 const newChecksum = await calculateChecksum ( data ) ;
501653
502654 try {
503- const existing = await this . driver . findOne ( this . tableName , {
504- object : this . tableName ,
655+ const existing = await this . _findOne ( this . tableName , {
505656 where : this . baseFilter ( type , name ) ,
506657 } ) ;
507658
@@ -520,7 +671,7 @@ export class DatabaseLoader implements MetadataLoader {
520671 // Update existing record
521672 const version = ( ( existing . version as number ) ?? 0 ) + 1 ;
522673
523- await this . driver . update ( this . tableName , existing . id as string , {
674+ await this . _update ( this . tableName , existing . id as string , {
524675 metadata : metadataJson ,
525676 version,
526677 checksum : newChecksum ,
@@ -548,7 +699,7 @@ export class DatabaseLoader implements MetadataLoader {
548699 } else {
549700 // Create new record
550701 const id = generateId ( ) ;
551- await this . driver . create ( this . tableName , {
702+ await this . _create ( this . tableName , {
552703 id,
553704 name,
554705 type,
@@ -598,8 +749,7 @@ export class DatabaseLoader implements MetadataLoader {
598749 await this . ensureSchema ( ) ;
599750
600751 // Find the existing record to get its ID
601- const existing = await this . driver . findOne ( this . tableName , {
602- object : this . tableName ,
752+ const existing = await this . _findOne ( this . tableName , {
603753 where : this . baseFilter ( type , name ) ,
604754 } ) ;
605755
@@ -609,7 +759,7 @@ export class DatabaseLoader implements MetadataLoader {
609759 }
610760
611761 // Delete from the main metadata table using the record's ID
612- await this . driver . delete ( this . tableName , existing . id as string ) ;
762+ await this . _delete ( this . tableName , existing . id as string ) ;
613763 }
614764}
615765
0 commit comments