Skip to content

fix(driver-memory,driver-mongodb): one storage form per temporal field type (#4047) - #4060

Merged
os-zhuang merged 1 commit into
mainfrom
claude/dashboard-date-range-data-loss-b7qt5o
Jul 30, 2026
Merged

fix(driver-memory,driver-mongodb): one storage form per temporal field type (#4047)#4060
os-zhuang merged 1 commit into
mainfrom
claude/dashboard-date-range-data-loss-b7qt5o

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4047。ADR-0053 D-B(#3912,SQL 侧「每方言单一存储形态」)的非 SQL 对应物;续 #4041 / #4048(边界语义)。

验收门(先红后绿)

两个驱动各一份行结果探针,在未动实现前先跑红

修复前 修复后
mongodb {$gte:'2026-04-29', $lte:'2026-07-28'} ['s_evening','s_morning'](Date 行全不可见) 4 行全中
mongodb Date 比较值 ['d_midnight','d_yesterday'](字符串行全不可见) 4 行全中
memory 同上 8 条断言红 9/9 绿

mongodb 探针跑在真实 MongoDB 上(mongodb-memory-server,本环境可用),不是形状断言。

根因

MongoDB 按 BSON 类型桶比较,mingo 对 JS 类型照搬同一规则:字符串比较值与 Date 值互不匹配,所有操作符都如此($gte 也不例外)。SQLite 的 affinity 至少还留下一半可达;这里窗口是「谁的类型对上就答谁」。

而两个 datetime 列确实是混的:驱动自己的 created_at/updated_at 默认值写一种形态(mongo 是 new Date(),memory 是 ISO 文本),REST/JSON 写入、相对日期 token、initialData 夹具供另一种。mongo 上 dashboard 默认窗口(字符串边界 × BSON Date 的 created_at)返回空集——比 #3777 的「丢一天」更糟。

每驱动一个 canon,按该存储原生有什么来选

驱动 datetime 理由
driver-mongodb BSON Date 方言原生瞬时类型:可索引、可范围查询、无时区歧义。这个驱动的 timestamptz
driver-memory canonical UTC ISO 文本 无原生瞬时类型;ISO-8601 UTC 在 mingo 所做的纯字符串比较下按时间序排列,且是 wire form,JSON 持久化往返不变

Field.date 两边都保持时区无关文本——转成瞬时等于凭空造一个午夜并把它重新绑到某个时区。

写路径与过滤路径共用同一规则:mongodb 的 create/update/updateMany/bulkCreate/bulkUpdate 转换,translateFilter 接一个 temporal-kind resolver(find/deleteMany/聚合 $match 全覆盖);memory 两条 lowering 与三个写路径同理。

三个承重细节

  1. 未声明的对象原样不动。 两者都是 schemaless 存储,字段类型来自 syncSchema(驱动唯一被交付对象定义的地方)。从值猜类型会去强转平台从未声明拥有的数据——有专门测试钉住。
  2. memory 会追认已在表里的行:schema 到达时收敛存量行,这才捕获得到 initialData 夹具与持久化恢复(两者都早于任何 schema 声明)。是 backfillCanonicalDatetimes 的内存版,同样幂等。mongo 不做自动 backfill——重写真实 collection 是迁移不是启动步骤,PR 描述里明说了这条边界。
  3. dashboard 的日期区间上界打在 datetime 列上丢失当天数据 —— 默认配置即命中 #3777/driver-memory / driver-mongodb:裸日期 $lte 上界在 datetime 值上同样丢当天数据(#3777 的非 SQL 驱动对齐) #4042 的顺序是承重的:日历日上界改写是日历操作,先在裸日期字符串上跑,只有产出的边界才转存储形态。反过来会把 Date 交给 nextUtcCalendarDay(它会正确地拒绝放宽),整天窗口就悄悄缩回一个瞬时。

覆盖

每驱动一套:混合形态窗口、Date 比较值、收敛后的边界语义($gte/$lt/完整 ISO 不放宽)、$between + 数组拼写、date 列不被放宽、未声明对象透传;memory 另有 initialData 追认与 update() 形态两条。

一处测试自纠update() 最初我拿 created_at 做被测字段,红了——该驱动有意created_at 不可变(每次 update 都重新保留原值)。改用可变 datetime 字段,并在注释里记下这个语义。

测试

driver-memory 194 ✅(含新 9)/ driver-mongodb 107 ✅(含新 7);两包 turbo build(带 dts)✅;改动文件 eslint 干净。既有断言零改动。

构建期修了一处自己引入的类型收窄:toStorageForms 显式返回 Record<string, any>update()| null 不再被 any 吞掉,破坏了 IDataDriver 兼容——改为保型泛型。

关联

  • ADR-0053 新增 D-E 附录(D-E1 类型桶、D-E2 每驱动 canon、D-E3 存量数据与顺序、D-E4 对 D-A3 的影响)。
  • 仍开放:@objectstack/formula.matchesFilterCondition(RLS 写侧 check,第五个求值面)因层级约束(不依赖 core)未对齐 D-D/D-E 两条规则,归属待定(私有拷贝 vs 工具下沉 spec/types),已记入 ADR D-E4。

🤖 Generated with Claude Code

https://claude.ai/code/session_01EkL3RGJrzjLEGURsLxfS2f


Generated by Claude Code

…d type (#4047)

The non-SQL counterpart of ADR-0053 D-B (#3912). Both drivers let the
writer decide a datetime value's runtime type, and both compare across
types by type bracket rather than by value — so a string comparand never
matched a Date value, in either direction, for EVERY operator including
$gte. SQLite's affinity rules at least left one half reachable; here a
window silently answers with whichever half matches the comparand type.

Both columns really were mixed: each driver's own created_at/updated_at
default writes one form (new Date() on mongo, ISO text in memory) while
REST/JSON writes, relative-date tokens and initialData fixtures supply
the other. On MongoDB the dashboard's default window — string bounds
against a BSON-Date created_at — returned NOTHING, worse than the
final-day loss #3777 fixed.

One canon per driver, chosen by what the store natively has, applied on
write and to every filter comparand:

- driver-mongodb: BSON Date (the dialect's native instant, its
  timestamptz) — create/update/updateMany/bulkCreate/bulkUpdate convert,
  and translateFilter takes a temporal-kind resolver so find(),
  deleteMany and the aggregation $match all coerce comparands.
- driver-memory: canonical UTC ISO text (sorts chronologically under the
  string comparison mingo performs; survives JSON persistence) — create/
  update/updateMany convert, and both filter lowerings coerce.

Both learn their temporal fields from syncSchema; an undeclared object
is left exactly as written — schemaless stores, so guessing a type from
a value would coerce data the platform never claimed. driver-memory also
converges rows already in the table when the schema arrives, catching
initialData and persistence restores (the in-memory analogue of
backfillCanonicalDatetimes, idempotent like it).

Field.date stays timezone-naive text on both — an instant would invent a
midnight and re-couple it to a zone. Ordering where this meets #3777/
#4042 is load-bearing: the calendar-day rewrite runs on the bare-day
STRING first, and only the resulting bound is converted to storage form.

Tests: row-result probes on both, red before the fix — mixed-form
windows, Date-object comparands, bound semantics on top of the converged
storage, $between + array spelling, date-column non-widening, undeclared
passthrough; mongodb runs against a real MongoDB (mongodb-memory-server).
ADR-0053 gains the D-E addendum.

Closes #4047

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkL3RGJrzjLEGURsLxfS2f
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 30, 2026 7:31am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/xl labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/driver-memory, @objectstack/driver-mongodb.

10 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/deployment/vercel.mdx (via @objectstack/driver-memory)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/driver-memory)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-memory)
  • content/docs/permissions/authentication.mdx (via @objectstack/driver-memory)
  • content/docs/plugins/index.mdx (via @objectstack/driver-memory)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 07:55
@os-zhuang
os-zhuang merged commit 9e8f04d into main Jul 30, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/dashboard-date-range-data-loss-b7qt5o branch July 30, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

driver-memory / driver-mongodb:Field.datetime 无单一存储形态,跨类型比较恒 false —— #3912 的非 SQL 版

2 participants