@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 +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.js +48 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -6133,6 +6133,7 @@ var ObjectStackProtocolImplementation = _ObjectStackProtocolImplementation;
|
|
|
6133
6133
|
|
|
6134
6134
|
// src/engine.ts
|
|
6135
6135
|
import { AsyncLocalStorage } from "async_hooks";
|
|
6136
|
+
import { parseAutonumberFormat, renderAutonumber, missingFieldValues } from "@objectstack/spec/data";
|
|
6136
6137
|
import { ExecutionContextSchema } from "@objectstack/spec/kernel";
|
|
6137
6138
|
import { createLogger } from "@objectstack/core";
|
|
6138
6139
|
import { CoreServiceName, StorageNameMapping } from "@objectstack/spec/system";
|
|
@@ -7558,8 +7559,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7558
7559
|
const tx = execCtx?.transaction !== void 0 ? execCtx.transaction : this.txStore.getStore()?.transaction;
|
|
7559
7560
|
const hasTx = tx !== void 0;
|
|
7560
7561
|
const hasTenant = execCtx?.tenantId !== void 0;
|
|
7562
|
+
const hasTz = execCtx?.timezone !== void 0;
|
|
7561
7563
|
const isSystem = execCtx?.isSystem === true;
|
|
7562
|
-
if (!hasTx && !hasTenant && !isSystem) return base;
|
|
7564
|
+
if (!hasTx && !hasTenant && !isSystem && !hasTz) return base;
|
|
7563
7565
|
const opts = base && typeof base === "object" ? { ...base } : {};
|
|
7564
7566
|
if (hasTx && opts.transaction === void 0) {
|
|
7565
7567
|
opts.transaction = tx;
|
|
@@ -7567,6 +7569,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7567
7569
|
if (hasTenant && opts.tenantId === void 0) {
|
|
7568
7570
|
opts.tenantId = execCtx.tenantId;
|
|
7569
7571
|
}
|
|
7572
|
+
if (hasTz && opts.timezone === void 0) {
|
|
7573
|
+
opts.timezone = execCtx.timezone;
|
|
7574
|
+
}
|
|
7570
7575
|
if (isSystem && opts.bypassTenantAudit === void 0) {
|
|
7571
7576
|
opts.bypassTenantAudit = true;
|
|
7572
7577
|
}
|
|
@@ -7640,29 +7645,48 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7640
7645
|
* owns the value, not the client.
|
|
7641
7646
|
*
|
|
7642
7647
|
* In the fallback path the next value is `max(existing) + 1`, seeded once per
|
|
7643
|
-
* `object.field
|
|
7644
|
-
* the process, resilient to deletions). `autonumberFormat`
|
|
7645
|
-
*
|
|
7648
|
+
* `object.field.<scope>` from the store then incremented in memory (monotonic
|
|
7649
|
+
* within the process, resilient to deletions). The shared `autonumberFormat`
|
|
7650
|
+
* renderer is honored end-to-end, so date tokens (`AD{YYYYMMDD}{0000}`), field
|
|
7651
|
+
* interpolation (`{island_zone}{000}`) and per-scope reset behave identically
|
|
7652
|
+
* to the SQL driver's persistent sequence (#1603). NOTE: this in-memory seeding
|
|
7653
|
+
* is single-instance.
|
|
7646
7654
|
*/
|
|
7647
7655
|
async applyAutonumbers(object, record, execCtx, driverOwnsAutonumber) {
|
|
7648
7656
|
if (driverOwnsAutonumber) return;
|
|
7649
7657
|
const fields = this.getSchema(object)?.fields;
|
|
7650
7658
|
if (!fields || typeof fields !== "object" || Array.isArray(fields)) return;
|
|
7659
|
+
const now = /* @__PURE__ */ new Date();
|
|
7660
|
+
const timezone = execCtx?.timezone;
|
|
7651
7661
|
for (const [name, def] of Object.entries(fields)) {
|
|
7652
7662
|
if (def?.type !== "autonumber") continue;
|
|
7653
7663
|
const current = record[name];
|
|
7654
7664
|
if (current != null && current !== "") continue;
|
|
7655
|
-
const key = `${object}.${name}`;
|
|
7656
|
-
let next = this.autonumberCounters.get(key);
|
|
7657
|
-
if (next == null) next = await this.seedAutonumber(object, name, execCtx);
|
|
7658
|
-
next += 1;
|
|
7659
|
-
this.autonumberCounters.set(key, next);
|
|
7660
7665
|
const fmt = def.autonumberFormat ?? def.format;
|
|
7661
|
-
|
|
7666
|
+
const tokens = parseAutonumberFormat(typeof fmt === "string" ? fmt : "");
|
|
7667
|
+
const missing = missingFieldValues(tokens, record);
|
|
7668
|
+
if (missing.length > 0) {
|
|
7669
|
+
throw new Error(
|
|
7670
|
+
`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.`
|
|
7671
|
+
);
|
|
7672
|
+
}
|
|
7673
|
+
const probe = renderAutonumber({ tokens, seq: 0, record, now, timezone });
|
|
7674
|
+
const counterKey = `${object}.${name}.${probe.scope}`;
|
|
7675
|
+
let next = this.autonumberCounters.get(counterKey);
|
|
7676
|
+
if (next == null) next = await this.seedAutonumber(object, name, probe.prefix, execCtx);
|
|
7677
|
+
next += 1;
|
|
7678
|
+
this.autonumberCounters.set(counterKey, next);
|
|
7679
|
+
record[name] = renderAutonumber({ tokens, seq: next, record, now, timezone }).value;
|
|
7662
7680
|
}
|
|
7663
7681
|
}
|
|
7664
|
-
/**
|
|
7665
|
-
|
|
7682
|
+
/**
|
|
7683
|
+
* Seed the autonumber counter from the current max in store, scoped to
|
|
7684
|
+
* `prefix`. With a non-empty prefix (date/field formats) only rows in the
|
|
7685
|
+
* same scope count, and the counter is the digit-run immediately after the
|
|
7686
|
+
* prefix; with an empty prefix (legacy fixed-prefix formats) the last digit
|
|
7687
|
+
* run of the whole value is used, preserving the original behaviour.
|
|
7688
|
+
*/
|
|
7689
|
+
async seedAutonumber(object, field, prefix, execCtx) {
|
|
7666
7690
|
try {
|
|
7667
7691
|
const rows = await this.find(object, {
|
|
7668
7692
|
select: ["id", field],
|
|
@@ -7673,22 +7697,24 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7673
7697
|
for (const r of rows || []) {
|
|
7674
7698
|
const v = r?.[field];
|
|
7675
7699
|
if (v == null) continue;
|
|
7676
|
-
const
|
|
7677
|
-
if (
|
|
7700
|
+
const s = String(v);
|
|
7701
|
+
if (prefix && !s.startsWith(prefix)) continue;
|
|
7702
|
+
const tail = prefix ? s.slice(prefix.length) : s;
|
|
7703
|
+
let digits;
|
|
7704
|
+
if (prefix) {
|
|
7705
|
+
const head = tail.match(/^\d+/);
|
|
7706
|
+
digits = head ? head[0] : void 0;
|
|
7707
|
+
} else {
|
|
7708
|
+
const runs = tail.match(/\d+/g);
|
|
7709
|
+
digits = runs ? runs[runs.length - 1] : void 0;
|
|
7710
|
+
}
|
|
7711
|
+
if (digits) max = Math.max(max, parseInt(digits, 10) || 0);
|
|
7678
7712
|
}
|
|
7679
7713
|
return max;
|
|
7680
7714
|
} catch {
|
|
7681
7715
|
return 0;
|
|
7682
7716
|
}
|
|
7683
7717
|
}
|
|
7684
|
-
/** Apply an autonumber format like `CASE-{0000}`; default to the bare number. */
|
|
7685
|
-
formatAutonumber(format, value) {
|
|
7686
|
-
if (!format) return String(value);
|
|
7687
|
-
const m = format.match(/\{(0+)\}/);
|
|
7688
|
-
if (!m) return format.includes("{0}") ? format.replace("{0}", String(value)) : `${format}${value}`;
|
|
7689
|
-
const padded = String(value).padStart(m[1].length, "0");
|
|
7690
|
-
return format.replace(m[0], padded);
|
|
7691
|
-
}
|
|
7692
7718
|
/**
|
|
7693
7719
|
* Register contribution (Manifest)
|
|
7694
7720
|
*
|