Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e22957d
feat(spec): declare the two duration-rule exemptions on the schema (#…
claude Sep 5, 2026
414e515
feat(spec)!: move the six epoch instants onto EpochMs and mark the ex…
claude Sep 5, 2026
3f95444
feat(spec): publish the externalVocabulary standard on the reference …
claude Sep 5, 2026
884646a
docs(changeset): the two duration-rule exemptions (#15676)
claude Sep 5, 2026
c1949b9
chore(spec): regenerate the derived artifacts and fix the consumers t…
claude Sep 5, 2026
796f24f
wip(spec): rename the 12 api/ duration keys, tombstones on the old sp…
claude Sep 5, 2026
7e870b1
wip(spec): readers, ADR-0087 registrations, ledger row for the api/ r…
claude Sep 5, 2026
37fc158
wip(spec): tombstone refusal tests, alias retarget, regenerated artif…
claude Sep 5, 2026
8cd4d8c
docs(changeset): the twelve api/ duration renames (#15677)
claude Sep 5, 2026
d7ebd6c
fix(docs-audit): declare the conversion-replay exclusion kind (b) rel…
claude Sep 5, 2026
dc3b847
docs: move the hand-written pages onto the renamed keys, and strip th…
claude Sep 5, 2026
9605808
docs(spec): drop the internal issue id from the four epoch-instant to…
claude Sep 5, 2026
19de17a
Merge remote-tracking branch 'origin/claude/issue-15676-epoch-ms-and-…
claude Sep 5, 2026
b463390
chore(spec): regenerate the reference page the merge deferred (#15677)
claude Sep 5, 2026
ecbc4be
wip(spec): rename the 14 kernel/ duration keys, tombstones on the old…
claude Sep 5, 2026
cf5c834
wip(spec): readers and ADR-0087 registrations for the kernel/ renames…
claude Sep 5, 2026
6bf1119
wip(spec): tombstone refusal tests and regenerated reference pages (#…
claude Sep 5, 2026
8f2b8f3
docs(changeset): the fourteen kernel/ duration renames (#15678)
claude Sep 5, 2026
b5f2a84
wip(spec): rename the 15 system/ duration keys, tombstones on the old…
claude Sep 5, 2026
28f60ba
wip(spec): readers, tombstone refusal tests and ADR-0087 registration…
claude Sep 5, 2026
dc67a94
wip(spec): regenerated artifacts and reference pages for the system/ …
claude Sep 5, 2026
d1d63f3
test(spec): fix the three new metrics pins — required label, and asse…
claude Sep 5, 2026
45a3589
docs(changeset): the fifteen system/ duration renames (#15679)
claude Sep 5, 2026
18526c9
wip(spec): rename the 7 data/ ui/ ai/ integration/ duration keys, tom…
claude Sep 5, 2026
1db0129
wip(spec): readers, ADR-0087 registrations and regenerated artifacts …
claude Sep 5, 2026
e6701a1
test(spec): tombstone refusal pins for the 7 renames; narrow credenti…
claude Sep 5, 2026
3a56ae0
chore(i18n): regenerate the metadata-form bundles for the dashboard k…
claude Sep 5, 2026
e10e2c7
docs(changeset): the seven data/ ui/ ai/ integration/ duration rename…
claude Sep 5, 2026
3351a86
docs(changeset): declare the ADR-0087 disposition on the driver-memor…
claude Sep 5, 2026
8c39a94
wip(spec): widen check:duration-unit-keys to every workspace package'…
claude Sep 5, 2026
8c12a91
feat(driver-turso)!: rename the published config timeout to timeoutMs…
claude Sep 5, 2026
eb55348
fix(scripts): declare the three workspace-enumerator exports the wide…
claude Sep 5, 2026
8086918
fix(service-datasource): read the canonical turso `config.timeoutMs` …
claude Sep 5, 2026
55cd03d
Merge branch 'claude/issue-15680-data-ui-ai-integration-duration-unit…
claude Sep 5, 2026
0ae5a8a
docs(spec): correct the exclusion pin's stale comment to the fixture …
claude Sep 5, 2026
c496f56
Merge branch 'claude/issue-14478-duration-unit-in-key-name' into clau…
claude Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changeset/driver-turso-config-timeout-ms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
"@objectstack/driver-turso": minor
---

feat(driver-turso)!: the published connection config names its timeout's unit (#15682, ruling B on #14478)

<!-- adr-0087: not-required (already-registered turso-config-timeout-to-timeout-ms) that protocol-18 conversion rewrites `datasources[].config.timeout` to `timeoutMs` for turso datasources, which is the same authored key this package's schema mirrors — a second entry would restate the same rewrite without converting anything more -->

**BREAKING** — `TursoConfigSchema`'s `timeout` is renamed to **`timeoutMs`**. The
value is unchanged: the same milliseconds, the same `min(0)` bound, the same
optionality.

`@objectstack/spec`'s own turso contract renamed the same authored key in
#15680. This package publishes a parallel schema for the same connection config
— the Spec / Studio metadata a host reads to expose Turso configuration UI — so
until now the two declarations of one setting disagreed on its spelling. They
agree again.

The unit was never in the key name, only in the describe prose, while
`sync.intervalSeconds` — the same shape, three keys above — already spelled its
own. One published config carrying both conventions is what made the bare name
dangerous rather than untidy: an author who has just written
`intervalSeconds: 30` has no reason to read `timeout: 30` as milliseconds, and
nothing in the schema, the type or the parse would have told them otherwise.

The old spelling is not dropped in silence. `TursoConfigSchema` is a plain
`z.object`, so a bare deletion would have STRIPPED `timeout` and parsed
successfully. The key stays declared as a tombstone instead: `tsc` refuses it on
anything typed `TursoConfig`, and a value that reaches the parse raises a
message naming `timeoutMs` rather than a generic unrecognised-key error.

```diff
- TursoConfigSchema.parse({ url: 'libsql://app.turso.io', timeout: 30000 })
+ TursoConfigSchema.parse({ url: 'libsql://app.turso.io', timeoutMs: 30000 })
```

`TursoDriverConfig` — this package's TypeScript constructor option, a separate
declaration — keeps its `timeout` spelling and is untouched here.
45 changes: 37 additions & 8 deletions packages/drivers/driver-turso/src/spec/turso.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ describe('TursoConfigSchema', () => {
intervalSeconds: 120,
onConnect: false,
},
timeout: 30000,
timeoutMs: 30000,
wasm: true,
});

expect(config.encryptionKey).toBe('my-secret-key-256');
expect(config.concurrency).toBe(50);
expect(config.timeout).toBe(30000);
expect(config.timeoutMs).toBe(30000);
expect(config.wasm).toBe(true);
});

Expand All @@ -83,7 +83,7 @@ describe('TursoConfigSchema', () => {
expect(config.syncUrl).toBeUndefined();
expect(config.localPath).toBeUndefined();
expect(config.sync).toBeUndefined();
expect(config.timeout).toBeUndefined();
expect(config.timeoutMs).toBeUndefined();
expect(config.wasm).toBeUndefined();
});

Expand Down Expand Up @@ -124,13 +124,42 @@ describe('TursoConfigSchema', () => {
})).toThrow();
});

it('should reject config with negative timeout', () => {
it('should reject config with negative timeoutMs', () => {
expect(() => TursoConfigSchema.parse({
url: ':memory:',
timeout: -1,
timeoutMs: -1,
})).toThrow();
});

// [#15682, ruling B on #14478] The rename's tombstone. Asserted on the
// REFUSAL ENVELOPE — which key was refused and what the message prescribes —
// rather than on `toThrow()` alone: a bare `toThrow()` here stays green for a
// schema that lost the tombstone entirely and simply required `url`, and it
// would stay green for a strip that never refused at all. What must hold is
// that the old spelling is REFUSED and that the refusal names the new key.
it('refuses the retired `timeout` spelling, and the refusal names `timeoutMs`', () => {
const result = TursoConfigSchema.safeParse({ url: ':memory:', timeout: 30000 });

expect(result.success).toBe(false);
const issue = result.error!.issues.find((i) => i.path.join('.') === 'timeout');
expect(issue).toBeDefined();
expect(issue!.message).toContain('timeoutMs');
expect(issue!.message).toContain('milliseconds');
// The standardized closing sentence — the one channel an upgrading author
// is guaranteed to hit carries the command, not just the diagnosis.
expect(issue!.message).toContain('os migrate meta --from 17');
});

// The other half of the tombstone: it refuses a VALUE, it does not make the
// whole config unparseable. A tombstone that took the object down with it
// would read identically in the case above.
it('the tombstone leaves a config that never wrote `timeout` untouched', () => {
const config = TursoConfigSchema.parse({ url: ':memory:', timeoutMs: 5000 });

expect(config.timeoutMs).toBe(5000);
expect('timeout' in config).toBe(false);
});

it('should accept config with environment variable patterns', () => {
const config = TursoConfigSchema.parse({
url: '${TURSO_DATABASE_URL}',
Expand All @@ -150,13 +179,13 @@ describe('TursoConfigSchema', () => {
expect(config.concurrency).toBe(100);
});

it('should accept zero timeout (no timeout)', () => {
it('should accept zero timeoutMs (no timeout)', () => {
const config = TursoConfigSchema.parse({
url: ':memory:',
timeout: 0,
timeoutMs: 0,
});

expect(config.timeout).toBe(0);
expect(config.timeoutMs).toBe(0);
});
});

Expand Down
44 changes: 43 additions & 1 deletion packages/drivers/driver-turso/src/spec/turso.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ export const TursoSyncConfigSchema = lazySchema(() => z.object({
// 2. Connection Configuration
// ==========================================================================

/**
* The prescription the retired `timeout` key raises, and the text `tsc` and the
* parse both carry. Standardized closing sentence — the `os migrate meta`
* wording states a property of the TOOL and is not a choice (see
* `retired-key.ts` in `@objectstack/spec`, whose header owns that ruling).
*
* ⛔ No internal issue id in this string: it is customer-facing text and
* `check:doc-authoring` refuses one. The ids live in the comments beside the
* keys below.
*/
const TIMEOUT_RETIRED =
'`turso config.timeout` was renamed to `timeoutMs` in @objectstack/driver-turso 17 — the unit of a '
+ 'duration-shaped number lives in the key name, not only in the describe prose. Rename the key to '
+ '`timeoutMs`; the value (milliseconds) is unchanged. '
+ 'Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.';

export const TursoConfigSchema = lazySchema(() => z.object({
/**
* Database URL.
Expand Down Expand Up @@ -100,8 +116,34 @@ export const TursoConfigSchema = lazySchema(() => z.object({

/**
* Timeout for database operations in milliseconds.
*
* Renamed from `timeout` (#15682, ruling B on #14478): the unit lived only in
* the describe prose, while `sync.intervalSeconds` — the same shape, three
* keys above — already spelled ITS unit. One published connection config
* carrying both conventions is what made the bare name dangerous rather than
* untidy. `@objectstack/spec`'s own turso contract renamed the same authored
* key in #15680; this mirror now agrees with it, and with the ADR-0087
* conversion (`turso-config-timeout-to-timeout-ms`) that rewrites the stored
* spelling on load.
*/
timeoutMs: z.number().int().min(0).optional().describe('Operation timeout in milliseconds'),

/**
* Tombstone for the rename above (#15682, ruling B on #14478).
*
* This is a plain `z.object`, so zod's default STRIP posture would make a
* bare deletion SILENT: an author's `timeout: 30000` would vanish and the
* parse would still succeed. `z.never()` keeps the key DECLARED and
* unwritable, so the old spelling raises the prescription instead of
* disappearing — the two channels an upgrading author actually meets (`tsc`
* sees `never` at the authoring site; the parse raises the text itself).
*
* Spelled inline rather than through `@objectstack/spec`'s `retiredKey()`:
* that helper is internal to the spec package and is not on its published
* `./shared` entry point, so this package cannot import it. The shape is the
* same one-liner.
*/
timeout: z.number().int().min(0).optional().describe('Operation timeout in milliseconds'),
timeout: z.never({ error: () => TIMEOUT_RETIRED }).optional().describe(`[REMOVED] ${TIMEOUT_RETIRED}`),

/**
* Enable WASM mode.
Expand Down
Loading
Loading