From 5dd91ac22753451f42c279bd0bf125eb0489d730 Mon Sep 17 00:00:00 2001 From: Ben Guericke Date: Fri, 13 Mar 2026 11:34:59 -0600 Subject: [PATCH 1/4] fix(db-ivm): hash Temporal objects by value instead of identity Temporal objects (PlainDate, ZonedDateTime, etc.) have no enumerable own properties, so Object.keys() returns [] and all instances produce identical hashes. This causes the IVM join Index to treat old and new rows as equal, silently swallowing updates when only a Temporal field changed. Hash Temporal objects by their Symbol.toStringTag type and toString() representation to produce correct, value-based hashes. Fixes https://github.com/TanStack/db/issues/1367 Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-temporal-hash.md | 5 ++ packages/db-ivm/src/hashing/hash.ts | 30 +++++++++++ packages/db-ivm/tests/utils.test.ts | 56 ++++++++++++++++++++ packages/db/tests/query/join.test.ts | 77 ++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 .changeset/fix-temporal-hash.md diff --git a/.changeset/fix-temporal-hash.md b/.changeset/fix-temporal-hash.md new file mode 100644 index 000000000..97efec3f3 --- /dev/null +++ b/.changeset/fix-temporal-hash.md @@ -0,0 +1,5 @@ +--- +'@tanstack/db-ivm': patch +--- + +Fix Temporal objects (PlainDate, ZonedDateTime, etc.) producing identical hashes in the IVM hash function. Temporal objects have no enumerable own properties, so Object.keys() returns [] and all instances were hashed identically. This caused join live queries to silently swallow updates when only a Temporal field changed. Temporal objects are now hashed by their type tag and string representation. diff --git a/packages/db-ivm/src/hashing/hash.ts b/packages/db-ivm/src/hashing/hash.ts index d1b9d0a6a..71dd76de6 100644 --- a/packages/db-ivm/src/hashing/hash.ts +++ b/packages/db-ivm/src/hashing/hash.ts @@ -18,6 +18,7 @@ const ARRAY_MARKER = randomHash() const MAP_MARKER = randomHash() const SET_MARKER = randomHash() const UINT8ARRAY_MARKER = randomHash() +const TEMPORAL_MARKER = randomHash() // Maximum byte length for Uint8Arrays to hash by content instead of reference // Arrays smaller than this will be hashed by content, allowing proper equality comparisons @@ -59,6 +60,11 @@ function hashObject(input: object): number { } else if (input instanceof File) { // Files are always hashed by reference due to their potentially large size return cachedReferenceHash(input) + } else if (isTemporal(input)) { + // Temporal objects (PlainDate, ZonedDateTime, etc.) have no enumerable own + // properties, so Object.keys() returns [] and hashPlainObject would produce + // identical hashes for all instances. Hash by toString() instead. + valueHash = hashTemporal(input) } else { let plainObjectInput = input let marker = OBJECT_MARKER @@ -103,6 +109,30 @@ function hashUint8Array(input: Uint8Array): number { return hasher.digest() } +const temporalTypes = [ + `Temporal.PlainDate`, + `Temporal.PlainTime`, + `Temporal.PlainDateTime`, + `Temporal.PlainYearMonth`, + `Temporal.PlainMonthDay`, + `Temporal.ZonedDateTime`, + `Temporal.Instant`, + `Temporal.Duration`, +] + +function isTemporal(input: object): boolean { + const tag = (input as any)[Symbol.toStringTag] + return typeof tag === `string` && temporalTypes.includes(tag) +} + +function hashTemporal(input: object): number { + const hasher = new MurmurHashStream() + hasher.update(TEMPORAL_MARKER) + hasher.update((input as any)[Symbol.toStringTag]) + hasher.update(input.toString()) + return hasher.digest() +} + function hashPlainObject(input: object, marker: number): number { const hasher = new MurmurHashStream() diff --git a/packages/db-ivm/tests/utils.test.ts b/packages/db-ivm/tests/utils.test.ts index 9f6986577..6ec3b127f 100644 --- a/packages/db-ivm/tests/utils.test.ts +++ b/packages/db-ivm/tests/utils.test.ts @@ -2,6 +2,15 @@ import { describe, expect, it } from 'vitest' import { DefaultMap } from '../src/utils.js' import { hash } from '../src/hashing/index.js' +// Minimal mock that mimics Temporal objects: Symbol.toStringTag + toString() +// without requiring the temporal-polyfill dependency. +function createTemporalLike(tag: string, value: string) { + return Object.create(null, { + [Symbol.toStringTag]: { value: tag }, + toString: { value: () => value }, + }) +} + describe(`DefaultMap`, () => { it(`should return default value for missing keys`, () => { const map = new DefaultMap(() => 0) @@ -170,6 +179,53 @@ describe(`hash`, () => { expect(hash1).not.toBe(hash3) // Different dates should have different hash }) + it(`should hash Temporal objects by value`, () => { + const date1 = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) + const date2 = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) + const date3 = createTemporalLike(`Temporal.PlainDate`, `2024-06-15`) + + const hash1 = hash(date1) + const hash2 = hash(date2) + const hash3 = hash(date3) + + expect(typeof hash1).toBe(hashType) + expect(hash1).toBe(hash2) // Same Temporal date should have same hash + expect(hash1).not.toBe(hash3) // Different Temporal dates should have different hash + + // Different Temporal types with overlapping string representations should differ + const plainDate = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) + const plainDateTime = createTemporalLike( + `Temporal.PlainDateTime`, + `2024-01-15T00:00:00`, + ) + + expect(hash(plainDate)).not.toBe(hash(plainDateTime)) + + // Other Temporal types should also hash correctly + const time1 = createTemporalLike(`Temporal.PlainTime`, `10:30:00`) + const time2 = createTemporalLike(`Temporal.PlainTime`, `10:30:00`) + const time3 = createTemporalLike(`Temporal.PlainTime`, `14:00:00`) + + expect(hash(time1)).toBe(hash(time2)) + expect(hash(time1)).not.toBe(hash(time3)) + + const instant1 = createTemporalLike( + `Temporal.Instant`, + `2024-01-15T00:00:00Z`, + ) + const instant2 = createTemporalLike( + `Temporal.Instant`, + `2024-01-15T00:00:00Z`, + ) + const instant3 = createTemporalLike( + `Temporal.Instant`, + `2024-06-15T00:00:00Z`, + ) + + expect(hash(instant1)).toBe(hash(instant2)) + expect(hash(instant1)).not.toBe(hash(instant3)) + }) + it(`should hash RegExp objects`, () => { const regex1 = /test/g const regex2 = /test/g diff --git a/packages/db/tests/query/join.test.ts b/packages/db/tests/query/join.test.ts index 0219cdaf2..07cc92a76 100644 --- a/packages/db/tests/query/join.test.ts +++ b/packages/db/tests/query/join.test.ts @@ -1,9 +1,11 @@ import { beforeEach, describe, expect, test } from 'vitest' +import { Temporal } from 'temporal-polyfill' import { concat, createLiveQueryCollection, eq, gt, + inArray, isNull, isUndefined, lt, @@ -12,6 +14,7 @@ import { } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { + flushPromises, mockSyncCollectionOptions, mockSyncCollectionOptionsNoInitialState, } from '../utils.js' @@ -2022,6 +2025,80 @@ function createJoinTests(autoIndex: `off` | `eager`): void { chainedJoinQuery.toArray.every((r) => r.balance_amount !== undefined), ).toBe(true) }) + + // Regression test for https://github.com/TanStack/db/issues/1367 + // Temporal objects (PlainDate, ZonedDateTime, etc.) have no enumerable own + // properties, so Object.keys() returns []. Without special handling in the + // hash function, all Temporal instances produce identical hashes, causing the + // IVM join Index to treat old and new rows as equal and silently swallow updates. + test(`join should propagate Temporal field updates through live queries`, async () => { + type Task = { + id: number + name: string + project_id: number + dueDate: Temporal.PlainDate + } + + type Project = { + id: number + name: string + } + + const taskCollection = createCollection( + mockSyncCollectionOptions({ + id: `test-temporal-join-${autoIndex}`, + getKey: (task) => task.id, + initialData: [ + { + id: 1, + name: `Task A`, + project_id: 10, + dueDate: Temporal.PlainDate.from(`2024-01-15`), + }, + ], + autoIndex, + }), + ) + + const projectCollection = createCollection( + mockSyncCollectionOptions({ + id: `test-temporal-join-projects-${autoIndex}`, + getKey: (project) => project.id, + initialData: [{ id: 10, name: `Project Alpha` }], + autoIndex, + }), + ) + + const liveQuery = createLiveQueryCollection({ + startSync: true, + query: (q) => + q + .from({ task: taskCollection }) + .where(({ task }) => inArray(task.id, [1])) + .innerJoin({ project: projectCollection }, ({ task, project }) => + eq(task.project_id, project.id), + ) + .select(({ task, project }) => ({ + task, + project, + })), + }) + + await liveQuery.preload() + expect(liveQuery.toArray).toHaveLength(1) + expect( + (liveQuery.toArray[0]!.task.dueDate as Temporal.PlainDate).toString(), + ).toBe(`2024-01-15`) + + taskCollection.update(1, (draft) => { + ;(draft as any).dueDate = Temporal.PlainDate.from(`2024-06-15`) + }) + await flushPromises() + + expect( + (liveQuery.toArray[0]!.task.dueDate as Temporal.PlainDate).toString(), + ).toBe(`2024-06-15`) + }) } describe(`Query JOIN Operations`, () => { From 962ed2f33c7158fe9a3ef710dc075d8b5a7a0438 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Mar 2026 12:58:00 -0600 Subject: [PATCH 2/4] fix(db, db-ivm): support Temporal objects in join hashing and normalization Temporal objects have no enumerable properties, so hashPlainObject() produced identical hashes for all Temporal values. This caused join index updates to be silently swallowed when a Temporal field changed. - Add Temporal-aware hashing via Symbol.toStringTag + toString() - Add Temporal normalization in normalizeValue() for join key matching - Add Temporal handling in ascComparator for correct sort ordering - Add null guard to exported isTemporal() - Convert temporalTypes from Array to Set for consistency Fixes #1367 Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-temporal-join-hashing.md | 6 ++++++ packages/db-ivm/src/hashing/hash.ts | 27 +++++++++++++++++++++++++ packages/db/src/utils.ts | 17 +++++++--------- packages/db/src/utils/comparison.ts | 14 +++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-temporal-join-hashing.md diff --git a/.changeset/fix-temporal-join-hashing.md b/.changeset/fix-temporal-join-hashing.md new file mode 100644 index 000000000..3b8e13b45 --- /dev/null +++ b/.changeset/fix-temporal-join-hashing.md @@ -0,0 +1,6 @@ +--- +'@tanstack/db': patch +'@tanstack/db-ivm': patch +--- + +Fix Temporal objects breaking live query updates when used with joins. Temporal objects (e.g. `Temporal.PlainDate`) have no enumerable properties, so the structural hash function produced identical hashes for all Temporal values, causing join index updates to be silently swallowed. Also add Temporal support to value normalization for join key matching and to the comparator for correct sort ordering. diff --git a/packages/db-ivm/src/hashing/hash.ts b/packages/db-ivm/src/hashing/hash.ts index d1b9d0a6a..68a76a5ce 100644 --- a/packages/db-ivm/src/hashing/hash.ts +++ b/packages/db-ivm/src/hashing/hash.ts @@ -18,6 +18,23 @@ const ARRAY_MARKER = randomHash() const MAP_MARKER = randomHash() const SET_MARKER = randomHash() const UINT8ARRAY_MARKER = randomHash() +const TEMPORAL_MARKER = randomHash() + +const temporalTypes = new Set([ + `Temporal.Duration`, + `Temporal.Instant`, + `Temporal.PlainDate`, + `Temporal.PlainDateTime`, + `Temporal.PlainMonthDay`, + `Temporal.PlainTime`, + `Temporal.PlainYearMonth`, + `Temporal.ZonedDateTime`, +]) + +function isTemporal(input: object): boolean { + const tag = (input as any)[Symbol.toStringTag] + return typeof tag === `string` && temporalTypes.has(tag) +} // Maximum byte length for Uint8Arrays to hash by content instead of reference // Arrays smaller than this will be hashed by content, allowing proper equality comparisons @@ -59,6 +76,8 @@ function hashObject(input: object): number { } else if (input instanceof File) { // Files are always hashed by reference due to their potentially large size return cachedReferenceHash(input) + } else if (isTemporal(input)) { + valueHash = hashTemporal(input) } else { let plainObjectInput = input let marker = OBJECT_MARKER @@ -103,6 +122,14 @@ function hashUint8Array(input: Uint8Array): number { return hasher.digest() } +function hashTemporal(input: object): number { + const hasher = new MurmurHashStream() + hasher.update(TEMPORAL_MARKER) + hasher.update((input as any)[Symbol.toStringTag]) + hasher.update((input as any).toString()) + return hasher.digest() +} + function hashPlainObject(input: object, marker: number): number { const hasher = new MurmurHashStream() diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index 00292e37a..ba347bd75 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -144,8 +144,8 @@ function deepEqualsInternal( // Handle Temporal objects // Check if both are Temporal objects of the same type if (isTemporal(a) && isTemporal(b)) { - const aTag = getStringTag(a) - const bTag = getStringTag(b) + const aTag = a[Symbol.toStringTag] + const bTag = b[Symbol.toStringTag] // If they're different Temporal types, they're not equal if (aTag !== bTag) return false @@ -211,7 +211,7 @@ function deepEqualsInternal( return false } -const temporalTypes = [ +const temporalTypes = new Set([ `Temporal.Duration`, `Temporal.Instant`, `Temporal.PlainDate`, @@ -220,16 +220,13 @@ const temporalTypes = [ `Temporal.PlainTime`, `Temporal.PlainYearMonth`, `Temporal.ZonedDateTime`, -] - -function getStringTag(a: any): any { - return a[Symbol.toStringTag] -} +]) /** Checks if the value is a Temporal object by checking for the Temporal brand */ export function isTemporal(a: any): boolean { - const tag = getStringTag(a) - return typeof tag === `string` && temporalTypes.includes(tag) + if (a == null || typeof a !== `object`) return false + const tag = a[Symbol.toStringTag] + return typeof tag === `string` && temporalTypes.has(tag) } export const DEFAULT_COMPARE_OPTIONS: CompareOptions = { diff --git a/packages/db/src/utils/comparison.ts b/packages/db/src/utils/comparison.ts index 650642b55..bf5ac1a91 100644 --- a/packages/db/src/utils/comparison.ts +++ b/packages/db/src/utils/comparison.ts @@ -1,3 +1,4 @@ +import { isTemporal } from '../utils' import type { CompareOptions } from '../query/builder/types' // WeakMap to store stable IDs for objects @@ -54,6 +55,15 @@ export const ascComparator = (a: any, b: any, opts: CompareOptions): number => { return a.getTime() - b.getTime() } + // If both are Temporal objects of the same type, compare by string representation + if (isTemporal(a) && isTemporal(b)) { + const aStr = a.toString() + const bStr = b.toString() + if (aStr < bStr) return -1 + if (aStr > bStr) return 1 + return 0 + } + // If at least one of the values is an object, use stable IDs for comparison const aIsObject = typeof a === `object` const bIsObject = typeof b === `object` @@ -154,6 +164,10 @@ export function normalizeValue(value: any): any { return value.getTime() } + if (isTemporal(value)) { + return `__temporal__${value[Symbol.toStringTag]}__${value.toString()}` + } + // Normalize Uint8Arrays/Buffers to a string representation for Map key usage // This enables content-based equality for binary data like ULIDs const isUint8Array = From bfea297e4f836720cb68e188bd5fc31b7e1956bc Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Mar 2026 14:20:48 -0600 Subject: [PATCH 3/4] fix: replace as-any casts with proper TemporalLike types Address review feedback from Sam Willis and CodeRabbit: - Add TemporalLike interface in db-ivm for type-safe Temporal detection - Make isTemporal a proper type guard (returns input is TemporalLike) - Remove as-any casts in hashTemporal - Add return type to createTemporalLike test helper - Remove unnecessary casts in join regression test Co-Authored-By: Claude Opus 4.6 --- packages/db-ivm/src/hashing/hash.ts | 15 ++++++++++----- packages/db-ivm/tests/utils.test.ts | 5 ++++- packages/db/src/utils.ts | 10 ++++++++-- packages/db/tests/query/join.test.ts | 12 ++++-------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/db-ivm/src/hashing/hash.ts b/packages/db-ivm/src/hashing/hash.ts index 68a76a5ce..813e4ed35 100644 --- a/packages/db-ivm/src/hashing/hash.ts +++ b/packages/db-ivm/src/hashing/hash.ts @@ -31,8 +31,13 @@ const temporalTypes = new Set([ `Temporal.ZonedDateTime`, ]) -function isTemporal(input: object): boolean { - const tag = (input as any)[Symbol.toStringTag] +interface TemporalLike { + [Symbol.toStringTag]: string + toString: () => string +} + +function isTemporal(input: object): input is TemporalLike { + const tag = (input as Record)[Symbol.toStringTag] return typeof tag === `string` && temporalTypes.has(tag) } @@ -122,11 +127,11 @@ function hashUint8Array(input: Uint8Array): number { return hasher.digest() } -function hashTemporal(input: object): number { +function hashTemporal(input: TemporalLike): number { const hasher = new MurmurHashStream() hasher.update(TEMPORAL_MARKER) - hasher.update((input as any)[Symbol.toStringTag]) - hasher.update((input as any).toString()) + hasher.update(input[Symbol.toStringTag]) + hasher.update(input.toString()) return hasher.digest() } diff --git a/packages/db-ivm/tests/utils.test.ts b/packages/db-ivm/tests/utils.test.ts index 6ec3b127f..223f5c615 100644 --- a/packages/db-ivm/tests/utils.test.ts +++ b/packages/db-ivm/tests/utils.test.ts @@ -4,7 +4,10 @@ import { hash } from '../src/hashing/index.js' // Minimal mock that mimics Temporal objects: Symbol.toStringTag + toString() // without requiring the temporal-polyfill dependency. -function createTemporalLike(tag: string, value: string) { +function createTemporalLike( + tag: string, + value: string, +): { toString: () => string; [Symbol.toStringTag]: string } { return Object.create(null, { [Symbol.toStringTag]: { value: tag }, toString: { value: () => value }, diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index ba347bd75..e65208741 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -222,10 +222,16 @@ const temporalTypes = new Set([ `Temporal.ZonedDateTime`, ]) +export interface TemporalLike { + [Symbol.toStringTag]: string + toString: () => string + equals?: (other: unknown) => boolean +} + /** Checks if the value is a Temporal object by checking for the Temporal brand */ -export function isTemporal(a: any): boolean { +export function isTemporal(a: unknown): a is TemporalLike { if (a == null || typeof a !== `object`) return false - const tag = a[Symbol.toStringTag] + const tag = (a as Record)[Symbol.toStringTag] return typeof tag === `string` && temporalTypes.has(tag) } diff --git a/packages/db/tests/query/join.test.ts b/packages/db/tests/query/join.test.ts index 07cc92a76..41be9605f 100644 --- a/packages/db/tests/query/join.test.ts +++ b/packages/db/tests/query/join.test.ts @@ -2086,18 +2086,14 @@ function createJoinTests(autoIndex: `off` | `eager`): void { await liveQuery.preload() expect(liveQuery.toArray).toHaveLength(1) - expect( - (liveQuery.toArray[0]!.task.dueDate as Temporal.PlainDate).toString(), - ).toBe(`2024-01-15`) + expect(String(liveQuery.toArray[0]!.task.dueDate)).toBe(`2024-01-15`) - taskCollection.update(1, (draft) => { - ;(draft as any).dueDate = Temporal.PlainDate.from(`2024-06-15`) + taskCollection.update(1, (draft: Task) => { + draft.dueDate = Temporal.PlainDate.from(`2024-06-15`) }) await flushPromises() - expect( - (liveQuery.toArray[0]!.task.dueDate as Temporal.PlainDate).toString(), - ).toBe(`2024-06-15`) + expect(String(liveQuery.toArray[0]!.task.dueDate)).toBe(`2024-06-15`) }) } From bcf3ef55d73c8ef8458e2326fa3aedb10f03f643 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 13 Mar 2026 14:37:16 -0600 Subject: [PATCH 4/4] fix: use real Temporal objects in hash tests instead of mocks Add temporal-polyfill as devDependency to db-ivm and replace createTemporalLike mocks with real Temporal.PlainDate, PlainTime, PlainDateTime, and Instant objects in hash tests. Co-Authored-By: Claude Opus 4.6 --- packages/db-ivm/package.json | 3 +- packages/db-ivm/tests/utils.test.ts | 47 ++++++++--------------------- pnpm-lock.yaml | 10 +++++- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/packages/db-ivm/package.json b/packages/db-ivm/package.json index b5e5d6833..ac529434e 100644 --- a/packages/db-ivm/package.json +++ b/packages/db-ivm/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@types/debug": "^4.1.12", - "@vitest/coverage-istanbul": "^3.2.4" + "@vitest/coverage-istanbul": "^3.2.4", + "temporal-polyfill": "^0.3.0" } } diff --git a/packages/db-ivm/tests/utils.test.ts b/packages/db-ivm/tests/utils.test.ts index 223f5c615..a3eb0685b 100644 --- a/packages/db-ivm/tests/utils.test.ts +++ b/packages/db-ivm/tests/utils.test.ts @@ -1,19 +1,8 @@ import { describe, expect, it } from 'vitest' +import { Temporal } from 'temporal-polyfill' import { DefaultMap } from '../src/utils.js' import { hash } from '../src/hashing/index.js' -// Minimal mock that mimics Temporal objects: Symbol.toStringTag + toString() -// without requiring the temporal-polyfill dependency. -function createTemporalLike( - tag: string, - value: string, -): { toString: () => string; [Symbol.toStringTag]: string } { - return Object.create(null, { - [Symbol.toStringTag]: { value: tag }, - toString: { value: () => value }, - }) -} - describe(`DefaultMap`, () => { it(`should return default value for missing keys`, () => { const map = new DefaultMap(() => 0) @@ -183,9 +172,9 @@ describe(`hash`, () => { }) it(`should hash Temporal objects by value`, () => { - const date1 = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) - const date2 = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) - const date3 = createTemporalLike(`Temporal.PlainDate`, `2024-06-15`) + const date1 = Temporal.PlainDate.from(`2024-01-15`) + const date2 = Temporal.PlainDate.from(`2024-01-15`) + const date3 = Temporal.PlainDate.from(`2024-06-15`) const hash1 = hash(date1) const hash2 = hash(date2) @@ -196,34 +185,22 @@ describe(`hash`, () => { expect(hash1).not.toBe(hash3) // Different Temporal dates should have different hash // Different Temporal types with overlapping string representations should differ - const plainDate = createTemporalLike(`Temporal.PlainDate`, `2024-01-15`) - const plainDateTime = createTemporalLike( - `Temporal.PlainDateTime`, - `2024-01-15T00:00:00`, - ) + const plainDate = Temporal.PlainDate.from(`2024-01-15`) + const plainDateTime = Temporal.PlainDateTime.from(`2024-01-15T00:00:00`) expect(hash(plainDate)).not.toBe(hash(plainDateTime)) // Other Temporal types should also hash correctly - const time1 = createTemporalLike(`Temporal.PlainTime`, `10:30:00`) - const time2 = createTemporalLike(`Temporal.PlainTime`, `10:30:00`) - const time3 = createTemporalLike(`Temporal.PlainTime`, `14:00:00`) + const time1 = Temporal.PlainTime.from(`10:30:00`) + const time2 = Temporal.PlainTime.from(`10:30:00`) + const time3 = Temporal.PlainTime.from(`14:00:00`) expect(hash(time1)).toBe(hash(time2)) expect(hash(time1)).not.toBe(hash(time3)) - const instant1 = createTemporalLike( - `Temporal.Instant`, - `2024-01-15T00:00:00Z`, - ) - const instant2 = createTemporalLike( - `Temporal.Instant`, - `2024-01-15T00:00:00Z`, - ) - const instant3 = createTemporalLike( - `Temporal.Instant`, - `2024-06-15T00:00:00Z`, - ) + const instant1 = Temporal.Instant.from(`2024-01-15T00:00:00Z`) + const instant2 = Temporal.Instant.from(`2024-01-15T00:00:00Z`) + const instant3 = Temporal.Instant.from(`2024-06-15T00:00:00Z`) expect(hash(instant1)).toBe(hash(instant2)) expect(hash(instant1)).not.toBe(hash(instant3)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e09a11881..64962845c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -667,7 +667,7 @@ importers: version: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8) drizzle-zod: specifier: ^0.8.3 - version: 0.8.3(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8))(zod@4.3.6) + version: 0.8.3(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8))(zod@3.25.76) express: specifier: ^5.2.1 version: 5.2.1 @@ -858,6 +858,9 @@ importers: '@vitest/coverage-istanbul': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) + temporal-polyfill: + specifier: ^0.3.0 + version: 0.3.0 packages/electric-db-collection: dependencies: @@ -16132,6 +16135,11 @@ snapshots: pg: 8.19.0 postgres: 3.4.8 + drizzle-zod@0.8.3(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8))(zod@3.25.76): + dependencies: + drizzle-orm: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8) + zod: 3.25.76 + drizzle-zod@0.8.3(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8))(zod@4.3.6): dependencies: drizzle-orm: 0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(gel@2.1.1)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8)