From 7fffd680123cb5166659a0d5616ca51f16e75ea8 Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Fri, 13 Sep 2024 11:28:21 +0100 Subject: [PATCH 1/3] fix(core): drafts not using server actions generate a created at always --- packages/@sanity/mutator/src/document/Mutation.ts | 12 ++++++++++-- .../@sanity/mutator/src/document/SquashingBuffer.ts | 4 +++- packages/@sanity/mutator/src/document/types.ts | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/@sanity/mutator/src/document/Mutation.ts b/packages/@sanity/mutator/src/document/Mutation.ts index 0d04d187750..759a2afb992 100644 --- a/packages/@sanity/mutator/src/document/Mutation.ts +++ b/packages/@sanity/mutator/src/document/Mutation.ts @@ -1,3 +1,5 @@ +import {isString} from 'lodash' + import {Patcher} from '../patch' import {debug} from './debug' import {luid} from './luid' @@ -122,12 +124,18 @@ export class Mutation { return } + // creation requires a _createdAt + const getGuaranteedCreatedAt = (doc: Doc): string => + doc && isString(doc._createdAt) + ? doc._createdAt + : this.params.timestamp || new Date().toISOString() + if (mutation.createIfNotExists) { const createIfNotExists = mutation.createIfNotExists || {} operations.push((doc) => doc === null ? Object.assign(createIfNotExists, { - _createdAt: createIfNotExists._createdAt || this.params.timestamp, + _createdAt: getGuaranteedCreatedAt(createIfNotExists), }) : doc, ) @@ -138,7 +146,7 @@ export class Mutation { const createOrReplace = mutation.createOrReplace || {} operations.push(() => Object.assign(createOrReplace, { - _createdAt: createOrReplace._createdAt || this.params.timestamp, + _createdAt: getGuaranteedCreatedAt(createOrReplace), }), ) return diff --git a/packages/@sanity/mutator/src/document/SquashingBuffer.ts b/packages/@sanity/mutator/src/document/SquashingBuffer.ts index d495e91f101..91e1e1defca 100644 --- a/packages/@sanity/mutator/src/document/SquashingBuffer.ts +++ b/packages/@sanity/mutator/src/document/SquashingBuffer.ts @@ -239,7 +239,9 @@ export class SquashingBuffer { nextOps.push(...this.staged) if (nextOps.length > 0) { - this.PRESTAGE = new Mutation({mutations: nextOps}).apply(this.PRESTAGE) as Doc + this.PRESTAGE = new Mutation({mutations: nextOps, timestamp: new Date().toISOString()}).apply( + this.PRESTAGE, + ) as Doc this.staged = [] this.setOperations = {} } diff --git a/packages/@sanity/mutator/src/document/types.ts b/packages/@sanity/mutator/src/document/types.ts index acd147e4b7d..215cf2c67d2 100644 --- a/packages/@sanity/mutator/src/document/types.ts +++ b/packages/@sanity/mutator/src/document/types.ts @@ -16,6 +16,7 @@ export interface Doc { _type: string _rev?: string _updatedAt?: string + _createdAt?: string [attribute: string]: unknown } From ee1114e1dbcea1622df204d449ce1950e681fab2 Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Fri, 13 Sep 2024 11:59:35 +0100 Subject: [PATCH 2/3] test(core): resolving failing tests due to no mocked global date in SquashingBuffer --- .../@sanity/mutator/src/document/Mutation.ts | 18 +++++++++--------- .../mutator/src/document/SquashingBuffer.ts | 4 +--- .../mutator/test/SquashingBuffer.test.ts | 13 +++++++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/@sanity/mutator/src/document/Mutation.ts b/packages/@sanity/mutator/src/document/Mutation.ts index 759a2afb992..61a23a4721b 100644 --- a/packages/@sanity/mutator/src/document/Mutation.ts +++ b/packages/@sanity/mutator/src/document/Mutation.ts @@ -108,28 +108,28 @@ export class Mutation { compile(): void { const operations: ((doc: Doc | null) => Doc | null)[] = [] + // creation requires a _createdAt + const getGuaranteedCreatedAt = (doc: Doc): string => + doc && isString(doc._createdAt) + ? doc._createdAt + : this.params.timestamp || new Date().toISOString() + this.mutations.forEach((mutation) => { if (mutation.create) { // TODO: Fail entire patch if document did exist - const create = mutation.create || {} + const create = (mutation.create || {}) as Doc operations.push((doc): Doc => { if (doc) { return doc } - return Object.assign(create as Doc, { - _createdAt: create._createdAt || this.params.timestamp, + return Object.assign(create, { + _createdAt: getGuaranteedCreatedAt(create), }) }) return } - // creation requires a _createdAt - const getGuaranteedCreatedAt = (doc: Doc): string => - doc && isString(doc._createdAt) - ? doc._createdAt - : this.params.timestamp || new Date().toISOString() - if (mutation.createIfNotExists) { const createIfNotExists = mutation.createIfNotExists || {} operations.push((doc) => diff --git a/packages/@sanity/mutator/src/document/SquashingBuffer.ts b/packages/@sanity/mutator/src/document/SquashingBuffer.ts index 91e1e1defca..d495e91f101 100644 --- a/packages/@sanity/mutator/src/document/SquashingBuffer.ts +++ b/packages/@sanity/mutator/src/document/SquashingBuffer.ts @@ -239,9 +239,7 @@ export class SquashingBuffer { nextOps.push(...this.staged) if (nextOps.length > 0) { - this.PRESTAGE = new Mutation({mutations: nextOps, timestamp: new Date().toISOString()}).apply( - this.PRESTAGE, - ) as Doc + this.PRESTAGE = new Mutation({mutations: nextOps}).apply(this.PRESTAGE) as Doc this.staged = [] this.setOperations = {} } diff --git a/packages/@sanity/mutator/test/SquashingBuffer.test.ts b/packages/@sanity/mutator/test/SquashingBuffer.test.ts index ab5ee308a59..4ae821d92a3 100644 --- a/packages/@sanity/mutator/test/SquashingBuffer.test.ts +++ b/packages/@sanity/mutator/test/SquashingBuffer.test.ts @@ -1,4 +1,4 @@ -import {expect, test} from '@jest/globals' +import {expect, jest, test} from '@jest/globals' import {type PatchMutationOperation} from '@sanity/types' import {Mutation} from '../src/document/Mutation' @@ -110,6 +110,9 @@ test('de-duplicate createIfNotExists', () => { }) test('de-duplicate create respects deletes', () => { + const globalMockDate = new Date('2020-01-01T12:34:55.000Z') + const globalDateSpy = jest.spyOn(global, 'Date').mockReturnValue(globalMockDate) + const initial = {_id: '1', _type: 'test', a: 'A string value', c: 'Some value'} const sb = new SquashingBuffer(initial) add(sb, {createIfNotExists: {_id: '1', _type: 'test', a: 'A string value', c: 'Some value'}}) @@ -124,7 +127,7 @@ test('de-duplicate create respects deletes', () => { if (!tx) { throw new Error('buffer purge did not result in a mutation') } - tx.params.timestamp = '2021-01-01T12:34:55Z' + tx.params.timestamp = '2021-01-01T12:34:55.000Z' const creates = tx.mutations.filter((mut) => !!mut.createIfNotExists) expect(creates.length).toBe(2) // Only a single create mutation expected (note: bn - is this correct?) @@ -134,14 +137,16 @@ test('de-duplicate create respects deletes', () => { expect(final).toEqual({ _id: '1', _type: 'test', - _createdAt: '2021-01-01T12:34:55Z', - _updatedAt: '2021-01-01T12:34:55Z', + _createdAt: '2020-01-01T12:34:55.000Z', + _updatedAt: '2021-01-01T12:34:55.000Z', _rev: 'txn_id', a: { b: 'A wrapped value', }, c: 'Changed', }) + + globalDateSpy.mockRestore() }) test('de-duplicate create respects rebasing', () => { From 4d06ccdcc778ee21acd10c82d4b5fbe9b155e500 Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Sun, 15 Sep 2024 23:45:03 +0200 Subject: [PATCH 3/3] refactor(core): simplifiying guarantee on createdAt date; testing around missing timestamp --- .../@sanity/mutator/src/document/Mutation.ts | 6 +--- .../mutator/test/SquashingBuffer.test.ts | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/@sanity/mutator/src/document/Mutation.ts b/packages/@sanity/mutator/src/document/Mutation.ts index 61a23a4721b..ad145532652 100644 --- a/packages/@sanity/mutator/src/document/Mutation.ts +++ b/packages/@sanity/mutator/src/document/Mutation.ts @@ -1,5 +1,3 @@ -import {isString} from 'lodash' - import {Patcher} from '../patch' import {debug} from './debug' import {luid} from './luid' @@ -110,9 +108,7 @@ export class Mutation { // creation requires a _createdAt const getGuaranteedCreatedAt = (doc: Doc): string => - doc && isString(doc._createdAt) - ? doc._createdAt - : this.params.timestamp || new Date().toISOString() + doc?._createdAt || this.params.timestamp || new Date().toISOString() this.mutations.forEach((mutation) => { if (mutation.create) { diff --git a/packages/@sanity/mutator/test/SquashingBuffer.test.ts b/packages/@sanity/mutator/test/SquashingBuffer.test.ts index 4ae821d92a3..6544bddf769 100644 --- a/packages/@sanity/mutator/test/SquashingBuffer.test.ts +++ b/packages/@sanity/mutator/test/SquashingBuffer.test.ts @@ -109,6 +109,35 @@ test('de-duplicate createIfNotExists', () => { expect(tx2 && tx2.mutations.length).toBe(1) }) +test.each(['create', 'createIfNotExists', 'createOrReplace'])( + '%s defaults to current created at time', + (createFnc) => { + const globalMockDate = new Date('2020-01-01T12:34:55.000Z') + const globalDateSpy = jest.spyOn(global, 'Date').mockReturnValue(globalMockDate) + + const sb = new SquashingBuffer(null) + + add(sb, {[createFnc]: {_id: '1', _type: 'test', a: 'A string value'}}) + + const tx = sb.purge('txn_id') + if (!tx) { + throw new Error('buffer purge did not result in a mutation') + } + + const final = tx.apply(null) + + expect(final).toEqual({ + _id: '1', + _rev: 'txn_id', + _createdAt: '2020-01-01T12:34:55.000Z', + _type: 'test', + a: 'A string value', + }) + + globalDateSpy.mockRestore() + }, +) + test('de-duplicate create respects deletes', () => { const globalMockDate = new Date('2020-01-01T12:34:55.000Z') const globalDateSpy = jest.spyOn(global, 'Date').mockReturnValue(globalMockDate)