@objectstack/objectql 3.0.11 → 3.1.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/objectql@3.0.11 build /home/runner/work/spec/spec/packages/objectql
2
+ > @objectstack/objectql@3.1.1 build /home/runner/work/spec/spec/packages/objectql
3
3
  > tsup --config ../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- CJS dist/index.js 90.75 KB
14
- CJS dist/index.js.map 184.04 KB
15
- CJS ⚡️ Build success in 144ms
16
- ESM dist/index.mjs 89.01 KB
17
- ESM dist/index.mjs.map 182.74 KB
18
- ESM ⚡️ Build success in 151ms
13
+ CJS dist/index.js 95.52 KB
14
+ CJS dist/index.js.map 193.28 KB
15
+ CJS ⚡️ Build success in 194ms
16
+ ESM dist/index.mjs 93.78 KB
17
+ ESM dist/index.mjs.map 191.98 KB
18
+ ESM ⚡️ Build success in 201ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 11115ms
21
- DTS dist/index.d.mts 73.43 KB
22
- DTS dist/index.d.ts 73.43 KB
20
+ DTS ⚡️ Build success in 20923ms
21
+ DTS dist/index.d.mts 74.26 KB
22
+ DTS dist/index.d.ts 74.26 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @objectstack/objectql
2
2
 
3
+ ## 3.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [953d667]
8
+ - @objectstack/spec@3.1.1
9
+ - @objectstack/core@3.1.1
10
+ - @objectstack/types@3.1.1
11
+
12
+ ## 3.1.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [0088830]
17
+ - @objectstack/spec@3.1.0
18
+ - @objectstack/core@3.1.0
19
+ - @objectstack/types@3.1.0
20
+
3
21
  ## 3.0.11
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1329,6 +1329,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1329
1329
  getData(request: {
1330
1330
  object: string;
1331
1331
  id: string;
1332
+ expand?: string | string[];
1333
+ select?: string | string[];
1332
1334
  }): Promise<{
1333
1335
  object: string;
1334
1336
  id: string;
@@ -1569,6 +1571,21 @@ declare class ObjectQL implements IDataEngine {
1569
1571
  */
1570
1572
  init(): Promise<void>;
1571
1573
  destroy(): Promise<void>;
1574
+ /** Maximum depth for recursive expand to prevent infinite loops */
1575
+ private static readonly MAX_EXPAND_DEPTH;
1576
+ /**
1577
+ * Post-process expand: resolve lookup/master_detail fields by batch-loading related records.
1578
+ *
1579
+ * This is a driver-agnostic implementation that uses secondary queries ($in batches)
1580
+ * to load related records, then injects them into the result set.
1581
+ *
1582
+ * @param objectName - The source object name
1583
+ * @param records - The records returned by the driver
1584
+ * @param expand - The expand map from QueryAST (field name → nested QueryAST)
1585
+ * @param depth - Current recursion depth (0-based)
1586
+ * @returns Records with expanded lookup fields (IDs replaced by full objects)
1587
+ */
1588
+ private expandRelatedRecords;
1572
1589
  private toQueryAST;
1573
1590
  find(object: string, query?: DataEngineQueryOptions): Promise<any[]>;
1574
1591
  findOne(objectName: string, query?: DataEngineQueryOptions): Promise<any>;
package/dist/index.d.ts CHANGED
@@ -1329,6 +1329,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
1329
1329
  getData(request: {
1330
1330
  object: string;
1331
1331
  id: string;
1332
+ expand?: string | string[];
1333
+ select?: string | string[];
1332
1334
  }): Promise<{
1333
1335
  object: string;
1334
1336
  id: string;
@@ -1569,6 +1571,21 @@ declare class ObjectQL implements IDataEngine {
1569
1571
  */
1570
1572
  init(): Promise<void>;
1571
1573
  destroy(): Promise<void>;
1574
+ /** Maximum depth for recursive expand to prevent infinite loops */
1575
+ private static readonly MAX_EXPAND_DEPTH;
1576
+ /**
1577
+ * Post-process expand: resolve lookup/master_detail fields by batch-loading related records.
1578
+ *
1579
+ * This is a driver-agnostic implementation that uses secondary queries ($in batches)
1580
+ * to load related records, then injects them into the result set.
1581
+ *
1582
+ * @param objectName - The source object name
1583
+ * @param records - The records returned by the driver
1584
+ * @param expand - The expand map from QueryAST (field name → nested QueryAST)
1585
+ * @param depth - Current recursion depth (0-based)
1586
+ * @returns Records with expanded lookup fields (IDs replaced by full objects)
1587
+ */
1588
+ private expandRelatedRecords;
1572
1589
  private toQueryAST;
1573
1590
  find(object: string, query?: DataEngineQueryOptions): Promise<any[]>;
1574
1591
  findOne(objectName: string, query?: DataEngineQueryOptions): Promise<any>;
package/dist/index.js CHANGED
@@ -774,6 +774,16 @@ var ObjectStackProtocolImplementation = class {
774
774
  if (typeof options.populate === "string") {
775
775
  options.populate = options.populate.split(",").map((s) => s.trim()).filter(Boolean);
776
776
  }
777
+ const expandValue = options.$expand ?? options.expand;
778
+ if (expandValue && !options.populate) {
779
+ if (typeof expandValue === "string") {
780
+ options.populate = expandValue.split(",").map((s) => s.trim()).filter(Boolean);
781
+ } else if (Array.isArray(expandValue)) {
782
+ options.populate = expandValue;
783
+ }
784
+ }
785
+ delete options.$expand;
786
+ delete options.expand;
777
787
  for (const key of ["distinct", "count"]) {
778
788
  if (options[key] === "true") options[key] = true;
779
789
  else if (options[key] === "false") options[key] = false;
@@ -790,9 +800,16 @@ var ObjectStackProtocolImplementation = class {
790
800
  };
791
801
  }
792
802
  async getData(request) {
793
- const result = await this.engine.findOne(request.object, {
803
+ const queryOptions = {
794
804
  filter: { _id: request.id }
795
- });
805
+ };
806
+ if (request.select) {
807
+ queryOptions.select = typeof request.select === "string" ? request.select.split(",").map((s) => s.trim()).filter(Boolean) : request.select;
808
+ }
809
+ if (request.expand) {
810
+ queryOptions.populate = typeof request.expand === "string" ? request.expand.split(",").map((s) => s.trim()).filter(Boolean) : request.expand;
811
+ }
812
+ const result = await this.engine.findOne(request.object, queryOptions);
796
813
  if (result) {
797
814
  return {
798
815
  object: request.object,
@@ -1270,7 +1287,7 @@ var ObjectStackProtocolImplementation = class {
1270
1287
  var import_kernel2 = require("@objectstack/spec/kernel");
1271
1288
  var import_core = require("@objectstack/core");
1272
1289
  var import_system = require("@objectstack/spec/system");
1273
- var ObjectQL = class _ObjectQL {
1290
+ var _ObjectQL = class _ObjectQL {
1274
1291
  constructor(hostContext = {}) {
1275
1292
  this.drivers = /* @__PURE__ */ new Map();
1276
1293
  this.defaultDriver = null;
@@ -1765,6 +1782,89 @@ var ObjectQL = class _ObjectQL {
1765
1782
  }
1766
1783
  this.logger.info("ObjectQL engine destroyed");
1767
1784
  }
1785
+ /**
1786
+ * Post-process expand: resolve lookup/master_detail fields by batch-loading related records.
1787
+ *
1788
+ * This is a driver-agnostic implementation that uses secondary queries ($in batches)
1789
+ * to load related records, then injects them into the result set.
1790
+ *
1791
+ * @param objectName - The source object name
1792
+ * @param records - The records returned by the driver
1793
+ * @param expand - The expand map from QueryAST (field name → nested QueryAST)
1794
+ * @param depth - Current recursion depth (0-based)
1795
+ * @returns Records with expanded lookup fields (IDs replaced by full objects)
1796
+ */
1797
+ async expandRelatedRecords(objectName, records, expand, depth = 0) {
1798
+ if (!records || records.length === 0) return records;
1799
+ if (depth >= _ObjectQL.MAX_EXPAND_DEPTH) return records;
1800
+ const objectSchema = SchemaRegistry.getObject(objectName);
1801
+ if (!objectSchema || !objectSchema.fields) return records;
1802
+ for (const [fieldName, nestedAST] of Object.entries(expand)) {
1803
+ const fieldDef = objectSchema.fields[fieldName];
1804
+ if (!fieldDef || !fieldDef.reference) continue;
1805
+ if (fieldDef.type !== "lookup" && fieldDef.type !== "master_detail") continue;
1806
+ const referenceObject = fieldDef.reference;
1807
+ const allIds = [];
1808
+ for (const record of records) {
1809
+ const val = record[fieldName];
1810
+ if (val == null) continue;
1811
+ if (Array.isArray(val)) {
1812
+ allIds.push(...val.filter((id) => id != null));
1813
+ } else if (typeof val === "object") {
1814
+ continue;
1815
+ } else {
1816
+ allIds.push(val);
1817
+ }
1818
+ }
1819
+ const uniqueIds = [...new Set(allIds)];
1820
+ if (uniqueIds.length === 0) continue;
1821
+ try {
1822
+ const relatedQuery = {
1823
+ object: referenceObject,
1824
+ where: { _id: { $in: uniqueIds } },
1825
+ ...nestedAST.fields ? { fields: nestedAST.fields } : {},
1826
+ ...nestedAST.orderBy ? { orderBy: nestedAST.orderBy } : {}
1827
+ };
1828
+ const driver = this.getDriver(referenceObject);
1829
+ const relatedRecords = await driver.find(referenceObject, relatedQuery) ?? [];
1830
+ const recordMap = /* @__PURE__ */ new Map();
1831
+ for (const rec of relatedRecords) {
1832
+ const id = rec._id ?? rec.id;
1833
+ if (id != null) recordMap.set(String(id), rec);
1834
+ }
1835
+ if (nestedAST.expand && Object.keys(nestedAST.expand).length > 0) {
1836
+ const expandedRelated = await this.expandRelatedRecords(
1837
+ referenceObject,
1838
+ relatedRecords,
1839
+ nestedAST.expand,
1840
+ depth + 1
1841
+ );
1842
+ recordMap.clear();
1843
+ for (const rec of expandedRelated) {
1844
+ const id = rec._id ?? rec.id;
1845
+ if (id != null) recordMap.set(String(id), rec);
1846
+ }
1847
+ }
1848
+ for (const record of records) {
1849
+ const val = record[fieldName];
1850
+ if (val == null) continue;
1851
+ if (Array.isArray(val)) {
1852
+ record[fieldName] = val.map((id) => recordMap.get(String(id)) ?? id);
1853
+ } else if (typeof val !== "object") {
1854
+ record[fieldName] = recordMap.get(String(val)) ?? val;
1855
+ }
1856
+ }
1857
+ } catch (e) {
1858
+ this.logger.warn("Failed to expand relationship field; retaining foreign key IDs", {
1859
+ object: objectName,
1860
+ field: fieldName,
1861
+ reference: referenceObject,
1862
+ error: e.message
1863
+ });
1864
+ }
1865
+ }
1866
+ return records;
1867
+ }
1768
1868
  // ============================================
1769
1869
  // Helper: Query Conversion
1770
1870
  // ============================================
@@ -1824,7 +1924,10 @@ var ObjectQL = class _ObjectQL {
1824
1924
  };
1825
1925
  await this.triggerHooks("beforeFind", hookContext);
1826
1926
  try {
1827
- const result = await driver.find(object, hookContext.input.ast, hookContext.input.options);
1927
+ let result = await driver.find(object, hookContext.input.ast, hookContext.input.options);
1928
+ if (ast.expand && Object.keys(ast.expand).length > 0 && Array.isArray(result)) {
1929
+ result = await this.expandRelatedRecords(object, result, ast.expand, 0);
1930
+ }
1828
1931
  hookContext.event = "afterFind";
1829
1932
  hookContext.result = result;
1830
1933
  await this.triggerHooks("afterFind", hookContext);
@@ -1850,7 +1953,12 @@ var ObjectQL = class _ObjectQL {
1850
1953
  context: query?.context
1851
1954
  };
1852
1955
  await this.executeWithMiddleware(opCtx, async () => {
1853
- return driver.findOne(objectName, opCtx.ast);
1956
+ let result = await driver.findOne(objectName, opCtx.ast);
1957
+ if (ast.expand && Object.keys(ast.expand).length > 0 && result != null) {
1958
+ const expanded = await this.expandRelatedRecords(objectName, [result], ast.expand, 0);
1959
+ result = expanded[0];
1960
+ }
1961
+ return result;
1854
1962
  });
1855
1963
  return opCtx.result;
1856
1964
  }
@@ -2199,6 +2307,12 @@ var ObjectQL = class _ObjectQL {
2199
2307
  return ql;
2200
2308
  }
2201
2309
  };
2310
+ // ============================================
2311
+ // Helper: Expand Related Records
2312
+ // ============================================
2313
+ /** Maximum depth for recursive expand to prevent infinite loops */
2314
+ _ObjectQL.MAX_EXPAND_DEPTH = 3;
2315
+ var ObjectQL = _ObjectQL;
2202
2316
  var ObjectRepository = class {
2203
2317
  constructor(objectName, context, engine) {
2204
2318
  this.objectName = objectName;