@objectstack/objectql 1.1.0 → 2.0.1
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 +20 -0
- package/dist/index.d.mts +276 -122
- package/dist/index.d.ts +276 -122
- package/dist/index.js +316 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +316 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/engine.ts +41 -15
- package/src/index.ts +2 -0
- package/src/plugin.ts +2 -0
- package/src/protocol.ts +323 -33
- package/src/registry.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -538,7 +538,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
538
538
|
constructor(engine) {
|
|
539
539
|
this.engine = engine;
|
|
540
540
|
}
|
|
541
|
-
async getDiscovery(
|
|
541
|
+
async getDiscovery() {
|
|
542
542
|
return {
|
|
543
543
|
version: "1.0",
|
|
544
544
|
apiName: "ObjectStack API",
|
|
@@ -546,33 +546,69 @@ var ObjectStackProtocolImplementation = class {
|
|
|
546
546
|
graphql: false,
|
|
547
547
|
search: false,
|
|
548
548
|
websockets: false,
|
|
549
|
-
files:
|
|
550
|
-
analytics:
|
|
551
|
-
|
|
549
|
+
files: false,
|
|
550
|
+
analytics: true,
|
|
551
|
+
ai: false,
|
|
552
|
+
workflow: false,
|
|
553
|
+
notifications: false,
|
|
554
|
+
i18n: false
|
|
552
555
|
},
|
|
553
556
|
endpoints: {
|
|
554
557
|
data: "/api/data",
|
|
555
558
|
metadata: "/api/meta",
|
|
556
|
-
|
|
559
|
+
analytics: "/api/analytics"
|
|
560
|
+
},
|
|
561
|
+
services: {
|
|
562
|
+
// --- Kernel-provided (objectql is an example kernel implementation) ---
|
|
563
|
+
metadata: { enabled: true, status: "degraded", route: "/api/meta", provider: "objectql", message: "In-memory registry only; DB persistence not yet implemented" },
|
|
564
|
+
data: { enabled: true, status: "available", route: "/api/data", provider: "objectql" },
|
|
565
|
+
analytics: { enabled: true, status: "available", route: "/api/analytics", provider: "objectql" },
|
|
566
|
+
// --- Plugin-provided (kernel does NOT handle these) ---
|
|
567
|
+
auth: { enabled: false, status: "unavailable", message: "Install an auth plugin (e.g. plugin-auth) to enable" },
|
|
568
|
+
automation: { enabled: false, status: "unavailable", message: "Install an automation plugin (e.g. plugin-automation) to enable" },
|
|
569
|
+
// --- Core infrastructure (plugin-provided) ---
|
|
570
|
+
cache: { enabled: false, status: "unavailable", message: "Install a cache plugin (e.g. plugin-redis) to enable" },
|
|
571
|
+
queue: { enabled: false, status: "unavailable", message: "Install a queue plugin (e.g. plugin-bullmq) to enable" },
|
|
572
|
+
job: { enabled: false, status: "unavailable", message: "Install a job scheduler plugin to enable" },
|
|
573
|
+
// --- Optional services (all plugin-provided) ---
|
|
574
|
+
ui: { enabled: false, status: "unavailable", message: "Install a UI plugin to enable" },
|
|
575
|
+
workflow: { enabled: false, status: "unavailable", message: "Install a workflow plugin to enable" },
|
|
576
|
+
realtime: { enabled: false, status: "unavailable", message: "Install a realtime plugin to enable" },
|
|
577
|
+
notification: { enabled: false, status: "unavailable", message: "Install a notification plugin to enable" },
|
|
578
|
+
ai: { enabled: false, status: "unavailable", message: "Install an AI plugin to enable" },
|
|
579
|
+
i18n: { enabled: false, status: "unavailable", message: "Install an i18n plugin to enable" },
|
|
580
|
+
graphql: { enabled: false, status: "unavailable", message: "Install a GraphQL plugin to enable" },
|
|
581
|
+
"file-storage": { enabled: false, status: "unavailable", message: "Install a file-storage plugin to enable" },
|
|
582
|
+
search: { enabled: false, status: "unavailable", message: "Install a search plugin to enable" }
|
|
557
583
|
}
|
|
558
584
|
};
|
|
559
585
|
}
|
|
560
|
-
async getMetaTypes(
|
|
586
|
+
async getMetaTypes() {
|
|
561
587
|
return {
|
|
562
588
|
types: SchemaRegistry.getRegisteredTypes()
|
|
563
589
|
};
|
|
564
590
|
}
|
|
565
591
|
async getMetaItems(request) {
|
|
592
|
+
let items = SchemaRegistry.listItems(request.type);
|
|
593
|
+
if (items.length === 0) {
|
|
594
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
595
|
+
items = SchemaRegistry.listItems(alt);
|
|
596
|
+
}
|
|
566
597
|
return {
|
|
567
598
|
type: request.type,
|
|
568
|
-
items
|
|
599
|
+
items
|
|
569
600
|
};
|
|
570
601
|
}
|
|
571
602
|
async getMetaItem(request) {
|
|
603
|
+
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
604
|
+
if (item === void 0) {
|
|
605
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
606
|
+
item = SchemaRegistry.getItem(alt, request.name);
|
|
607
|
+
}
|
|
572
608
|
return {
|
|
573
609
|
type: request.type,
|
|
574
610
|
name: request.name,
|
|
575
|
-
item
|
|
611
|
+
item
|
|
576
612
|
};
|
|
577
613
|
}
|
|
578
614
|
async getUiView(request) {
|
|
@@ -722,8 +758,80 @@ var ObjectStackProtocolImplementation = class {
|
|
|
722
758
|
// ==========================================
|
|
723
759
|
// Batch Operations
|
|
724
760
|
// ==========================================
|
|
725
|
-
async batchData(
|
|
726
|
-
|
|
761
|
+
async batchData(request) {
|
|
762
|
+
const { object, request: batchReq } = request;
|
|
763
|
+
const { operation, records, options } = batchReq;
|
|
764
|
+
const results = [];
|
|
765
|
+
let succeeded = 0;
|
|
766
|
+
let failed = 0;
|
|
767
|
+
for (const record of records) {
|
|
768
|
+
try {
|
|
769
|
+
switch (operation) {
|
|
770
|
+
case "create": {
|
|
771
|
+
const created = await this.engine.insert(object, record.data || record);
|
|
772
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
773
|
+
succeeded++;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
case "update": {
|
|
777
|
+
if (!record.id) throw new Error("Record id is required for update");
|
|
778
|
+
const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
|
|
779
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
780
|
+
succeeded++;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
case "upsert": {
|
|
784
|
+
if (record.id) {
|
|
785
|
+
try {
|
|
786
|
+
const existing = await this.engine.findOne(object, { filter: { _id: record.id } });
|
|
787
|
+
if (existing) {
|
|
788
|
+
const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
|
|
789
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
790
|
+
} else {
|
|
791
|
+
const created = await this.engine.insert(object, { _id: record.id, ...record.data || {} });
|
|
792
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
793
|
+
}
|
|
794
|
+
} catch {
|
|
795
|
+
const created = await this.engine.insert(object, { _id: record.id, ...record.data || {} });
|
|
796
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
const created = await this.engine.insert(object, record.data || record);
|
|
800
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
801
|
+
}
|
|
802
|
+
succeeded++;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "delete": {
|
|
806
|
+
if (!record.id) throw new Error("Record id is required for delete");
|
|
807
|
+
await this.engine.delete(object, { filter: { _id: record.id } });
|
|
808
|
+
results.push({ id: record.id, success: true });
|
|
809
|
+
succeeded++;
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
default:
|
|
813
|
+
results.push({ id: record.id, success: false, error: `Unknown operation: ${operation}` });
|
|
814
|
+
failed++;
|
|
815
|
+
}
|
|
816
|
+
} catch (err) {
|
|
817
|
+
results.push({ id: record.id, success: false, error: err.message });
|
|
818
|
+
failed++;
|
|
819
|
+
if (options?.atomic) {
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
if (!options?.continueOnError) {
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
success: failed === 0,
|
|
829
|
+
operation,
|
|
830
|
+
total: records.length,
|
|
831
|
+
succeeded,
|
|
832
|
+
failed,
|
|
833
|
+
results: options?.returnRecords !== false ? results : results.map((r) => ({ id: r.id, success: r.success, error: r.error }))
|
|
834
|
+
};
|
|
727
835
|
}
|
|
728
836
|
async createManyData(request) {
|
|
729
837
|
const records = await this.engine.insert(request.object, request.records);
|
|
@@ -733,26 +841,174 @@ var ObjectStackProtocolImplementation = class {
|
|
|
733
841
|
count: records.length
|
|
734
842
|
};
|
|
735
843
|
}
|
|
736
|
-
async updateManyData(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
844
|
+
async updateManyData(request) {
|
|
845
|
+
const { object, records, options } = request;
|
|
846
|
+
const results = [];
|
|
847
|
+
let succeeded = 0;
|
|
848
|
+
let failed = 0;
|
|
849
|
+
for (const record of records) {
|
|
850
|
+
try {
|
|
851
|
+
const updated = await this.engine.update(object, record.data, { filter: { _id: record.id } });
|
|
852
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
853
|
+
succeeded++;
|
|
854
|
+
} catch (err) {
|
|
855
|
+
results.push({ id: record.id, success: false, error: err.message });
|
|
856
|
+
failed++;
|
|
857
|
+
if (!options?.continueOnError) {
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
success: failed === 0,
|
|
864
|
+
operation: "update",
|
|
865
|
+
total: records.length,
|
|
866
|
+
succeeded,
|
|
867
|
+
failed,
|
|
868
|
+
results
|
|
869
|
+
};
|
|
744
870
|
}
|
|
745
|
-
async
|
|
746
|
-
|
|
871
|
+
async analyticsQuery(request) {
|
|
872
|
+
const { query, cube } = request;
|
|
873
|
+
const object = cube;
|
|
874
|
+
const groupBy = query.dimensions || [];
|
|
875
|
+
const aggregations = [];
|
|
876
|
+
if (query.measures) {
|
|
877
|
+
for (const measure of query.measures) {
|
|
878
|
+
if (measure === "count" || measure === "count_all") {
|
|
879
|
+
aggregations.push({ field: "*", method: "count", alias: "count" });
|
|
880
|
+
} else if (measure.includes(".")) {
|
|
881
|
+
const [field, method] = measure.split(".");
|
|
882
|
+
aggregations.push({ field, method, alias: `${field}_${method}` });
|
|
883
|
+
} else {
|
|
884
|
+
aggregations.push({ field: measure, method: "sum", alias: measure });
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
let filter = void 0;
|
|
889
|
+
if (query.filters && query.filters.length > 0) {
|
|
890
|
+
const conditions = query.filters.map((f) => {
|
|
891
|
+
const op = this.mapAnalyticsOperator(f.operator);
|
|
892
|
+
if (f.values && f.values.length === 1) {
|
|
893
|
+
return { [f.member]: { [op]: f.values[0] } };
|
|
894
|
+
} else if (f.values && f.values.length > 1) {
|
|
895
|
+
return { [f.member]: { $in: f.values } };
|
|
896
|
+
}
|
|
897
|
+
return { [f.member]: { [op]: true } };
|
|
898
|
+
});
|
|
899
|
+
filter = conditions.length === 1 ? conditions[0] : { $and: conditions };
|
|
900
|
+
}
|
|
901
|
+
const rows = await this.engine.aggregate(object, {
|
|
902
|
+
filter,
|
|
903
|
+
groupBy: groupBy.length > 0 ? groupBy : void 0,
|
|
904
|
+
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({ field: a.field, method: a.method, alias: a.alias })) : [{ field: "*", method: "count", alias: "count" }]
|
|
905
|
+
});
|
|
906
|
+
const fields = [
|
|
907
|
+
...groupBy.map((d) => ({ name: d, type: "string" })),
|
|
908
|
+
...aggregations.map((a) => ({ name: a.alias, type: "number" }))
|
|
909
|
+
];
|
|
910
|
+
return {
|
|
911
|
+
success: true,
|
|
912
|
+
data: {
|
|
913
|
+
rows,
|
|
914
|
+
fields
|
|
915
|
+
}
|
|
916
|
+
};
|
|
747
917
|
}
|
|
748
|
-
async
|
|
749
|
-
|
|
918
|
+
async getAnalyticsMeta(request) {
|
|
919
|
+
const objects = SchemaRegistry.listItems("object");
|
|
920
|
+
const cubeFilter = request?.cube;
|
|
921
|
+
const cubes = [];
|
|
922
|
+
for (const obj of objects) {
|
|
923
|
+
const schema = obj;
|
|
924
|
+
if (cubeFilter && schema.name !== cubeFilter) continue;
|
|
925
|
+
const measures = {};
|
|
926
|
+
const dimensions = {};
|
|
927
|
+
const fields = schema.fields || {};
|
|
928
|
+
measures["count"] = {
|
|
929
|
+
name: "count",
|
|
930
|
+
label: "Count",
|
|
931
|
+
type: "count",
|
|
932
|
+
sql: "*"
|
|
933
|
+
};
|
|
934
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
935
|
+
const fd = fieldDef;
|
|
936
|
+
const fieldType = fd.type || "text";
|
|
937
|
+
if (["number", "currency", "percent"].includes(fieldType)) {
|
|
938
|
+
measures[`${fieldName}_sum`] = {
|
|
939
|
+
name: `${fieldName}_sum`,
|
|
940
|
+
label: `${fd.label || fieldName} (Sum)`,
|
|
941
|
+
type: "sum",
|
|
942
|
+
sql: fieldName
|
|
943
|
+
};
|
|
944
|
+
measures[`${fieldName}_avg`] = {
|
|
945
|
+
name: `${fieldName}_avg`,
|
|
946
|
+
label: `${fd.label || fieldName} (Avg)`,
|
|
947
|
+
type: "avg",
|
|
948
|
+
sql: fieldName
|
|
949
|
+
};
|
|
950
|
+
dimensions[fieldName] = {
|
|
951
|
+
name: fieldName,
|
|
952
|
+
label: fd.label || fieldName,
|
|
953
|
+
type: "number",
|
|
954
|
+
sql: fieldName
|
|
955
|
+
};
|
|
956
|
+
} else if (["date", "datetime"].includes(fieldType)) {
|
|
957
|
+
dimensions[fieldName] = {
|
|
958
|
+
name: fieldName,
|
|
959
|
+
label: fd.label || fieldName,
|
|
960
|
+
type: "time",
|
|
961
|
+
sql: fieldName,
|
|
962
|
+
granularities: ["day", "week", "month", "quarter", "year"]
|
|
963
|
+
};
|
|
964
|
+
} else if (["boolean"].includes(fieldType)) {
|
|
965
|
+
dimensions[fieldName] = {
|
|
966
|
+
name: fieldName,
|
|
967
|
+
label: fd.label || fieldName,
|
|
968
|
+
type: "boolean",
|
|
969
|
+
sql: fieldName
|
|
970
|
+
};
|
|
971
|
+
} else {
|
|
972
|
+
dimensions[fieldName] = {
|
|
973
|
+
name: fieldName,
|
|
974
|
+
label: fd.label || fieldName,
|
|
975
|
+
type: "string",
|
|
976
|
+
sql: fieldName
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
cubes.push({
|
|
981
|
+
name: schema.name,
|
|
982
|
+
title: schema.label || schema.name,
|
|
983
|
+
description: schema.description,
|
|
984
|
+
sql: schema.name,
|
|
985
|
+
measures,
|
|
986
|
+
dimensions,
|
|
987
|
+
public: true
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
success: true,
|
|
992
|
+
data: { cubes }
|
|
993
|
+
};
|
|
750
994
|
}
|
|
751
|
-
|
|
752
|
-
|
|
995
|
+
mapAnalyticsOperator(op) {
|
|
996
|
+
const map = {
|
|
997
|
+
equals: "$eq",
|
|
998
|
+
notEquals: "$ne",
|
|
999
|
+
contains: "$contains",
|
|
1000
|
+
notContains: "$notContains",
|
|
1001
|
+
gt: "$gt",
|
|
1002
|
+
gte: "$gte",
|
|
1003
|
+
lt: "$lt",
|
|
1004
|
+
lte: "$lte",
|
|
1005
|
+
set: "$ne",
|
|
1006
|
+
notSet: "$eq"
|
|
1007
|
+
};
|
|
1008
|
+
return map[op] || "$eq";
|
|
753
1009
|
}
|
|
754
|
-
async
|
|
755
|
-
throw new Error(
|
|
1010
|
+
async triggerAutomation(_request) {
|
|
1011
|
+
throw new Error('triggerAutomation requires plugin-automation service. Install and register a plugin that provides the "automation" service.');
|
|
756
1012
|
}
|
|
757
1013
|
async deleteManyData(request) {
|
|
758
1014
|
return this.engine.delete(request.object, {
|
|
@@ -932,15 +1188,35 @@ var ObjectQL = class {
|
|
|
932
1188
|
this.logger.debug("Registered manifest-as-app", { app: manifest.name, from: id });
|
|
933
1189
|
}
|
|
934
1190
|
const metadataArrayKeys = [
|
|
1191
|
+
// UI Protocol
|
|
935
1192
|
"actions",
|
|
1193
|
+
"views",
|
|
1194
|
+
"pages",
|
|
936
1195
|
"dashboards",
|
|
937
1196
|
"reports",
|
|
1197
|
+
"themes",
|
|
1198
|
+
// Automation Protocol
|
|
938
1199
|
"flows",
|
|
1200
|
+
"workflows",
|
|
1201
|
+
"approvals",
|
|
1202
|
+
"webhooks",
|
|
1203
|
+
// Security Protocol
|
|
1204
|
+
"roles",
|
|
1205
|
+
"permissions",
|
|
1206
|
+
"profiles",
|
|
1207
|
+
"sharingRules",
|
|
1208
|
+
"policies",
|
|
1209
|
+
// AI Protocol
|
|
939
1210
|
"agents",
|
|
940
|
-
"apis",
|
|
941
1211
|
"ragPipelines",
|
|
942
|
-
|
|
943
|
-
"
|
|
1212
|
+
// API Protocol
|
|
1213
|
+
"apis",
|
|
1214
|
+
// Data Extensions
|
|
1215
|
+
"hooks",
|
|
1216
|
+
"mappings",
|
|
1217
|
+
"analyticsCubes",
|
|
1218
|
+
// Integration Protocol
|
|
1219
|
+
"connectors"
|
|
944
1220
|
];
|
|
945
1221
|
for (const key of metadataArrayKeys) {
|
|
946
1222
|
const items = manifest[key];
|
|
@@ -1243,7 +1519,17 @@ var ObjectQL = class {
|
|
|
1243
1519
|
object = this.resolveObjectName(object);
|
|
1244
1520
|
const driver = this.getDriver(object);
|
|
1245
1521
|
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
1246
|
-
|
|
1522
|
+
const ast = {
|
|
1523
|
+
object,
|
|
1524
|
+
where: query.filter,
|
|
1525
|
+
groupBy: query.groupBy,
|
|
1526
|
+
aggregations: query.aggregations?.map((agg) => ({
|
|
1527
|
+
function: agg.method,
|
|
1528
|
+
field: agg.field,
|
|
1529
|
+
alias: agg.alias || `${agg.method}_${agg.field || "all"}`
|
|
1530
|
+
}))
|
|
1531
|
+
};
|
|
1532
|
+
return driver.find(object, ast);
|
|
1247
1533
|
}
|
|
1248
1534
|
async execute(command, options) {
|
|
1249
1535
|
if (options?.object) {
|