@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.mjs CHANGED
@@ -504,7 +504,7 @@ var ObjectStackProtocolImplementation = class {
504
504
  constructor(engine) {
505
505
  this.engine = engine;
506
506
  }
507
- async getDiscovery(_request) {
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: true,
516
- analytics: false,
517
- hub: false
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
- auth: "/api/auth"
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(_request) {
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: SchemaRegistry.listItems(request.type, request.packageId)
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: SchemaRegistry.getItem(request.type, request.name)
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(_request) {
692
- throw new Error("Batch operations not yet fully implemented in protocol adapter");
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(_request) {
703
- throw new Error("updateManyData not implemented");
704
- }
705
- async analyticsQuery(_request) {
706
- throw new Error("analyticsQuery not implemented");
707
- }
708
- async getAnalyticsMeta(_request) {
709
- throw new Error("getAnalyticsMeta not implemented");
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 triggerAutomation(_request) {
712
- throw new Error("triggerAutomation not implemented");
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 listSpaces(_request) {
715
- throw new Error("listSpaces not implemented");
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
- async createSpace(_request) {
718
- throw new Error("createSpace not implemented");
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 installPlugin(_request) {
721
- throw new Error("installPlugin not implemented");
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
- "profiles",
909
- "sharingRules"
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
- throw new Error("Aggregate not yet fully implemented in ObjectQL->Driver mapping");
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) {