@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/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(_request) {
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: true,
550
- analytics: false,
551
- hub: false
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
- auth: "/api/auth"
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(_request) {
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: SchemaRegistry.listItems(request.type, request.packageId)
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: SchemaRegistry.getItem(request.type, request.name)
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(_request) {
726
- throw new Error("Batch operations not yet fully implemented in protocol adapter");
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(_request) {
737
- throw new Error("updateManyData not implemented");
738
- }
739
- async analyticsQuery(_request) {
740
- throw new Error("analyticsQuery not implemented");
741
- }
742
- async getAnalyticsMeta(_request) {
743
- throw new Error("getAnalyticsMeta not implemented");
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 triggerAutomation(_request) {
746
- throw new Error("triggerAutomation not implemented");
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 listSpaces(_request) {
749
- throw new Error("listSpaces not implemented");
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
- async createSpace(_request) {
752
- throw new Error("createSpace not implemented");
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 installPlugin(_request) {
755
- throw new Error("installPlugin not implemented");
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
- "profiles",
943
- "sharingRules"
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
- throw new Error("Aggregate not yet fully implemented in ObjectQL->Driver mapping");
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) {