From 6932399b1d6f92af8e308847c242adce82fad323 Mon Sep 17 00:00:00 2001 From: jgoux Date: Wed, 13 Mar 2024 11:39:36 +0100 Subject: [PATCH 1/5] feat(core): add path connection strategy --- packages/seed/src/core/plan/plan.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/seed/src/core/plan/plan.ts b/packages/seed/src/core/plan/plan.ts index a904ff53..acf849d2 100644 --- a/packages/seed/src/core/plan/plan.ts +++ b/packages/seed/src/core/plan/plan.ts @@ -94,12 +94,14 @@ export class Plan implements IPlan { inputs, }: PlanInputs & { ctx?: { + connectPath?: Array<{ model: string; rowId: number }>; index?: number; path?: Array; }; }, options: Required, ) { + const connectPath = ctx?.connectPath ?? []; const path = ctx?.path ?? [model]; const userModels = options.models; const modelStructure = this.dataModel.models[model]; @@ -195,12 +197,24 @@ export class Plan implements IPlan { if (parentField === undefined) { const connectFallback = userModels[parentModelName].connect; if (connectFallback) { - return connectFallback({ + const parent = connectFallback({ $store: this.ctx.store._store, store: this.store._store, index, seed: `${modelSeed}/${field.name}`, }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (parent !== undefined) { + return parent; + } + } + + // if the connect store is empty for this model, we attempt the path connection strategy + const candidate = connectPath.findLast( + (p) => p.model === parentModelName, + ); + if (candidate) { + return this.store._store[parentModelName][candidate.rowId]; } } @@ -340,6 +354,7 @@ export class Plan implements IPlan { }), }); + const rowId = this.store._store[model].length - 1; for (const field of fields.children) { const childModelName = field.type; const childField = inputsData[field.name] as ChildField | undefined; @@ -409,6 +424,7 @@ export class Plan implements IPlan { { ctx: { path: [...path, index, field.name], + connectPath: [...connectPath, { model, rowId }], }, model: childModelName, inputs: childInputs, From d224abdd6b55c280a65f683bb349d3c56a6d5dc2 Mon Sep 17 00:00:00 2001 From: jgoux Date: Wed, 13 Mar 2024 15:05:50 +0100 Subject: [PATCH 2/5] add tests --- packages/seed/e2e/e2e.test.ts | 156 ++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/packages/seed/e2e/e2e.test.ts b/packages/seed/e2e/e2e.test.ts index 84d70c6a..f7b169a5 100644 --- a/packages/seed/e2e/e2e.test.ts +++ b/packages/seed/e2e/e2e.test.ts @@ -271,6 +271,162 @@ for (const dialect of Object.keys(adapters) as Array) { expect(bookings[0].student_id).toEqual(students[0].student_id); expect(bookings[1].student_id).toEqual(students[0].student_id); }); + + test("basic path connection", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + await seed.boards([{ + columns: [{ + items: [{}, {}] + }] + }]) + `, + }); + + const boards = await db.query<{ id: string }>("select * from board"); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + expect(boards).toHaveLength(1); + expect(columns).toHaveLength(1); + expect(items).toEqual([ + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + ]); + }); + + test("multiple path connections", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + await seed.boards((x) => x(2, { + columns: (x) => x(2, { + items: (x) => x(2) + }) + })) + `, + }); + + const boards = await db.query<{ id: string }>("select * from board"); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + expect(boards).toHaveLength(2); + expect(columns).toHaveLength(4); + expect(items).toEqual([ + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + ]); + }); + + test("connect option overrides path connection", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + const store = await seed.boards([{ name: 'connected board' }]) + + await seed.boards([{ + columns: [{ + items: [{}, {}] + }] + }], { connect: store }) + `, + }); + + const boards = await db.query<{ id: string; name: string }>( + "select * from board", + ); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectedBoard = boards.find( + (b) => b.name === "connected board", + )!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const pathBoard = boards.find((b) => b.name !== "connected board")!; + + expect(boards).toHaveLength(2); + expect(columns).toEqual([ + expect.objectContaining({ + board_id: pathBoard.id, + }), + ]); + expect(items).toEqual([ + expect.objectContaining({ board_id: connectedBoard.id }), + expect.objectContaining({ board_id: connectedBoard.id }), + ]); + }); }, { timeout: 45000, From 3c3aabdb25b2999b219c704b82d16ab57d1a052b Mon Sep 17 00:00:00 2001 From: jgoux Date: Thu, 4 Apr 2024 11:29:45 +0200 Subject: [PATCH 3/5] isolate tests --- packages/seed/e2e/e2e.paths.test.ts | 165 ++++++++++++++++++++++++++++ packages/seed/vitest.config.ts | 2 +- 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 packages/seed/e2e/e2e.paths.test.ts diff --git a/packages/seed/e2e/e2e.paths.test.ts b/packages/seed/e2e/e2e.paths.test.ts new file mode 100644 index 00000000..0a5b5c49 --- /dev/null +++ b/packages/seed/e2e/e2e.paths.test.ts @@ -0,0 +1,165 @@ +import { test as _test, type TestFunction, expect } from "vitest"; +import { adapterEntries } from "#test/adapters.js"; +import { setupProject } from "#test/setupProject.js"; + +for (const [dialect, adapter] of adapterEntries) { + const computeName = (name: string) => `e2e > ${dialect} > ${name}`; + const test = (name: string, fn: TestFunction) => { + // eslint-disable-next-line vitest/expect-expect, vitest/valid-title + _test.concurrent(computeName(name), fn); + }; + + test("basic path connection", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + await seed.boards([{ + columns: [{ + items: [{}, {}] + }] + }]) + `, + }); + + const boards = await db.query<{ id: string }>("select * from board"); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + expect(boards).toHaveLength(1); + expect(columns).toHaveLength(1); + expect(items).toEqual([ + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + ]); + }); + + test("multiple path connections", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + await seed.boards((x) => x(2, { + columns: (x) => x(2, { + items: (x) => x(2) + }) + })) + `, + }); + + const boards = await db.query<{ id: string }>("select * from board"); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + expect(boards).toHaveLength(2); + expect(columns).toHaveLength(4); + expect(items).toEqual([ + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[0].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + expect.objectContaining({ board_id: boards[1].id }), + ]); + }); + + test("connect option overrides path connection", async () => { + const { db } = await setupProject({ + adapter, + databaseSchema: ` + create table board ( + id uuid not null primary key, + name text not null + ); + create table "column" ( + id uuid not null primary key, + name text not null, + board_id uuid not null references board(id) + ); + create table item ( + id uuid not null primary key, + name text not null, + column_id uuid not null references "column"(id), + board_id uuid not null references board(id) + ); + `, + seedScript: ` + import { createSeedClient } from '#seed' + + const seed = await createSeedClient() + + const store = await seed.boards([{ name: 'connected board' }]) + + await seed.boards([{ + columns: [{ + items: [{}, {}] + }] + }], { connect: store }) + `, + }); + + const boards = await db.query<{ id: string; name: string }>( + "select * from board", + ); + const columns = await db.query('select * from "column"'); + const items = await db.query("select * from item"); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const connectedBoard = boards.find((b) => b.name === "connected board")!; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const pathBoard = boards.find((b) => b.name !== "connected board")!; + + expect(boards).toHaveLength(2); + expect(columns).toEqual([ + expect.objectContaining({ + board_id: pathBoard.id, + }), + ]); + expect(items).toEqual([ + expect.objectContaining({ board_id: connectedBoard.id }), + expect.objectContaining({ board_id: connectedBoard.id }), + ]); + }); +} diff --git a/packages/seed/vitest.config.ts b/packages/seed/vitest.config.ts index 61f84e13..8f173410 100644 --- a/packages/seed/vitest.config.ts +++ b/packages/seed/vitest.config.ts @@ -14,7 +14,7 @@ export default defineProject({ name: pkg.name, root, testTimeout: 120_000, - maxConcurrency: 7, + maxConcurrency: 6, }, esbuild: { target: "es2022", From 80dff52af198f30c36fa247686108bd565c1fb1a Mon Sep 17 00:00:00 2001 From: jgoux Date: Thu, 4 Apr 2024 14:24:31 +0200 Subject: [PATCH 4/5] set maxConcurrency to its default of 5 --- packages/seed/vitest.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/seed/vitest.config.ts b/packages/seed/vitest.config.ts index 8f173410..e4c28385 100644 --- a/packages/seed/vitest.config.ts +++ b/packages/seed/vitest.config.ts @@ -14,7 +14,6 @@ export default defineProject({ name: pkg.name, root, testTimeout: 120_000, - maxConcurrency: 6, }, esbuild: { target: "es2022", From 8a4cf27aeaee1f1f43edc5d56e3d4d5880627029 Mon Sep 17 00:00:00 2001 From: jgoux Date: Thu, 4 Apr 2024 15:13:51 +0200 Subject: [PATCH 5/5] remove duplicate tests --- packages/seed/e2e/e2e.test.ts | 154 ---------------------------------- 1 file changed, 154 deletions(-) diff --git a/packages/seed/e2e/e2e.test.ts b/packages/seed/e2e/e2e.test.ts index a044eb85..d91cc6f0 100644 --- a/packages/seed/e2e/e2e.test.ts +++ b/packages/seed/e2e/e2e.test.ts @@ -379,160 +379,6 @@ for (const [dialect, adapter] of adapterEntries) { }, ); - test("basic path connection", async () => { - const { db } = await setupProject({ - adapter, - databaseSchema: ` - create table board ( - id uuid not null primary key, - name text not null - ); - create table "column" ( - id uuid not null primary key, - name text not null, - board_id uuid not null references board(id) - ); - create table item ( - id uuid not null primary key, - name text not null, - column_id uuid not null references "column"(id), - board_id uuid not null references board(id) - ); - `, - seedScript: ` - import { createSeedClient } from '#seed' - - const seed = await createSeedClient() - - await seed.boards([{ - columns: [{ - items: [{}, {}] - }] - }]) - `, - }); - - const boards = await db.query<{ id: string }>("select * from board"); - const columns = await db.query('select * from "column"'); - const items = await db.query("select * from item"); - - expect(boards).toHaveLength(1); - expect(columns).toHaveLength(1); - expect(items).toEqual([ - expect.objectContaining({ board_id: boards[0].id }), - expect.objectContaining({ board_id: boards[0].id }), - ]); - }); - - test("multiple path connections", async () => { - const { db } = await setupProject({ - adapter, - databaseSchema: ` - create table board ( - id uuid not null primary key, - name text not null - ); - create table "column" ( - id uuid not null primary key, - name text not null, - board_id uuid not null references board(id) - ); - create table item ( - id uuid not null primary key, - name text not null, - column_id uuid not null references "column"(id), - board_id uuid not null references board(id) - ); - `, - seedScript: ` - import { createSeedClient } from '#seed' - - const seed = await createSeedClient() - - await seed.boards((x) => x(2, { - columns: (x) => x(2, { - items: (x) => x(2) - }) - })) - `, - }); - - const boards = await db.query<{ id: string }>("select * from board"); - const columns = await db.query('select * from "column"'); - const items = await db.query("select * from item"); - - expect(boards).toHaveLength(2); - expect(columns).toHaveLength(4); - expect(items).toEqual([ - expect.objectContaining({ board_id: boards[0].id }), - expect.objectContaining({ board_id: boards[0].id }), - expect.objectContaining({ board_id: boards[0].id }), - expect.objectContaining({ board_id: boards[0].id }), - expect.objectContaining({ board_id: boards[1].id }), - expect.objectContaining({ board_id: boards[1].id }), - expect.objectContaining({ board_id: boards[1].id }), - expect.objectContaining({ board_id: boards[1].id }), - ]); - }); - - test("connect option overrides path connection", async () => { - const { db } = await setupProject({ - adapter, - databaseSchema: ` - create table board ( - id uuid not null primary key, - name text not null - ); - create table "column" ( - id uuid not null primary key, - name text not null, - board_id uuid not null references board(id) - ); - create table item ( - id uuid not null primary key, - name text not null, - column_id uuid not null references "column"(id), - board_id uuid not null references board(id) - ); - `, - seedScript: ` - import { createSeedClient } from '#seed' - - const seed = await createSeedClient() - - const store = await seed.boards([{ name: 'connected board' }]) - - await seed.boards([{ - columns: [{ - items: [{}, {}] - }] - }], { connect: store }) - `, - }); - - const boards = await db.query<{ id: string; name: string }>( - "select * from board", - ); - const columns = await db.query('select * from "column"'); - const items = await db.query("select * from item"); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const connectedBoard = boards.find((b) => b.name === "connected board")!; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const pathBoard = boards.find((b) => b.name !== "connected board")!; - - expect(boards).toHaveLength(2); - expect(columns).toEqual([ - expect.objectContaining({ - board_id: pathBoard.id, - }), - ]); - expect(items).toEqual([ - expect.objectContaining({ board_id: connectedBoard.id }), - expect.objectContaining({ board_id: connectedBoard.id }), - ]); - }); - test("seeds are unique per seed. call", async () => { const { db } = await setupProject({ adapter,