@objectstack/objectql 3.3.0 → 4.0.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +33 -0
- package/dist/index.d.mts +34 -20
- package/dist/index.d.ts +34 -20
- package/dist/index.js +282 -122
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +282 -122
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/engine.test.ts +13 -13
- package/src/engine.ts +36 -77
- package/src/plugin.integration.test.ts +212 -0
- package/src/plugin.ts +73 -18
- package/src/protocol-data.test.ts +41 -38
- package/src/protocol-discovery.test.ts +25 -25
- package/src/protocol-meta.test.ts +440 -0
- package/src/protocol.ts +258 -68
- package/tsconfig.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -547,7 +547,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
547
547
|
const registeredServices = this.getServicesRegistry ? this.getServicesRegistry() : /* @__PURE__ */ new Map();
|
|
548
548
|
const services = {
|
|
549
549
|
// --- Kernel-provided (objectql is an example kernel implementation) ---
|
|
550
|
-
metadata: { enabled: true, status: "
|
|
550
|
+
metadata: { enabled: true, status: "available", route: "/api/v1/meta", provider: "objectql" },
|
|
551
551
|
data: { enabled: true, status: "available", route: "/api/v1/data", provider: "objectql" },
|
|
552
552
|
analytics: { enabled: true, status: "available", route: "/api/v1/analytics", provider: "objectql" }
|
|
553
553
|
};
|
|
@@ -609,7 +609,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
609
609
|
metadata: "/api/v1/meta",
|
|
610
610
|
...optionalRoutes
|
|
611
611
|
};
|
|
612
|
-
const
|
|
612
|
+
const wellKnown = {
|
|
613
613
|
feed: registeredServices.has("feed"),
|
|
614
614
|
comments: registeredServices.has("feed"),
|
|
615
615
|
automation: registeredServices.has("automation"),
|
|
@@ -618,6 +618,10 @@ var ObjectStackProtocolImplementation = class {
|
|
|
618
618
|
export: registeredServices.has("automation") || registeredServices.has("queue"),
|
|
619
619
|
chunkedUpload: registeredServices.has("file-storage")
|
|
620
620
|
};
|
|
621
|
+
const capabilities = {};
|
|
622
|
+
for (const [key, enabled] of Object.entries(wellKnown)) {
|
|
623
|
+
capabilities[key] = { enabled };
|
|
624
|
+
}
|
|
621
625
|
return {
|
|
622
626
|
version: "1.0",
|
|
623
627
|
apiName: "ObjectStack API",
|
|
@@ -637,6 +641,33 @@ var ObjectStackProtocolImplementation = class {
|
|
|
637
641
|
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
638
642
|
items = SchemaRegistry.listItems(alt);
|
|
639
643
|
}
|
|
644
|
+
if (items.length === 0) {
|
|
645
|
+
try {
|
|
646
|
+
const allRecords = await this.engine.find("sys_metadata", {
|
|
647
|
+
where: { type: request.type, state: "active" }
|
|
648
|
+
});
|
|
649
|
+
if (allRecords && allRecords.length > 0) {
|
|
650
|
+
items = allRecords.map((record) => {
|
|
651
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
652
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
653
|
+
return data;
|
|
654
|
+
});
|
|
655
|
+
} else {
|
|
656
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
657
|
+
const altRecords = await this.engine.find("sys_metadata", {
|
|
658
|
+
where: { type: alt, state: "active" }
|
|
659
|
+
});
|
|
660
|
+
if (altRecords && altRecords.length > 0) {
|
|
661
|
+
items = altRecords.map((record) => {
|
|
662
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
663
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
664
|
+
return data;
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
}
|
|
640
671
|
return {
|
|
641
672
|
type: request.type,
|
|
642
673
|
items
|
|
@@ -648,6 +679,27 @@ var ObjectStackProtocolImplementation = class {
|
|
|
648
679
|
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
649
680
|
item = SchemaRegistry.getItem(alt, request.name);
|
|
650
681
|
}
|
|
682
|
+
if (item === void 0) {
|
|
683
|
+
try {
|
|
684
|
+
const record = await this.engine.findOne("sys_metadata", {
|
|
685
|
+
where: { type: request.type, name: request.name, state: "active" }
|
|
686
|
+
});
|
|
687
|
+
if (record) {
|
|
688
|
+
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
689
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
690
|
+
} else {
|
|
691
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
692
|
+
const altRecord = await this.engine.findOne("sys_metadata", {
|
|
693
|
+
where: { type: alt, name: request.name, state: "active" }
|
|
694
|
+
});
|
|
695
|
+
if (altRecord) {
|
|
696
|
+
item = typeof altRecord.metadata === "string" ? JSON.parse(altRecord.metadata) : altRecord.metadata;
|
|
697
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
651
703
|
return {
|
|
652
704
|
type: request.type,
|
|
653
705
|
name: request.name,
|
|
@@ -711,12 +763,22 @@ var ObjectStackProtocolImplementation = class {
|
|
|
711
763
|
}
|
|
712
764
|
async findData(request) {
|
|
713
765
|
const options = { ...request.query };
|
|
714
|
-
if (options.top != null)
|
|
715
|
-
|
|
766
|
+
if (options.top != null) {
|
|
767
|
+
options.limit = Number(options.top);
|
|
768
|
+
delete options.top;
|
|
769
|
+
}
|
|
770
|
+
if (options.skip != null) {
|
|
771
|
+
options.offset = Number(options.skip);
|
|
772
|
+
delete options.skip;
|
|
773
|
+
}
|
|
716
774
|
if (options.limit != null) options.limit = Number(options.limit);
|
|
775
|
+
if (options.offset != null) options.offset = Number(options.offset);
|
|
717
776
|
if (typeof options.select === "string") {
|
|
718
|
-
options.
|
|
777
|
+
options.fields = options.select.split(",").map((s) => s.trim()).filter(Boolean);
|
|
778
|
+
} else if (Array.isArray(options.select)) {
|
|
779
|
+
options.fields = options.select;
|
|
719
780
|
}
|
|
781
|
+
if (options.select !== void 0) delete options.select;
|
|
720
782
|
const sortValue = options.orderBy ?? options.sort;
|
|
721
783
|
if (typeof sortValue === "string") {
|
|
722
784
|
const parsed = sortValue.split(",").map((part) => {
|
|
@@ -727,62 +789,75 @@ var ObjectStackProtocolImplementation = class {
|
|
|
727
789
|
const [field, order] = trimmed.split(/\s+/);
|
|
728
790
|
return { field, order: order?.toLowerCase() === "desc" ? "desc" : "asc" };
|
|
729
791
|
}).filter((s) => s.field);
|
|
730
|
-
options.
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (options.filters !== void 0 && options.filter === void 0) {
|
|
734
|
-
options.filter = options.filters;
|
|
792
|
+
options.orderBy = parsed;
|
|
793
|
+
} else if (Array.isArray(sortValue)) {
|
|
794
|
+
options.orderBy = sortValue;
|
|
735
795
|
}
|
|
796
|
+
delete options.sort;
|
|
797
|
+
const filterValue = options.filter ?? options.filters ?? options.$filter ?? options.where;
|
|
798
|
+
delete options.filter;
|
|
736
799
|
delete options.filters;
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
800
|
+
delete options.$filter;
|
|
801
|
+
if (filterValue !== void 0) {
|
|
802
|
+
let parsedFilter = filterValue;
|
|
803
|
+
if (typeof parsedFilter === "string") {
|
|
804
|
+
try {
|
|
805
|
+
parsedFilter = JSON.parse(parsedFilter);
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
741
808
|
}
|
|
809
|
+
if (isFilterAST(parsedFilter)) {
|
|
810
|
+
parsedFilter = parseFilterAST(parsedFilter);
|
|
811
|
+
}
|
|
812
|
+
options.where = parsedFilter;
|
|
742
813
|
}
|
|
743
|
-
|
|
744
|
-
options.filter = parseFilterAST(options.filter);
|
|
745
|
-
}
|
|
746
|
-
if (typeof options.populate === "string") {
|
|
747
|
-
options.populate = options.populate.split(",").map((s) => s.trim()).filter(Boolean);
|
|
748
|
-
}
|
|
814
|
+
const populateValue = options.populate;
|
|
749
815
|
const expandValue = options.$expand ?? options.expand;
|
|
750
|
-
|
|
816
|
+
const expandNames = [];
|
|
817
|
+
if (typeof populateValue === "string") {
|
|
818
|
+
expandNames.push(...populateValue.split(",").map((s) => s.trim()).filter(Boolean));
|
|
819
|
+
} else if (Array.isArray(populateValue)) {
|
|
820
|
+
expandNames.push(...populateValue);
|
|
821
|
+
}
|
|
822
|
+
if (!expandNames.length && expandValue) {
|
|
751
823
|
if (typeof expandValue === "string") {
|
|
752
|
-
|
|
824
|
+
expandNames.push(...expandValue.split(",").map((s) => s.trim()).filter(Boolean));
|
|
753
825
|
} else if (Array.isArray(expandValue)) {
|
|
754
|
-
|
|
826
|
+
expandNames.push(...expandValue);
|
|
755
827
|
}
|
|
756
828
|
}
|
|
829
|
+
delete options.populate;
|
|
757
830
|
delete options.$expand;
|
|
758
|
-
|
|
831
|
+
if (typeof options.expand !== "object" || options.expand === null) {
|
|
832
|
+
delete options.expand;
|
|
833
|
+
}
|
|
834
|
+
if (expandNames.length > 0 && !options.expand) {
|
|
835
|
+
options.expand = {};
|
|
836
|
+
for (const rel of expandNames) {
|
|
837
|
+
options.expand[rel] = { object: rel };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
759
840
|
for (const key of ["distinct", "count"]) {
|
|
760
841
|
if (options[key] === "true") options[key] = true;
|
|
761
842
|
else if (options[key] === "false") options[key] = false;
|
|
762
843
|
}
|
|
763
844
|
const knownParams = /* @__PURE__ */ new Set([
|
|
764
845
|
"top",
|
|
765
|
-
"skip",
|
|
766
846
|
"limit",
|
|
767
847
|
"offset",
|
|
768
|
-
"sort",
|
|
769
848
|
"orderBy",
|
|
770
|
-
"select",
|
|
771
849
|
"fields",
|
|
772
|
-
"
|
|
773
|
-
"filters",
|
|
774
|
-
"$filter",
|
|
775
|
-
"populate",
|
|
850
|
+
"where",
|
|
776
851
|
"expand",
|
|
777
|
-
"$expand",
|
|
778
852
|
"distinct",
|
|
779
853
|
"count",
|
|
780
854
|
"aggregations",
|
|
781
855
|
"groupBy",
|
|
782
856
|
"search",
|
|
783
|
-
"context"
|
|
857
|
+
"context",
|
|
858
|
+
"cursor"
|
|
784
859
|
]);
|
|
785
|
-
if (!options.
|
|
860
|
+
if (!options.where) {
|
|
786
861
|
const implicitFilters = {};
|
|
787
862
|
for (const key of Object.keys(options)) {
|
|
788
863
|
if (!knownParams.has(key)) {
|
|
@@ -791,14 +866,14 @@ var ObjectStackProtocolImplementation = class {
|
|
|
791
866
|
}
|
|
792
867
|
}
|
|
793
868
|
if (Object.keys(implicitFilters).length > 0) {
|
|
794
|
-
options.
|
|
869
|
+
options.where = implicitFilters;
|
|
795
870
|
}
|
|
796
871
|
}
|
|
797
872
|
const records = await this.engine.find(request.object, options);
|
|
798
873
|
return {
|
|
799
874
|
object: request.object,
|
|
800
875
|
value: records,
|
|
801
|
-
// OData
|
|
876
|
+
// OData compatibility
|
|
802
877
|
records,
|
|
803
878
|
// Legacy
|
|
804
879
|
total: records.length,
|
|
@@ -807,13 +882,17 @@ var ObjectStackProtocolImplementation = class {
|
|
|
807
882
|
}
|
|
808
883
|
async getData(request) {
|
|
809
884
|
const queryOptions = {
|
|
810
|
-
|
|
885
|
+
where: { id: request.id }
|
|
811
886
|
};
|
|
812
887
|
if (request.select) {
|
|
813
|
-
queryOptions.
|
|
888
|
+
queryOptions.fields = typeof request.select === "string" ? request.select.split(",").map((s) => s.trim()).filter(Boolean) : request.select;
|
|
814
889
|
}
|
|
815
890
|
if (request.expand) {
|
|
816
|
-
|
|
891
|
+
const expandNames = typeof request.expand === "string" ? request.expand.split(",").map((s) => s.trim()).filter(Boolean) : request.expand;
|
|
892
|
+
queryOptions.expand = {};
|
|
893
|
+
for (const rel of expandNames) {
|
|
894
|
+
queryOptions.expand[rel] = { object: rel };
|
|
895
|
+
}
|
|
817
896
|
}
|
|
818
897
|
const result = await this.engine.findOne(request.object, queryOptions);
|
|
819
898
|
if (result) {
|
|
@@ -834,7 +913,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
834
913
|
};
|
|
835
914
|
}
|
|
836
915
|
async updateData(request) {
|
|
837
|
-
const result = await this.engine.update(request.object, request.data, {
|
|
916
|
+
const result = await this.engine.update(request.object, request.data, { where: { id: request.id } });
|
|
838
917
|
return {
|
|
839
918
|
object: request.object,
|
|
840
919
|
id: request.id,
|
|
@@ -842,7 +921,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
842
921
|
};
|
|
843
922
|
}
|
|
844
923
|
async deleteData(request) {
|
|
845
|
-
await this.engine.delete(request.object, {
|
|
924
|
+
await this.engine.delete(request.object, { where: { id: request.id } });
|
|
846
925
|
return {
|
|
847
926
|
object: request.object,
|
|
848
927
|
id: request.id,
|
|
@@ -905,7 +984,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
905
984
|
}
|
|
906
985
|
case "update": {
|
|
907
986
|
if (!record.id) throw new Error("Record id is required for update");
|
|
908
|
-
const updated = await this.engine.update(object, record.data || {}, {
|
|
987
|
+
const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });
|
|
909
988
|
results.push({ id: record.id, success: true, record: updated });
|
|
910
989
|
succeeded++;
|
|
911
990
|
break;
|
|
@@ -913,9 +992,9 @@ var ObjectStackProtocolImplementation = class {
|
|
|
913
992
|
case "upsert": {
|
|
914
993
|
if (record.id) {
|
|
915
994
|
try {
|
|
916
|
-
const existing = await this.engine.findOne(object, {
|
|
995
|
+
const existing = await this.engine.findOne(object, { where: { id: record.id } });
|
|
917
996
|
if (existing) {
|
|
918
|
-
const updated = await this.engine.update(object, record.data || {}, {
|
|
997
|
+
const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });
|
|
919
998
|
results.push({ id: record.id, success: true, record: updated });
|
|
920
999
|
} else {
|
|
921
1000
|
const created = await this.engine.insert(object, { id: record.id, ...record.data || {} });
|
|
@@ -934,7 +1013,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
934
1013
|
}
|
|
935
1014
|
case "delete": {
|
|
936
1015
|
if (!record.id) throw new Error("Record id is required for delete");
|
|
937
|
-
await this.engine.delete(object, {
|
|
1016
|
+
await this.engine.delete(object, { where: { id: record.id } });
|
|
938
1017
|
results.push({ id: record.id, success: true });
|
|
939
1018
|
succeeded++;
|
|
940
1019
|
break;
|
|
@@ -978,7 +1057,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
978
1057
|
let failed = 0;
|
|
979
1058
|
for (const record of records) {
|
|
980
1059
|
try {
|
|
981
|
-
const updated = await this.engine.update(object, record.data, {
|
|
1060
|
+
const updated = await this.engine.update(object, record.data, { where: { id: record.id } });
|
|
982
1061
|
results.push({ id: record.id, success: true, record: updated });
|
|
983
1062
|
succeeded++;
|
|
984
1063
|
} catch (err) {
|
|
@@ -1029,9 +1108,9 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1029
1108
|
filter = conditions.length === 1 ? conditions[0] : { $and: conditions };
|
|
1030
1109
|
}
|
|
1031
1110
|
const rows = await this.engine.aggregate(object, {
|
|
1032
|
-
filter,
|
|
1111
|
+
where: filter,
|
|
1033
1112
|
groupBy: groupBy.length > 0 ? groupBy : void 0,
|
|
1034
|
-
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({
|
|
1113
|
+
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({ function: a.method, field: a.field, alias: a.alias })) : [{ function: "count", alias: "count" }]
|
|
1035
1114
|
});
|
|
1036
1115
|
const fields = [
|
|
1037
1116
|
...groupBy.map((d) => ({ name: d, type: "string" })),
|
|
@@ -1142,7 +1221,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1142
1221
|
}
|
|
1143
1222
|
async deleteManyData(request) {
|
|
1144
1223
|
return this.engine.delete(request.object, {
|
|
1145
|
-
|
|
1224
|
+
where: { id: { $in: request.ids } },
|
|
1146
1225
|
...request.options
|
|
1147
1226
|
});
|
|
1148
1227
|
}
|
|
@@ -1151,10 +1230,76 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1151
1230
|
throw new Error("Item data is required");
|
|
1152
1231
|
}
|
|
1153
1232
|
SchemaRegistry.registerItem(request.type, request.item, "name");
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1233
|
+
try {
|
|
1234
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1235
|
+
const existing = await this.engine.findOne("sys_metadata", {
|
|
1236
|
+
where: { type: request.type, name: request.name }
|
|
1237
|
+
});
|
|
1238
|
+
if (existing) {
|
|
1239
|
+
await this.engine.update("sys_metadata", {
|
|
1240
|
+
metadata: JSON.stringify(request.item),
|
|
1241
|
+
updated_at: now,
|
|
1242
|
+
version: (existing.version || 0) + 1
|
|
1243
|
+
}, {
|
|
1244
|
+
where: { id: existing.id }
|
|
1245
|
+
});
|
|
1246
|
+
} else {
|
|
1247
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `meta_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1248
|
+
await this.engine.insert("sys_metadata", {
|
|
1249
|
+
id,
|
|
1250
|
+
name: request.name,
|
|
1251
|
+
type: request.type,
|
|
1252
|
+
scope: "platform",
|
|
1253
|
+
metadata: JSON.stringify(request.item),
|
|
1254
|
+
state: "active",
|
|
1255
|
+
version: 1,
|
|
1256
|
+
created_at: now,
|
|
1257
|
+
updated_at: now
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
success: true,
|
|
1262
|
+
message: "Saved to database and registry"
|
|
1263
|
+
};
|
|
1264
|
+
} catch (dbError) {
|
|
1265
|
+
console.warn(`[Protocol] DB persistence failed for ${request.type}/${request.name}: ${dbError.message}`);
|
|
1266
|
+
return {
|
|
1267
|
+
success: true,
|
|
1268
|
+
message: "Saved to memory registry (DB persistence unavailable)",
|
|
1269
|
+
warning: dbError.message
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Hydrate SchemaRegistry from the database on startup.
|
|
1275
|
+
* Loads all active metadata records and registers them in the in-memory registry.
|
|
1276
|
+
* Safe to call repeatedly — idempotent (latest DB record wins).
|
|
1277
|
+
*/
|
|
1278
|
+
async loadMetaFromDb() {
|
|
1279
|
+
let loaded = 0;
|
|
1280
|
+
let errors = 0;
|
|
1281
|
+
try {
|
|
1282
|
+
const records = await this.engine.find("sys_metadata", {
|
|
1283
|
+
where: { state: "active" }
|
|
1284
|
+
});
|
|
1285
|
+
for (const record of records) {
|
|
1286
|
+
try {
|
|
1287
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1288
|
+
if (record.type === "object") {
|
|
1289
|
+
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1290
|
+
} else {
|
|
1291
|
+
SchemaRegistry.registerItem(record.type, data, "name");
|
|
1292
|
+
}
|
|
1293
|
+
loaded++;
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
errors++;
|
|
1296
|
+
console.warn(`[Protocol] Failed to hydrate ${record.type}/${record.name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
} catch (e) {
|
|
1300
|
+
console.warn(`[Protocol] DB hydration skipped: ${e.message}`);
|
|
1301
|
+
}
|
|
1302
|
+
return { loaded, errors };
|
|
1158
1303
|
}
|
|
1159
1304
|
// ==========================================
|
|
1160
1305
|
// Feed Operations
|
|
@@ -1872,46 +2017,18 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1872
2017
|
return records;
|
|
1873
2018
|
}
|
|
1874
2019
|
// ============================================
|
|
1875
|
-
// Helper: Query Conversion
|
|
1876
|
-
// ============================================
|
|
1877
|
-
toQueryAST(object, options) {
|
|
1878
|
-
const ast = { object };
|
|
1879
|
-
if (!options) return ast;
|
|
1880
|
-
if (options.filter) {
|
|
1881
|
-
ast.where = options.filter;
|
|
1882
|
-
}
|
|
1883
|
-
if (options.select) {
|
|
1884
|
-
ast.fields = options.select;
|
|
1885
|
-
}
|
|
1886
|
-
if (options.sort) {
|
|
1887
|
-
if (Array.isArray(options.sort)) {
|
|
1888
|
-
ast.orderBy = options.sort;
|
|
1889
|
-
} else {
|
|
1890
|
-
ast.orderBy = Object.entries(options.sort).map(([field, order]) => ({
|
|
1891
|
-
field,
|
|
1892
|
-
order: order === -1 || order === "desc" ? "desc" : "asc"
|
|
1893
|
-
}));
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
if (options.top !== void 0) ast.limit = options.top;
|
|
1897
|
-
else if (options.limit !== void 0) ast.limit = options.limit;
|
|
1898
|
-
if (options.skip !== void 0) ast.offset = options.skip;
|
|
1899
|
-
if (options.populate && options.populate.length > 0) {
|
|
1900
|
-
ast.expand = {};
|
|
1901
|
-
for (const rel of options.populate) {
|
|
1902
|
-
ast.expand[rel] = { object: rel };
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
return ast;
|
|
1906
|
-
}
|
|
1907
|
-
// ============================================
|
|
1908
2020
|
// Data Access Methods (IDataEngine Interface)
|
|
1909
2021
|
// ============================================
|
|
1910
2022
|
async find(object, query) {
|
|
1911
2023
|
object = this.resolveObjectName(object);
|
|
1912
2024
|
this.logger.debug("Find operation starting", { object, query });
|
|
1913
2025
|
const driver = this.getDriver(object);
|
|
1914
|
-
const ast =
|
|
2026
|
+
const ast = { object, ...query };
|
|
2027
|
+
delete ast.context;
|
|
2028
|
+
if (ast.top != null && ast.limit == null) {
|
|
2029
|
+
ast.limit = ast.top;
|
|
2030
|
+
}
|
|
2031
|
+
delete ast.top;
|
|
1915
2032
|
const opCtx = {
|
|
1916
2033
|
object,
|
|
1917
2034
|
operation: "find",
|
|
@@ -1949,8 +2066,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1949
2066
|
objectName = this.resolveObjectName(objectName);
|
|
1950
2067
|
this.logger.debug("FindOne operation", { objectName });
|
|
1951
2068
|
const driver = this.getDriver(objectName);
|
|
1952
|
-
const ast =
|
|
1953
|
-
ast.
|
|
2069
|
+
const ast = { object: objectName, ...query, limit: 1 };
|
|
2070
|
+
delete ast.context;
|
|
2071
|
+
delete ast.top;
|
|
1954
2072
|
const opCtx = {
|
|
1955
2073
|
object: objectName,
|
|
1956
2074
|
operation: "findOne",
|
|
@@ -2016,9 +2134,8 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2016
2134
|
this.logger.debug("Update operation starting", { object });
|
|
2017
2135
|
const driver = this.getDriver(object);
|
|
2018
2136
|
let id = data.id;
|
|
2019
|
-
if (!id && options?.
|
|
2020
|
-
|
|
2021
|
-
else if (options.filter.id) id = options.filter.id;
|
|
2137
|
+
if (!id && options?.where && typeof options.where === "object" && "id" in options.where) {
|
|
2138
|
+
id = options.where.id;
|
|
2022
2139
|
}
|
|
2023
2140
|
const opCtx = {
|
|
2024
2141
|
object,
|
|
@@ -2042,7 +2159,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2042
2159
|
if (hookContext.input.id) {
|
|
2043
2160
|
result = await driver.update(object, hookContext.input.id, hookContext.input.data, hookContext.input.options);
|
|
2044
2161
|
} else if (options?.multi && driver.updateMany) {
|
|
2045
|
-
const ast =
|
|
2162
|
+
const ast = { object, where: options.where };
|
|
2046
2163
|
result = await driver.updateMany(object, ast, hookContext.input.data, hookContext.input.options);
|
|
2047
2164
|
} else {
|
|
2048
2165
|
throw new Error("Update requires an ID or options.multi=true");
|
|
@@ -2063,9 +2180,8 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2063
2180
|
this.logger.debug("Delete operation starting", { object });
|
|
2064
2181
|
const driver = this.getDriver(object);
|
|
2065
2182
|
let id = void 0;
|
|
2066
|
-
if (options?.
|
|
2067
|
-
|
|
2068
|
-
else if (options.filter.id) id = options.filter.id;
|
|
2183
|
+
if (options?.where && typeof options.where === "object" && "id" in options.where) {
|
|
2184
|
+
id = options.where.id;
|
|
2069
2185
|
}
|
|
2070
2186
|
const opCtx = {
|
|
2071
2187
|
object,
|
|
@@ -2088,7 +2204,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2088
2204
|
if (hookContext.input.id) {
|
|
2089
2205
|
result = await driver.delete(object, hookContext.input.id, hookContext.input.options);
|
|
2090
2206
|
} else if (options?.multi && driver.deleteMany) {
|
|
2091
|
-
const ast =
|
|
2207
|
+
const ast = { object, where: options.where };
|
|
2092
2208
|
result = await driver.deleteMany(object, ast, hookContext.input.options);
|
|
2093
2209
|
} else {
|
|
2094
2210
|
throw new Error("Delete requires an ID or options.multi=true");
|
|
@@ -2115,10 +2231,10 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2115
2231
|
};
|
|
2116
2232
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
2117
2233
|
if (driver.count) {
|
|
2118
|
-
const ast =
|
|
2234
|
+
const ast = { object, where: query?.where };
|
|
2119
2235
|
return driver.count(object, ast);
|
|
2120
2236
|
}
|
|
2121
|
-
const res = await this.find(object, {
|
|
2237
|
+
const res = await this.find(object, { where: query?.where, fields: ["id"] });
|
|
2122
2238
|
return res.length;
|
|
2123
2239
|
});
|
|
2124
2240
|
return opCtx.result;
|
|
@@ -2136,13 +2252,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2136
2252
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
2137
2253
|
const ast = {
|
|
2138
2254
|
object,
|
|
2139
|
-
where: query.
|
|
2255
|
+
where: query.where,
|
|
2140
2256
|
groupBy: query.groupBy,
|
|
2141
|
-
aggregations: query.aggregations
|
|
2142
|
-
function: agg.method,
|
|
2143
|
-
field: agg.field,
|
|
2144
|
-
alias: agg.alias || `${agg.method}_${agg.field || "all"}`
|
|
2145
|
-
}))
|
|
2257
|
+
aggregations: query.aggregations
|
|
2146
2258
|
};
|
|
2147
2259
|
return driver.find(object, ast);
|
|
2148
2260
|
});
|
|
@@ -2370,7 +2482,7 @@ var ObjectRepository = class {
|
|
|
2370
2482
|
/** Update a single record by ID */
|
|
2371
2483
|
async updateById(id, data) {
|
|
2372
2484
|
return this.engine.update(this.objectName, { ...data, id }, {
|
|
2373
|
-
|
|
2485
|
+
where: { id },
|
|
2374
2486
|
context: this.context
|
|
2375
2487
|
});
|
|
2376
2488
|
}
|
|
@@ -2383,7 +2495,7 @@ var ObjectRepository = class {
|
|
|
2383
2495
|
/** Delete a single record by ID */
|
|
2384
2496
|
async deleteById(id) {
|
|
2385
2497
|
return this.engine.delete(this.objectName, {
|
|
2386
|
-
|
|
2498
|
+
where: { id },
|
|
2387
2499
|
context: this.context
|
|
2388
2500
|
});
|
|
2389
2501
|
}
|
|
@@ -2679,7 +2791,7 @@ var ObjectQLPlugin = class {
|
|
|
2679
2791
|
if (hookCtx.input?.id && !hookCtx.previous) {
|
|
2680
2792
|
try {
|
|
2681
2793
|
const existing = await this.ql.findOne(hookCtx.object, {
|
|
2682
|
-
|
|
2794
|
+
where: { id: hookCtx.input.id }
|
|
2683
2795
|
});
|
|
2684
2796
|
if (existing) {
|
|
2685
2797
|
hookCtx.previous = existing;
|
|
@@ -2692,7 +2804,7 @@ var ObjectQLPlugin = class {
|
|
|
2692
2804
|
if (hookCtx.input?.id && !hookCtx.previous) {
|
|
2693
2805
|
try {
|
|
2694
2806
|
const existing = await this.ql.findOne(hookCtx.object, {
|
|
2695
|
-
|
|
2807
|
+
where: { id: hookCtx.input.id }
|
|
2696
2808
|
});
|
|
2697
2809
|
if (existing) {
|
|
2698
2810
|
hookCtx.previous = existing;
|
|
@@ -2730,9 +2842,13 @@ var ObjectQLPlugin = class {
|
|
|
2730
2842
|
/**
|
|
2731
2843
|
* Synchronize all registered object schemas to the database.
|
|
2732
2844
|
*
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
2845
|
+
* Groups objects by their responsible driver, then:
|
|
2846
|
+
* - If the driver advertises `supports.batchSchemaSync` and implements
|
|
2847
|
+
* `syncSchemasBatch()`, submits all schemas in a single call (reducing
|
|
2848
|
+
* network round-trips for remote drivers like Turso).
|
|
2849
|
+
* - Otherwise falls back to sequential `syncSchema()` per object.
|
|
2850
|
+
*
|
|
2851
|
+
* This is idempotent — drivers must tolerate repeated calls without
|
|
2736
2852
|
* duplicating tables or erroring out.
|
|
2737
2853
|
*
|
|
2738
2854
|
* Drivers that do not implement `syncSchema` are silently skipped.
|
|
@@ -2743,6 +2859,7 @@ var ObjectQLPlugin = class {
|
|
|
2743
2859
|
if (allObjects.length === 0) return;
|
|
2744
2860
|
let synced = 0;
|
|
2745
2861
|
let skipped = 0;
|
|
2862
|
+
const driverGroups = /* @__PURE__ */ new Map();
|
|
2746
2863
|
for (const obj of allObjects) {
|
|
2747
2864
|
const driver = this.ql.getDriverForObject(obj.name);
|
|
2748
2865
|
if (!driver) {
|
|
@@ -2761,16 +2878,59 @@ var ObjectQLPlugin = class {
|
|
|
2761
2878
|
continue;
|
|
2762
2879
|
}
|
|
2763
2880
|
const tableName = obj.tableName || obj.name;
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2881
|
+
let group = driverGroups.get(driver);
|
|
2882
|
+
if (!group) {
|
|
2883
|
+
group = [];
|
|
2884
|
+
driverGroups.set(driver, group);
|
|
2885
|
+
}
|
|
2886
|
+
group.push({ obj, tableName });
|
|
2887
|
+
}
|
|
2888
|
+
for (const [driver, entries] of driverGroups) {
|
|
2889
|
+
if (driver.supports?.batchSchemaSync && typeof driver.syncSchemasBatch === "function") {
|
|
2890
|
+
const batchPayload = entries.map((e) => ({
|
|
2891
|
+
object: e.tableName,
|
|
2892
|
+
schema: e.obj
|
|
2893
|
+
}));
|
|
2894
|
+
try {
|
|
2895
|
+
await driver.syncSchemasBatch(batchPayload);
|
|
2896
|
+
synced += entries.length;
|
|
2897
|
+
ctx.logger.debug("Batch schema sync succeeded", {
|
|
2898
|
+
driver: driver.name,
|
|
2899
|
+
count: entries.length
|
|
2900
|
+
});
|
|
2901
|
+
} catch (e) {
|
|
2902
|
+
ctx.logger.warn("Batch schema sync failed, falling back to sequential", {
|
|
2903
|
+
driver: driver.name,
|
|
2904
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2905
|
+
});
|
|
2906
|
+
for (const { obj, tableName } of entries) {
|
|
2907
|
+
try {
|
|
2908
|
+
await driver.syncSchema(tableName, obj);
|
|
2909
|
+
synced++;
|
|
2910
|
+
} catch (seqErr) {
|
|
2911
|
+
ctx.logger.warn("Failed to sync schema for object", {
|
|
2912
|
+
object: obj.name,
|
|
2913
|
+
tableName,
|
|
2914
|
+
driver: driver.name,
|
|
2915
|
+
error: seqErr instanceof Error ? seqErr.message : String(seqErr)
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
} else {
|
|
2921
|
+
for (const { obj, tableName } of entries) {
|
|
2922
|
+
try {
|
|
2923
|
+
await driver.syncSchema(tableName, obj);
|
|
2924
|
+
synced++;
|
|
2925
|
+
} catch (e) {
|
|
2926
|
+
ctx.logger.warn("Failed to sync schema for object", {
|
|
2927
|
+
object: obj.name,
|
|
2928
|
+
tableName,
|
|
2929
|
+
driver: driver.name,
|
|
2930
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2774
2934
|
}
|
|
2775
2935
|
}
|
|
2776
2936
|
if (synced > 0 || skipped > 0) {
|