From b766d0f40ad2e8a58499615f0b2bd669661db518 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 19 Jul 2024 16:09:06 -0400 Subject: [PATCH] feat(NODE-6031): add `t` and `i` to Timestamp (#704) --- src/timestamp.ts | 22 +++++++++++++++++--- test/node/timestamp.test.ts | 40 ++++++++++++++++++++++++++++++++++++- test/types/bson.test-d.ts | 3 +++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/timestamp.ts b/src/timestamp.ts index 9d1e205ce..0cba6f693 100644 --- a/src/timestamp.ts +++ b/src/timestamp.ts @@ -28,6 +28,8 @@ export interface TimestampExtended { /** * @public * @category BSONType + * + * A special type for _internal_ MongoDB use and is **not** associated with the regular Date type. */ export class Timestamp extends LongWithoutOverridesClass { get _bsontype(): 'Timestamp' { @@ -36,6 +38,20 @@ export class Timestamp extends LongWithoutOverridesClass { static readonly MAX_VALUE = Long.MAX_UNSIGNED_VALUE; + /** + * An incrementing ordinal for operations within a given second. + */ + get i(): number { + return this.low >>> 0; + } + + /** + * A `time_t` value measuring seconds since the Unix epoch + */ + get t(): number { + return this.high >>> 0; + } + /** * @param int - A 64-bit bigint representing the Timestamp. */ @@ -127,7 +143,7 @@ export class Timestamp extends LongWithoutOverridesClass { /** @internal */ toExtendedJSON(): TimestampExtended { - return { $timestamp: { t: this.high >>> 0, i: this.low >>> 0 } }; + return { $timestamp: { t: this.t, i: this.i } }; } /** @internal */ @@ -144,8 +160,8 @@ export class Timestamp extends LongWithoutOverridesClass { inspect(depth?: number, options?: unknown, inspect?: InspectFn): string { inspect ??= defaultInspect; - const t = inspect(this.high >>> 0, options); - const i = inspect(this.low >>> 0, options); + const t = inspect(this.t, options); + const i = inspect(this.i, options); return `new Timestamp({ t: ${t}, i: ${i} })`; } } diff --git a/test/node/timestamp.test.ts b/test/node/timestamp.test.ts index 41ae7f4e2..410bb28fc 100644 --- a/test/node/timestamp.test.ts +++ b/test/node/timestamp.test.ts @@ -9,6 +9,37 @@ describe('Timestamp', () => { }); }); + describe('get i() and get t()', () => { + it('i returns lower bits', () => { + const l = new BSON.Long(1, 2); + const ts = new BSON.Timestamp(l); + expect(ts.i).to.equal(l.low); + }); + + it('t returns higher bits', () => { + const l = new BSON.Long(1, 2); + const ts = new BSON.Timestamp(l); + expect(ts.t).to.equal(l.high); + }); + + describe('when signed negative input is provided to the constructor', () => { + it('t and i return unsigned values', () => { + const l = new BSON.Long(-1, -2); + // Check the assumption that Long did NOT change the values to unsigned. + expect(l).to.have.property('low', -1); + expect(l).to.have.property('high', -2); + + const ts = new BSON.Timestamp(l); + expect(ts).to.have.property('i', 0xffffffff); // -1 unsigned + expect(ts).to.have.property('t', 0xfffffffe); // -2 unsigned + + // Timestamp is a subclass of Long, high and low do not change: + expect(ts).to.have.property('low', -1); + expect(ts).to.have.property('high', -2); + }); + }); + }); + it('should always be an unsigned value', () => { let bigIntInputs: Timestamp[] = []; if (!__noBigInt__) { @@ -23,7 +54,8 @@ describe('Timestamp', () => { new BSON.Timestamp({ t: 0xffff_ffff, i: 0xffff_ffff }), // @ts-expect-error We do not advertise support for Int32 in the constructor of Timestamp // We do expect it to work so that round tripping the Int32 instance inside a Timestamp works - new BSON.Timestamp({ t: new BSON.Int32(0x7fff_ffff), i: new BSON.Int32(0x7fff_ffff) }) + new BSON.Timestamp({ t: new BSON.Int32(0x7fff_ffff), i: new BSON.Int32(0x7fff_ffff) }), + new BSON.Timestamp(new BSON.Timestamp({ t: 0xffff_ffff, i: 0xffff_ffff })) ]; for (const timestamp of table) { @@ -69,6 +101,12 @@ describe('Timestamp', () => { expect(timestamp.toExtendedJSON()).to.deep.equal({ $timestamp: input }); }); + it('accepts timestamp object as input', () => { + const input = new BSON.Timestamp({ t: 89, i: 144 }); + const timestamp = new BSON.Timestamp(input); + expect(timestamp.toExtendedJSON()).to.deep.equal({ $timestamp: { t: input.t, i: input.i } }); + }); + it('accepts { t, i } object as input and coerce to integer', () => { const input = { t: new BSON.Int32(89), i: new BSON.Int32(144) }; // @ts-expect-error We do not advertise support for Int32 in the constructor of Timestamp diff --git a/test/types/bson.test-d.ts b/test/types/bson.test-d.ts index c0e46bac1..88e90aef9 100644 --- a/test/types/bson.test-d.ts +++ b/test/types/bson.test-d.ts @@ -82,3 +82,6 @@ expectType<(depth?: number | undefined, options?: unknown, inspect?: InspectFn | expectNotDeprecated(new ObjectId('foo')); expectDeprecated(new ObjectId(42)); expectNotDeprecated(new ObjectId(42 as string | number)); + +// Timestamp accepts timestamp because constructor allows: {i:number, t:number} +new Timestamp(new Timestamp(0n))