@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.mjs
CHANGED
|
@@ -504,7 +504,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
504
504
|
constructor(engine) {
|
|
505
505
|
this.engine = engine;
|
|
506
506
|
}
|
|
507
|
-
async getDiscovery(
|
|
507
|
+
async getDiscovery() {
|
|
508
508
|
return {
|
|
509
509
|
version: "1.0",
|
|
510
510
|
apiName: "ObjectStack API",
|
|
@@ -512,33 +512,69 @@ var ObjectStackProtocolImplementation = class {
|
|
|
512
512
|
graphql: false,
|
|
513
513
|
search: false,
|
|
514
514
|
websockets: false,
|
|
515
|
-
files:
|
|
516
|
-
analytics:
|
|
517
|
-
|
|
515
|
+
files: false,
|
|
516
|
+
analytics: true,
|
|
517
|
+
ai: false,
|
|
518
|
+
workflow: false,
|
|
519
|
+
notifications: false,
|
|
520
|
+
i18n: false
|
|
518
521
|
},
|
|
519
522
|
endpoints: {
|
|
520
523
|
data: "/api/data",
|
|
521
524
|
metadata: "/api/meta",
|
|
522
|
-
|
|
525
|
+
analytics: "/api/analytics"
|
|
526
|
+
},
|
|
527
|
+
services: {
|
|
528
|
+
// --- Kernel-provided (objectql is an example kernel implementation) ---
|
|
529
|
+
metadata: { enabled: true, status: "degraded", route: "/api/meta", provider: "objectql", message: "In-memory registry only; DB persistence not yet implemented" },
|
|
530
|
+
data: { enabled: true, status: "available", route: "/api/data", provider: "objectql" },
|
|
531
|
+
analytics: { enabled: true, status: "available", route: "/api/analytics", provider: "objectql" },
|
|
532
|
+
// --- Plugin-provided (kernel does NOT handle these) ---
|
|
533
|
+
auth: { enabled: false, status: "unavailable", message: "Install an auth plugin (e.g. plugin-auth) to enable" },
|
|
534
|
+
automation: { enabled: false, status: "unavailable", message: "Install an automation plugin (e.g. plugin-automation) to enable" },
|
|
535
|
+
// --- Core infrastructure (plugin-provided) ---
|
|
536
|
+
cache: { enabled: false, status: "unavailable", message: "Install a cache plugin (e.g. plugin-redis) to enable" },
|
|
537
|
+
queue: { enabled: false, status: "unavailable", message: "Install a queue plugin (e.g. plugin-bullmq) to enable" },
|
|
538
|
+
job: { enabled: false, status: "unavailable", message: "Install a job scheduler plugin to enable" },
|
|
539
|
+
// --- Optional services (all plugin-provided) ---
|
|
540
|
+
ui: { enabled: false, status: "unavailable", message: "Install a UI plugin to enable" },
|
|
541
|
+
workflow: { enabled: false, status: "unavailable", message: "Install a workflow plugin to enable" },
|
|
542
|
+
realtime: { enabled: false, status: "unavailable", message: "Install a realtime plugin to enable" },
|
|
543
|
+
notification: { enabled: false, status: "unavailable", message: "Install a notification plugin to enable" },
|
|
544
|
+
ai: { enabled: false, status: "unavailable", message: "Install an AI plugin to enable" },
|
|
545
|
+
i18n: { enabled: false, status: "unavailable", message: "Install an i18n plugin to enable" },
|
|
546
|
+
graphql: { enabled: false, status: "unavailable", message: "Install a GraphQL plugin to enable" },
|
|
547
|
+
"file-storage": { enabled: false, status: "unavailable", message: "Install a file-storage plugin to enable" },
|
|
548
|
+
search: { enabled: false, status: "unavailable", message: "Install a search plugin to enable" }
|
|
523
549
|
}
|
|
524
550
|
};
|
|
525
551
|
}
|
|
526
|
-
async getMetaTypes(
|
|
552
|
+
async getMetaTypes() {
|
|
527
553
|
return {
|
|
528
554
|
types: SchemaRegistry.getRegisteredTypes()
|
|
529
555
|
};
|
|
530
556
|
}
|
|
531
557
|
async getMetaItems(request) {
|
|
558
|
+
let items = SchemaRegistry.listItems(request.type);
|
|
559
|
+
if (items.length === 0) {
|
|
560
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
561
|
+
items = SchemaRegistry.listItems(alt);
|
|
562
|
+
}
|
|
532
563
|
return {
|
|
533
564
|
type: request.type,
|
|
534
|
-
items
|
|
565
|
+
items
|
|
535
566
|
};
|
|
536
567
|
}
|
|
537
568
|
async getMetaItem(request) {
|
|
569
|
+
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
570
|
+
if (item === void 0) {
|
|
571
|
+
const alt = request.type.endsWith("s") ? request.type.slice(0, -1) : request.type + "s";
|
|
572
|
+
item = SchemaRegistry.getItem(alt, request.name);
|
|
573
|
+
}
|
|
538
574
|
return {
|
|
539
575
|
type: request.type,
|
|
540
576
|
name: request.name,
|
|
541
|
-
item
|
|
577
|
+
item
|
|
542
578
|
};
|
|
543
579
|
}
|
|
544
580
|
async getUiView(request) {
|
|
@@ -688,8 +724,80 @@ var ObjectStackProtocolImplementation = class {
|
|
|
688
724
|
// ==========================================
|
|
689
725
|
// Batch Operations
|
|
690
726
|
// ==========================================
|
|
691
|
-
async batchData(
|
|
692
|
-
|
|
727
|
+
async batchData(request) {
|
|
728
|
+
const { object, request: batchReq } = request;
|
|
729
|
+
const { operation, records, options } = batchReq;
|
|
730
|
+
const results = [];
|
|
731
|
+
let succeeded = 0;
|
|
732
|
+
let failed = 0;
|
|
733
|
+
for (const record of records) {
|
|
734
|
+
try {
|
|
735
|
+
switch (operation) {
|
|
736
|
+
case "create": {
|
|
737
|
+
const created = await this.engine.insert(object, record.data || record);
|
|
738
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
739
|
+
succeeded++;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case "update": {
|
|
743
|
+
if (!record.id) throw new Error("Record id is required for update");
|
|
744
|
+
const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
|
|
745
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
746
|
+
succeeded++;
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
case "upsert": {
|
|
750
|
+
if (record.id) {
|
|
751
|
+
try {
|
|
752
|
+
const existing = await this.engine.findOne(object, { filter: { _id: record.id } });
|
|
753
|
+
if (existing) {
|
|
754
|
+
const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
|
|
755
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
756
|
+
} else {
|
|
757
|
+
const created = await this.engine.insert(object, { _id: record.id, ...record.data || {} });
|
|
758
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
const created = await this.engine.insert(object, { _id: record.id, ...record.data || {} });
|
|
762
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
const created = await this.engine.insert(object, record.data || record);
|
|
766
|
+
results.push({ id: created._id || created.id, success: true, record: created });
|
|
767
|
+
}
|
|
768
|
+
succeeded++;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
case "delete": {
|
|
772
|
+
if (!record.id) throw new Error("Record id is required for delete");
|
|
773
|
+
await this.engine.delete(object, { filter: { _id: record.id } });
|
|
774
|
+
results.push({ id: record.id, success: true });
|
|
775
|
+
succeeded++;
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
default:
|
|
779
|
+
results.push({ id: record.id, success: false, error: `Unknown operation: ${operation}` });
|
|
780
|
+
failed++;
|
|
781
|
+
}
|
|
782
|
+
} catch (err) {
|
|
783
|
+
results.push({ id: record.id, success: false, error: err.message });
|
|
784
|
+
failed++;
|
|
785
|
+
if (options?.atomic) {
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
if (!options?.continueOnError) {
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
success: failed === 0,
|
|
795
|
+
operation,
|
|
796
|
+
total: records.length,
|
|
797
|
+
succeeded,
|
|
798
|
+
failed,
|
|
799
|
+
results: options?.returnRecords !== false ? results : results.map((r) => ({ id: r.id, success: r.success, error: r.error }))
|
|
800
|
+
};
|
|
693
801
|
}
|
|
694
802
|
async createManyData(request) {
|
|
695
803
|
const records = await this.engine.insert(request.object, request.records);
|
|
@@ -699,26 +807,174 @@ var ObjectStackProtocolImplementation = class {
|
|
|
699
807
|
count: records.length
|
|
700
808
|
};
|
|
701
809
|
}
|
|
702
|
-
async updateManyData(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
810
|
+
async updateManyData(request) {
|
|
811
|
+
const { object, records, options } = request;
|
|
812
|
+
const results = [];
|
|
813
|
+
let succeeded = 0;
|
|
814
|
+
let failed = 0;
|
|
815
|
+
for (const record of records) {
|
|
816
|
+
try {
|
|
817
|
+
const updated = await this.engine.update(object, record.data, { filter: { _id: record.id } });
|
|
818
|
+
results.push({ id: record.id, success: true, record: updated });
|
|
819
|
+
succeeded++;
|
|
820
|
+
} catch (err) {
|
|
821
|
+
results.push({ id: record.id, success: false, error: err.message });
|
|
822
|
+
failed++;
|
|
823
|
+
if (!options?.continueOnError) {
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
success: failed === 0,
|
|
830
|
+
operation: "update",
|
|
831
|
+
total: records.length,
|
|
832
|
+
succeeded,
|
|
833
|
+
failed,
|
|
834
|
+
results
|
|
835
|
+
};
|
|
710
836
|
}
|
|
711
|
-
async
|
|
712
|
-
|
|
837
|
+
async analyticsQuery(request) {
|
|
838
|
+
const { query, cube } = request;
|
|
839
|
+
const object = cube;
|
|
840
|
+
const groupBy = query.dimensions || [];
|
|
841
|
+
const aggregations = [];
|
|
842
|
+
if (query.measures) {
|
|
843
|
+
for (const measure of query.measures) {
|
|
844
|
+
if (measure === "count" || measure === "count_all") {
|
|
845
|
+
aggregations.push({ field: "*", method: "count", alias: "count" });
|
|
846
|
+
} else if (measure.includes(".")) {
|
|
847
|
+
const [field, method] = measure.split(".");
|
|
848
|
+
aggregations.push({ field, method, alias: `${field}_${method}` });
|
|
849
|
+
} else {
|
|
850
|
+
aggregations.push({ field: measure, method: "sum", alias: measure });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
let filter = void 0;
|
|
855
|
+
if (query.filters && query.filters.length > 0) {
|
|
856
|
+
const conditions = query.filters.map((f) => {
|
|
857
|
+
const op = this.mapAnalyticsOperator(f.operator);
|
|
858
|
+
if (f.values && f.values.length === 1) {
|
|
859
|
+
return { [f.member]: { [op]: f.values[0] } };
|
|
860
|
+
} else if (f.values && f.values.length > 1) {
|
|
861
|
+
return { [f.member]: { $in: f.values } };
|
|
862
|
+
}
|
|
863
|
+
return { [f.member]: { [op]: true } };
|
|
864
|
+
});
|
|
865
|
+
filter = conditions.length === 1 ? conditions[0] : { $and: conditions };
|
|
866
|
+
}
|
|
867
|
+
const rows = await this.engine.aggregate(object, {
|
|
868
|
+
filter,
|
|
869
|
+
groupBy: groupBy.length > 0 ? groupBy : void 0,
|
|
870
|
+
aggregations: aggregations.length > 0 ? aggregations.map((a) => ({ field: a.field, method: a.method, alias: a.alias })) : [{ field: "*", method: "count", alias: "count" }]
|
|
871
|
+
});
|
|
872
|
+
const fields = [
|
|
873
|
+
...groupBy.map((d) => ({ name: d, type: "string" })),
|
|
874
|
+
...aggregations.map((a) => ({ name: a.alias, type: "number" }))
|
|
875
|
+
];
|
|
876
|
+
return {
|
|
877
|
+
success: true,
|
|
878
|
+
data: {
|
|
879
|
+
rows,
|
|
880
|
+
fields
|
|
881
|
+
}
|
|
882
|
+
};
|
|
713
883
|
}
|
|
714
|
-
async
|
|
715
|
-
|
|
884
|
+
async getAnalyticsMeta(request) {
|
|
885
|
+
const objects = SchemaRegistry.listItems("object");
|
|
886
|
+
const cubeFilter = request?.cube;
|
|
887
|
+
const cubes = [];
|
|
888
|
+
for (const obj of objects) {
|
|
889
|
+
const schema = obj;
|
|
890
|
+
if (cubeFilter && schema.name !== cubeFilter) continue;
|
|
891
|
+
const measures = {};
|
|
892
|
+
const dimensions = {};
|
|
893
|
+
const fields = schema.fields || {};
|
|
894
|
+
measures["count"] = {
|
|
895
|
+
name: "count",
|
|
896
|
+
label: "Count",
|
|
897
|
+
type: "count",
|
|
898
|
+
sql: "*"
|
|
899
|
+
};
|
|
900
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
901
|
+
const fd = fieldDef;
|
|
902
|
+
const fieldType = fd.type || "text";
|
|
903
|
+
if (["number", "currency", "percent"].includes(fieldType)) {
|
|
904
|
+
measures[`${fieldName}_sum`] = {
|
|
905
|
+
name: `${fieldName}_sum`,
|
|
906
|
+
label: `${fd.label || fieldName} (Sum)`,
|
|
907
|
+
type: "sum",
|
|
908
|
+
sql: fieldName
|
|
909
|
+
};
|
|
910
|
+
measures[`${fieldName}_avg`] = {
|
|
911
|
+
name: `${fieldName}_avg`,
|
|
912
|
+
label: `${fd.label || fieldName} (Avg)`,
|
|
913
|
+
type: "avg",
|
|
914
|
+
sql: fieldName
|
|
915
|
+
};
|
|
916
|
+
dimensions[fieldName] = {
|
|
917
|
+
name: fieldName,
|
|
918
|
+
label: fd.label || fieldName,
|
|
919
|
+
type: "number",
|
|
920
|
+
sql: fieldName
|
|
921
|
+
};
|
|
922
|
+
} else if (["date", "datetime"].includes(fieldType)) {
|
|
923
|
+
dimensions[fieldName] = {
|
|
924
|
+
name: fieldName,
|
|
925
|
+
label: fd.label || fieldName,
|
|
926
|
+
type: "time",
|
|
927
|
+
sql: fieldName,
|
|
928
|
+
granularities: ["day", "week", "month", "quarter", "year"]
|
|
929
|
+
};
|
|
930
|
+
} else if (["boolean"].includes(fieldType)) {
|
|
931
|
+
dimensions[fieldName] = {
|
|
932
|
+
name: fieldName,
|
|
933
|
+
label: fd.label || fieldName,
|
|
934
|
+
type: "boolean",
|
|
935
|
+
sql: fieldName
|
|
936
|
+
};
|
|
937
|
+
} else {
|
|
938
|
+
dimensions[fieldName] = {
|
|
939
|
+
name: fieldName,
|
|
940
|
+
label: fd.label || fieldName,
|
|
941
|
+
type: "string",
|
|
942
|
+
sql: fieldName
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
cubes.push({
|
|
947
|
+
name: schema.name,
|
|
948
|
+
title: schema.label || schema.name,
|
|
949
|
+
description: schema.description,
|
|
950
|
+
sql: schema.name,
|
|
951
|
+
measures,
|
|
952
|
+
dimensions,
|
|
953
|
+
public: true
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
return {
|
|
957
|
+
success: true,
|
|
958
|
+
data: { cubes }
|
|
959
|
+
};
|
|
716
960
|
}
|
|
717
|
-
|
|
718
|
-
|
|
961
|
+
mapAnalyticsOperator(op) {
|
|
962
|
+
const map = {
|
|
963
|
+
equals: "$eq",
|
|
964
|
+
notEquals: "$ne",
|
|
965
|
+
contains: "$contains",
|
|
966
|
+
notContains: "$notContains",
|
|
967
|
+
gt: "$gt",
|
|
968
|
+
gte: "$gte",
|
|
969
|
+
lt: "$lt",
|
|
970
|
+
lte: "$lte",
|
|
971
|
+
set: "$ne",
|
|
972
|
+
notSet: "$eq"
|
|
973
|
+
};
|
|
974
|
+
return map[op] || "$eq";
|
|
719
975
|
}
|
|
720
|
-
async
|
|
721
|
-
throw new Error(
|
|
976
|
+
async triggerAutomation(_request) {
|
|
977
|
+
throw new Error('triggerAutomation requires plugin-automation service. Install and register a plugin that provides the "automation" service.');
|
|
722
978
|
}
|
|
723
979
|
async deleteManyData(request) {
|
|
724
980
|
return this.engine.delete(request.object, {
|
|
@@ -898,15 +1154,35 @@ var ObjectQL = class {
|
|
|
898
1154
|
this.logger.debug("Registered manifest-as-app", { app: manifest.name, from: id });
|
|
899
1155
|
}
|
|
900
1156
|
const metadataArrayKeys = [
|
|
1157
|
+
// UI Protocol
|
|
901
1158
|
"actions",
|
|
1159
|
+
"views",
|
|
1160
|
+
"pages",
|
|
902
1161
|
"dashboards",
|
|
903
1162
|
"reports",
|
|
1163
|
+
"themes",
|
|
1164
|
+
// Automation Protocol
|
|
904
1165
|
"flows",
|
|
1166
|
+
"workflows",
|
|
1167
|
+
"approvals",
|
|
1168
|
+
"webhooks",
|
|
1169
|
+
// Security Protocol
|
|
1170
|
+
"roles",
|
|
1171
|
+
"permissions",
|
|
1172
|
+
"profiles",
|
|
1173
|
+
"sharingRules",
|
|
1174
|
+
"policies",
|
|
1175
|
+
// AI Protocol
|
|
905
1176
|
"agents",
|
|
906
|
-
"apis",
|
|
907
1177
|
"ragPipelines",
|
|
908
|
-
|
|
909
|
-
"
|
|
1178
|
+
// API Protocol
|
|
1179
|
+
"apis",
|
|
1180
|
+
// Data Extensions
|
|
1181
|
+
"hooks",
|
|
1182
|
+
"mappings",
|
|
1183
|
+
"analyticsCubes",
|
|
1184
|
+
// Integration Protocol
|
|
1185
|
+
"connectors"
|
|
910
1186
|
];
|
|
911
1187
|
for (const key of metadataArrayKeys) {
|
|
912
1188
|
const items = manifest[key];
|
|
@@ -1209,7 +1485,17 @@ var ObjectQL = class {
|
|
|
1209
1485
|
object = this.resolveObjectName(object);
|
|
1210
1486
|
const driver = this.getDriver(object);
|
|
1211
1487
|
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
1212
|
-
|
|
1488
|
+
const ast = {
|
|
1489
|
+
object,
|
|
1490
|
+
where: query.filter,
|
|
1491
|
+
groupBy: query.groupBy,
|
|
1492
|
+
aggregations: query.aggregations?.map((agg) => ({
|
|
1493
|
+
function: agg.method,
|
|
1494
|
+
field: agg.field,
|
|
1495
|
+
alias: agg.alias || `${agg.method}_${agg.field || "all"}`
|
|
1496
|
+
}))
|
|
1497
|
+
};
|
|
1498
|
+
return driver.find(object, ast);
|
|
1213
1499
|
}
|
|
1214
1500
|
async execute(command, options) {
|
|
1215
1501
|
if (options?.object) {
|