From 7916d9b3abd1fcb8a0fe6cc24c92b3bb62bb7110 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:22:09 +0200 Subject: [PATCH 1/9] refactor: plugin.diff.file|type -> plugin.detectChanges - simpler API - no redundant reporting of before and after closes https://github.com/opral/lix-sdk/issues/40 --- lix/packages/sdk/src/change-queue.test.ts | 75 +++--- lix/packages/sdk/src/commit.test.ts | 41 ++- .../sdk/src/discussion/discussion.test.ts | 36 +-- lix/packages/sdk/src/file-handlers.ts | 233 +++++++++--------- lix/packages/sdk/src/merge/merge.test.ts | 85 ++----- lix/packages/sdk/src/merge/merge.ts | 14 +- .../sdk/src/mock/mock-csv-plugin.test.ts | 45 ++-- lix/packages/sdk/src/mock/mock-csv-plugin.ts | 89 +++---- lix/packages/sdk/src/open/openLix.test.ts | 1 - lix/packages/sdk/src/plugin.test-d.ts | 44 ---- lix/packages/sdk/src/plugin.ts | 53 ++-- .../query-utilities/get-leaf-change.bench.ts | 10 +- .../resolve-conflict-by-selecting.test.ts | 3 - .../resolve-conflict-with-new-change.test.ts | 16 -- 14 files changed, 287 insertions(+), 458 deletions(-) delete mode 100644 lix/packages/sdk/src/plugin.test-d.ts diff --git a/lix/packages/sdk/src/change-queue.test.ts b/lix/packages/sdk/src/change-queue.test.ts index 6727f531af..413fadbb39 100644 --- a/lix/packages/sdk/src/change-queue.test.ts +++ b/lix/packages/sdk/src/change-queue.test.ts @@ -1,33 +1,30 @@ import { expect, test, vi } from "vitest"; import { openLixInMemory } from "./open/openLixInMemory.js"; import { newLixFile } from "./newLix.js"; -import type { DiffReport, LixPlugin } from "./plugin.js"; +import type { DetectedChange, LixPlugin } from "./plugin.js"; test("should use queue and settled correctly", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: async ({ before, after }) => { - const textBefore = before - ? new TextDecoder().decode(before?.data) - : undefined; - const textAfter = after - ? new TextDecoder().decode(after.data) - : undefined; - - if (textBefore === textAfter) { - return []; - } - return [ - { - type: "text", - entity_id: "test", - before: textBefore ? { text: textBefore } : undefined, - after: textAfter ? { text: textAfter } : undefined, - }, - ]; - }, + detectChanges: async ({ before, after }) => { + const textBefore = before + ? new TextDecoder().decode(before?.data) + : undefined; + const textAfter = after + ? new TextDecoder().decode(after.data) + : undefined; + + if (textBefore === textAfter) { + return []; + } + return [ + { + type: "text", + entity_id: "test", + snapshot: textAfter ? { text: textAfter } : undefined, + }, + ]; }, }; @@ -210,28 +207,22 @@ test("changes should contain the author", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn().mockResolvedValue([ - { - type: "mock", - entity_id: "mock", - before: undefined, - after: { - text: "value1", - }, + detectChanges: vi.fn().mockResolvedValue([ + { + type: "mock", + entity_id: "mock", + snapshot: { + text: "value1", }, - { - type: "mock", - entity_id: "mock", - before: { - text: "value1", - }, - after: { - text: "value2", - }, + }, + { + type: "mock", + entity_id: "mock", + snapshot: { + text: "value2", }, - ] satisfies DiffReport[]), - }, + }, + ] satisfies DetectedChange[]), }; const lix = await openLixInMemory({ blob: await newLixFile(), diff --git a/lix/packages/sdk/src/commit.test.ts b/lix/packages/sdk/src/commit.test.ts index 4a17b9d585..3f5ef34092 100644 --- a/lix/packages/sdk/src/commit.test.ts +++ b/lix/packages/sdk/src/commit.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ + import { expect, test } from "vitest"; import { openLixInMemory } from "./open/openLixInMemory.js"; import { newLixFile } from "./newLix.js"; @@ -9,27 +9,24 @@ test("should be able to add and commit changes", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: async ({ before, after }) => { - const textBefore = before - ? new TextDecoder().decode(before?.data) - : undefined; - const textAfter = after - ? new TextDecoder().decode(after.data) - : undefined; - - if (textBefore === textAfter) { - return []; - } - return [ - { - type: "text", - entity_id: "test", - before: textBefore ? { text: textBefore } : undefined, - after: textAfter ? { text: textAfter } : undefined, - }, - ]; - }, + detectChanges: async ({ before, after }) => { + const textBefore = before + ? new TextDecoder().decode(before?.data) + : undefined; + const textAfter = after + ? new TextDecoder().decode(after.data) + : undefined; + + if (textBefore === textAfter) { + return []; + } + return [ + { + type: "text", + entity_id: "test", + snapshot: textAfter ? { text: textAfter } : undefined, + }, + ]; }, }; diff --git a/lix/packages/sdk/src/discussion/discussion.test.ts b/lix/packages/sdk/src/discussion/discussion.test.ts index bf18f0b716..7377517df6 100644 --- a/lix/packages/sdk/src/discussion/discussion.test.ts +++ b/lix/packages/sdk/src/discussion/discussion.test.ts @@ -6,30 +6,16 @@ import type { LixPlugin } from "../plugin.js"; const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: async ({ before }) => { - return [ - !before - ? { - type: "text", - before: undefined, - entity_id: "test", - after: { - text: "inserted text", - }, - } - : { - type: "text", - entity_id: "test", - before: { - text: "inserted text", - }, - after: { - text: "updated text", - }, - }, - ]; - }, + detectChanges: async ({ after }) => { + return [ + { + type: "text", + entity_id: "test", + snapshot: { + text: after ? new TextDecoder().decode(after.data) : undefined, + }, + }, + ]; }, }; @@ -52,9 +38,7 @@ test("should be able to start a discussion on changes", async () => { const changes = await lix.db .selectFrom("change") - .innerJoin("snapshot", "snapshot.id", "change.snapshot_id") .selectAll("change") - .select("snapshot.value") .execute(); const discussion = await lix.createDiscussion({ diff --git a/lix/packages/sdk/src/file-handlers.ts b/lix/packages/sdk/src/file-handlers.ts index a1ff696cd1..1d9cc3a7c9 100644 --- a/lix/packages/sdk/src/file-handlers.ts +++ b/lix/packages/sdk/src/file-handlers.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { LixDatabaseSchema, LixFile } from "./database/schema.js"; -import type { DiffReport, LixPlugin } from "./plugin.js"; +import type { DetectedChange, LixPlugin } from "./plugin.js"; import { minimatch } from "minimatch"; import { Kysely } from "kysely"; @@ -19,54 +20,58 @@ export async function handleFileInsert(args: { currentAuthor?: string; queueEntry: any; }) { - const pluginDiffs: { - diffs: DiffReport[]; - pluginKey: string; - }[] = []; - // console.log({ args }); + const detectedChanges: Array = []; + for (const plugin of args.plugins) { // glob expressions are expressed relative without leading / but path has leading / if (!minimatch(normalizePath(args.after.path), "/" + plugin.glob)) { break; } - const diffs = await plugin.diff?.file?.({ + if (plugin.detectChanges === undefined) { + const error = new Error( + "Plugin does not support detecting changes even though the glob matches.", + ); + // https://linear.app/opral/issue/LIXDK-195/await-change-queue-to-log-errors + console.error(error); + throw error; + } + for (const change of await plugin.detectChanges({ before: undefined, after: args.after, - }); - - pluginDiffs.push({ diffs: diffs ?? [], pluginKey: plugin.key }); + })) { + detectedChanges.push({ + ...change, + pluginKey: plugin.key, + }); + } } await args.db.transaction().execute(async (trx) => { - for (const { diffs, pluginKey } of pluginDiffs) { - for (const diff of diffs ?? []) { - const value = diff.before ?? diff.after; - - const snapshot = await trx - .insertInto("snapshot") - .values({ - // @ts-expect-error - database expects stringified json - value: JSON.stringify(value), - }) - .returning("id") - .executeTakeFirstOrThrow(); - - await trx - .insertInto("change") - .values({ - type: diff.type, - file_id: args.after.id, - author: args.currentAuthor, - entity_id: diff.entity_id, - plugin_key: pluginKey, - snapshot_id: snapshot.id, - // @ts-expect-error - database expects stringified json - meta: JSON.stringify(diff.meta), - // add queueId interesting for debugging or knowning what changes were generated in same worker run - }) - .execute(); - } + for (const detectedChange of detectedChanges) { + const snapshot = await trx + .insertInto("snapshot") + .values({ + // TODO use empty snapshot id const https://github.com/opral/lix-sdk/issues/101 + id: detectedChange.snapshot ? undefined : "EMPTY_SNAPSHOT_ID", + // @ts-expect-error - database expects stringified json + value: JSON.stringify(detectedChange.snapshot), + }) + .onConflict((oc) => oc.doNothing()) + .returning("id") + .executeTakeFirstOrThrow(); + + await trx + .insertInto("change") + .values({ + type: detectedChange.type, + file_id: args.after.id, + author: args.currentAuthor, + entity_id: detectedChange.entity_id, + plugin_key: detectedChange.pluginKey, + snapshot_id: snapshot.id, + }) + .execute(); } // TODO: decide if TRIGGER or in js land with await trx.insertInto('file_internal').values({ id: args.fileId, blob: args.newBlob, path: args.newPath }).execute() @@ -87,97 +92,99 @@ export async function handleFileChange(args: { }) { const fileId = args.after?.id ?? args.before?.id; - const pluginDiffs: { - diffs: DiffReport[]; - pluginKey: string; - }[] = []; + const detectedChanges: Array = []; for (const plugin of args.plugins) { // glob expressions are expressed relative without leading / but path has leading / if (!minimatch(normalizePath(args.after.path), "/" + plugin.glob)) { break; } - - const diffs = await plugin.diff?.file?.({ - before: args.before, + if (plugin.detectChanges === undefined) { + const error = new Error( + "Plugin does not support detecting changes even though the glob matches.", + ); + // https://linear.app/opral/issue/LIXDK-195/await-change-queue-to-log-errors + console.error(error); + throw error; + } + for (const change of await plugin.detectChanges({ + before: undefined, after: args.after, - }); - - pluginDiffs.push({ - diffs: diffs ?? [], - pluginKey: plugin.key, - }); + })) { + detectedChanges.push({ + ...change, + pluginKey: plugin.key, + }); + } } await args.db.transaction().execute(async (trx) => { - for (const { diffs, pluginKey } of pluginDiffs) { - for (const diff of diffs ?? []) { - // assume an insert or update operation as the default - // if diff.neu is not present, it's a delete operationd - const value = diff.after ?? diff.before; - - // TODO: save hash of changed fles in every commit to discover inconsistent commits with blob? - - const previousChanges = await trx - .selectFrom("change") - .selectAll() - .where("file_id", "=", fileId) - .where("plugin_key", "=", pluginKey) - .where("type", "=", diff.type) - .where("entity_id", "=", diff.entity_id) - .execute(); - - // we need to finde the real leaf change as multiple changes can be set in the same created timestamp second or clockskew - let previousChange; // = previousChanges.at(-1); - for (let i = previousChanges.length - 1; i >= 0; i--) { - for (const change of previousChanges) { - if (change.parent_id === previousChanges[i]?.id) { - break; - } - } - previousChange = previousChanges[i]; - break; - } - - // working change exists but is identical to previously committed change - if (previousChange) { - const previousSnapshot = await trx - .selectFrom("snapshot") - .selectAll() - .where("id", "=", previousChange.snapshot_id) - .executeTakeFirstOrThrow(); - - if ( - // json stringify should be good enough if the plugin diff function is deterministic - JSON.stringify(previousSnapshot.value) === JSON.stringify(value) - ) { - // drop the change because it's identical - continue; + for (const detectedChange of detectedChanges) { + // TODO: save hash of changed fles in every commit to discover inconsistent commits with blob? + + const previousChanges = await trx + .selectFrom("change") + .selectAll() + .where("file_id", "=", fileId) + .where("plugin_key", "=", detectedChange.pluginKey) + .where("type", "=", detectedChange.type) + .where("entity_id", "=", detectedChange.entity_id) + .execute(); + + // we need to finde the real leaf change as multiple changes can be set in the same created timestamp second or clockskew + let previousChange; // = previousChanges.at(-1); + for (let i = previousChanges.length - 1; i >= 0; i--) { + for (const change of previousChanges) { + if (change.parent_id === previousChanges[i]?.id) { + break; } } + previousChange = previousChanges[i]; + break; + } - const snapshot = await trx - .insertInto("snapshot") - .values({ - // @ts-expect-error - database expects stringified json - value: JSON.stringify(value), - }) - .returning("id") + // working change exists but is identical to previously committed change + if (previousChange) { + const previousSnapshot = await trx + .selectFrom("snapshot") + .selectAll() + .where("id", "=", previousChange.snapshot_id) .executeTakeFirstOrThrow(); - await trx - .insertInto("change") - .values({ - type: diff.type, - file_id: fileId, - plugin_key: pluginKey, - entity_id: diff.entity_id, - author: args.currentAuthor, - parent_id: previousChange?.id, - snapshot_id: snapshot.id, - }) - .execute(); + if ( + // json stringify should be good enough if the plugin diff function is deterministic + JSON.stringify(previousSnapshot.value) === + JSON.stringify(detectedChange.snapshot) + ) { + // drop the change because it's identical + continue; + } } + + const snapshot = await trx + .insertInto("snapshot") + .values({ + // TODO use empty snapshot id const https://github.com/opral/lix-sdk/issues/101 + id: detectedChange.snapshot ? undefined : "EMPTY_SNAPSHOT_ID", + // @ts-expect-error - database expects stringified json + value: JSON.stringify(detectedChange.snapshot), + }) + .onConflict((oc) => oc.doNothing()) + .returning("id") + .executeTakeFirstOrThrow(); + + await trx + .insertInto("change") + .values({ + type: detectedChange.type, + file_id: fileId, + plugin_key: detectedChange.pluginKey, + entity_id: detectedChange.entity_id, + author: args.currentAuthor, + parent_id: previousChange?.id, + snapshot_id: snapshot.id, + }) + .execute(); } await trx diff --git a/lix/packages/sdk/src/merge/merge.test.ts b/lix/packages/sdk/src/merge/merge.test.ts index f9baae668b..371a3d3509 100644 --- a/lix/packages/sdk/src/merge/merge.test.ts +++ b/lix/packages/sdk/src/merge/merge.test.ts @@ -56,9 +56,7 @@ test("it should copy changes from the sourceLix into the targetLix that do not e const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -172,9 +170,7 @@ test("it should save change conflicts", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([ { change_id: mockChanges[1]!.id, @@ -278,9 +274,7 @@ test("diffing should not be invoked to prevent the generation of duplicate chang const mockPluginInSourceLix: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -288,9 +282,7 @@ test("diffing should not be invoked to prevent the generation of duplicate chang const mockPluginInTargetLix: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -336,21 +328,15 @@ test("diffing should not be invoked to prevent the generation of duplicate chang await merge({ sourceLix, targetLix }); - expect(mockPluginInSourceLix.diff.file).toHaveBeenCalledTimes(0); + expect(mockPluginInSourceLix.detectChanges).toHaveBeenCalledTimes(0); // once for the mock file insert - expect(mockPluginInTargetLix.diff.file).toHaveBeenCalledTimes(1); + expect(mockPluginInTargetLix.detectChanges).toHaveBeenCalledTimes(1); }); test("it should apply changes that are not conflicting", async () => { const mockSnapshots: NewSnapshot[] = [ - { - id: "sn1", - value: { color: "red" }, - }, - { - id: "sn2", - value: { color: "blue" }, - }, + { id: "sn1", value: { color: "red" } }, + { id: "sn2", value: { color: "blue" } }, ]; const mockChanges: NewChange[] = [ @@ -375,11 +361,6 @@ test("it should apply changes that are not conflicting", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", - glob: "*", - diff: { - file: vi.fn(), - }, - detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: async ({ changes }) => { const lastChange = changes[changes.length - 1]; const fileData = new TextEncoder().encode( @@ -491,9 +472,7 @@ test("subsequent merges should not lead to duplicate changes and/or conflicts", const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([ { change_id: commonChanges[0]!.id, @@ -600,9 +579,7 @@ test("it should naively copy changes from the sourceLix into the targetLix that const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: vi.fn(), - }, + detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -651,34 +628,20 @@ test("it should copy discussion and related comments and mappings", async () => const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: { - file: async ({ before: old }) => { - return [ - !old - ? { - type: "text", - before: undefined, - entity_id: "test", - after: { - text: "inserted text", - }, - } - : { - type: "text", - entity_id: "test", - before: { - text: "inserted text", - }, - after: { - text: "updated text", - }, - }, - ]; - }, - }, - detectConflicts: vi.fn().mockResolvedValue([]), - applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), + detectChanges: async ({ after }) => { + return [ + { + type: "text", + entity_id: "test", + snapshot: after + ? { text: new TextDecoder().decode(after?.data) } + : undefined, + }, + ]; + }, + applyChanges: async () => ({ fileData: new Uint8Array() }), }; + const lix1 = await openLixInMemory({ blob: await newLixFile(), providePlugins: [mockPlugin], @@ -690,7 +653,7 @@ test("it should copy discussion and related comments and mappings", async () => await lix1.db .insertInto("file") - .values({ id: "test", path: "test.txt", data: enc.encode("test") }) + .values({ id: "test", path: "test.txt", data: enc.encode("inserted text") }) .execute(); await lix1.settled(); diff --git a/lix/packages/sdk/src/merge/merge.ts b/lix/packages/sdk/src/merge/merge.ts index bdc9ac02f5..e2d188601f 100644 --- a/lix/packages/sdk/src/merge/merge.ts +++ b/lix/packages/sdk/src/merge/merge.ts @@ -41,14 +41,14 @@ export async function merge(args: { // TODO function assumes that all changes belong to the same file if (args.sourceLix.plugins.length !== 1) { throw new Error("Unimplemented. Only one plugin is supported for now"); - } else if (plugin.detectConflicts === undefined) { - throw new Error("Plugin does not support conflict detection"); } - const conflicts = await plugin.detectConflicts({ - sourceLix: args.sourceLix, - targetLix: args.targetLix, - leafChangesOnlyInSource, - }); + + const conflicts = + (await plugin.detectConflicts?.({ + sourceLix: args.sourceLix, + targetLix: args.targetLix, + leafChangesOnlyInSource, + })) ?? []; const changesPerFile: Record = {}; diff --git a/lix/packages/sdk/src/mock/mock-csv-plugin.test.ts b/lix/packages/sdk/src/mock/mock-csv-plugin.test.ts index 9e03772a50..3cc0afd7aa 100644 --- a/lix/packages/sdk/src/mock/mock-csv-plugin.test.ts +++ b/lix/packages/sdk/src/mock/mock-csv-plugin.test.ts @@ -1,5 +1,6 @@ import { newLixFile } from "../newLix.js"; import { openLixInMemory } from "../open/openLixInMemory.js"; +import type { DetectedChange } from "../plugin.js"; import { mockCsvPlugin } from "./mock-csv-plugin.js"; import { describe, expect, test } from "vitest"; @@ -77,15 +78,15 @@ describe("applyChanges()", () => { }); }); -describe("diff.file()", () => { +describe("detectChanges()", () => { test("it should report no changes for identical files", async () => { const before = new TextEncoder().encode("Name,Age\nAnna,20\nPeter,50"); const after = before; - const diffs = await mockCsvPlugin.diff.file?.({ + const changes = await mockCsvPlugin.detectChanges?.({ before: { id: "random", path: "x.csv", data: before, metadata: null }, after: { id: "random", path: "x.csv", data: after, metadata: null }, }); - expect(diffs).toEqual([]); + expect(changes).toEqual([]); }); test("it should report a create diff", async () => { @@ -93,58 +94,50 @@ describe("diff.file()", () => { const after = new TextEncoder().encode( "Name,Age\nAnna,20\nPeter,50\nJohn,30", ); - const diffs = await mockCsvPlugin.diff.file?.({ + const changes = await mockCsvPlugin.detectChanges?.({ before: { id: "random", path: "x.csv", data: before, metadata: null }, after: { id: "random", path: "x.csv", data: after, metadata: null }, }); - expect(diffs).toEqual([ + expect(changes).toEqual([ { + entity_id: "3-0", type: "cell", - before: undefined, - after: { rowIndex: 3, columnIndex: 0, text: "John" }, + snapshot: { rowIndex: 3, columnIndex: 0, text: "John" }, }, { + entity_id: "3-1", type: "cell", - before: undefined, - after: { rowIndex: 3, columnIndex: 1, text: "30" }, + snapshot: { rowIndex: 3, columnIndex: 1, text: "30" }, }, - ]); + ] satisfies DetectedChange[]); }); test("it should an update diff", async () => { const before = new TextEncoder().encode("Name,Age\nAnna,20\nPeter,50"); const after = new TextEncoder().encode("Name,Age\nAnna,21\nPeter,50"); - const diffs = await mockCsvPlugin.diff.file?.({ + const diffs = await mockCsvPlugin.detectChanges?.({ before: { id: "random", path: "x.csv", data: before, metadata: null }, after: { id: "random", path: "x.csv", data: after, metadata: null }, }); expect(diffs).toEqual([ { type: "cell", - before: { rowIndex: 1, columnIndex: 1, text: "20" }, - after: { rowIndex: 1, columnIndex: 1, text: "21" }, + entity_id: "1-1", + snapshot: { rowIndex: 1, columnIndex: 1, text: "21" }, }, - ]); + ] satisfies DetectedChange[]); }); test("it should report a delete diff", async () => { const before = new TextEncoder().encode("Name,Age\nAnna,20\nPeter,50"); const after = new TextEncoder().encode("Name,Age\nAnna,20"); - const diffs = await mockCsvPlugin.diff.file?.({ + const diffs = await mockCsvPlugin.detectChanges?.({ before: { id: "random", path: "x.csv", data: before, metadata: null }, after: { id: "random", path: "x.csv", data: after, metadata: null }, }); expect(diffs).toEqual([ - { - type: "cell", - before: { rowIndex: 2, columnIndex: 0, text: "Peter" }, - after: undefined, - }, - { - type: "cell", - before: { rowIndex: 2, columnIndex: 1, text: "50" }, - after: undefined, - }, - ]); + { entity_id: "2-0", type: "cell", snapshot: undefined }, + { entity_id: "2-1", type: "cell", snapshot: undefined }, + ] satisfies DetectedChange[]); }); }); diff --git a/lix/packages/sdk/src/mock/mock-csv-plugin.ts b/lix/packages/sdk/src/mock/mock-csv-plugin.ts index 81bda3ed6c..b48c9fd098 100644 --- a/lix/packages/sdk/src/mock/mock-csv-plugin.ts +++ b/lix/packages/sdk/src/mock/mock-csv-plugin.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { DiffReport, LixPlugin } from "../plugin.js"; +import type { DetectedChange, LixPlugin } from "../plugin.js"; import papaparse from "papaparse"; type Cell = { rowIndex: number; columnIndex: number; text: string }; @@ -7,9 +7,7 @@ type Cell = { rowIndex: number; columnIndex: number; text: string }; /** * A mock plugin that can be used for testing purposes. */ -export const mockCsvPlugin: LixPlugin<{ - cell: Cell; -}> = { +export const mockCsvPlugin: LixPlugin = { key: "csv", glob: "*.csv", applyChanges: async ({ file, changes, lix }) => { @@ -66,41 +64,36 @@ export const mockCsvPlugin: LixPlugin<{ fileData: new TextEncoder().encode(csv), }; }, - diff: { - file: async ({ before, after }) => { - const result: DiffReport[] = []; - const beforeParsed = before - ? papaparse.parse(new TextDecoder().decode(before.data)) - : undefined; - const afterParsed = after - ? papaparse.parse(new TextDecoder().decode(after.data)) - : undefined; + detectChanges: async ({ before, after }) => { + const result: DetectedChange[] = []; + const beforeParsed = before + ? papaparse.parse(new TextDecoder().decode(before.data)) + : undefined; + const afterParsed = after + ? papaparse.parse(new TextDecoder().decode(after.data)) + : undefined; - const numRows = Math.max( - beforeParsed?.data.length ?? 0, - afterParsed?.data.length ?? 0, - ); + const numRows = Math.max( + beforeParsed?.data.length ?? 0, + afterParsed?.data.length ?? 0, + ); - if (afterParsed) { - for (let i = 0; i < numRows; i++) { - const beforeRow = beforeParsed?.data[i] as string[]; - const afterRow = afterParsed.data[i] as string[]; - const numColumns = Math.max( - beforeRow?.length ?? 0, - afterRow?.length ?? 0, - ); - for (let j = 0; j < numColumns; j++) { - const beforeText = beforeRow?.[j]; - const afterText = afterRow?.[j]; - const diff = await mockCsvPlugin.diff.cell({ - before: beforeText - ? { - rowIndex: i, - columnIndex: j, - text: beforeText, - } - : undefined, - after: afterText + if (afterParsed) { + for (let i = 0; i < numRows; i++) { + const beforeRow = beforeParsed?.data[i] as string[]; + const afterRow = afterParsed.data[i] as string[]; + const numColumns = Math.max( + beforeRow?.length ?? 0, + afterRow?.length ?? 0, + ); + for (let j = 0; j < numColumns; j++) { + const beforeText = beforeRow?.[j]; + const afterText = afterRow?.[j]; + if (beforeText !== afterText) { + result.push({ + type: "cell", + entity_id: `${i}-${j}`, + snapshot: afterText ? { rowIndex: i, columnIndex: j, @@ -108,28 +101,10 @@ export const mockCsvPlugin: LixPlugin<{ } : undefined, }); - - if (diff.length > 0) { - result.push(...diff); - } } } } - return result; - }, - // @ts-expect-error type narrowing bug - cell: async ({ before, after }) => { - if (before?.text === after?.text) { - return []; - } else { - return [ - { - type: "cell", - before, - after, - }, - ]; - } - }, + } + return result; }, }; diff --git a/lix/packages/sdk/src/open/openLix.test.ts b/lix/packages/sdk/src/open/openLix.test.ts index e200e6214f..d68ac378c5 100644 --- a/lix/packages/sdk/src/open/openLix.test.ts +++ b/lix/packages/sdk/src/open/openLix.test.ts @@ -7,7 +7,6 @@ test("providing plugins should be possible", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", glob: "*", - diff: {}, }; const lix = await openLixInMemory({ blob: await newLixFile(), diff --git a/lix/packages/sdk/src/plugin.test-d.ts b/lix/packages/sdk/src/plugin.test-d.ts deleted file mode 100644 index 16d047e312..0000000000 --- a/lix/packages/sdk/src/plugin.test-d.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { LixPlugin } from "./plugin.js"; - -type Types = { - bundle: { id: string; name: string }; - message: { id: string; color: string }; - variant: { id: string; day: string }; -}; - -// ------------------- PLUGIN ------------------- - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const plugin: LixPlugin = { - key: "inlang-lix-plugin-v1", - glob: "*", - diff: { - file: () => { - throw new Error("Not implemented"); - }, - bundle: ({ before, after }) => { - // expect old and neu to be of type Types["bundle"] - // @ts-expect-error - TODO - before satisfies Types["bundle"]; - // @ts-expect-error - TODO - after satisfies Types["bundle"]; - throw new Error("Not implemented"); - }, - message: ({ before, after }) => { - // expect old and neu to be of type Types["message"] - // @ts-expect-error - TODO - before satisfies Types["message"]; - // @ts-expect-error - TODO - after satisfies Types["message"]; - throw new Error("Not implemented"); - }, - variant: ({ before, after }) => { - // expect old and neu to be of type Types["variant"] - // @ts-expect-error - TODO - before satisfies Types["variant"]; - // @ts-expect-error - TODO - after satisfies Types["variant"]; - throw new Error("Not implemented"); - }, - }, -}; diff --git a/lix/packages/sdk/src/plugin.ts b/lix/packages/sdk/src/plugin.ts index 4ae9c6a5e7..cd6d60056b 100644 --- a/lix/packages/sdk/src/plugin.ts +++ b/lix/packages/sdk/src/plugin.ts @@ -1,18 +1,16 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Change, ChangeWithSnapshot, LixFile, NewConflict, + Snapshot, } from "./database/schema.js"; import type { LixReadonly } from "./types.js"; // named lixplugin to avoid conflict with built-in plugin type -export type LixPlugin< - T extends Record> = Record, -> = { +export type LixPlugin = { key: string; - glob: string; + glob?: string; // TODO https://github.com/opral/lix-sdk/issues/37 // idea: // 1. runtime reflection for lix on the change schema @@ -22,6 +20,15 @@ export type LixPlugin< // message: Message, // variant: Variant, // }, + /** + * Detects changes between the `before` and `after` file update(s). + * + * The function is invoked by lix based on the plugin's `glob` pattern. + */ + detectChanges?: (args: { + before?: LixFile; + after?: LixFile; + }) => Promise>; /** * Detects changes from the source lix that conflict with changes in the target lix. */ @@ -50,38 +57,18 @@ export type LixPlugin< tryResolveConflict?: () => Promise< { success: true; change: Change } | { success: false } >; - // getting around bundling for the prototype - setup?: () => Promise; - diffComponent?: { - file?: () => HTMLElement; - } & Record< - // other primitives - keyof T, - (() => HTMLElement) | undefined - >; - diff: { - file?: (args: { - before?: LixFile; - after?: LixFile; - }) => MaybePromise>; - } & Record< - // other primitives - keyof T, - (args: { - before?: T[keyof T]; - after?: T[keyof T]; - }) => MaybePromise> - >; }; -type MaybePromise = T | Promise; - /** - * A diff report is a report if a change has been made. + * A detected change that lix ingests in to the database. + * + * If the snapshot is `undefined`, the change is considered to be a deletion. */ -export type DiffReport = { +export type DetectedChange = { type: string; entity_id: string; - before?: Record; - after?: Record; + /** + * The change is considered a deletion if `snapshot` is `undefined`. + */ + snapshot?: Snapshot["value"]; }; diff --git a/lix/packages/sdk/src/query-utilities/get-leaf-change.bench.ts b/lix/packages/sdk/src/query-utilities/get-leaf-change.bench.ts index 265ea9c4da..032e91c0d0 100644 --- a/lix/packages/sdk/src/query-utilities/get-leaf-change.bench.ts +++ b/lix/packages/sdk/src/query-utilities/get-leaf-change.bench.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { v4 } from "uuid"; -import { afterEach, beforeEach, bench, describe, expect } from "vitest"; +import { bench, describe } from "vitest"; import { getLeafChange, newLixFile, @@ -7,7 +8,6 @@ import { type Change, type NewChange, type NewSnapshot, - type Snapshot, } from "../index.js"; const createChange = ( @@ -15,7 +15,6 @@ const createChange = ( payload: any, parentChangeId: string | null, ): { change: NewChange; snapshot: NewSnapshot } => { - const changeId = v4(); const entityId = payload[type].id; const snapshotId = v4(); const snapshot: NewSnapshot = { @@ -31,7 +30,6 @@ const createChange = ( snapshot_id: snapshotId, entity_id: entityId, commit_id: undefined, - meta: undefined, created_at: "", }; return { @@ -87,8 +85,6 @@ const setupLix = async (nMessages: number) => { const batchSize = 256; // Insert changes in batches for (let i = 0; i < mockChanges.length; i += batchSize) { - const batch = mockChanges.slice(i, i + batchSize); - // Extract the changes const changesArray = mockChanges .slice(i, i + batchSize) @@ -119,7 +115,7 @@ for (let i = 0; i < 5; i++) { nMessages * (1 + 6 + 100) + " changes", async () => { - let project = await setupLix(nMessages); + const project = await setupLix(nMessages); bench("getLeafChange", async () => { await getLeafChange({ lix: project.lix, diff --git a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts index f6be65d3cb..c37502f68e 100644 --- a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts +++ b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts @@ -47,9 +47,6 @@ test("it should resolve a conflict by applying the change and marking the confli JSON.stringify(mockSnapshots[0]?.value), ), }), - diff: { - file: vi.fn(), - }, }; const lix = await openLixInMemory({ diff --git a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-with-new-change.test.ts b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-with-new-change.test.ts index e8f7988519..c1f36d65be 100644 --- a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-with-new-change.test.ts +++ b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-with-new-change.test.ts @@ -45,15 +45,11 @@ test("it should throw if the to be resolved with change already exists", async ( const mockPlugin: LixPlugin = { key: "plugin1", - glob: "*", applyChanges: vi.fn().mockResolvedValue({ fileData: new TextEncoder().encode( JSON.stringify(mockSnapshots[0]?.value), ), }), - diff: { - file: vi.fn(), - }, }; const lix = await openLixInMemory({ @@ -137,15 +133,11 @@ test("resolving a conflict should throw if the to be resolved with change is not const mockPlugin: LixPlugin = { key: "plugin1", - glob: "*", applyChanges: vi.fn().mockResolvedValue({ fileData: new TextEncoder().encode( JSON.stringify(mockSnapshots[0]?.value), ), }), - diff: { - file: vi.fn(), - }, }; const lix = await openLixInMemory({ @@ -234,15 +226,11 @@ test("resolving a conflict should throw if the change to resolve with does not b ]; const mockPlugin: LixPlugin = { key: "plugin1", - glob: "*", applyChanges: vi.fn().mockResolvedValue({ fileData: new TextEncoder().encode( JSON.stringify(mockSnapshots[0]?.value), ), }), - diff: { - file: vi.fn(), - }, }; const lix = await openLixInMemory({ @@ -324,13 +312,9 @@ test("resolving a conflict with a new change should insert the change and mark t const mockPlugin: LixPlugin = { key: "plugin1", - glob: "*", applyChanges: vi.fn().mockResolvedValue({ fileData: new TextEncoder().encode('{"id":"value3"}'), }), - diff: { - file: vi.fn(), - }, }; const lix = await openLixInMemory({ From f956969e225b2a0d2bac07c02aa2c2f4d6abbfbf Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:27:54 +0200 Subject: [PATCH 2/9] fix: remove detect changes globs --- lix/packages/sdk/src/merge/merge.test.ts | 13 ++----------- .../resolve-conflict-by-selecting.test.ts | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lix/packages/sdk/src/merge/merge.test.ts b/lix/packages/sdk/src/merge/merge.test.ts index 371a3d3509..2025e8289a 100644 --- a/lix/packages/sdk/src/merge/merge.test.ts +++ b/lix/packages/sdk/src/merge/merge.test.ts @@ -55,8 +55,6 @@ test("it should copy changes from the sourceLix into the targetLix that do not e const mockPlugin: LixPlugin = { key: "mock-plugin", - glob: "*", - detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -169,8 +167,6 @@ test("it should save change conflicts", async () => { const mockPlugin: LixPlugin = { key: "mock-plugin", - glob: "*", - detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([ { change_id: mockChanges[1]!.id, @@ -273,8 +269,7 @@ test("diffing should not be invoked to prevent the generation of duplicate chang const mockPluginInSourceLix: LixPlugin = { key: "mock-plugin", - glob: "*", - detectChanges: vi.fn(), + detectChanges: vi.fn().mockRejectedValue([]), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -282,7 +277,7 @@ test("diffing should not be invoked to prevent the generation of duplicate chang const mockPluginInTargetLix: LixPlugin = { key: "mock-plugin", glob: "*", - detectChanges: vi.fn(), + detectChanges: vi.fn().mockResolvedValue([]), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; @@ -471,8 +466,6 @@ test("subsequent merges should not lead to duplicate changes and/or conflicts", const mockPlugin: LixPlugin = { key: "mock-plugin", - glob: "*", - detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([ { change_id: commonChanges[0]!.id, @@ -578,8 +571,6 @@ test("it should naively copy changes from the sourceLix into the targetLix that const mockPlugin: LixPlugin = { key: "mock-plugin", - glob: "*", - detectChanges: vi.fn(), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; diff --git a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts index c37502f68e..54365783db 100644 --- a/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts +++ b/lix/packages/sdk/src/resolve-conflict/resolve-conflict-by-selecting.test.ts @@ -41,7 +41,6 @@ test("it should resolve a conflict by applying the change and marking the confli const mockPlugin: LixPlugin = { key: "plugin1", - glob: "*", applyChanges: vi.fn().mockResolvedValue({ fileData: new TextEncoder().encode( JSON.stringify(mockSnapshots[0]?.value), From 1a51e34092eaaaa3d5dd9a011dc9c9d24f6acd79 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:29:44 +0200 Subject: [PATCH 3/9] fix: resolved not rejected value --- lix/packages/sdk/src/merge/merge.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lix/packages/sdk/src/merge/merge.test.ts b/lix/packages/sdk/src/merge/merge.test.ts index 2025e8289a..dc8606c645 100644 --- a/lix/packages/sdk/src/merge/merge.test.ts +++ b/lix/packages/sdk/src/merge/merge.test.ts @@ -269,7 +269,7 @@ test("diffing should not be invoked to prevent the generation of duplicate chang const mockPluginInSourceLix: LixPlugin = { key: "mock-plugin", - detectChanges: vi.fn().mockRejectedValue([]), + detectChanges: vi.fn().mockResolvedValue([]), detectConflicts: vi.fn().mockResolvedValue([]), applyChanges: vi.fn().mockResolvedValue({ fileData: new Uint8Array() }), }; From 3beef3d874b43d2524a77e7248ffcecdd875172c Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:05:24 +0200 Subject: [PATCH 4/9] Update lix/packages/sdk/src/file-handlers.ts Co-authored-by: martin-lysk <113943358+martin-lysk@users.noreply.github.com> --- lix/packages/sdk/src/file-handlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lix/packages/sdk/src/file-handlers.ts b/lix/packages/sdk/src/file-handlers.ts index 1d9cc3a7c9..302cb0f084 100644 --- a/lix/packages/sdk/src/file-handlers.ts +++ b/lix/packages/sdk/src/file-handlers.ts @@ -108,7 +108,7 @@ export async function handleFileChange(args: { throw error; } for (const change of await plugin.detectChanges({ - before: undefined, + before: args.before, after: args.after, })) { detectedChanges.push({ From f7f4ebd012a6d96a15120cfc455f92bd843c1765 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:20:26 +0200 Subject: [PATCH 5/9] better ts errors --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 52b2674b8f..6a2f6de6f1 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "esbenp.prettier-vscode", "inlang.vs-code-extension", "vitest.explorer", - "aaron-bond.better-comments" + "aaron-bond.better-comments", + "yoavbls.pretty-ts-errors" ] } From 59431ea90a5ab027deea1bb2b165928728a355e3 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:34:25 +0200 Subject: [PATCH 6/9] fix: use simulated current branch --- lix/packages/sdk/src/query-utilities/get-leaf-change.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lix/packages/sdk/src/query-utilities/get-leaf-change.ts b/lix/packages/sdk/src/query-utilities/get-leaf-change.ts index ee9c8526f5..a1aaacceb4 100644 --- a/lix/packages/sdk/src/query-utilities/get-leaf-change.ts +++ b/lix/packages/sdk/src/query-utilities/get-leaf-change.ts @@ -1,6 +1,6 @@ -import { sql } from "kysely"; import type { Change } from "../database/schema.js"; import type { LixReadonly } from "../types.js"; +import { isInSimulatedCurrentBranch } from "./is-in-simulated-branch.js"; /** * Find the last "child" change of the given change. @@ -17,6 +17,7 @@ export async function getLeafChange(args: { const childChange = await args.lix.db .selectFrom("change") .selectAll() + .where(isInSimulatedCurrentBranch) .where("parent_id", "=", nextChange.id) .executeTakeFirst(); From 93dd4a33956b60b3f9cc38cabcfb245e4cdf1631 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:50:23 +0200 Subject: [PATCH 7/9] fix: get leaf change --- lix/packages/sdk/src/file-handlers.ts | 55 +++++++++------------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/lix/packages/sdk/src/file-handlers.ts b/lix/packages/sdk/src/file-handlers.ts index 302cb0f084..eb2c667ef1 100644 --- a/lix/packages/sdk/src/file-handlers.ts +++ b/lix/packages/sdk/src/file-handlers.ts @@ -3,6 +3,8 @@ import type { LixDatabaseSchema, LixFile } from "./database/schema.js"; import type { DetectedChange, LixPlugin } from "./plugin.js"; import { minimatch } from "minimatch"; import { Kysely } from "kysely"; +import { getLeafChange } from "./query-utilities/get-leaf-change.js"; +import { isInSimulatedCurrentBranch } from "./query-utilities/is-in-simulated-branch.js"; // start a new normalize path function that has the absolute minimum implementation. function normalizePath(path: string) { @@ -120,46 +122,27 @@ export async function handleFileChange(args: { await args.db.transaction().execute(async (trx) => { for (const detectedChange of detectedChanges) { - // TODO: save hash of changed fles in every commit to discover inconsistent commits with blob? - - const previousChanges = await trx + // heuristic to find the previous change + // there is no guarantee that the previous change is the leaf change + // because sorting by time is unreliable in a distributed system + const previousChange = await trx .selectFrom("change") .selectAll() .where("file_id", "=", fileId) - .where("plugin_key", "=", detectedChange.pluginKey) .where("type", "=", detectedChange.type) .where("entity_id", "=", detectedChange.entity_id) - .execute(); - - // we need to finde the real leaf change as multiple changes can be set in the same created timestamp second or clockskew - let previousChange; // = previousChanges.at(-1); - for (let i = previousChanges.length - 1; i >= 0; i--) { - for (const change of previousChanges) { - if (change.parent_id === previousChanges[i]?.id) { - break; - } - } - previousChange = previousChanges[i]; - break; - } - - // working change exists but is identical to previously committed change - if (previousChange) { - const previousSnapshot = await trx - .selectFrom("snapshot") - .selectAll() - .where("id", "=", previousChange.snapshot_id) - .executeTakeFirstOrThrow(); - - if ( - // json stringify should be good enough if the plugin diff function is deterministic - JSON.stringify(previousSnapshot.value) === - JSON.stringify(detectedChange.snapshot) - ) { - // drop the change because it's identical - continue; - } - } + .where(isInSimulatedCurrentBranch) + // walk from the end of the tree to minimize the amount of changes to walk + .orderBy("id", "desc") + .executeTakeFirst(); + + // get the leaf change of the assumed previous change + const leafChange = !previousChange + ? undefined + : await getLeafChange({ + lix: { db: trx }, + change: previousChange, + }); const snapshot = await trx .insertInto("snapshot") @@ -181,7 +164,7 @@ export async function handleFileChange(args: { plugin_key: detectedChange.pluginKey, entity_id: detectedChange.entity_id, author: args.currentAuthor, - parent_id: previousChange?.id, + parent_id: leafChange?.id, snapshot_id: snapshot.id, }) .execute(); From 3ca8aa0d385a04667c4238963e84af4d90fc8e85 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:51:38 +0200 Subject: [PATCH 8/9] improve: minimize api surface --- lix/packages/sdk/src/query-utilities/get-leaf-change.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lix/packages/sdk/src/query-utilities/get-leaf-change.ts b/lix/packages/sdk/src/query-utilities/get-leaf-change.ts index a1aaacceb4..cf786ea2b8 100644 --- a/lix/packages/sdk/src/query-utilities/get-leaf-change.ts +++ b/lix/packages/sdk/src/query-utilities/get-leaf-change.ts @@ -7,7 +7,7 @@ import { isInSimulatedCurrentBranch } from "./is-in-simulated-branch.js"; */ export async function getLeafChange(args: { change: Change; - lix: LixReadonly; + lix: Pick; }): Promise { const _true = true; From f375f422b9c58ef4b0775b439d07e51e0f7b69e1 Mon Sep 17 00:00:00 2001 From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:56:14 +0200 Subject: [PATCH 9/9] fix: let database handle id generation --- lix/packages/sdk/src/file-handlers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lix/packages/sdk/src/file-handlers.ts b/lix/packages/sdk/src/file-handlers.ts index eb2c667ef1..c026cc60e5 100644 --- a/lix/packages/sdk/src/file-handlers.ts +++ b/lix/packages/sdk/src/file-handlers.ts @@ -54,10 +54,10 @@ export async function handleFileInsert(args: { const snapshot = await trx .insertInto("snapshot") .values({ - // TODO use empty snapshot id const https://github.com/opral/lix-sdk/issues/101 - id: detectedChange.snapshot ? undefined : "EMPTY_SNAPSHOT_ID", - // @ts-expect-error - database expects stringified json - value: JSON.stringify(detectedChange.snapshot), + // @ts-expect-error- database expects stringified json + value: detectedChange.snapshot + ? JSON.stringify(detectedChange.snapshot) + : null, }) .onConflict((oc) => oc.doNothing()) .returning("id") @@ -147,10 +147,10 @@ export async function handleFileChange(args: { const snapshot = await trx .insertInto("snapshot") .values({ - // TODO use empty snapshot id const https://github.com/opral/lix-sdk/issues/101 - id: detectedChange.snapshot ? undefined : "EMPTY_SNAPSHOT_ID", - // @ts-expect-error - database expects stringified json - value: JSON.stringify(detectedChange.snapshot), + // @ts-expect-error- database expects stringified json + value: detectedChange.snapshot + ? JSON.stringify(detectedChange.snapshot) + : null, }) .onConflict((oc) => oc.doNothing()) .returning("id")