@objectstack/objectql 9.7.0 → 9.8.0

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
@@ -2773,6 +2773,13 @@ function normaliseVersionToken(v) {
2773
2773
  }
2774
2774
  return s;
2775
2775
  }
2776
+ var CLONE_STRIP_FIELDS = [
2777
+ "id",
2778
+ "created_at",
2779
+ "created_by",
2780
+ "updated_at",
2781
+ "updated_by"
2782
+ ];
2776
2783
  var SERVICE_CONFIG = {
2777
2784
  auth: { route: "/api/v1/auth", plugin: "plugin-auth" },
2778
2785
  automation: { route: "/api/v1/automation", plugin: "plugin-automation" },
@@ -4028,6 +4035,68 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4028
4035
  record: result
4029
4036
  };
4030
4037
  }
4038
+ /**
4039
+ * Clone a record — read the source, drop engine-owned columns, and
4040
+ * insert a fresh copy. Gated by the object's `enable.clone` capability
4041
+ * (default `true`; only an explicit `enable.clone === false` disables it).
4042
+ *
4043
+ * Shallow by design: it duplicates the record's own scalar/business field
4044
+ * values, not its related child records. The insert path re-stamps audit
4045
+ * columns, regenerates `autonumber` fields, and recomputes derived
4046
+ * (`formula`/`summary`) fields, so the copy is a valid new row rather than
4047
+ * a byte-identical twin. Caller-supplied `overrides` are applied last and
4048
+ * win over the copied values — the natural place to set a new `name`,
4049
+ * clear a unique field, or reset status before insert.
4050
+ */
4051
+ async cloneData(request) {
4052
+ const schema = this.engine.registry.getObject(request.object);
4053
+ if (!schema) {
4054
+ const err = new Error(`Object '${request.object}' not found`);
4055
+ err.code = "OBJECT_NOT_FOUND";
4056
+ err.status = 404;
4057
+ err.object = request.object;
4058
+ throw err;
4059
+ }
4060
+ if (schema.enable?.clone === false) {
4061
+ const err = new Error(`Cloning is disabled for object '${request.object}'`);
4062
+ err.code = "CLONE_DISABLED";
4063
+ err.status = 403;
4064
+ err.object = request.object;
4065
+ throw err;
4066
+ }
4067
+ const ctx = request.context;
4068
+ const ctxOpt = ctx !== void 0 ? { context: ctx } : void 0;
4069
+ const source = await this.engine.findOne(
4070
+ request.object,
4071
+ { where: { id: request.id }, ...ctxOpt }
4072
+ );
4073
+ if (!source) {
4074
+ const err = new Error(`Record ${request.id} not found in ${request.object}`);
4075
+ err.code = "RECORD_NOT_FOUND";
4076
+ err.status = 404;
4077
+ err.object = request.object;
4078
+ throw err;
4079
+ }
4080
+ const data = { ...source };
4081
+ for (const f of CLONE_STRIP_FIELDS) delete data[f];
4082
+ const fields = schema.fields || {};
4083
+ for (const [name, def] of Object.entries(fields)) {
4084
+ if (!def) continue;
4085
+ if (def.system === true || def.type === "autonumber" || def.type === "formula" || def.type === "summary") {
4086
+ delete data[name];
4087
+ }
4088
+ }
4089
+ if (request.overrides && typeof request.overrides === "object") {
4090
+ Object.assign(data, request.overrides);
4091
+ }
4092
+ const result = await this.engine.insert(request.object, data, ctxOpt);
4093
+ return {
4094
+ object: request.object,
4095
+ id: result.id,
4096
+ sourceId: request.id,
4097
+ record: result
4098
+ };
4099
+ }
4031
4100
  async updateData(request) {
4032
4101
  await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);
4033
4102
  const opts = { where: { id: request.id } };
@@ -7222,6 +7291,11 @@ function applyFormulaPlan(plan, records) {
7222
7291
  }
7223
7292
  }
7224
7293
  }
7294
+ function mergeReadContext(fromQuery, fromOptions) {
7295
+ if (fromOptions == null) return fromQuery;
7296
+ if (fromQuery == null) return fromOptions;
7297
+ return { ...fromQuery, ...fromOptions };
7298
+ }
7225
7299
  function resolveMetadataItemName(key, item) {
7226
7300
  if (!item) return void 0;
7227
7301
  if (item.name) return item.name;
@@ -8535,7 +8609,7 @@ var _ObjectQL = class _ObjectQL {
8535
8609
  // ============================================
8536
8610
  // Data Access Methods (IDataEngine Interface)
8537
8611
  // ============================================
8538
- async find(object, query) {
8612
+ async find(object, query, options) {
8539
8613
  object = this.resolveObjectName(object);
8540
8614
  this.logger.debug("Find operation starting", { object, query });
8541
8615
  const driver = this.getDriver(object);
@@ -8564,7 +8638,7 @@ var _ObjectQL = class _ObjectQL {
8564
8638
  operation: "find",
8565
8639
  ast,
8566
8640
  options: query,
8567
- context: query?.context
8641
+ context: mergeReadContext(query?.context, options?.context)
8568
8642
  };
8569
8643
  await this.executeWithMiddleware(opCtx, async () => {
8570
8644
  const hookContext = {
@@ -8596,7 +8670,7 @@ var _ObjectQL = class _ObjectQL {
8596
8670
  });
8597
8671
  return opCtx.result;
8598
8672
  }
8599
- async findOne(objectName, query) {
8673
+ async findOne(objectName, query, options) {
8600
8674
  objectName = this.resolveObjectName(objectName);
8601
8675
  this.logger.debug("FindOne operation", { objectName });
8602
8676
  const driver = this.getDriver(objectName);
@@ -8619,7 +8693,7 @@ var _ObjectQL = class _ObjectQL {
8619
8693
  operation: "findOne",
8620
8694
  ast,
8621
8695
  options: query,
8622
- context: query?.context
8696
+ context: mergeReadContext(query?.context, options?.context)
8623
8697
  };
8624
8698
  await this.executeWithMiddleware(opCtx, async () => {
8625
8699
  const findOneOpts = this.buildDriverOptions(opCtx.context);
@@ -8965,14 +9039,14 @@ var _ObjectQL = class _ObjectQL {
8965
9039
  });
8966
9040
  return opCtx.result;
8967
9041
  }
8968
- async count(object, query) {
9042
+ async count(object, query, options) {
8969
9043
  object = this.resolveObjectName(object);
8970
9044
  const driver = this.getDriver(object);
8971
9045
  const opCtx = {
8972
9046
  object,
8973
9047
  operation: "count",
8974
9048
  options: query,
8975
- context: query?.context
9049
+ context: mergeReadContext(query?.context, options?.context)
8976
9050
  };
8977
9051
  await this.executeWithMiddleware(opCtx, async () => {
8978
9052
  const countOpts = this.buildDriverOptions(opCtx.context);
@@ -8985,7 +9059,7 @@ var _ObjectQL = class _ObjectQL {
8985
9059
  });
8986
9060
  return opCtx.result;
8987
9061
  }
8988
- async aggregate(object, query) {
9062
+ async aggregate(object, query, options) {
8989
9063
  object = this.resolveObjectName(object);
8990
9064
  const driver = this.getDriver(object);
8991
9065
  this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
@@ -8993,7 +9067,7 @@ var _ObjectQL = class _ObjectQL {
8993
9067
  object,
8994
9068
  operation: "aggregate",
8995
9069
  options: query,
8996
- context: query?.context
9070
+ context: mergeReadContext(query?.context, options?.context)
8997
9071
  };
8998
9072
  await this.executeWithMiddleware(opCtx, async () => {
8999
9073
  const ast = {