Skip to content

Commit a03b2fa

Browse files
Complete driver-localstorage migration to v4.0.0
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent 8b680b1 commit a03b2fa

4 files changed

Lines changed: 412 additions & 4 deletions

File tree

packages/drivers/localstorage/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@objectql/driver-localstorage",
3-
"version": "3.0.1",
3+
"version": "4.0.0",
44
"description": "LocalStorage driver for ObjectQL - Browser-based persistent storage",
55
"keywords": [
66
"objectql",
@@ -20,7 +20,8 @@
2020
"test": "jest"
2121
},
2222
"dependencies": {
23-
"@objectql/types": "workspace:*"
23+
"@objectql/types": "workspace:*",
24+
"@objectstack/spec": "^0.2.0"
2425
},
2526
"devDependencies": {
2627
"@types/jest": "^29.0.0",

packages/drivers/localstorage/src/index.ts

Lines changed: 230 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,31 @@
3232
*/
3333

3434
import { Driver, ObjectQLError } from '@objectql/types';
35+
import { DriverInterface, QueryAST, FilterNode, SortNode } from '@objectstack/spec';
36+
37+
/**
38+
* Command interface for executeCommand method
39+
*/
40+
export interface Command {
41+
type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
42+
object: string;
43+
data?: any;
44+
id?: string | number;
45+
ids?: Array<string | number>;
46+
records?: any[];
47+
updates?: Array<{id: string | number, data: any}>;
48+
options?: any;
49+
}
50+
51+
/**
52+
* Command result interface
53+
*/
54+
export interface CommandResult {
55+
success: boolean;
56+
data?: any;
57+
affected: number;
58+
error?: string;
59+
}
3560

3661
/**
3762
* Configuration options for the LocalStorage driver.
@@ -55,10 +80,10 @@ export interface LocalStorageDriverConfig {
5580
*
5681
* Example: `objectql:users:user-123` → `{"id":"user-123","name":"Alice",...}`
5782
*/
58-
export class LocalStorageDriver implements Driver {
83+
export class LocalStorageDriver implements Driver, DriverInterface {
5984
// Driver metadata (ObjectStack-compatible)
6085
public readonly name = 'LocalStorageDriver';
61-
public readonly version = '3.0.1';
86+
public readonly version = '4.0.0';
6287
public readonly supports = {
6388
transactions: false,
6489
joins: false,
@@ -536,8 +561,211 @@ export class LocalStorageDriver implements Driver {
536561
// No-op: LocalStorage driver doesn't need cleanup
537562
}
538563

564+
/**
565+
* Execute a query using QueryAST (DriverInterface v4.0 method)
566+
*
567+
* This method handles all query operations using the standard QueryAST format
568+
* from @objectstack/spec. It converts the AST to the legacy query format
569+
* and delegates to the existing find() method.
570+
*
571+
* @param ast - The query AST to execute
572+
* @param options - Optional execution options
573+
* @returns Query results with value array and count
574+
*/
575+
async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
576+
const objectName = ast.object || '';
577+
578+
// Convert QueryAST to legacy query format
579+
const legacyQuery: any = {
580+
fields: ast.fields,
581+
filters: this.convertFilterNodeToLegacy(ast.filters),
582+
sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
583+
limit: ast.top,
584+
skip: ast.skip,
585+
};
586+
587+
// Use existing find method
588+
const results = await this.find(objectName, legacyQuery, options);
589+
590+
return {
591+
value: results,
592+
count: results.length
593+
};
594+
}
595+
596+
/**
597+
* Execute a command (DriverInterface v4.0 method)
598+
*
599+
* This method handles all mutation operations (create, update, delete)
600+
* using a unified command interface.
601+
*
602+
* @param command - The command to execute
603+
* @param options - Optional execution options
604+
* @returns Command execution result
605+
*/
606+
async executeCommand(command: Command, options?: any): Promise<CommandResult> {
607+
try {
608+
const cmdOptions = { ...options, ...command.options };
609+
610+
switch (command.type) {
611+
case 'create':
612+
if (!command.data) {
613+
throw new Error('Create command requires data');
614+
}
615+
const created = await this.create(command.object, command.data, cmdOptions);
616+
return {
617+
success: true,
618+
data: created,
619+
affected: 1
620+
};
621+
622+
case 'update':
623+
if (!command.id || !command.data) {
624+
throw new Error('Update command requires id and data');
625+
}
626+
const updated = await this.update(command.object, command.id, command.data, cmdOptions);
627+
return {
628+
success: true,
629+
data: updated,
630+
affected: 1
631+
};
632+
633+
case 'delete':
634+
if (!command.id) {
635+
throw new Error('Delete command requires id');
636+
}
637+
await this.delete(command.object, command.id, cmdOptions);
638+
return {
639+
success: true,
640+
affected: 1
641+
};
642+
643+
case 'bulkCreate':
644+
if (!command.records || !Array.isArray(command.records)) {
645+
throw new Error('BulkCreate command requires records array');
646+
}
647+
const bulkCreated = [];
648+
for (const record of command.records) {
649+
const created = await this.create(command.object, record, cmdOptions);
650+
bulkCreated.push(created);
651+
}
652+
return {
653+
success: true,
654+
data: bulkCreated,
655+
affected: command.records.length
656+
};
657+
658+
case 'bulkUpdate':
659+
if (!command.updates || !Array.isArray(command.updates)) {
660+
throw new Error('BulkUpdate command requires updates array');
661+
}
662+
const updateResults = [];
663+
for (const update of command.updates) {
664+
const result = await this.update(command.object, update.id, update.data, cmdOptions);
665+
updateResults.push(result);
666+
}
667+
return {
668+
success: true,
669+
data: updateResults,
670+
affected: command.updates.length
671+
};
672+
673+
case 'bulkDelete':
674+
if (!command.ids || !Array.isArray(command.ids)) {
675+
throw new Error('BulkDelete command requires ids array');
676+
}
677+
let deleted = 0;
678+
for (const id of command.ids) {
679+
const result = await this.delete(command.object, id, cmdOptions);
680+
if (result) deleted++;
681+
}
682+
return {
683+
success: true,
684+
affected: deleted
685+
};
686+
687+
default:
688+
throw new Error(`Unsupported command type: ${(command as any).type}`);
689+
}
690+
} catch (error: any) {
691+
return {
692+
success: false,
693+
affected: 0,
694+
error: error.message || 'Unknown error occurred'
695+
};
696+
}
697+
}
698+
699+
/**
700+
* Execute raw command (for compatibility)
701+
*
702+
* @param command - Command string or object
703+
* @param parameters - Command parameters
704+
* @param options - Execution options
705+
*/
706+
async execute(command: any, parameters?: any[], options?: any): Promise<any> {
707+
throw new Error('LocalStorage driver does not support raw command execution. Use executeCommand() instead.');
708+
}
709+
539710
// ========== Helper Methods (Same as MemoryDriver) ==========
540711

712+
/**
713+
* Convert FilterNode from QueryAST to legacy filter format.
714+
*
715+
* @param node - The FilterNode to convert
716+
* @returns Legacy filter array format
717+
*/
718+
private convertFilterNodeToLegacy(node?: FilterNode): any {
719+
if (!node) return undefined;
720+
721+
switch (node.type) {
722+
case 'comparison':
723+
// Convert comparison node to [field, operator, value] format
724+
const operator = node.operator || '=';
725+
return [[node.field, operator, node.value]];
726+
727+
case 'and':
728+
// Convert AND node to array with 'and' separator
729+
if (!node.children || node.children.length === 0) return undefined;
730+
const andResults: any[] = [];
731+
for (const child of node.children) {
732+
const converted = this.convertFilterNodeToLegacy(child);
733+
if (converted) {
734+
if (andResults.length > 0) {
735+
andResults.push('and');
736+
}
737+
andResults.push(...(Array.isArray(converted) ? converted : [converted]));
738+
}
739+
}
740+
return andResults.length > 0 ? andResults : undefined;
741+
742+
case 'or':
743+
// Convert OR node to array with 'or' separator
744+
if (!node.children || node.children.length === 0) return undefined;
745+
const orResults: any[] = [];
746+
for (const child of node.children) {
747+
const converted = this.convertFilterNodeToLegacy(child);
748+
if (converted) {
749+
if (orResults.length > 0) {
750+
orResults.push('or');
751+
}
752+
orResults.push(...(Array.isArray(converted) ? converted : [converted]));
753+
}
754+
}
755+
return orResults.length > 0 ? orResults : undefined;
756+
757+
case 'not':
758+
// NOT is complex - we'll just process the first child for now
759+
if (node.children && node.children.length > 0) {
760+
return this.convertFilterNodeToLegacy(node.children[0]);
761+
}
762+
return undefined;
763+
764+
default:
765+
return undefined;
766+
}
767+
}
768+
541769
/**
542770
* Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
543771
* This ensures backward compatibility while supporting the new @objectstack/spec interface.

0 commit comments

Comments
 (0)