@objectstack/objectql 9.10.0 → 9.11.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.d.mts CHANGED
@@ -2225,15 +2225,22 @@ declare class ObjectQL implements IDataEngine {
2225
2225
  * owns the value, not the client.
2226
2226
  *
2227
2227
  * In the fallback path the next value is `max(existing) + 1`, seeded once per
2228
- * `object.field` from the store then incremented in memory (monotonic within
2229
- * the process, resilient to deletions). `autonumberFormat` is honored, e.g.
2230
- * `CASE-{0000}` `CASE-0042`. NOTE: this in-memory seeding is single-instance.
2228
+ * `object.field.<scope>` from the store then incremented in memory (monotonic
2229
+ * within the process, resilient to deletions). The shared `autonumberFormat`
2230
+ * renderer is honored end-to-end, so date tokens (`AD{YYYYMMDD}{0000}`), field
2231
+ * interpolation (`{island_zone}{000}`) and per-scope reset behave identically
2232
+ * to the SQL driver's persistent sequence (#1603). NOTE: this in-memory seeding
2233
+ * is single-instance.
2231
2234
  */
2232
2235
  private applyAutonumbers;
2233
- /** Seed the autonumber counter from the current max numeric value in store. */
2236
+ /**
2237
+ * Seed the autonumber counter from the current max in store, scoped to
2238
+ * `prefix`. With a non-empty prefix (date/field formats) only rows in the
2239
+ * same scope count, and the counter is the digit-run immediately after the
2240
+ * prefix; with an empty prefix (legacy fixed-prefix formats) the last digit
2241
+ * run of the whole value is used, preserving the original behaviour.
2242
+ */
2234
2243
  private seedAutonumber;
2235
- /** Apply an autonumber format like `CASE-{0000}`; default to the bare number. */
2236
- private formatAutonumber;
2237
2244
  /**
2238
2245
  * Register contribution (Manifest)
2239
2246
  *
package/dist/index.d.ts CHANGED
@@ -2225,15 +2225,22 @@ declare class ObjectQL implements IDataEngine {
2225
2225
  * owns the value, not the client.
2226
2226
  *
2227
2227
  * In the fallback path the next value is `max(existing) + 1`, seeded once per
2228
- * `object.field` from the store then incremented in memory (monotonic within
2229
- * the process, resilient to deletions). `autonumberFormat` is honored, e.g.
2230
- * `CASE-{0000}` `CASE-0042`. NOTE: this in-memory seeding is single-instance.
2228
+ * `object.field.<scope>` from the store then incremented in memory (monotonic
2229
+ * within the process, resilient to deletions). The shared `autonumberFormat`
2230
+ * renderer is honored end-to-end, so date tokens (`AD{YYYYMMDD}{0000}`), field
2231
+ * interpolation (`{island_zone}{000}`) and per-scope reset behave identically
2232
+ * to the SQL driver's persistent sequence (#1603). NOTE: this in-memory seeding
2233
+ * is single-instance.
2231
2234
  */
2232
2235
  private applyAutonumbers;
2233
- /** Seed the autonumber counter from the current max numeric value in store. */
2236
+ /**
2237
+ * Seed the autonumber counter from the current max in store, scoped to
2238
+ * `prefix`. With a non-empty prefix (date/field formats) only rows in the
2239
+ * same scope count, and the counter is the digit-run immediately after the
2240
+ * prefix; with an empty prefix (legacy fixed-prefix formats) the last digit
2241
+ * run of the whole value is used, preserving the original behaviour.
2242
+ */
2234
2243
  private seedAutonumber;
2235
- /** Apply an autonumber format like `CASE-{0000}`; default to the bare number. */
2236
- private formatAutonumber;
2237
2244
  /**
2238
2245
  * Register contribution (Manifest)
2239
2246
  *
package/dist/index.js CHANGED
@@ -6192,6 +6192,7 @@ var ObjectStackProtocolImplementation = _ObjectStackProtocolImplementation;
6192
6192
 
6193
6193
  // src/engine.ts
6194
6194
  var import_node_async_hooks = require("async_hooks");
6195
+ var import_data4 = require("@objectstack/spec/data");
6195
6196
  var import_kernel6 = require("@objectstack/spec/kernel");
6196
6197
  var import_core2 = require("@objectstack/core");
6197
6198
  var import_system2 = require("@objectstack/spec/system");
@@ -7617,8 +7618,9 @@ var _ObjectQL = class _ObjectQL {
7617
7618
  const tx = execCtx?.transaction !== void 0 ? execCtx.transaction : this.txStore.getStore()?.transaction;
7618
7619
  const hasTx = tx !== void 0;
7619
7620
  const hasTenant = execCtx?.tenantId !== void 0;
7621
+ const hasTz = execCtx?.timezone !== void 0;
7620
7622
  const isSystem = execCtx?.isSystem === true;
7621
- if (!hasTx && !hasTenant && !isSystem) return base;
7623
+ if (!hasTx && !hasTenant && !isSystem && !hasTz) return base;
7622
7624
  const opts = base && typeof base === "object" ? { ...base } : {};
7623
7625
  if (hasTx && opts.transaction === void 0) {
7624
7626
  opts.transaction = tx;
@@ -7626,6 +7628,9 @@ var _ObjectQL = class _ObjectQL {
7626
7628
  if (hasTenant && opts.tenantId === void 0) {
7627
7629
  opts.tenantId = execCtx.tenantId;
7628
7630
  }
7631
+ if (hasTz && opts.timezone === void 0) {
7632
+ opts.timezone = execCtx.timezone;
7633
+ }
7629
7634
  if (isSystem && opts.bypassTenantAudit === void 0) {
7630
7635
  opts.bypassTenantAudit = true;
7631
7636
  }
@@ -7699,29 +7704,48 @@ var _ObjectQL = class _ObjectQL {
7699
7704
  * owns the value, not the client.
7700
7705
  *
7701
7706
  * In the fallback path the next value is `max(existing) + 1`, seeded once per
7702
- * `object.field` from the store then incremented in memory (monotonic within
7703
- * the process, resilient to deletions). `autonumberFormat` is honored, e.g.
7704
- * `CASE-{0000}` `CASE-0042`. NOTE: this in-memory seeding is single-instance.
7707
+ * `object.field.<scope>` from the store then incremented in memory (monotonic
7708
+ * within the process, resilient to deletions). The shared `autonumberFormat`
7709
+ * renderer is honored end-to-end, so date tokens (`AD{YYYYMMDD}{0000}`), field
7710
+ * interpolation (`{island_zone}{000}`) and per-scope reset behave identically
7711
+ * to the SQL driver's persistent sequence (#1603). NOTE: this in-memory seeding
7712
+ * is single-instance.
7705
7713
  */
7706
7714
  async applyAutonumbers(object, record, execCtx, driverOwnsAutonumber) {
7707
7715
  if (driverOwnsAutonumber) return;
7708
7716
  const fields = this.getSchema(object)?.fields;
7709
7717
  if (!fields || typeof fields !== "object" || Array.isArray(fields)) return;
7718
+ const now = /* @__PURE__ */ new Date();
7719
+ const timezone = execCtx?.timezone;
7710
7720
  for (const [name, def] of Object.entries(fields)) {
7711
7721
  if (def?.type !== "autonumber") continue;
7712
7722
  const current = record[name];
7713
7723
  if (current != null && current !== "") continue;
7714
- const key = `${object}.${name}`;
7715
- let next = this.autonumberCounters.get(key);
7716
- if (next == null) next = await this.seedAutonumber(object, name, execCtx);
7717
- next += 1;
7718
- this.autonumberCounters.set(key, next);
7719
7724
  const fmt = def.autonumberFormat ?? def.format;
7720
- record[name] = this.formatAutonumber(fmt, next);
7725
+ const tokens = (0, import_data4.parseAutonumberFormat)(typeof fmt === "string" ? fmt : "");
7726
+ const missing = (0, import_data4.missingFieldValues)(tokens, record);
7727
+ if (missing.length > 0) {
7728
+ throw new Error(
7729
+ `Cannot generate autonumber "${object}.${name}" (format "${fmt}"): referenced field(s) [${missing.join(", ")}] are empty on the record. Fields interpolated into an autonumber format must be set before the record is created.`
7730
+ );
7731
+ }
7732
+ const probe = (0, import_data4.renderAutonumber)({ tokens, seq: 0, record, now, timezone });
7733
+ const counterKey = `${object}.${name}.${probe.scope}`;
7734
+ let next = this.autonumberCounters.get(counterKey);
7735
+ if (next == null) next = await this.seedAutonumber(object, name, probe.prefix, execCtx);
7736
+ next += 1;
7737
+ this.autonumberCounters.set(counterKey, next);
7738
+ record[name] = (0, import_data4.renderAutonumber)({ tokens, seq: next, record, now, timezone }).value;
7721
7739
  }
7722
7740
  }
7723
- /** Seed the autonumber counter from the current max numeric value in store. */
7724
- async seedAutonumber(object, field, execCtx) {
7741
+ /**
7742
+ * Seed the autonumber counter from the current max in store, scoped to
7743
+ * `prefix`. With a non-empty prefix (date/field formats) only rows in the
7744
+ * same scope count, and the counter is the digit-run immediately after the
7745
+ * prefix; with an empty prefix (legacy fixed-prefix formats) the last digit
7746
+ * run of the whole value is used, preserving the original behaviour.
7747
+ */
7748
+ async seedAutonumber(object, field, prefix, execCtx) {
7725
7749
  try {
7726
7750
  const rows = await this.find(object, {
7727
7751
  select: ["id", field],
@@ -7732,22 +7756,24 @@ var _ObjectQL = class _ObjectQL {
7732
7756
  for (const r of rows || []) {
7733
7757
  const v = r?.[field];
7734
7758
  if (v == null) continue;
7735
- const m = String(v).match(/(\d+)(?!.*\d)/);
7736
- if (m) max = Math.max(max, parseInt(m[1], 10) || 0);
7759
+ const s = String(v);
7760
+ if (prefix && !s.startsWith(prefix)) continue;
7761
+ const tail = prefix ? s.slice(prefix.length) : s;
7762
+ let digits;
7763
+ if (prefix) {
7764
+ const head = tail.match(/^\d+/);
7765
+ digits = head ? head[0] : void 0;
7766
+ } else {
7767
+ const runs = tail.match(/\d+/g);
7768
+ digits = runs ? runs[runs.length - 1] : void 0;
7769
+ }
7770
+ if (digits) max = Math.max(max, parseInt(digits, 10) || 0);
7737
7771
  }
7738
7772
  return max;
7739
7773
  } catch {
7740
7774
  return 0;
7741
7775
  }
7742
7776
  }
7743
- /** Apply an autonumber format like `CASE-{0000}`; default to the bare number. */
7744
- formatAutonumber(format, value) {
7745
- if (!format) return String(value);
7746
- const m = format.match(/\{(0+)\}/);
7747
- if (!m) return format.includes("{0}") ? format.replace("{0}", String(value)) : `${format}${value}`;
7748
- const padded = String(value).padStart(m[1].length, "0");
7749
- return format.replace(m[0], padded);
7750
- }
7751
7777
  /**
7752
7778
  * Register contribution (Manifest)
7753
7779
  *