From 088a1b863e3da01bcdc4cbb31f973de9099c81de Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 07:21:10 +0000 Subject: [PATCH] refactor(driver-sql): read the autonumber default from the contract, not a hardcoded fallback (#7263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Execution half 3/3 of the route-3 ruling on #6555. `{0000}` is a declared contract default now (`DEFAULT_AUTONUMBER_FORMAT` / `resolveAutonumberFormat`, landed in `@objectstack/spec/data` by #7265), so this driver stops keeping its own copy of it. `initObjects` and the external-object registration path each spelled the same four lines by hand — canonical `autonumberFormat`, then the `format` shorthand, then `|| '{0000}'`. Both are now `resolveAutonumberFormat(field)`: one symbol added to an import this file already had, no new dependency. Behaviour-neutral by construction and by measurement. The resolver's precedence and its non-empty-string test were taken from these very lines, so a differential check over 484 field documents (both spellings x 22 value shapes) returns the identical string in every case — `format: ''`, `autonumberFormat: ''` and the non-string values included. All eight driver-sql autonumber suites (64 tests) and the cross-side parity integration test stay green unedited; the one test-file change is prose, re-pointing a comment at the declared default while keeping true its note that the engine still emits a bare counter until #7262. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TcsV4ES5JJjCZ9BLrfhTy8 --- ...autonumber-format-defallback-driver-sql.md | 45 +++++++++++++++++++ .../src/sql-driver-autonumber-suffix.test.ts | 11 ++--- packages/drivers/driver-sql/src/sql-driver.ts | 14 ++---- 3 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 .changeset/autonumber-format-defallback-driver-sql.md diff --git a/.changeset/autonumber-format-defallback-driver-sql.md b/.changeset/autonumber-format-defallback-driver-sql.md new file mode 100644 index 0000000000..1f23613771 --- /dev/null +++ b/.changeset/autonumber-format-defallback-driver-sql.md @@ -0,0 +1,45 @@ +--- +"@objectstack/driver-sql": patch +--- + +refactor(driver-sql): read the autonumber default from the contract instead of a hardcoded fallback (#7263) + +Execution half 3/3 of the maintainer's route-3 ruling on #6555. `{0000}` is now a +declared contract default (`DEFAULT_AUTONUMBER_FORMAT`, landed with +`resolveAutonumberFormat` in `@objectstack/spec/data`), so this driver stops +writing the default down for itself. + +Two sites in `sql-driver.ts` — `initObjects` and the external-object +registration path — each spelled the same four lines by hand: + +```ts +const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat) + ? field.autonumberFormat + : (typeof field.format === 'string' && field.format ? field.format : ''); +const fmt = rawFmt || '{0000}'; +``` + +Both are now `const fmt = resolveAutonumberFormat(field);`. That is the whole +change: one symbol added to an import this file already had, no new dependency, +and the `#1603` comment about honouring both spellings retired to the resolver's +own docstring, which carries it. + +**Behaviour-neutral, by construction and by measurement.** `resolveAutonumberFormat`'s +precedence — canonical `autonumberFormat`, then the `format` shorthand, then the +declared default, with anything that is not a **non-empty string** counting as +undeclared — was deliberately taken from these very lines, including their +truthiness rule (not the engine's `??`). A differential check over 484 field +documents, spanning both spellings across 22 value shapes (absent key, +`undefined`, `null`, `''`, non-empty strings, numbers, booleans, `NaN`, arrays, +objects, a boxed `String`, `Symbol`, function, `BigInt`), found the old +expressions and the resolver returning the identical string in every case — +`format: ''`, `autonumberFormat: ''` and the non-string values included, not just +the happy path. + +Compatibility note, per the ruling: choosing {0000} keeps stored driver-sql data +undisturbed; engine-fallback deployments flip from bare 1 to 0001 for newly +issued numbers. Counter continuity itself is unaffected (#6468 pinned it). + +The engine half of the same ruling is #7262; #6555 stays open until it lands, so +a format-less field still renders `0001` on SQL and a bare `1` on the engine's +in-memory fallback until then. This half moves neither. diff --git a/packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts b/packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts index f14e440a9a..b859a8f547 100644 --- a/packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts @@ -165,11 +165,12 @@ describe('SqlDriver autonumber seeding — the counter is located by the declare // lexicographic one — so the counter continues at 11. // // The RENDERING of a format-less field is a separate, pre-existing matter - // this fix does not touch: this driver substitutes `{0000}` for a missing - // format (see `initObjects`), so 11 renders `0011` here while the engine's - // fallback emits the bare `11`. That divergence is in the render default, - // not in the seeding parse #6468 is about, so the cross-side parity test - // uses explicitly-formatted fields. + // this fix does not touch: a format-less field resolves to the contract + // default `{0000}` (`resolveAutonumberFormat`, #6555), so 11 renders + // `0011` here — while the engine's fallback still emits the bare `11` + // until #7262 lands the other half. That divergence is in the render + // default, not in the seeding parse #6468 is about, so the cross-side + // parity test uses explicitly-formatted fields. await initRec(); await seedRows(['1', '2', '10']); diff --git a/packages/drivers/driver-sql/src/sql-driver.ts b/packages/drivers/driver-sql/src/sql-driver.ts index a72ef68f07..543f3cc91a 100644 --- a/packages/drivers/driver-sql/src/sql-driver.ts +++ b/packages/drivers/driver-sql/src/sql-driver.ts @@ -8,7 +8,7 @@ */ import type { DriverOptions, FilterCondition, SchemaMode } from '@objectstack/spec/data'; -import { parseAutonumberFormat, renderAutonumber, readAutonumberCounter, missingFieldValues, isTenancyDisabled, type AutonumberToken } from '@objectstack/spec/data'; +import { parseAutonumberFormat, renderAutonumber, resolveAutonumberFormat, readAutonumberCounter, missingFieldValues, isTenancyDisabled, type AutonumberToken } from '@objectstack/spec/data'; // The DECLARED aggregate vocabulary (#5907). Read from the spec so this driver's // "the protocol has no such function" refusal cannot drift from what // `AggregationNodeSchema.function` actually admits. @@ -5353,10 +5353,7 @@ export class SqlDriver implements IDataDriver { if (type === 'datetime') datetimeCols.push(name); if (type === 'time') timeCols.push(name); if (type === 'auto_number' || type === 'autonumber') { - const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat) - ? field.autonumberFormat - : (typeof field.format === 'string' && field.format ? field.format : ''); - const fmt = rawFmt || '{0000}'; + const fmt = resolveAutonumberFormat(field); autoNumberCols.push({ name, format: fmt, tokens: parseAutonumberFormat(fmt), tenantField }); } } @@ -5433,12 +5430,7 @@ export class SqlDriver implements IDataDriver { (this.timeFields[tableName] ??= new Set()).add(name); } if (type === 'auto_number' || type === 'autonumber') { - // Honor either the spec-canonical `autonumberFormat` or the - // shorthand `format` (both appear in metadata) — see #1603. - const rawFmt = (typeof field.autonumberFormat === 'string' && field.autonumberFormat) - ? field.autonumberFormat - : (typeof field.format === 'string' && field.format ? field.format : ''); - const fmt = rawFmt || '{0000}'; + const fmt = resolveAutonumberFormat(field); // Tokenize once: the renderer resolves date tokens (`{YYYYMMDD}`), // field interpolation (`{island_zone}`) and the sequence slot at // fill time. The counter scopes to whatever renders before the slot.