@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.js
CHANGED
|
@@ -587,7 +587,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
587
587
|
const registeredServices = this.getServicesRegistry ? this.getServicesRegistry() : /* @__PURE__ */ new Map();
|
|
588
588
|
const services = {
|
|
589
589
|
// --- Kernel-provided (objectql is an example kernel implementation) ---
|
|
590
|
-
metadata: { enabled: true, status: "
|
|
590
|
+
metadata: { enabled: true, status: "available", route: "/api/v1/meta", provider: "objectql" },
|
|
591
591
|
data: { enabled: true, status: "available", route: "/api/v1/data", provider: "objectql" },
|
|
592
592
|
analytics: { enabled: true, status: "available", route: "/api/v1/analytics", provider: "objectql" }
|
|
593
593
|
};
|
|
@@ -649,7 +649,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
649
649
|
metadata: "/api/v1/meta",
|
|
650
650
|
...optionalRoutes
|
|
651
651
|
};
|
|
652
|
-
const
|
|
652
|
+
const wellKnown = {
|
|
653
653
|
feed: registeredServices.has("feed"),
|
|
654
654
|
comments: registeredServices.has("feed"),
|
|
655
655
|
automation: registeredServices.has("automation"),
|
|
@@ -658,6 +658,10 @@ var ObjectStackProtocolImplementation = class {
|
|
|
658
658
|
export: registeredServices.has("automation") || registeredServices.has("queue"),
|
|
659
659
|
chunkedUpload: registeredServices.has("file-storage")
|
|
660
660
|
};
|
|
661
|
+
const capabilities = {};
|
|
662
|
+
for (const [key, enabled] of Object.entries(wellKnown)) {
|
|
663
|
+
capabilities[key] = { enabled };
|
|
664
|
+
}
|
|
661
665
|
return {
|
|
662
666
|
version: "1.0",
|
|
663
667
|
apiName: "ObjectStack API",
|
|
@@ -677,6 +681,33 @@ var ObjectStackProtocolImplementation = class {
|
|
|
677
681
|
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
678
682
|
items = SchemaRegistry.listItems(alt);
|
|
679
683
|
}
|
|
684
|
+
if (items.length === 0) {
|
|
685
|
+
try {
|
|
686
|
+
const allRecords = await this.engine.find("sys_metadata", {
|
|
687
|
+
where: { type: request.type, state: "active" }
|
|
688
|
+
});
|
|
689
|
+
if (allRecords && allRecords.length > 0) {
|
|
690
|
+
items = allRecords.map((record) => {
|
|
691
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
692
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
693
|
+
return data;
|
|
694
|
+
});
|
|
695
|
+
} else {
|
|
696
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
697
|
+
const altRecords = await this.engine.find("sys_metadata", {
|
|
698
|
+
where: { type: alt, state: "active" }
|
|
699
|
+
});
|
|
700
|
+
if (altRecords && altRecords.length > 0) {
|
|
701
|
+
items = altRecords.map((record) => {
|
|
702
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
703
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
704
|
+
return data;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
}
|
|
680
711
|
return {
|
|
681
712
|
type: request.type,
|
|
682
713
|
items
|
|
@@ -688,6 +719,27 @@ var ObjectStackProtocolImplementation = class {
|
|
|
688
719
|
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
689
720
|
item = SchemaRegistry.getItem(alt, request.name);
|
|
690
721
|
}
|
|
722
|
+
if (item === void 0) {
|
|
723
|
+
try {
|
|
724
|
+
const record = await this.engine.findOne("sys_metadata", {
|
|
725
|
+
where: { type: request.type, name: request.name, state: "active" }
|
|
726
|
+
});
|
|
727
|
+
if (record) {
|
|
728
|
+
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
729
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
730
|
+
} else {
|
|
731
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
732
|
+
const altRecord = await this.engine.findOne("sys_metadata", {
|
|
733
|
+
where: { type: alt, name: request.name, state: "active" }
|
|
734
|
+
});
|
|
735
|
+
if (altRecord) {
|
|
736
|
+
item = typeof altRecord.metadata === "string" ? JSON.parse(altRecord.metadata) : altRecord.metadata;
|
|
737
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
}
|
|
691
743
|
return {
|
|
692
744
|
type: request.type,
|
|
693
745
|
name: request.name,
|
|
@@ -751,12 +803,22 @@ var ObjectStackProtocolImplementation = class {
|
|
|
751
803
|
}
|
|
752
804
|
async findData(request) {
|
|
753
805
|
const options = { ...request.query };
|
|
754
|
-
if (options.top != null)
|
|
755
|
-
|
|
806
|
+
if (options.top != null) {
|
|
807
|
+
options.limit = Number(options.top);
|
|
808
|
+
delete options.top;
|
|
809
|
+
}
|
|
810
|
+
if (options.skip != null) {
|
|
811
|
+
options.offset = Number(options.skip);
|
|
812
|
+
delete options.skip;
|
|
813
|
+
}
|
|
756
814
|
if (options.limit != null) options.limit = Number(options.limit);
|
|
815
|
+
if (options.offset != null) options.offset = Number(options.offset);
|
|
757
816
|
if (typeof options.select === "string") {
|
|
758
|
-
options.
|
|
817
|
+
options.fields = options.select.split(",").map((s) => s.trim()).filter(Boolean);
|
|
818
|
+
} else if (Array.isArray(options.select)) {
|
|
819
|
+
options.fields = options.select;
|
|
759
820
|
}
|
|
821
|
+
if (options.select !== void 0) delete options.select;
|
|
760
822
|
const sortValue = options.orderBy ?? options.sort;
|
|
761
823
|
if (typeof sortValue === "string") {
|
|
762
824
|
const parsed = sortValue.split(",").map((part) => {
|
|
@@ -767,62 +829,75 @@ var ObjectStackProtocolImplementation = class {
|
|
|
767
829
|
const [field, order] = trimmed.split(/\s+/);
|
|
768
830
|
return { field, order: order?.toLowerCase() === "desc" ? "desc" : "asc" };
|
|
769
831
|
}).filter((s) => s.field);
|
|
770
|
-
options.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (options.filters !== void 0 && options.filter === void 0) {
|
|
774
|
-
options.filter = options.filters;
|
|
832
|
+
options.orderBy = parsed;
|
|
833
|
+
} else if (Array.isArray(sortValue)) {
|
|
834
|
+
options.orderBy = sortValue;
|
|
775
835
|
}
|
|
836
|
+
delete options.sort;
|
|
837
|
+
const filterValue = options.filter ?? options.filters ?? options.$filter ?? options.where;
|
|
838
|
+
delete options.filter;
|
|
776
839
|
delete options.filters;
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
840
|
+
delete options.$filter;
|
|
841
|
+
if (filterValue !== void 0) {
|
|
842
|
+
let parsedFilter = filterValue;
|
|
843
|
+
if (typeof parsedFilter === "string") {
|
|
844
|
+
try {
|
|
845
|
+
parsedFilter = JSON.parse(parsedFilter);
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
781
848
|
}
|
|
849
|
+
if ((0, import_data2.isFilterAST)(parsedFilter)) {
|
|
850
|
+
parsedFilter = (0, import_data2.parseFilterAST)(parsedFilter);
|
|
851
|
+
}
|
|
852
|
+
options.where = parsedFilter;
|
|
782
853
|
}
|
|
783
|
-
|
|
784
|
-
options.filter = (0, import_data2.parseFilterAST)(options.filter);
|
|
785
|
-
}
|
|
786
|
-
if (typeof options.populate === "string") {
|
|
787
|
-
options.populate = options.populate.split(",").map((s) => s.trim()).filter(Boolean);
|
|
788
|
-
}
|
|
854
|
+
const populateValue = options.populate;
|
|
789
855
|
const expandValue = options.$expand ?? options.expand;
|
|
790
|
-
|
|
856
|
+
const expandNames = [];
|
|
857
|
+
if (typeof populateValue === "string") {
|
|
858
|
+
expandNames.push(...populateValue.split(",").map((s) => s.trim()).filter(Boolean));
|
|
859
|
+
} else if (Array.isArray(populateValue)) {
|
|
860
|
+
expandNames.push(...populateValue);
|
|
861
|
+
}
|
|
862
|
+
if (!expandNames.length && expandValue) {
|
|
791
863
|
if (typeof expandValue === "string") {
|
|
792
|
-
|
|
864
|
+
expandNames.push(...expandValue.split(",").map((s) => s.trim()).filter(Boolean));
|
|
793
865
|
} else if (Array.isArray(expandValue)) {
|
|
794
|
-
|
|
866
|
+
expandNames.push(...expandValue);
|
|
795
867
|
}
|
|
796
868
|
}
|
|
869
|
+
delete options.populate;
|
|
797
870
|
delete options.$expand;
|
|
798
|
-
|
|
871
|
+
if (typeof options.expand !== "object" || options.expand === null) {
|
|
872
|
+
delete options.expand;
|
|
873
|
+
}
|
|
874
|
+
if (expandNames.length > 0 && !options.expand) {
|
|
875
|
+
options.expand = {};
|
|
876
|
+
for (const rel of expandNames) {
|
|
877
|
+
options.expand[rel] = { object: rel };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
799
880
|
for (const key of ["distinct", "count"]) {
|
|
800
881
|
if (options[key] === "true") options[key] = true;
|
|
801
882
|
else if (options[key] === "false") options[key] = false;
|
|
802
883
|
}
|
|
803
884
|
const knownParams = /* @__PURE__ */ new Set([
|
|
804
885
|
"top",
|
|
805
|
-
"skip",
|
|
806
886
|
"limit",
|
|
807
887
|
"offset",
|
|
808
|
-
"sort",
|
|
809
888
|
"orderBy",
|
|
810
|
-
"select",
|
|
811
889
|
"fields",
|
|
812
|
-
"
|
|
813
|
-
"filters",
|
|
814
|
-
"$filter",
|
|
815
|
-
"populate",
|
|
890
|
+
"where",
|
|
816
891
|
"expand",
|
|
817
|
-
"$expand",
|
|
818
892
|
"distinct",
|
|
819
893
|
"count",
|
|
820
894
|
"aggregations",
|
|
821
895
|
"groupBy",
|
|
822
896
|
"search",
|
|
823
|
-
"context"
|
|
897
|
+
"context",
|
|
898
|
+
"cursor"
|
|
824
899
|
]);
|
|
825
|
-
if (!options.
|
|
900
|
+
if (!options.where) {
|
|
826
901
|
const implicitFilters = {};
|
|
827
902
|
for (const key of Object.keys(options)) {
|
|
828
903
|
if (!knownParams.has(key)) {
|
|
@@ -831,14 +906,14 @@ var ObjectStackProtocolImplementation = class {
|
|
|
831
906
|
}
|
|
832
907
|
}
|
|
833
908
|
if (Object.keys(implicitFilters).length > 0) {
|
|
834
|
-
options.
|
|
909
|
+
options.where = implicitFilters;
|
|
835
910
|
}
|
|
836
911
|
}
|
|
837
912
|
const records = await this.engine.find(request.object, options);
|
|
838
913
|
return {
|
|
839
914
|
object: request.object,
|
|
840
915
|
value: records,
|
|
841
|
-
// OData
|
|
916
|
+
// OData compatibility
|
|
842
917
|
records,
|
|
843
918
|
// Legacy
|
|
844
919
|
total: records.length,
|
|
@@ -847,13 +922,17 @@ var ObjectStackProtocolImplementation = class {
|
|
|
847
922
|
}
|
|
848
923
|
async getData(request) {
|
|
849
924
|
const queryOptions = {
|
|
850
|
-
|
|
925
|
+
where: { id: request.id }
|
|
851
926
|
};
|
|
852
927
|
if (request.select) {
|
|
853
|
-
queryOptions.
|
|
928
|
+
queryOptions.fields = typeof request.select === "string" ? request.select.split(",").map((s) => s.trim()).filter(Boolean) : request.select;
|
|
854
929
|
}
|
|
855
930
|
if (request.expand) {
|
|
856
|
-
|
|
931
|
+
const expandNames = typeof request.expand === "string" ? request.expand.split(",").map((s) => s.trim()).filter(Boolean) : request.expand;
|
|
932
|
+
queryOptions.expand = {};
|
|
933
|
+
for (const rel of expandNames) {
|
|
934
|
+
queryOptions.expand[rel] = { object: rel };
|
|
935
|
+
}
|
|
857
936
|
}
|
|
858
937
|
const result = await this.engine.findOne(request.object, queryOptions);
|
|
859
938
|
if (result) {
|
|
@@ -874,7 +953,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
874
953
|
};
|
|
875
954
|
}
|
|
876
955
|
async updateData(request) {
|
|
877
|
-
const result = await this.engine.update(request.object, request.data, {
|
|
956
|
+
const result = await this.engine.update(request.object, request.data, { where: { id: request.id } });
|
|
878
957
|
return {
|
|
879
958
|
object: request.object,
|
|
880
959
|
id: request.id,
|
|
@@ -882,7 +961,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
882
961
|
};
|
|
883
962
|
}
|
|
884
963
|
async deleteData(request) {
|
|
885
|
-
await this.engine.delete(request.object, {
|
|
964
|
+
await this.engine.delete(request.object, { where: { id: request.id } });
|
|
886
965
|
return {
|
|
887
966
|
object: request.object,
|
|
888
967
|
id: request.id,
|
|
@@ -945,7 +1024,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
945
1024
|
}
|
|
946
1025
|
case "update": {
|
|
947
1026
|
if (!record.id) throw new Error("Record id is required for update");
|
|
948
|
-
const updated = await this.engine.update(object, record.data || {}, {
|
|
1027
|
+
const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });
|
|
949
1028
|
results.push({ id: record.id, success: true, record: updated });
|
|
950
1029
|
succeeded++;
|
|
951
1030
|
break;
|
|
@@ -953,9 +1032,9 @@ var ObjectStackProtocolImplementation = class {
|
|
|
953
1032
|
case "upsert": {
|
|
954
1033
|
if (record.id) {
|
|
955
1034
|
try {
|
|
956
|
-
const existing = await this.engine.findOne(object, {
|
|
1035
|
+
const existing = await this.engine.findOne(object, { where: { id: record.id } });
|
|
957
1036
|
if (existing) {
|
|
958
|
-
const updated = await this.engine.update(object, record.data || {}, {
|
|
1037
|
+
const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });
|
|
959
1038
|
results.push({ id: record.id, success: true, record: updated });
|
|
960
1039
|
} else {
|
|
961
1040
|
const created = await this.engine.insert(object, { id: record.id, ...record.data || {} });
|
|
@@ -974,7 +1053,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
974
1053
|
}
|
|
975
1054
|
case "delete": {
|
|
976
1055
|
if (!record.id) throw new Error("Record id is required for delete");
|
|
977
|
-
await this.engine.delete(object, {
|
|
1056
|
+
await this.engine.delete(object, { where: { id: record.id } });
|
|
978
1057
|
results.push({ id: record.id, success: true });
|
|
979
1058
|
succeeded++;
|
|
980
1059
|
break;
|
|
@@ -1018,7 +1097,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1018
1097
|
let failed = 0;
|
|
1019
1098
|
for (const record of records) {
|
|
1020
1099
|
try {
|
|
1021
|
-
const updated = await this.engine.update(object, record.data, {
|
|
1100
|
+
const updated = await this.engine.update(object, record.data, { where: { id: record.id } });
|
|
1022
1101
|
results.push({ id: record.id, success: true, record: updated });
|
|
1023
1102
|
succeeded++;
|
|
1024
1103
|
} catch (err) {
|
|
@@ -1069,9 +1148,9 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1069
1148
|
filter = conditions.length === 1 ? conditions[0] : { $and: conditions };
|
|
1070
1149
|
}
|
|
1071
1150
|
const rows = await this.engine.aggregate(object, {
|
|
1072
|
-
filter,
|
|
1151
|
+
where: filter,
|
|
1073
1152
|
groupBy: groupBy.length > 0 ? groupBy : void 0,
|
|
1074
|
-
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({
|
|
1153
|
+
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({ function: a.method, field: a.field, alias: a.alias })) : [{ function: "count", alias: "count" }]
|
|
1075
1154
|
});
|
|
1076
1155
|
const fields = [
|
|
1077
1156
|
...groupBy.map((d) => ({ name: d, type: "string" })),
|
|
@@ -1182,7 +1261,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1182
1261
|
}
|
|
1183
1262
|
async deleteManyData(request) {
|
|
1184
1263
|
return this.engine.delete(request.object, {
|
|
1185
|
-
|
|
1264
|
+
where: { id: { $in: request.ids } },
|
|
1186
1265
|
...request.options
|
|
1187
1266
|
});
|
|
1188
1267
|
}
|
|
@@ -1191,10 +1270,76 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1191
1270
|
throw new Error("Item data is required");
|
|
1192
1271
|
}
|
|
1193
1272
|
SchemaRegistry.registerItem(request.type, request.item, "name");
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1273
|
+
try {
|
|
1274
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1275
|
+
const existing = await this.engine.findOne("sys_metadata", {
|
|
1276
|
+
where: { type: request.type, name: request.name }
|
|
1277
|
+
});
|
|
1278
|
+
if (existing) {
|
|
1279
|
+
await this.engine.update("sys_metadata", {
|
|
1280
|
+
metadata: JSON.stringify(request.item),
|
|
1281
|
+
updated_at: now,
|
|
1282
|
+
version: (existing.version || 0) + 1
|
|
1283
|
+
}, {
|
|
1284
|
+
where: { id: existing.id }
|
|
1285
|
+
});
|
|
1286
|
+
} else {
|
|
1287
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `meta_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1288
|
+
await this.engine.insert("sys_metadata", {
|
|
1289
|
+
id,
|
|
1290
|
+
name: request.name,
|
|
1291
|
+
type: request.type,
|
|
1292
|
+
scope: "platform",
|
|
1293
|
+
metadata: JSON.stringify(request.item),
|
|
1294
|
+
state: "active",
|
|
1295
|
+
version: 1,
|
|
1296
|
+
created_at: now,
|
|
1297
|
+
updated_at: now
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
return {
|
|
1301
|
+
success: true,
|
|
1302
|
+
message: "Saved to database and registry"
|
|
1303
|
+
};
|
|
1304
|
+
} catch (dbError) {
|
|
1305
|
+
console.warn(`[Protocol] DB persistence failed for ${request.type}/${request.name}: ${dbError.message}`);
|
|
1306
|
+
return {
|
|
1307
|
+
success: true,
|
|
1308
|
+
message: "Saved to memory registry (DB persistence unavailable)",
|
|
1309
|
+
warning: dbError.message
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Hydrate SchemaRegistry from the database on startup.
|
|
1315
|
+
* Loads all active metadata records and registers them in the in-memory registry.
|
|
1316
|
+
* Safe to call repeatedly — idempotent (latest DB record wins).
|
|
1317
|
+
*/
|
|
1318
|
+
async loadMetaFromDb() {
|
|
1319
|
+
let loaded = 0;
|
|
1320
|
+
let errors = 0;
|
|
1321
|
+
try {
|
|
1322
|
+
const records = await this.engine.find("sys_metadata", {
|
|
1323
|
+
where: { state: "active" }
|
|
1324
|
+
});
|
|
1325
|
+
for (const record of records) {
|
|
1326
|
+
try {
|
|
1327
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1328
|
+
if (record.type === "object") {
|
|
1329
|
+
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1330
|
+
} else {
|
|
1331
|
+
SchemaRegistry.registerItem(record.type, data, "name");
|
|
1332
|
+
}
|
|
1333
|
+
loaded++;
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
errors++;
|
|
1336
|
+
console.warn(`[Protocol] Failed to hydrate ${record.type}/${record.name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
} catch (e) {
|
|
1340
|
+
console.warn(`[Protocol] DB hydration skipped: ${e.message}`);
|
|
1341
|
+
}
|
|
1342
|
+
return { loaded, errors };
|
|
1198
1343
|
}
|
|
1199
1344
|
// ==========================================
|
|
1200
1345
|
// Feed Operations
|
|
@@ -1912,46 +2057,18 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1912
2057
|
return records;
|
|
1913
2058
|
}
|
|
1914
2059
|
// ============================================
|
|
1915
|
-
// Helper: Query Conversion
|
|
1916
|
-
// ============================================
|
|
1917
|
-
toQueryAST(object, options) {
|
|
1918
|
-
const ast = { object };
|
|
1919
|
-
if (!options) return ast;
|
|
1920
|
-
if (options.filter) {
|
|
1921
|
-
ast.where = options.filter;
|
|
1922
|
-
}
|
|
1923
|
-
if (options.select) {
|
|
1924
|
-
ast.fields = options.select;
|
|
1925
|
-
}
|
|
1926
|
-
if (options.sort) {
|
|
1927
|
-
if (Array.isArray(options.sort)) {
|
|
1928
|
-
ast.orderBy = options.sort;
|
|
1929
|
-
} else {
|
|
1930
|
-
ast.orderBy = Object.entries(options.sort).map(([field, order]) => ({
|
|
1931
|
-
field,
|
|
1932
|
-
order: order === -1 || order === "desc" ? "desc" : "asc"
|
|
1933
|
-
}));
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
if (options.top !== void 0) ast.limit = options.top;
|
|
1937
|
-
else if (options.limit !== void 0) ast.limit = options.limit;
|
|
1938
|
-
if (options.skip !== void 0) ast.offset = options.skip;
|
|
1939
|
-
if (options.populate && options.populate.length > 0) {
|
|
1940
|
-
ast.expand = {};
|
|
1941
|
-
for (const rel of options.populate) {
|
|
1942
|
-
ast.expand[rel] = { object: rel };
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
return ast;
|
|
1946
|
-
}
|
|
1947
|
-
// ============================================
|
|
1948
2060
|
// Data Access Methods (IDataEngine Interface)
|
|
1949
2061
|
// ============================================
|
|
1950
2062
|
async find(object, query) {
|
|
1951
2063
|
object = this.resolveObjectName(object);
|
|
1952
2064
|
this.logger.debug("Find operation starting", { object, query });
|
|
1953
2065
|
const driver = this.getDriver(object);
|
|
1954
|
-
const ast =
|
|
2066
|
+
const ast = { object, ...query };
|
|
2067
|
+
delete ast.context;
|
|
2068
|
+
if (ast.top != null && ast.limit == null) {
|
|
2069
|
+
ast.limit = ast.top;
|
|
2070
|
+
}
|
|
2071
|
+
delete ast.top;
|
|
1955
2072
|
const opCtx = {
|
|
1956
2073
|
object,
|
|
1957
2074
|
operation: "find",
|
|
@@ -1989,8 +2106,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1989
2106
|
objectName = this.resolveObjectName(objectName);
|
|
1990
2107
|
this.logger.debug("FindOne operation", { objectName });
|
|
1991
2108
|
const driver = this.getDriver(objectName);
|
|
1992
|
-
const ast =
|
|
1993
|
-
ast.
|
|
2109
|
+
const ast = { object: objectName, ...query, limit: 1 };
|
|
2110
|
+
delete ast.context;
|
|
2111
|
+
delete ast.top;
|
|
1994
2112
|
const opCtx = {
|
|
1995
2113
|
object: objectName,
|
|
1996
2114
|
operation: "findOne",
|
|
@@ -2056,9 +2174,8 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2056
2174
|
this.logger.debug("Update operation starting", { object });
|
|
2057
2175
|
const driver = this.getDriver(object);
|
|
2058
2176
|
let id = data.id;
|
|
2059
|
-
if (!id && options?.
|
|
2060
|
-
|
|
2061
|
-
else if (options.filter.id) id = options.filter.id;
|
|
2177
|
+
if (!id && options?.where && typeof options.where === "object" && "id" in options.where) {
|
|
2178
|
+
id = options.where.id;
|
|
2062
2179
|
}
|
|
2063
2180
|
const opCtx = {
|
|
2064
2181
|
object,
|
|
@@ -2082,7 +2199,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2082
2199
|
if (hookContext.input.id) {
|
|
2083
2200
|
result = await driver.update(object, hookContext.input.id, hookContext.input.data, hookContext.input.options);
|
|
2084
2201
|
} else if (options?.multi && driver.updateMany) {
|
|
2085
|
-
const ast =
|
|
2202
|
+
const ast = { object, where: options.where };
|
|
2086
2203
|
result = await driver.updateMany(object, ast, hookContext.input.data, hookContext.input.options);
|
|
2087
2204
|
} else {
|
|
2088
2205
|
throw new Error("Update requires an ID or options.multi=true");
|
|
@@ -2103,9 +2220,8 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2103
2220
|
this.logger.debug("Delete operation starting", { object });
|
|
2104
2221
|
const driver = this.getDriver(object);
|
|
2105
2222
|
let id = void 0;
|
|
2106
|
-
if (options?.
|
|
2107
|
-
|
|
2108
|
-
else if (options.filter.id) id = options.filter.id;
|
|
2223
|
+
if (options?.where && typeof options.where === "object" && "id" in options.where) {
|
|
2224
|
+
id = options.where.id;
|
|
2109
2225
|
}
|
|
2110
2226
|
const opCtx = {
|
|
2111
2227
|
object,
|
|
@@ -2128,7 +2244,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2128
2244
|
if (hookContext.input.id) {
|
|
2129
2245
|
result = await driver.delete(object, hookContext.input.id, hookContext.input.options);
|
|
2130
2246
|
} else if (options?.multi && driver.deleteMany) {
|
|
2131
|
-
const ast =
|
|
2247
|
+
const ast = { object, where: options.where };
|
|
2132
2248
|
result = await driver.deleteMany(object, ast, hookContext.input.options);
|
|
2133
2249
|
} else {
|
|
2134
2250
|
throw new Error("Delete requires an ID or options.multi=true");
|
|
@@ -2155,10 +2271,10 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2155
2271
|
};
|
|
2156
2272
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
2157
2273
|
if (driver.count) {
|
|
2158
|
-
const ast =
|
|
2274
|
+
const ast = { object, where: query?.where };
|
|
2159
2275
|
return driver.count(object, ast);
|
|
2160
2276
|
}
|
|
2161
|
-
const res = await this.find(object, {
|
|
2277
|
+
const res = await this.find(object, { where: query?.where, fields: ["id"] });
|
|
2162
2278
|
return res.length;
|
|
2163
2279
|
});
|
|
2164
2280
|
return opCtx.result;
|
|
@@ -2176,13 +2292,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2176
2292
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
2177
2293
|
const ast = {
|
|
2178
2294
|
object,
|
|
2179
|
-
where: query.
|
|
2295
|
+
where: query.where,
|
|
2180
2296
|
groupBy: query.groupBy,
|
|
2181
|
-
aggregations: query.aggregations
|
|
2182
|
-
function: agg.method,
|
|
2183
|
-
field: agg.field,
|
|
2184
|
-
alias: agg.alias || `${agg.method}_${agg.field || "all"}`
|
|
2185
|
-
}))
|
|
2297
|
+
aggregations: query.aggregations
|
|
2186
2298
|
};
|
|
2187
2299
|
return driver.find(object, ast);
|
|
2188
2300
|
});
|
|
@@ -2410,7 +2522,7 @@ var ObjectRepository = class {
|
|
|
2410
2522
|
/** Update a single record by ID */
|
|
2411
2523
|
async updateById(id, data) {
|
|
2412
2524
|
return this.engine.update(this.objectName, { ...data, id }, {
|
|
2413
|
-
|
|
2525
|
+
where: { id },
|
|
2414
2526
|
context: this.context
|
|
2415
2527
|
});
|
|
2416
2528
|
}
|
|
@@ -2423,7 +2535,7 @@ var ObjectRepository = class {
|
|
|
2423
2535
|
/** Delete a single record by ID */
|
|
2424
2536
|
async deleteById(id) {
|
|
2425
2537
|
return this.engine.delete(this.objectName, {
|
|
2426
|
-
|
|
2538
|
+
where: { id },
|
|
2427
2539
|
context: this.context
|
|
2428
2540
|
});
|
|
2429
2541
|
}
|
|
@@ -2719,7 +2831,7 @@ var ObjectQLPlugin = class {
|
|
|
2719
2831
|
if (hookCtx.input?.id && !hookCtx.previous) {
|
|
2720
2832
|
try {
|
|
2721
2833
|
const existing = await this.ql.findOne(hookCtx.object, {
|
|
2722
|
-
|
|
2834
|
+
where: { id: hookCtx.input.id }
|
|
2723
2835
|
});
|
|
2724
2836
|
if (existing) {
|
|
2725
2837
|
hookCtx.previous = existing;
|
|
@@ -2732,7 +2844,7 @@ var ObjectQLPlugin = class {
|
|
|
2732
2844
|
if (hookCtx.input?.id && !hookCtx.previous) {
|
|
2733
2845
|
try {
|
|
2734
2846
|
const existing = await this.ql.findOne(hookCtx.object, {
|
|
2735
|
-
|
|
2847
|
+
where: { id: hookCtx.input.id }
|
|
2736
2848
|
});
|
|
2737
2849
|
if (existing) {
|
|
2738
2850
|
hookCtx.previous = existing;
|
|
@@ -2770,9 +2882,13 @@ var ObjectQLPlugin = class {
|
|
|
2770
2882
|
/**
|
|
2771
2883
|
* Synchronize all registered object schemas to the database.
|
|
2772
2884
|
*
|
|
2773
|
-
*
|
|
2774
|
-
*
|
|
2775
|
-
*
|
|
2885
|
+
* Groups objects by their responsible driver, then:
|
|
2886
|
+
* - If the driver advertises `supports.batchSchemaSync` and implements
|
|
2887
|
+
* `syncSchemasBatch()`, submits all schemas in a single call (reducing
|
|
2888
|
+
* network round-trips for remote drivers like Turso).
|
|
2889
|
+
* - Otherwise falls back to sequential `syncSchema()` per object.
|
|
2890
|
+
*
|
|
2891
|
+
* This is idempotent — drivers must tolerate repeated calls without
|
|
2776
2892
|
* duplicating tables or erroring out.
|
|
2777
2893
|
*
|
|
2778
2894
|
* Drivers that do not implement `syncSchema` are silently skipped.
|
|
@@ -2783,6 +2899,7 @@ var ObjectQLPlugin = class {
|
|
|
2783
2899
|
if (allObjects.length === 0) return;
|
|
2784
2900
|
let synced = 0;
|
|
2785
2901
|
let skipped = 0;
|
|
2902
|
+
const driverGroups = /* @__PURE__ */ new Map();
|
|
2786
2903
|
for (const obj of allObjects) {
|
|
2787
2904
|
const driver = this.ql.getDriverForObject(obj.name);
|
|
2788
2905
|
if (!driver) {
|
|
@@ -2801,16 +2918,59 @@ var ObjectQLPlugin = class {
|
|
|
2801
2918
|
continue;
|
|
2802
2919
|
}
|
|
2803
2920
|
const tableName = obj.tableName || obj.name;
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2921
|
+
let group = driverGroups.get(driver);
|
|
2922
|
+
if (!group) {
|
|
2923
|
+
group = [];
|
|
2924
|
+
driverGroups.set(driver, group);
|
|
2925
|
+
}
|
|
2926
|
+
group.push({ obj, tableName });
|
|
2927
|
+
}
|
|
2928
|
+
for (const [driver, entries] of driverGroups) {
|
|
2929
|
+
if (driver.supports?.batchSchemaSync && typeof driver.syncSchemasBatch === "function") {
|
|
2930
|
+
const batchPayload = entries.map((e) => ({
|
|
2931
|
+
object: e.tableName,
|
|
2932
|
+
schema: e.obj
|
|
2933
|
+
}));
|
|
2934
|
+
try {
|
|
2935
|
+
await driver.syncSchemasBatch(batchPayload);
|
|
2936
|
+
synced += entries.length;
|
|
2937
|
+
ctx.logger.debug("Batch schema sync succeeded", {
|
|
2938
|
+
driver: driver.name,
|
|
2939
|
+
count: entries.length
|
|
2940
|
+
});
|
|
2941
|
+
} catch (e) {
|
|
2942
|
+
ctx.logger.warn("Batch schema sync failed, falling back to sequential", {
|
|
2943
|
+
driver: driver.name,
|
|
2944
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2945
|
+
});
|
|
2946
|
+
for (const { obj, tableName } of entries) {
|
|
2947
|
+
try {
|
|
2948
|
+
await driver.syncSchema(tableName, obj);
|
|
2949
|
+
synced++;
|
|
2950
|
+
} catch (seqErr) {
|
|
2951
|
+
ctx.logger.warn("Failed to sync schema for object", {
|
|
2952
|
+
object: obj.name,
|
|
2953
|
+
tableName,
|
|
2954
|
+
driver: driver.name,
|
|
2955
|
+
error: seqErr instanceof Error ? seqErr.message : String(seqErr)
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
} else {
|
|
2961
|
+
for (const { obj, tableName } of entries) {
|
|
2962
|
+
try {
|
|
2963
|
+
await driver.syncSchema(tableName, obj);
|
|
2964
|
+
synced++;
|
|
2965
|
+
} catch (e) {
|
|
2966
|
+
ctx.logger.warn("Failed to sync schema for object", {
|
|
2967
|
+
object: obj.name,
|
|
2968
|
+
tableName,
|
|
2969
|
+
driver: driver.name,
|
|
2970
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2814
2974
|
}
|
|
2815
2975
|
}
|
|
2816
2976
|
if (synced > 0 || skipped > 0) {
|