diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json index dda0ca2a4..818f5e404 100644 --- a/drizzle-kit/package.json +++ b/drizzle-kit/package.json @@ -74,6 +74,7 @@ "@vercel/postgres": "^0.8.0", "ava": "^5.1.0", "better-sqlite3": "^9.4.3", + "bson": "^6.8.0", "camelcase": "^7.0.1", "chalk": "^5.2.0", "commander": "^12.1.0", diff --git a/drizzle-kit/src/introspect-singlestore.ts b/drizzle-kit/src/introspect-singlestore.ts index 8aa6e3dd7..e7614e972 100644 --- a/drizzle-kit/src/introspect-singlestore.ts +++ b/drizzle-kit/src/introspect-singlestore.ts @@ -12,7 +12,9 @@ const singlestoreImportsList = new Set([ 'singlestoreEnum', 'bigint', 'binary', + // TODO: add new type Blob 'boolean', + 'bson', 'char', 'date', 'datetime', @@ -21,8 +23,6 @@ const singlestoreImportsList = new Set([ 'float', 'int', 'json', - // TODO: add new type BSON - // TODO: add new type Blob // TODO: add new type UUID // TODO: add new type GUID // TODO: add new type Vector @@ -245,18 +245,6 @@ const mapColumnDefault = (defaultValue: any, isExpression?: boolean) => { return defaultValue; }; -const mapColumnDefaultForJson = (defaultValue: any) => { - if ( - typeof defaultValue === 'string' - && defaultValue.startsWith("('") - && defaultValue.endsWith("')") - ) { - return defaultValue.substring(2, defaultValue.length - 2); - } - - return defaultValue; -}; - const column = ( type: string, name: string, @@ -490,19 +478,25 @@ const column = ( return out; } - // in mysql json can't have default value. Will leave it in case smth ;) - // TODO: check if SingleStore has json can't have default value if (lowered === 'json') { let out = `${casing(name)}: json("${name}")`; out += defaultValue - ? `.default(${mapColumnDefaultForJson(defaultValue)})` + ? `.default(${mapColumnDefault(defaultValue)})` : ''; return out; } - // TODO: add new type BSON + if (lowered === 'bson') { + let out = `${casing(name)}: bson("${name}")`; + + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue)})` + : ''; + + return out; + } // TODO: add new type Blob diff --git a/drizzle-kit/src/serializer/singlestoreSerializer.ts b/drizzle-kit/src/serializer/singlestoreSerializer.ts index f275273f4..7f7fd7da1 100644 --- a/drizzle-kit/src/serializer/singlestoreSerializer.ts +++ b/drizzle-kit/src/serializer/singlestoreSerializer.ts @@ -12,6 +12,7 @@ import { RowDataPacket } from 'mysql2/promise'; import { withStyle } from '../cli/validations/outputs'; import { IntrospectStage, IntrospectStatus } from '../cli/views'; +import { BSON } from 'bson'; import type { DB } from '../utils'; import { sqlToStr } from '.'; import { @@ -130,6 +131,11 @@ export const generateSingleStoreSnapshot = ( } else { if (sqlTypeLowered === 'json') { columnToSet.default = `'${JSON.stringify(column.default)}'`; + } else if (sqlTypeLowered === 'bson') { + if (column.default !== null) { + const hexaCode = `0x${Buffer.from(BSON.serialize(column.default)).toString('hex')}`; + columnToSet.default = `${hexaCode}`; + } } else if (column.default instanceof Date) { if (sqlTypeLowered === 'date') { columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; diff --git a/drizzle-kit/tests/singlestore.test.ts b/drizzle-kit/tests/singlestore.test.ts index 63abf1755..de624b002 100644 --- a/drizzle-kit/tests/singlestore.test.ts +++ b/drizzle-kit/tests/singlestore.test.ts @@ -1,5 +1,7 @@ +import { BSON } from 'bson'; import { sql } from 'drizzle-orm'; import { + bson, index, json, primaryKey, @@ -495,6 +497,49 @@ test('add table #14', async () => { }); // TODO: add bson type tests +test('add table with bson field', async () => { + const to = { + users: singlestoreTable('table', { + bson: bson('bson'), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe('CREATE TABLE `table` (\n\t`bson` bson\n);\n'); +}); + +test('add table with bson field with empty default value', async () => { + const to = { + users: singlestoreTable('table', { + bson: bson('bson').default({}), + }), + }; + + const hexaCode = `0x${Buffer.from(BSON.serialize({})).toString('hex')}`; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`table\` (\n\t\`bson\` bson DEFAULT ${hexaCode}\n);\n`, + ); +}); + +test('add table with bson field with object default value', async () => { + const to = { + users: singlestoreTable('table', { + bson: bson('bson').default({ key: 'value' }), + }), + }; + + const hexaCode = `0x${Buffer.from(BSON.serialize({ key: 'value' })).toString('hex')}`; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`table\` (\n\t\`bson\` bson DEFAULT ${hexaCode}\n);\n`, + ); +}); // TODO: add blob type tests diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 888f7efcb..749aafb94 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -177,6 +177,7 @@ "@vercel/postgres": "^0.8.0", "@xata.io/client": "^0.29.3", "better-sqlite3": "^8.4.0", + "bson": "^6.8.0", "bun-types": "^0.6.6", "cpy": "^10.1.0", "expo-sqlite": "^13.2.0", diff --git a/drizzle-orm/src/singlestore-core/columns/bson.ts b/drizzle-orm/src/singlestore-core/columns/bson.ts index 1f2077895..5db513a76 100644 --- a/drizzle-orm/src/singlestore-core/columns/bson.ts +++ b/drizzle-orm/src/singlestore-core/columns/bson.ts @@ -2,12 +2,11 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; -import { sql } from '~/sql/sql.ts'; import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; export type SingleStoreBsonBuilderInitial = SingleStoreBsonBuilder<{ name: TName; - dataType: 'json'; // The bson is stored as a json string the same way binary is stored as a string (check `./binary.ts`) + dataType: 'buffer'; columnType: 'SingleStoreBson'; data: unknown; driverParam: string; @@ -15,13 +14,13 @@ export type SingleStoreBsonBuilderInitial = SingleStoreBso generated: undefined; }>; -export class SingleStoreBsonBuilder> +export class SingleStoreBsonBuilder> extends SingleStoreColumnBuilder { static readonly [entityKind]: string = 'SingleStoreBsonBuilder'; constructor(name: T['name']) { - super(name, 'json', 'SingleStoreBson'); + super(name, 'buffer', 'SingleStoreBson'); } /** @internal */ @@ -35,16 +34,16 @@ export class SingleStoreBsonBuilder> extends SingleStoreColumn { +export class SingleStoreBson> extends SingleStoreColumn { static readonly [entityKind]: string = 'SingleStoreBson'; getSQLType(): string { return 'bson'; } - override mapToDriverValue(value: T['data']) { - const json = JSON.stringify(value); - return sql`${json}:>BSON`; + override mapToDriverValue(value: T['data']): string { + const jsonData = JSON.stringify(value); + return `${jsonData}:>BSON`; } } diff --git a/drizzle-orm/src/singlestore-core/columns/json.ts b/drizzle-orm/src/singlestore-core/columns/json.ts index 0b069f256..17abe528e 100644 --- a/drizzle-orm/src/singlestore-core/columns/json.ts +++ b/drizzle-orm/src/singlestore-core/columns/json.ts @@ -42,6 +42,7 @@ export class SingleStoreJson>; + +const deleteAllStmt = db.delete(users).prepare(); +const deleteAllPrepared = await deleteAllStmt.execute(); +Expect>; + +const deleteWhere = await db.delete(users).where(eq(users.id, 1)); +Expect>; + +const deleteWhereStmt = db.delete(users).where(eq(users.id, 1)).prepare(); +const deleteWherePrepared = await deleteWhereStmt.execute(); +Expect>; + +const deleteReturningAll = await db.delete(users); +Expect>; + +const deleteReturningAllStmt = db.delete(users).prepare(); +const deleteReturningAllPrepared = await deleteReturningAllStmt.execute(); +Expect>; + +const deleteReturningPartial = await db.delete(users); +Expect>; + +const deleteReturningPartialStmt = db.delete(users).prepare(); +const deleteReturningPartialPrepared = await deleteReturningPartialStmt.execute(); +Expect>; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.delete(users).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .delete(users) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); + + db + .delete(users) + .$dynamic() + .where(sql``) + .where(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/generated-columns.ts b/drizzle-orm/type-tests/singlestore/generated-columns.ts new file mode 100644 index 000000000..e31ab050a --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/generated-columns.ts @@ -0,0 +1,158 @@ +import { type Equal, Expect } from 'type-tests/utils'; +import { type InferInsertModel, type InferSelectModel, sql } from '~/index'; +import { drizzle } from '~/mysql2'; +import { serial, singlestoreTable, text, varchar } from '~/singlestore-core'; +import { db } from './db'; + +const users = singlestoreTable( + 'users', + { + id: serial('id').primaryKey(), + firstName: varchar('first_name', { length: 255 }), + lastName: varchar('last_name', { length: 255 }), + email: text('email').notNull(), + fullName: text('full_name').generatedAlwaysAs(sql`concat_ws(first_name, ' ', last_name)`), + upperName: text('upper_name').generatedAlwaysAs( + sql` case when first_name is null then null else upper(first_name) end `, + ).$type(), // There is no way for drizzle to detect nullability in these cases. This is how the user can work around it + }, +); +{ + type User = typeof users.$inferSelect; + type NewUser = typeof users.$inferInsert; + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }, + User + > + >(); + + Expect< + Equal< + { + email: string; + id?: number | undefined; + firstName?: string | null | undefined; + lastName?: string | null | undefined; + }, + NewUser + > + >(); +} + +{ + type User = InferSelectModel; + type NewUser = InferInsertModel; + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }, + User + > + >(); + + Expect< + Equal< + { + email: string; + id?: number | undefined; + firstName?: string | null | undefined; + lastName?: string | null | undefined; + }, + NewUser + > + >(); +} + +{ + const dbUsers = await db.select().from(users); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }[], + typeof dbUsers + > + >(); +} + +{ + const db = drizzle({} as any, { schema: { users }, mode: 'default' }); + + const dbUser = await db.query.users.findFirst(); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + } | undefined, + typeof dbUser + > + >(); +} + +{ + const db = drizzle({} as any, { schema: { users }, mode: 'default' }); + + const dbUser = await db.query.users.findMany(); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }[], + typeof dbUser + > + >(); +} + +{ + // @ts-expect-error - Can't use the fullName because it's a generated column + await db.insert(users).values({ + firstName: 'test', + lastName: 'test', + email: 'test', + fullName: 'test', + }); +} + +{ + await db.update(users).set({ + firstName: 'test', + lastName: 'test', + email: 'test', + // @ts-expect-error - Can't use the fullName because it's a generated column + fullName: 'test', + }); +} diff --git a/drizzle-orm/type-tests/singlestore/insert.ts b/drizzle-orm/type-tests/singlestore/insert.ts new file mode 100644 index 000000000..738bf669d --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/insert.ts @@ -0,0 +1,135 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { int, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import type { SingleStoreInsert } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +const singlestoreInsertReturning = await db.insert(users).values({ + // ^? + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).$returningId(); + +Expect>; + +const insert = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertPrepared = await insertStmt.execute(); +Expect>; + +const insertSql = await db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}); +Expect>; + +const insertSqlStmt = db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}).prepare(); +const insertSqlPrepared = await insertSqlStmt.execute(); +Expect>; + +const insertReturning = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPrepared = await insertReturningStmt.execute(); +Expect>; + +const insertReturningPartial = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningPartialStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPartialPrepared = await insertReturningPartialStmt.execute(); +Expect>; + +const insertReturningSql = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}); +Expect>; + +const insertReturningSqlStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}).prepare(); +const insertReturningSqlPrepared = await insertReturningSqlStmt.execute(); +Expect>; + +{ + const users = singlestoreTable('users', { + id: int('id').autoincrement().primaryKey(), + name: text('name').notNull(), + age: int('age'), + occupation: text('occupation'), + }); + + await db.insert(users).values({ name: 'John Wick', age: 58, occupation: 'housekeeper' }); +} + +{ + function dynamic(qb: T) { + return qb.onDuplicateKeyUpdate({ set: {} }); + } + + const qbBase = db.insert(users).values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + + Expect>; +} + +{ + db + .insert(users) + .values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }) + .onDuplicateKeyUpdate({ set: {} }) + // @ts-expect-error method was already called + .onDuplicateKeyUpdate({ set: {} }); +} diff --git a/drizzle-orm/type-tests/singlestore/select.ts b/drizzle-orm/type-tests/singlestore/select.ts new file mode 100644 index 000000000..10a7551a7 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/select.ts @@ -0,0 +1,606 @@ +import { + and, + between, + eq, + exists, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + not, + notBetween, + notExists, + notIlike, + notInArray, + notLike, + or, +} from '~/expressions.ts'; +import { alias } from '~/singlestore-core/alias.ts'; +import { param, sql } from '~/sql/sql.ts'; + +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { QueryBuilder, type SingleStoreSelect, type SingleStoreSelectQueryBuilder } from '~/singlestore-core/index.ts'; +import { db } from './db.ts'; +import { cities, classes, newYorkers, users } from './tables.ts'; + +const city = alias(cities, 'city'); +const city1 = alias(cities, 'city1'); + +const join = await db + .select({ + users, + cities, + city, + city1: { + id: city1.id, + }, + }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)) + .rightJoin(city, eq(city.id, users.id)) + .rightJoin(city1, eq(city1.id, users.id)); + +Expect< + Equal< + { + users: { + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + cities: { + id: number; + name: string; + population: number | null; + } | null; + city: { + id: number; + name: string; + population: number | null; + } | null; + city1: { + id: number; + }; + }[], + typeof join + > +>; + +const join2 = await db + .select({ + userId: users.id, + cityId: cities.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + }[], + typeof join2 + > +>; + +const join3 = await db + .select({ + userId: users.id, + cityId: cities.id, + classId: classes.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)) + .rightJoin(classes, eq(users.id, classes.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + classId: number; + }[], + typeof join3 + > +>; + +db + .select() + .from(users) + .where(exists(db.select().from(cities).where(eq(users.homeCity, cities.id)))); + +function mapFunkyFuncResult(valueFromDriver: unknown) { + return { + foo: (valueFromDriver as Record)['foo'], + }; +} + +const age = 1; + +const allOperators = await db + .select({ + col2: sql`5 - ${users.id} + 1`, // unknown + col3: sql`${users.id} + 1`, // number + col33: sql`${users.id} + 1`.mapWith(users.id), // number + col34: sql`${users.id} + 1`.mapWith(mapFunkyFuncResult), // number + col4: sql`one_or_another(${users.id}, ${users.class})`, // string | number + col5: sql`true`, // unknown + col6: sql`true`, // boolean + col7: sql`random()`, // number + col8: sql`some_funky_func(${users.id})`.mapWith(mapFunkyFuncResult), // { foo: string } + col9: sql`greatest(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // unknown + col10: sql`date_or_false(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // Date | boolean + col11: sql`${users.age1} + ${age}`, // unknown + col12: sql`${users.age1} + ${param(age, users.age1)}`, // unknown + col13: sql`lower(${users.class})`, // unknown + col14: sql`length(${users.class})`, // number + count: sql`count(*)::int`, // number + }) + .from(users) + .where(and( + eq(users.id, 1), + ne(users.id, 1), + or(eq(users.id, 1), ne(users.id, 1)), + not(eq(users.id, 1)), + gt(users.id, 1), + gte(users.id, 1), + lt(users.id, 1), + lte(users.id, 1), + inArray(users.id, [1, 2, 3]), + inArray(users.id, db.select({ id: users.id }).from(users)), + inArray(users.id, sql`select id from ${users}`), + notInArray(users.id, [1, 2, 3]), + notInArray(users.id, db.select({ id: users.id }).from(users)), + notInArray(users.id, sql`select id from ${users}`), + isNull(users.subClass), + isNotNull(users.id), + exists(db.select({ id: users.id }).from(users)), + exists(sql`select id from ${users}`), + notExists(db.select({ id: users.id }).from(users)), + notExists(sql`select id from ${users}`), + between(users.id, 1, 2), + notBetween(users.id, 1, 2), + like(users.id, '%1%'), + notLike(users.id, '%1%'), + ilike(users.id, '%1%'), + notIlike(users.id, '%1%'), + )); + +Expect< + Equal<{ + col2: unknown; + col3: number; + col33: number; + col34: { foo: any }; + col4: string | number; + col5: unknown; + col6: boolean; + col7: number; + col8: { + foo: any; + }; + col9: unknown; + col10: boolean | Date; + col11: unknown; + col12: unknown; + col13: unknown; + col14: number; + count: number; + }[], typeof allOperators> +>; + +const textSelect = await db + .select({ + t: users.text, + }) + .from(users); + +Expect>; + +const homeCity = alias(cities, 'homeCity'); +const c = alias(classes, 'c'); +const otherClass = alias(classes, 'otherClass'); +const anotherClass = alias(classes, 'anotherClass'); +const friend = alias(users, 'friend'); +const currentCity = alias(cities, 'currentCity'); +const subscriber = alias(users, 'subscriber'); +const closestCity = alias(cities, 'closestCity'); + +const megaJoin = await db + .select({ + user: { + id: users.id, + maxAge: sql`max(${users.age1})`, + }, + city: { + id: cities.id, + }, + homeCity, + c, + otherClass, + anotherClass, + friend, + currentCity, + subscriber, + closestCity, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(homeCity, sql`${users.homeCity} = ${homeCity.id}`) + .innerJoin(c, eq(c.id, users.class)) + .innerJoin(otherClass, sql`${c.id} = ${otherClass.id}`) + .innerJoin(anotherClass, sql`${users.class} = ${anotherClass.id}`) + .innerJoin(friend, sql`${users.id} = ${friend.id}`) + .innerJoin(currentCity, sql`${homeCity.id} = ${currentCity.id}`) + .innerJoin(subscriber, sql`${users.class} = ${subscriber.id}`) + .innerJoin(closestCity, sql`${users.currentCity} = ${closestCity.id}`) + .where(and(sql`${users.age1} > 0`, eq(cities.id, 1))) + .limit(1) + .offset(1); + +Expect< + Equal< + { + user: { + id: number; + maxAge: unknown; + }; + city: { + id: number; + }; + homeCity: { + id: number; + name: string; + population: number | null; + }; + c: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + otherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + anotherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + currentCity: { + id: number; + name: string; + population: number | null; + }; + subscriber: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + closestCity: { + id: number; + name: string; + population: number | null; + }; + }[], + typeof megaJoin + > +>; + +const friends = alias(users, 'friends'); + +const join4 = await db + .select({ + user: { + id: users.id, + }, + city: { + id: cities.id, + }, + class: classes, + friend: friends, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(classes, sql`${cities.id} = ${classes.id}`) + .innerJoin(friends, sql`${friends.id} = ${users.id}`) + .where(sql`${users.age1} > 0`); + +Expect< + Equal<{ + user: { + id: number; + }; + city: { + id: number; + }; + class: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + }[], typeof join4> +>; + +{ + const authenticated = false as boolean; + + const result = await db + .select({ + id: users.id, + ...(authenticated ? { city: users.homeCity } : {}), + }) + .from(users); + + Expect< + Equal< + { + id: number; + city?: number; + }[], + typeof result + > + >; +} + +await db.select().from(users).for('update'); +await db.select().from(users).for('share', { skipLocked: true }); +await db.select().from(users).for('update', { noWait: true }); +await db + .select() + .from(users) + // @ts-expect-error - can't use both skipLocked and noWait + .for('share', { noWait: true, skipLocked: true }); + +{ + const result = await db.select().from(newYorkers); + Expect< + Equal< + { + userId: number; + cityId: number | null; + }[], + typeof result + > + >; +} + +{ + const result = await db.select({ userId: newYorkers.userId }).from(newYorkers); + Expect< + Equal< + { + userId: number; + }[], + typeof result + > + >; +} + +{ + const query = db.select().from(users).prepare().iterator(); + for await (const row of query) { + Expect>(); + } +} + +{ + db + .select() + .from(users) + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + .limit(10) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const qb = db.select().from(users).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +{ + // TODO: add to docs + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const query = new QueryBuilder().select().from(users).$dynamic(); + dynamic(query); +} + +{ + // TODO: add to docs + function paginated(qb: T, page: number) { + return qb.limit(10).offset((page - 1) * 10); + } + + const qb = db.select().from(users).$dynamic(); + const result = await paginated(qb, 1); + + Expect>; +} + +{ + db + .select() + .from(users) + .where(sql``) + .limit(10) + // @ts-expect-error method was already called + .where(sql``); + + db + .select() + .from(users) + .having(sql``) + .limit(10) + // @ts-expect-error method was already called + .having(sql``); + + db + .select() + .from(users) + .groupBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .groupBy(sql``); + + db + .select() + .from(users) + .orderBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .orderBy(sql``); + + db + .select() + .from(users) + .limit(10) + .where(sql``) + // @ts-expect-error method was already called + .limit(10); + + db + .select() + .from(users) + .offset(10) + .limit(10) + // @ts-expect-error method was already called + .offset(10); + + db + .select() + .from(users) + .for('update') + .limit(10) + // @ts-expect-error method was already called + .for('update'); +} diff --git a/drizzle-orm/type-tests/singlestore/set-operators.ts b/drizzle-orm/type-tests/singlestore/set-operators.ts new file mode 100644 index 000000000..8bd434262 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/set-operators.ts @@ -0,0 +1,286 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { eq } from '~/expressions.ts'; +import { + except, + exceptAll, + intersect, + intersectAll, + type SingleStoreSetOperator, + union, + unionAll, +} from '~/singlestore-core/index.ts'; +import { desc, sql } from '~/sql/index.ts'; +import { db } from './db.ts'; +import { cities, classes, newYorkers, users } from './tables.ts'; + +const unionTest = await db + .select({ id: users.id }) + .from(users) + .union( + db + .select({ id: users.id }) + .from(users), + ); + +Expect>; + +const unionAllTest = await db + .select({ id: users.id, age: users.age1 }) + .from(users) + .unionAll( + db.select({ id: users.id, age: users.age1 }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)), + ); + +Expect>; + +const intersectTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .intersect(({ intersect }) => + intersect( + db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users), + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ) + ); + +Expect>; + +const intersectAllTest = await db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .intersect( + db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)), + ); + +Expect>; + +const exceptTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .except( + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ); + +Expect>; + +const exceptAllTest = await db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .except( + db + .select({ id: users.id, homeCity: sql<'A' | 'C'>`${users.class}` }) + .from(users), + ); + +Expect>; + +const union2Test = await union(db.select().from(cities), db.select().from(cities), db.select().from(cities)); + +Expect>; + +const unionAll2Test = await unionAll( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select().from(cities), +); + +Expect>; + +const intersect2Test = await intersect( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), +); + +Expect>; + +const intersectAll2Test = await intersectAll( + union( + db.select({ + id: cities.id, + }).from(cities), + db.select({ + id: cities.id, + }) + .from(cities).where(sql``), + ), + db.select({ + id: cities.id, + }) + .from(cities), +).orderBy(desc(cities.id)).limit(23); + +Expect>; + +const except2Test = await except( + db.select({ + userId: newYorkers.userId, + }) + .from(newYorkers), + db.select({ + userId: newYorkers.userId, + }).from(newYorkers), +); + +Expect>; + +const exceptAll2Test = await exceptAll( + db.select({ + userId: newYorkers.userId, + cityId: newYorkers.cityId, + }) + .from(newYorkers).where(sql``), + db.select({ + userId: newYorkers.userId, + cityId: newYorkers.cityId, + }).from(newYorkers).leftJoin(users, sql``), +); + +Expect>; + +const unionfull = await union(db.select().from(users), db.select().from(users)).orderBy(sql``).limit(1).offset(2); + +Expect< + Equal<{ + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }[], typeof unionfull> +>; + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +union(db.select().from(users), db.select().from(users)) + .offset(1) + // @ts-expect-error - method was already called + .offset(2); + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +{ + function dynamic(qb: T) { + return qb.orderBy(sql``).limit(1).offset(2); + } + + const qb = union(db.select().from(users), db.select().from(users)).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + .intersect(({ intersect }) => intersect(db.select().from(users), db.select().from(users))); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select().from(classes).union(db.select({ id: classes.id }).from(classes)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes).where(sql``)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes)); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + db.select({ id: cities.id, name: cities.name }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select().from(cities), +); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities), + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``).limit(3).$dynamic(), + db.select({ id: cities.id, name: cities.name }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: newYorkers.userId }).from(newYorkers), + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities).where(sql``), + db.select({ id: sql`${cities.id}` }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities).where(sql``), +); diff --git a/drizzle-orm/type-tests/singlestore/subquery.ts b/drizzle-orm/type-tests/singlestore/subquery.ts new file mode 100644 index 000000000..e8ee4e80b --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/subquery.ts @@ -0,0 +1,97 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, eq } from '~/expressions.ts'; +import { alias, int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import type { DrizzleTypeError, Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = singlestoreTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const n1 = db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: sql`count(1)::int`.as('count1'), + }) + .from(names) + .groupBy(names.id, names.name, names.authorId) + .as('n1'); + +const n2 = db + .select({ + id: names.id, + authorId: names.authorId, + totalCount: sql`count(1)::int`.as('totalCount'), + }) + .from(names) + .groupBy(names.id, names.authorId) + .as('n2'); + +const result = await db + .select({ + name: n1.name, + authorId: n1.authorId, + count1: n1.count1, + totalCount: n2.totalCount, + }) + .from(n1) + .innerJoin(n2, and(eq(n2.id, n1.id), eq(n2.authorId, n1.authorId))); + +Expect< + Equal< + { + name: string | null; + authorId: number | null; + count1: number; + totalCount: number; + }[], + typeof result + > +>; + +const names2 = alias(names, 'names2'); + +const sq1 = db + .select({ + id: names.id, + name: names.name, + id2: names2.id, + }) + .from(names) + .leftJoin(names2, eq(names.name, names2.name)) + .as('sq1'); + +const res = await db.select().from(sq1); + +Expect< + Equal< + { + id: number; + name: string | null; + id2: number | null; + }[], + typeof res + > +>; + +{ + const sq = db.select({ count: sql`count(1)::int` }).from(names).as('sq'); + Expect ? true : false>; +} + +const sqUnion = db.select().from(names).union(db.select().from(names2)).as('sqUnion'); + +const resUnion = await db.select().from(sqUnion); + +Expect< + Equal<{ + id: number; + name: string | null; + authorId: number | null; + }[], typeof resUnion> +>; diff --git a/drizzle-orm/type-tests/singlestore/tables.ts b/drizzle-orm/type-tests/singlestore/tables.ts new file mode 100644 index 000000000..ea80c1db5 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/tables.ts @@ -0,0 +1,750 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { eq, gt } from '~/expressions.ts'; +import type { BuildColumn, InferSelectModel, Simplify } from '~/index.ts'; +import { + bigint, + char, + customType, + date, + datetime, + decimal, + index, + int, + json, + longtext, + mediumtext, + primaryKey, + serial, + type SingleStoreColumn, + singlestoreEnum, + singlestoreTable, + text, + timestamp, + tinytext, + unique, + uniqueIndex, + varchar, +} from '~/singlestore-core/index.ts'; +import { singlestoreSchema } from '~/singlestore-core/schema.ts'; +import { singlestoreView, type SingleStoreViewWithSelection } from '~/singlestore-core/view.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; + +export const users = singlestoreTable( + 'users_table', + { + id: serial('id').primaryKey(), + homeCity: int('home_city') + .notNull(), + currentCity: int('current_city'), + serialNullable: serial('serial1'), + serialNotNull: serial('serial2').notNull(), + class: text('class', { enum: ['A', 'C'] }).notNull(), + subClass: text('sub_class', { enum: ['B', 'D'] }), + text: text('text'), + age1: int('age1').notNull(), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + enumCol: singlestoreEnum('enum_col', ['a', 'b', 'c']).notNull(), + }, + (users) => ({ + usersAge1Idx: uniqueIndex('usersAge1Idx').on(users.class), + usersAge2Idx: index('usersAge2Idx').on(users.class), + uniqueClass: uniqueIndex('uniqueClass') + .on(users.class, users.subClass) + .lock('default') + .algorythm('copy') + .using(`btree`), + pk: primaryKey({ columns: [users.age1, users.class] }), + }), +); + +export const cities = singlestoreTable('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect< + Equal< + { + id: SingleStoreColumn<{ + name: 'id'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + isPrimaryKey: true; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isAutoincrement: true; + hasRuntimeDefault: false; + }, object>; + name: SingleStoreColumn<{ + name: 'name_db'; + tableName: 'cities_table'; + dataType: 'string'; + columnType: 'SingleStoreText'; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, object>; + population: SingleStoreColumn<{ + name: 'population'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: true; + isPrimaryKey: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, object>; + }, + typeof cities._.columns + > +>; + +Expect< + Equal<{ + id: number; + name_db: string; + population: number | null; + }, InferSelectModel> +>; + +Expect< + Equal<{ + id?: number; + name: string; + population?: number | null; + }, typeof cities.$inferInsert> +>; + +export const customSchema = singlestoreSchema('custom_schema'); + +export const citiesCustom = customSchema.table('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect>; + +export const classes = singlestoreTable('classes_table', { + id: serial('id').primaryKey(), + class: text('class', { enum: ['A', 'C'] }), + subClass: text('sub_class', { enum: ['B', 'D'] }).notNull(), +}); + +/* export const classes2 = singlestoreTable('classes_table', { + id: serial().primaryKey(), + class: text({ enum: ['A', 'C'] }).$dbName('class_db'), + subClass: text({ enum: ['B', 'D'] }).notNull(), +}); */ + +export const newYorkers = singlestoreView('new_yorkers') + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + +Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > +>; + +{ + const newYorkers = customSchema.view('new_yorkers') + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const customText = customType<{ data: string }>({ + dataType() { + return 'text'; + }, + }); + + const t = customText('name').notNull(); + Expect< + Equal< + { + brand: 'Column'; + name: 'name'; + tableName: 'table'; + dataType: 'custom'; + columnType: 'SingleStoreCustomColumn'; + data: string; + driverParam: unknown; + notNull: true; + hasDefault: false; + enumValues: undefined; + baseColumn: never; + dialect: 'singlestore'; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }, + Simplify['_']> + > + >; +} + +{ + singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }), + number: bigint('number', { mode: 'number' }), + date: date('date').default(new Date()), + date2: date('date2', { mode: 'date' }).default(new Date()), + date3: date('date3', { mode: 'string' }).default('2020-01-01'), + date4: date('date4', { mode: undefined }).default(new Date()), + datetime: datetime('datetime').default(new Date()), + datetime2: datetime('datetime2', { mode: 'date' }).default(new Date()), + datetime3: datetime('datetime3', { mode: 'string' }).default('2020-01-01'), + datetime4: datetime('datetime4', { mode: undefined }).default(new Date()), + timestamp: timestamp('timestamp').default(new Date()), + timestamp2: timestamp('timestamp2', { mode: 'date' }).default(new Date()), + timestamp3: timestamp('timestamp3', { mode: 'string' }).default('2020-01-01'), + timestamp4: timestamp('timestamp4', { mode: undefined }).default(new Date()), + }); +} + +{ + singlestoreTable('test', { + col1: decimal('col1').default('1'), + }); +} + +{ + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).notNull(), + test2: singlestoreEnum('test', ['a', 'b', 'c']).notNull(), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).notNull(), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).notNull(), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test6: text('test', { enum: ['a', 'b', 'c'] }).notNull(), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test14: char('test', { enum: ['a', 'b', 'c'] }).notNull(), + test15: text('test').notNull(), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} + +{ // All types with generated columns + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).generatedAlwaysAs(sql``), + test2: singlestoreEnum('test', ['a', 'b', 'c']).generatedAlwaysAs(sql``), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test6: text('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test14: char('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test15: text('test').generatedAlwaysAs(sql``), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} + +{ + const getUsersTable = (schemaName: TSchema) => { + return singlestoreSchema(schemaName).table('users', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }); + }; + + const users1 = getUsersTable('id1'); + Expect>; + + const users2 = getUsersTable('id2'); + Expect>; +} + +{ + const internalStaff = singlestoreTable('internal_staff', { + userId: int('user_id').notNull(), + }); + + const customUser = singlestoreTable('custom_user', { + id: int('id').notNull(), + }); + + const ticket = singlestoreTable('ticket', { + staffId: int('staff_id').notNull(), + }); + + const subq = db + .select() + .from(internalStaff) + .leftJoin( + customUser, + eq(internalStaff.userId, customUser.id), + ).as('internal_staff'); + + const mainQuery = await db + .select() + .from(ticket) + .leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)); + + Expect< + Equal<{ + internal_staff: { + internal_staff: { + userId: number; + }; + custom_user: { + id: number | null; + }; + } | null; + ticket: { + staffId: number; + }; + }[], typeof mainQuery> + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + await db.select().from(newYorkers).leftJoin(newYorkers, eq(newYorkers.userId, newYorkers.userId)); +} + +{ + const test = singlestoreTable('test', { + id: text('id').$defaultFn(() => crypto.randomUUID()).primaryKey(), + }); + + Expect< + Equal<{ + id?: string; + }, typeof test.$inferInsert> + >; +} + +{ + singlestoreTable('test', { + id: int('id').$default(() => 1), + id2: int('id').$defaultFn(() => 1), + // @ts-expect-error - should be number + id3: int('id').$default(() => '1'), + // @ts-expect-error - should be number + id4: int('id').$defaultFn(() => '1'), + }); +} +{ + const emailLog = singlestoreTable( + 'email_log', + { + id: int('id', { unsigned: true }).autoincrement().notNull(), + clientId: int('id_client', { unsigned: true }), + receiverEmail: varchar('receiver_email', { length: 255 }).notNull(), + messageId: varchar('message_id', { length: 255 }), + contextId: int('context_id', { unsigned: true }), + contextType: singlestoreEnum('context_type', ['test']).$type<['test']>(), + action: varchar('action', { length: 80 }).$type<['test']>(), + events: json('events').$type<{ t: 'test' }[]>(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().onUpdateNow(), + }, + (table) => { + return { + emailLogId: primaryKey({ columns: [table.id], name: 'email_log_id' }), + emailLogMessageIdUnique: unique('email_log_message_id_unique').on(table.messageId), + }; + }, + ); + + Expect< + Equal<{ + receiverEmail: string; + id?: number | undefined; + createdAt?: string | undefined; + clientId?: number | null | undefined; + messageId?: string | null | undefined; + contextId?: number | null | undefined; + contextType?: ['test'] | null | undefined; + action?: ['test'] | null | undefined; + events?: + | { + t: 'test'; + }[] + | null + | undefined; + updatedAt?: string | null | undefined; + }, typeof emailLog.$inferInsert> + >; +} diff --git a/drizzle-orm/type-tests/singlestore/update.ts b/drizzle-orm/type-tests/singlestore/update.ts new file mode 100644 index 000000000..3f10ae2e4 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/update.ts @@ -0,0 +1,26 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { SingleStoreUpdate } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/session.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.update(users).set({}).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .update(users) + .set({}) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/with.ts b/drizzle-orm/type-tests/singlestore/with.ts new file mode 100644 index 000000000..77309e32a --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/with.ts @@ -0,0 +1,80 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { gt, inArray } from '~/expressions.ts'; +import { int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; + +const orders = singlestoreTable('orders', { + id: serial('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull(), + amount: int('amount').notNull(), + quantity: int('quantity').notNull(), + generated: text('generatedText').generatedAlwaysAs(sql``), +}); + +{ + const regionalSales = db + .$with('regional_sales') + .as( + db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = db + .$with('top_regions') + .as( + db + .select({ + region: orders.region, + totalSales: orders.amount, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = await db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`sum(${orders.quantity})`, + productSales: sql`sum(${orders.amount})`, + }) + .from(orders) + .where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions))); + + Expect< + Equal<{ + region: string; + product: string; + productUnits: number; + productSales: number; + }[], typeof result> + >; + + const allOrdersWith = db.$with('all_orders_with').as(db.select().from(orders)); + const allFromWith = await db.with(allOrdersWith).select().from(allOrdersWith); + + Expect< + Equal<{ + id: number; + region: string; + product: string; + amount: number; + quantity: number; + generated: string | null; + }[], typeof allFromWith> + >; +} diff --git a/drizzle-typebox/package.json b/drizzle-typebox/package.json index 5e812f4fe..001e1e978 100644 --- a/drizzle-typebox/package.json +++ b/drizzle-typebox/package.json @@ -44,6 +44,7 @@ "orm", "pg", "mysql", + "singlestore", "postgresql", "postgres", "sqlite", @@ -63,6 +64,7 @@ "@rollup/plugin-typescript": "^11.1.0", "@sinclair/typebox": "^0.29.6", "@types/node": "^18.15.10", + "bson": "^6.8.0", "cpy": "^10.1.0", "drizzle-orm": "link:../drizzle-orm/dist", "rimraf": "^5.0.0", diff --git a/drizzle-typebox/src/index.ts b/drizzle-typebox/src/index.ts index e70d72c43..512b979e4 100644 --- a/drizzle-typebox/src/index.ts +++ b/drizzle-typebox/src/index.ts @@ -27,6 +27,7 @@ import { } from 'drizzle-orm'; import { MySqlChar, MySqlVarBinary, MySqlVarChar } from 'drizzle-orm/mysql-core'; import { type PgArray, PgChar, PgUUID, PgVarchar } from 'drizzle-orm/pg-core'; +import { SingleStoreChar, SingleStoreVarBinary, SingleStoreVarChar } from 'drizzle-orm/singlestore-core'; import { SQLiteText } from 'drizzle-orm/sqlite-core'; type TUnionLiterals = T extends readonly [ @@ -176,10 +177,11 @@ export function createInsertSchema< const columns = getTableColumns(table); const columnEntries = Object.entries(columns); + const columnMappedEntries = columnEntries.map(([name, column]) => { + return [name, mapColumnToSchema(column)]; + }); let schemaEntries = Object.fromEntries( - columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - }), + columnMappedEntries, ); if (refine) { @@ -301,6 +303,8 @@ function mapColumnToSchema(column: Column): TSchema { if (!type) { if (column.dataType === 'custom') { type = Type.Any(); + } else if (column.dataType === 'buffer') { + type = Type.Any(); } else if (column.dataType === 'json') { type = jsonSchema; } else if (column.dataType === 'array') { @@ -324,6 +328,9 @@ function mapColumnToSchema(column: Column): TSchema { || is(column, MySqlVarChar) || is(column, MySqlVarBinary) || is(column, MySqlChar) + || is(column, SingleStoreVarChar) + || is(column, SingleStoreVarBinary) + || is(column, SingleStoreChar) || is(column, SQLiteText)) && typeof column.length === 'number' ) { diff --git a/drizzle-typebox/tests/singlestore.test.ts b/drizzle-typebox/tests/singlestore.test.ts new file mode 100644 index 000000000..1e790ec41 --- /dev/null +++ b/drizzle-typebox/tests/singlestore.test.ts @@ -0,0 +1,387 @@ +import { Type } from '@sinclair/typebox'; +import { Value } from '@sinclair/typebox/value'; +import { BSON } from 'bson'; +import { + bigint, + binary, + boolean, + bson, + char, + customType, + date, + datetime, + decimal, + double, + float, + int, + json, + longtext, + mediumint, + mediumtext, + real, + serial, + singlestoreEnum, + singlestoreTable, + smallint, + text, + time, + timestamp, + tinyint, + tinytext, + varbinary, + varchar, + year, +} from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { createInsertSchema, createSelectSchema, jsonSchema } from '../src/index.ts'; +import { expectSchemaShape } from './utils.ts'; + +const customInt = customType<{ data: number }>({ + dataType() { + return 'int'; + }, +}); + +const testTable = singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }).notNull(), + bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), + binary: binary('binary').notNull(), + boolean: boolean('boolean').notNull(), + bson: bson('bson').notNull(), + char: char('char', { length: 4 }).notNull(), + charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), + customInt: customInt('customInt').notNull(), + date: date('date').notNull(), + dateString: date('dateString', { mode: 'string' }).notNull(), + datetime: datetime('datetime').notNull(), + datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), + decimal: decimal('decimal').notNull(), + double: double('double').notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + float: float('float').notNull(), + int: int('int').notNull(), + json: json('json').notNull(), + mediumint: mediumint('mediumint').notNull(), + real: real('real').notNull(), + serial: serial('serial').notNull(), + smallint: smallint('smallint').notNull(), + text: text('text').notNull(), + textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), + tinytext: tinytext('tinytext').notNull(), + tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + mediumtext: mediumtext('mediumtext').notNull(), + mediumtextEnum: mediumtext('mediumtextEnum', { + enum: ['a', 'b', 'c'], + }).notNull(), + longtext: longtext('longtext').notNull(), + longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + time: time('time').notNull(), + timestamp: timestamp('timestamp').notNull(), + timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), + tinyint: tinyint('tinyint').notNull(), + varbinary: varbinary('varbinary', { length: 200 }).notNull(), + varchar: varchar('varchar', { length: 200 }).notNull(), + varcharEnum: varchar('varcharEnum', { + length: 1, + enum: ['a', 'b', 'c'], + }).notNull(), + year: year('year').notNull(), + autoIncrement: int('autoIncrement').notNull().autoincrement(), +}); + +const testTableRow = { + bigint: BigInt(1), + bigintNumber: 1, + binary: 'binary', + boolean: true, + bson: BSON.serialize({ + data: 1, + }), + char: 'char', + charEnum: 'a', + customInt: { data: 1 }, + date: new Date(), + dateString: new Date().toISOString(), + datetime: new Date(), + datetimeString: new Date().toISOString(), + decimal: '1.1', + double: 1.1, + enum: 'a', + float: 1.1, + int: 1, + json: { data: 1 }, + mediumint: 1, + real: 1.1, + serial: 1, + smallint: 1, + text: 'text', + textEnum: 'a', + tinytext: 'tinytext', + tinytextEnum: 'a', + mediumtext: 'mediumtext', + mediumtextEnum: 'a', + longtext: 'longtext', + longtextEnum: 'a', + time: '00:00:00', + timestamp: new Date(), + timestampString: new Date().toISOString(), + tinyint: 1, + varbinary: 'A'.repeat(200), + varchar: 'A'.repeat(200), + varcharEnum: 'a', + year: 2021, + autoIncrement: 1, +}; + +test('insert valid row', () => { + const schema = createInsertSchema(testTable); + + expect(Value.Check( + schema, + testTableRow, + )).toBeTruthy(); +}); + +test('insert invalid varchar length', () => { + const schema = createInsertSchema(testTable); + + expect(Value.Check(schema, { + ...testTableRow, + varchar: 'A'.repeat(201), + })).toBeFalsy(); +}); + +test('insert smaller char length should work', () => { + const schema = createInsertSchema(testTable); + + expect(Value.Check(schema, { ...testTableRow, char: 'abc' })).toBeTruthy(); +}); + +test('insert larger char length should fail', () => { + const schema = createInsertSchema(testTable); + + expect(Value.Check(schema, { ...testTableRow, char: 'abcde' })).toBeFalsy(); +}); + +test('insert schema', (t) => { + const actual = createInsertSchema(testTable); + + const expected = Type.Object({ + bigint: Type.BigInt(), + bigintNumber: Type.Number(), + binary: Type.String(), + boolean: Type.Boolean(), + bson: Type.Any(), + char: Type.String({ minLength: 4, maxLength: 4 }), + charEnum: Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')]), + customInt: Type.Any(), + date: Type.Date(), + dateString: Type.String(), + datetime: Type.Date(), + datetimeString: Type.String(), + decimal: Type.String(), + double: Type.Number(), + enum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + float: Type.Number(), + int: Type.Number(), + json: jsonSchema, + mediumint: Type.Number(), + real: Type.Number(), + serial: Type.Optional(Type.Number()), + smallint: Type.Number(), + text: Type.String(), + textEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + tinytext: Type.String(), + tinytextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + mediumtext: Type.String(), + mediumtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + longtext: Type.String(), + longtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + time: Type.String(), + timestamp: Type.Date(), + timestampString: Type.String(), + tinyint: Type.Number(), + varbinary: Type.String({ maxLength: 200 }), + varchar: Type.String({ maxLength: 200 }), + varcharEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + year: Type.Number(), + autoIncrement: Type.Optional(Type.Number()), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema', (t) => { + const actual = createSelectSchema(testTable); + + const expected = Type.Object({ + bigint: Type.BigInt(), + bigintNumber: Type.Number(), + binary: Type.String(), + boolean: Type.Boolean(), + bson: Type.Any(), + char: Type.String({ minLength: 4, maxLength: 4 }), + charEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + customInt: Type.Any(), + date: Type.Date(), + dateString: Type.String(), + datetime: Type.Date(), + datetimeString: Type.String(), + decimal: Type.String(), + double: Type.Number(), + enum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + float: Type.Number(), + int: Type.Number(), + // + json: jsonSchema, + mediumint: Type.Number(), + real: Type.Number(), + serial: Type.Number(), + smallint: Type.Number(), + text: Type.String(), + textEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + tinytext: Type.String(), + tinytextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + mediumtext: Type.String(), + mediumtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + longtext: Type.String(), + longtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + time: Type.String(), + timestamp: Type.Date(), + timestampString: Type.String(), + tinyint: Type.Number(), + varbinary: Type.String({ maxLength: 200 }), + varchar: Type.String({ maxLength: 200 }), + varcharEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + year: Type.Number(), + autoIncrement: Type.Number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema w/ refine', (t) => { + const actual = createSelectSchema(testTable, { + bigint: (_) => Type.BigInt({ minimum: 0n }), + }); + + const expected = Type.Object({ + bigint: Type.BigInt({ minimum: 0n }), + bigintNumber: Type.Number(), + binary: Type.String(), + boolean: Type.Boolean(), + bson: Type.Any(), + char: Type.String({ minLength: 5, maxLength: 5 }), + charEnum: Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')]), + customInt: Type.Any(), + date: Type.Date(), + dateString: Type.String(), + datetime: Type.Date(), + datetimeString: Type.String(), + decimal: Type.String(), + double: Type.Number(), + enum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + float: Type.Number(), + int: Type.Number(), + json: jsonSchema, + mediumint: Type.Number(), + real: Type.Number(), + serial: Type.Number(), + smallint: Type.Number(), + text: Type.String(), + textEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + tinytext: Type.String(), + tinytextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + mediumtext: Type.String(), + mediumtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + longtext: Type.String(), + longtextEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + time: Type.String(), + timestamp: Type.Date(), + timestampString: Type.String(), + tinyint: Type.Number(), + varbinary: Type.String({ maxLength: 200 }), + varchar: Type.String({ maxLength: 200 }), + varcharEnum: Type.Union([ + Type.Literal('a'), + Type.Literal('b'), + Type.Literal('c'), + ]), + year: Type.Number(), + autoIncrement: Type.Number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); diff --git a/drizzle-valibot/package.json b/drizzle-valibot/package.json index 1d88fd26a..bf27607dd 100644 --- a/drizzle-valibot/package.json +++ b/drizzle-valibot/package.json @@ -62,6 +62,7 @@ "@rollup/plugin-terser": "^0.4.1", "@rollup/plugin-typescript": "^11.1.0", "@types/node": "^18.15.10", + "bson": "^6.8.0", "cpy": "^10.1.0", "drizzle-orm": "link:../drizzle-orm/dist", "rimraf": "^5.0.0", diff --git a/drizzle-valibot/tests/singlestore.test.ts b/drizzle-valibot/tests/singlestore.test.ts new file mode 100644 index 000000000..8ec563089 --- /dev/null +++ b/drizzle-valibot/tests/singlestore.test.ts @@ -0,0 +1,409 @@ +import { BSON } from 'bson'; +import { + bigint, + binary, + boolean, + bson, + char, + customType, + date, + datetime, + decimal, + double, + float, + int, + json, + longtext, + mediumint, + mediumtext, + real, + serial, + singlestoreEnum, + singlestoreTable, + smallint, + text, + time, + timestamp, + tinyint, + tinytext, + varbinary, + varchar, + year, +} from 'drizzle-orm/singlestore-core'; +import { + any, + bigint as valibigint, + boolean as valiboolean, + date as valiDate, + maxLength, + minLength, + minValue, + number, + object, + optional, + parse, + picklist, + string, +} from 'valibot'; +import { expect, test } from 'vitest'; +import { createInsertSchema, createSelectSchema, jsonSchema } from '../src/index.ts'; +import { expectSchemaShape } from './utils.ts'; + +const customInt = customType<{ data: number }>({ + dataType() { + return 'int'; + }, +}); + +const testTable = singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }).notNull(), + bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), + binary: binary('binary').notNull(), + boolean: boolean('boolean').notNull(), + bson: bson('bson').notNull(), + char: char('char', { length: 4 }).notNull(), + charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), + customInt: customInt('customInt').notNull(), + date: date('date').notNull(), + dateString: date('dateString', { mode: 'string' }).notNull(), + datetime: datetime('datetime').notNull(), + datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), + decimal: decimal('decimal').notNull(), + double: double('double').notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + float: float('float').notNull(), + int: int('int').notNull(), + json: json('json').notNull(), + mediumint: mediumint('mediumint').notNull(), + real: real('real').notNull(), + serial: serial('serial').notNull(), + smallint: smallint('smallint').notNull(), + text: text('text').notNull(), + textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), + tinytext: tinytext('tinytext').notNull(), + tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + mediumtext: mediumtext('mediumtext').notNull(), + mediumtextEnum: mediumtext('mediumtextEnum', { + enum: ['a', 'b', 'c'], + }).notNull(), + longtext: longtext('longtext').notNull(), + longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + time: time('time').notNull(), + timestamp: timestamp('timestamp').notNull(), + timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), + tinyint: tinyint('tinyint').notNull(), + varbinary: varbinary('varbinary', { length: 200 }).notNull(), + varchar: varchar('varchar', { length: 200 }).notNull(), + varcharEnum: varchar('varcharEnum', { + length: 1, + enum: ['a', 'b', 'c'], + }).notNull(), + year: year('year').notNull(), + autoIncrement: int('autoIncrement').notNull().autoincrement(), +}); + +const testTableRow = { + bigint: BigInt(1), + bigintNumber: 1, + binary: 'binary', + boolean: true, + bson: BSON.serialize({ + data: 1, + }), + char: 'char', + charEnum: 'a' as const, + customInt: { data: 1 }, + date: new Date(), + dateString: new Date().toISOString(), + datetime: new Date(), + datetimeString: new Date().toISOString(), + decimal: '1.1', + double: 1.1, + enum: 'a' as const, + float: 1.1, + int: 1, + json: { data: 1 }, + mediumint: 1, + real: 1.1, + serial: 1, + smallint: 1, + text: 'text', + textEnum: 'a' as const, + tinytext: 'tinytext', + tinytextEnum: 'a' as const, + mediumtext: 'mediumtext', + mediumtextEnum: 'a' as const, + longtext: 'longtext', + longtextEnum: 'a' as const, + time: '00:00:00', + timestamp: new Date(), + timestampString: new Date().toISOString(), + tinyint: 1, + varbinary: 'A'.repeat(200), + varchar: 'A'.repeat(200), + varcharEnum: 'a' as const, + year: 2021, + autoIncrement: 1, +}; + +test('insert valid row', () => { + const schema = createInsertSchema(testTable); + + expect(parse(schema, testTableRow)).toStrictEqual(testTableRow); +}); + +test('insert invalid varchar length', () => { + const schema = createInsertSchema(testTable); + + expect(() => + parse(schema, { + ...testTableRow, + varchar: 'A'.repeat(201), + }) + ).toThrow(undefined); +}); + +test('insert smaller char length should work', () => { + const schema = createInsertSchema(testTable); + + const input = { ...testTableRow, char: 'abc' }; + + expect(parse(schema, input)).toStrictEqual(input); +}); + +test('insert larger char length should fail', () => { + const schema = createInsertSchema(testTable); + + expect(() => parse(schema, { ...testTableRow, char: 'abcde' })).toThrow(undefined); +}); + +test('insert schema', (t) => { + const actual = createInsertSchema(testTable); + + const expected = object({ + bigint: valibigint(), + bigintNumber: number(), + binary: string(), + boolean: valiboolean(), + bson: any(), + char: string([minLength(4), maxLength(4)]), + charEnum: picklist([ + 'a', + 'b', + 'c', + ]), + customInt: any(), + date: valiDate(), + dateString: string(), + datetime: valiDate(), + datetimeString: string(), + decimal: string(), + double: number(), + enum: picklist([ + 'a', + 'b', + 'c', + ]), + float: number(), + int: number(), + json: jsonSchema, + mediumint: number(), + real: number(), + serial: optional(number()), + smallint: number(), + text: string(), + textEnum: picklist([ + 'a', + 'b', + 'c', + ]), + tinytext: string(), + tinytextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + mediumtext: string(), + mediumtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + longtext: string(), + longtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + time: string(), + timestamp: valiDate(), + timestampString: string(), + tinyint: number(), + varbinary: string([maxLength(200)]), + varchar: string([maxLength(200)]), + varcharEnum: picklist([ + 'a', + 'b', + 'c', + ]), + year: number(), + autoIncrement: optional(number()), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema', (t) => { + const actual = createSelectSchema(testTable); + + const expected = object({ + bigint: valibigint(), + bigintNumber: number(), + binary: string(), + boolean: valiboolean(), + bson: any(), + char: string([minLength(4), maxLength(4)]), + charEnum: picklist([ + 'a', + 'b', + 'c', + ]), + customInt: any(), + date: valiDate(), + dateString: string(), + datetime: valiDate(), + datetimeString: string(), + decimal: string(), + double: number(), + enum: picklist([ + 'a', + 'b', + 'c', + ]), + float: number(), + int: number(), + // + json: jsonSchema, + mediumint: number(), + real: number(), + serial: number(), + smallint: number(), + text: string(), + textEnum: picklist([ + 'a', + 'b', + 'c', + ]), + tinytext: string(), + tinytextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + mediumtext: string(), + mediumtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + longtext: string(), + longtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + time: string(), + timestamp: valiDate(), + timestampString: string(), + tinyint: number(), + varbinary: string([maxLength(200)]), + varchar: string([maxLength(200)]), + varcharEnum: picklist([ + 'a', + 'b', + 'c', + ]), + year: number(), + autoIncrement: number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema w/ refine', (t) => { + const actual = createSelectSchema(testTable, { + bigint: (_) => valibigint([minValue(0n)]), + }); + + const expected = object({ + bigint: valibigint([minValue(0n)]), + bigintNumber: number(), + binary: string(), + boolean: valiboolean(), + bson: any(), + char: string([minLength(5), maxLength(5)]), + charEnum: picklist([ + 'a', + 'b', + 'c', + ]), + customInt: any(), + date: valiDate(), + dateString: string(), + datetime: valiDate(), + datetimeString: string(), + decimal: string(), + double: number(), + enum: picklist([ + 'a', + 'b', + 'c', + ]), + float: number(), + int: number(), + json: jsonSchema, + mediumint: number(), + real: number(), + serial: number(), + smallint: number(), + text: string(), + textEnum: picklist([ + 'a', + 'b', + 'c', + ]), + tinytext: string(), + tinytextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + mediumtext: string(), + mediumtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + longtext: string(), + longtextEnum: picklist([ + 'a', + 'b', + 'c', + ]), + time: string(), + timestamp: valiDate(), + timestampString: string(), + tinyint: number(), + varbinary: string([maxLength(200)]), + varchar: string([maxLength(200)]), + varcharEnum: picklist([ + 'a', + 'b', + 'c', + ]), + year: number(), + autoIncrement: number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); diff --git a/drizzle-zod/package.json b/drizzle-zod/package.json index 4d3acef81..0b228b8db 100644 --- a/drizzle-zod/package.json +++ b/drizzle-zod/package.json @@ -71,6 +71,7 @@ "@rollup/plugin-terser": "^0.4.1", "@rollup/plugin-typescript": "^11.1.0", "@types/node": "^18.15.10", + "bson": "^6.8.0", "cpy": "^10.1.0", "drizzle-orm": "link:../drizzle-orm/dist", "rimraf": "^5.0.0", diff --git a/drizzle-zod/src/index.ts b/drizzle-zod/src/index.ts index 3f6547e0b..62a592347 100644 --- a/drizzle-zod/src/index.ts +++ b/drizzle-zod/src/index.ts @@ -204,6 +204,8 @@ function mapColumnToSchema(column: Column): z.ZodTypeAny { type = z.string().uuid(); } else if (column.dataType === 'custom') { type = z.any(); + } else if (column.dataType === 'buffer') { + type = z.any(); } else if (column.dataType === 'json') { type = jsonSchema; } else if (column.dataType === 'array') { diff --git a/drizzle-zod/tests/singlestore.test.ts b/drizzle-zod/tests/singlestore.test.ts new file mode 100644 index 000000000..4613a3db4 --- /dev/null +++ b/drizzle-zod/tests/singlestore.test.ts @@ -0,0 +1,298 @@ +import { BSON } from 'bson'; +import { + bigint, + binary, + boolean, + bson, + char, + customType, + date, + datetime, + decimal, + double, + float, + int, + json, + longtext, + mediumint, + mediumtext, + real, + serial, + singlestoreEnum, + singlestoreTable, + smallint, + text, + time, + timestamp, + tinyint, + tinytext, + varbinary, + varchar, + year, +} from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createInsertSchema, createSelectSchema, jsonSchema } from '~/index'; +import { expectSchemaShape } from './utils.ts'; + +const customInt = customType<{ data: number }>({ + dataType() { + return 'int'; + }, +}); + +const testTable = singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }).notNull(), + bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), + binary: binary('binary').notNull(), + boolean: boolean('boolean').notNull(), + bson: bson('bson').notNull(), + char: char('char', { length: 4 }).notNull(), + charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), + customInt: customInt('customInt').notNull(), + date: date('date').notNull(), + dateString: date('dateString', { mode: 'string' }).notNull(), + datetime: datetime('datetime').notNull(), + datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), + decimal: decimal('decimal').notNull(), + double: double('double').notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + float: float('float').notNull(), + int: int('int').notNull(), + json: json('json').notNull(), + mediumint: mediumint('mediumint').notNull(), + real: real('real').notNull(), + serial: serial('serial').notNull(), + smallint: smallint('smallint').notNull(), + text: text('text').notNull(), + textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), + tinytext: tinytext('tinytext').notNull(), + tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + mediumtext: mediumtext('mediumtext').notNull(), + mediumtextEnum: mediumtext('mediumtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + longtext: longtext('longtext').notNull(), + longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), + time: time('time').notNull(), + timestamp: timestamp('timestamp').notNull(), + timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), + tinyint: tinyint('tinyint').notNull(), + varbinary: varbinary('varbinary', { length: 200 }).notNull(), + varchar: varchar('varchar', { length: 200 }).notNull(), + varcharEnum: varchar('varcharEnum', { length: 1, enum: ['a', 'b', 'c'] }).notNull(), + year: year('year').notNull(), + autoIncrement: int('autoIncrement').notNull().autoincrement(), +}); + +const testTableRow = { + bigint: BigInt(1), + bigintNumber: 1, + binary: 'binary', + boolean: true, + bson: BSON.serialize({ + data: 1, + }), + char: 'char', + charEnum: 'a', + customInt: { data: 1 }, + date: new Date(), + dateString: new Date().toISOString(), + datetime: new Date(), + datetimeString: new Date().toISOString(), + decimal: '1.1', + double: 1.1, + enum: 'a', + float: 1.1, + int: 1, + json: { data: 1 }, + mediumint: 1, + real: 1.1, + serial: 1, + smallint: 1, + text: 'text', + textEnum: 'a', + tinytext: 'tinytext', + tinytextEnum: 'a', + mediumtext: 'mediumtext', + mediumtextEnum: 'a', + longtext: 'longtext', + longtextEnum: 'a', + time: '00:00:00', + timestamp: new Date(), + timestampString: new Date().toISOString(), + tinyint: 1, + varbinary: 'A'.repeat(200), + varchar: 'A'.repeat(200), + varcharEnum: 'a', + year: 2021, + autoIncrement: 1, +}; + +test('insert valid row', () => { + const schema = createInsertSchema(testTable); + + expect(schema.safeParse(testTableRow).success).toBeTruthy(); +}); + +test('insert invalid varchar length', () => { + const schema = createInsertSchema(testTable); + + expect(schema.safeParse({ ...testTableRow, varchar: 'A'.repeat(201) }).success).toBeFalsy(); +}); + +test('insert smaller char length should work', () => { + const schema = createInsertSchema(testTable); + + expect(schema.safeParse({ ...testTableRow, char: 'abc' }).success).toBeTruthy(); +}); + +test('insert larger char length should fail', () => { + const schema = createInsertSchema(testTable); + + expect(schema.safeParse({ ...testTableRow, char: 'abcde' }).success).toBeFalsy(); +}); + +test('insert schema', (t) => { + const actual = createInsertSchema(testTable); + + const expected = z.object({ + bigint: z.bigint(), + bigintNumber: z.number(), + binary: z.string(), + boolean: z.boolean(), + bson: z.any(), + char: z.string().length(4), + charEnum: z.enum(['a', 'b', 'c']), + customInt: z.any(), + date: z.date(), + dateString: z.string(), + datetime: z.date(), + datetimeString: z.string(), + decimal: z.string(), + double: z.number(), + enum: z.enum(['a', 'b', 'c']), + float: z.number(), + int: z.number(), + json: jsonSchema, + mediumint: z.number(), + real: z.number(), + serial: z.number().optional(), + smallint: z.number(), + text: z.string(), + textEnum: z.enum(['a', 'b', 'c']), + tinytext: z.string(), + tinytextEnum: z.enum(['a', 'b', 'c']), + mediumtext: z.string(), + mediumtextEnum: z.enum(['a', 'b', 'c']), + longtext: z.string(), + longtextEnum: z.enum(['a', 'b', 'c']), + time: z.string(), + timestamp: z.date(), + timestampString: z.string(), + tinyint: z.number(), + varbinary: z.string().max(200), + varchar: z.string().max(200), + varcharEnum: z.enum(['a', 'b', 'c']), + year: z.number(), + autoIncrement: z.number().optional(), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema', (t) => { + const actual = createSelectSchema(testTable); + + const expected = z.object({ + bigint: z.bigint(), + bigintNumber: z.number(), + binary: z.string(), + boolean: z.boolean(), + bson: z.any(), + char: z.string().length(4), + charEnum: z.enum(['a', 'b', 'c']), + customInt: z.any(), + date: z.date(), + dateString: z.string(), + datetime: z.date(), + datetimeString: z.string(), + decimal: z.string(), + double: z.number(), + enum: z.enum(['a', 'b', 'c']), + float: z.number(), + int: z.number(), + json: jsonSchema, + mediumint: z.number(), + real: z.number(), + serial: z.number(), + smallint: z.number(), + text: z.string(), + textEnum: z.enum(['a', 'b', 'c']), + tinytext: z.string(), + tinytextEnum: z.enum(['a', 'b', 'c']), + mediumtext: z.string(), + mediumtextEnum: z.enum(['a', 'b', 'c']), + longtext: z.string(), + longtextEnum: z.enum(['a', 'b', 'c']), + time: z.string(), + timestamp: z.date(), + timestampString: z.string(), + tinyint: z.number(), + varbinary: z.string().max(200), + varchar: z.string().max(200), + varcharEnum: z.enum(['a', 'b', 'c']), + year: z.number(), + autoIncrement: z.number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); + +test('select schema w/ refine', (t) => { + const actual = createSelectSchema(testTable, { + bigint: (schema) => schema.bigint.positive(), + }); + + const expected = z.object({ + bigint: z.bigint().positive(), + bigintNumber: z.number(), + binary: z.string(), + boolean: z.boolean(), + bson: z.any(), + char: z.string().length(5), + charEnum: z.enum(['a', 'b', 'c']), + customInt: z.any(), + date: z.date(), + dateString: z.string(), + datetime: z.date(), + datetimeString: z.string(), + decimal: z.string(), + double: z.number(), + enum: z.enum(['a', 'b', 'c']), + float: z.number(), + int: z.number(), + json: jsonSchema, + mediumint: z.number(), + real: z.number(), + serial: z.number(), + smallint: z.number(), + text: z.string(), + textEnum: z.enum(['a', 'b', 'c']), + tinytext: z.string(), + tinytextEnum: z.enum(['a', 'b', 'c']), + mediumtext: z.string(), + mediumtextEnum: z.enum(['a', 'b', 'c']), + longtext: z.string(), + longtextEnum: z.enum(['a', 'b', 'c']), + time: z.string(), + timestamp: z.date(), + timestampString: z.string(), + tinyint: z.number(), + varbinary: z.string().max(200), + varchar: z.string().max(200), + varcharEnum: z.enum(['a', 'b', 'c']), + year: z.number(), + autoIncrement: z.number(), + }); + + expectSchemaShape(t, expected).from(actual); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 388f8017c..c51a22b93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,6 +182,9 @@ importers: better-sqlite3: specifier: ^9.4.3 version: 9.6.0 + bson: + specifier: ^6.8.0 + version: 6.8.0 camelcase: specifier: ^7.0.1 version: 7.0.1 @@ -310,7 +313,7 @@ importers: version: 0.9.0 '@op-engineering/op-sqlite': specifier: ^2.0.16 - version: 2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + version: 2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1) '@opentelemetry/api': specifier: ^1.4.1 version: 1.8.0 @@ -350,6 +353,9 @@ importers: better-sqlite3: specifier: ^8.4.0 version: 8.7.0 + bson: + specifier: ^6.8.0 + version: 6.8.0 bun-types: specifier: ^0.6.6 version: 0.6.14 @@ -358,7 +364,7 @@ importers: version: 10.1.0 expo-sqlite: specifier: ^13.2.0 - version: 13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + version: 13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) knex: specifier: ^2.4.2 version: 2.5.1(better-sqlite3@8.7.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7) @@ -419,6 +425,9 @@ importers: '@types/node': specifier: ^18.15.10 version: 18.15.10 + bson: + specifier: ^6.8.0 + version: 6.8.0 cpy: specifier: ^10.1.0 version: 10.1.0 @@ -452,6 +461,9 @@ importers: '@types/node': specifier: ^18.15.10 version: 18.15.10 + bson: + specifier: ^6.8.0 + version: 6.8.0 cpy: specifier: ^10.1.0 version: 10.1.0 @@ -488,6 +500,9 @@ importers: '@types/node': specifier: ^18.15.10 version: 18.15.10 + bson: + specifier: ^6.8.0 + version: 6.8.0 cpy: specifier: ^10.1.0 version: 10.1.0 @@ -4761,6 +4776,10 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + bson@6.8.0: + resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} + engines: {node: '>=16.20.1'} + buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} @@ -10258,8 +10277,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sso-oidc': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10345,11 +10364,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)': + '@aws-sdk/client-sso-oidc@3.583.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10388,7 +10407,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: - - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso@3.478.0': @@ -10655,11 +10673,11 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/client-sts@3.583.0': + '@aws-sdk/client-sts@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10698,6 +10716,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.477.0': @@ -10852,7 +10871,7 @@ snapshots: '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)': dependencies: - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-process': 3.577.0 '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) @@ -11059,7 +11078,7 @@ snapshots: '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.583.0)': dependencies: - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 @@ -11260,7 +11279,7 @@ snapshots: '@aws-sdk/token-providers@3.568.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 @@ -11269,7 +11288,7 @@ snapshots: '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 @@ -12941,7 +12960,7 @@ snapshots: mv: 2.1.1 safe-json-stringify: 1.2.0 - '@expo/cli@0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)': + '@expo/cli@0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(utf-8-validate@6.0.3)': dependencies: '@babel/runtime': 7.24.6 '@expo/code-signing-certificates': 0.0.5 @@ -12959,7 +12978,7 @@ snapshots: '@expo/rudder-sdk-node': 1.1.1(encoding@0.1.13) '@expo/spawn-async': 1.7.2 '@expo/xcpretty': 4.3.1 - '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@urql/core': 2.3.6(graphql@15.8.0) '@urql/exchange-retry': 0.3.0(graphql@15.8.0) accepts: 1.3.8 @@ -13585,10 +13604,10 @@ snapshots: rimraf: 3.0.2 optional: true - '@op-engineering/op-sqlite@2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': + '@op-engineering/op-sqlite@2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1)': dependencies: react: 18.3.1 - react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3) '@opentelemetry/api@1.8.0': {} @@ -13725,7 +13744,7 @@ snapshots: transitivePeerDependencies: - encoding - '@react-native-community/cli-server-api@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native-community/cli-server-api@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@react-native-community/cli-debugger-ui': 13.6.6 '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) @@ -13735,7 +13754,7 @@ snapshots: nocache: 3.0.4 pretty-format: 26.6.2 serve-static: 1.15.0 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -13762,14 +13781,14 @@ snapshots: dependencies: joi: 17.13.1 - '@react-native-community/cli@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native-community/cli@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@react-native-community/cli-clean': 13.6.6(encoding@0.1.13) '@react-native-community/cli-config': 13.6.6(encoding@0.1.13) '@react-native-community/cli-debugger-ui': 13.6.6 '@react-native-community/cli-doctor': 13.6.6(encoding@0.1.13) '@react-native-community/cli-hermes': 13.6.6(encoding@0.1.13) - '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) '@react-native-community/cli-types': 13.6.6 chalk: 4.1.2 @@ -13858,16 +13877,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-native/community-cli-plugin@0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native/community-cli-plugin@0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: - '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) - '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native/metro-babel-transformer': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6)) chalk: 4.1.2 execa: 5.1.1 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) - metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-core: 0.80.9 node-fetch: 2.7.0(encoding@0.1.13) querystring: 0.2.1 @@ -13882,7 +13901,7 @@ snapshots: '@react-native/debugger-frontend@0.74.83': {} - '@react-native/dev-middleware@0.74.83(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native/dev-middleware@0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@isaacs/ttlcache': 1.4.1 '@react-native/debugger-frontend': 0.74.83 @@ -13896,7 +13915,7 @@ snapshots: selfsigned: 2.4.1 serve-static: 1.15.0 temp-dir: 2.0.0 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -13919,12 +13938,12 @@ snapshots: '@react-native/normalize-colors@0.74.83': {} - '@react-native/virtualized-lists@0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': + '@react-native/virtualized-lists@0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.3.1 - react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3) optionalDependencies: '@types/react': 18.3.1 @@ -15205,7 +15224,7 @@ snapshots: pathe: 1.1.2 picocolors: 1.0.1 sirv: 2.0.4 - vitest: 1.6.0(@types/node@18.19.33)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) + vitest: 1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) '@vitest/utils@1.6.0': dependencies: @@ -15671,6 +15690,8 @@ snapshots: dependencies: node-int64: 0.4.0 + bson@6.8.0: {} + buffer-alloc-unsafe@1.1.0: {} buffer-alloc@1.2.0: @@ -17238,35 +17259,35 @@ snapshots: expand-template@2.0.3: {} - expo-asset@10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-asset@10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@react-native/assets-registry': 0.74.83 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) - expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-constants@16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-constants@16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@expo/config': 9.0.2 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) transitivePeerDependencies: - supports-color - expo-file-system@17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-file-system@17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - expo-font@12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-font@12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) fontfaceobserver: 2.3.0 - expo-keep-awake@13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-keep-awake@13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) expo-modules-autolinking@1.11.1: dependencies: @@ -17280,24 +17301,24 @@ snapshots: dependencies: invariant: 2.2.4 - expo-sqlite@13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-sqlite@13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@expo/websql': 1.0.1 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13): + expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/runtime': 7.24.6 - '@expo/cli': 0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1) + '@expo/cli': 0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(utf-8-validate@6.0.3) '@expo/config': 9.0.2 '@expo/config-plugins': 8.0.4 '@expo/metro-config': 0.18.4 '@expo/vector-icons': 14.0.2 babel-preset-expo: 11.0.6(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6)) - expo-asset: 10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-file-system: 17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-font: 12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-keep-awake: 13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + expo-asset: 10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-file-system: 17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-font: 12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-keep-awake: 13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) expo-modules-autolinking: 1.11.1 expo-modules-core: 1.12.11 fbemitter: 3.0.0(encoding@0.1.13) @@ -18774,12 +18795,12 @@ snapshots: metro-core: 0.80.9 rimraf: 3.0.2 - metro-config@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro-config@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: connect: 3.7.0 cosmiconfig: 5.2.1 jest-validate: 29.7.0 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-cache: 0.80.9 metro-core: 0.80.9 metro-runtime: 0.80.9 @@ -18855,13 +18876,13 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-worker@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro-transform-worker@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/core': 7.24.6 '@babel/generator': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-babel-transformer: 0.80.9 metro-cache: 0.80.9 metro-cache-key: 0.80.9 @@ -18875,7 +18896,7 @@ snapshots: - supports-color - utf-8-validate - metro@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/code-frame': 7.24.6 '@babel/core': 7.24.6 @@ -18901,7 +18922,7 @@ snapshots: metro-babel-transformer: 0.80.9 metro-cache: 0.80.9 metro-cache-key: 0.80.9 - metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-core: 0.80.9 metro-file-map: 0.80.9 metro-resolver: 0.80.9 @@ -18909,7 +18930,7 @@ snapshots: metro-source-map: 0.80.9 metro-symbolicate: 0.80.9 metro-transform-plugins: 0.80.9 - metro-transform-worker: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro-transform-worker: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) mime-types: 2.1.35 node-fetch: 2.7.0(encoding@0.1.13) nullthrows: 1.1.1 @@ -18918,7 +18939,7 @@ snapshots: source-map: 0.5.7 strip-ansi: 6.0.1 throat: 5.0.0 - ws: 7.5.9(bufferutil@4.0.8) + ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3) yargs: 17.7.2 transitivePeerDependencies: - bufferutil @@ -19804,10 +19825,10 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-devtools-core@5.2.0(bufferutil@4.0.8): + react-devtools-core@5.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): dependencies: shell-quote: 1.8.1 - ws: 7.5.9(bufferutil@4.0.8) + ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -19820,19 +19841,19 @@ snapshots: react-is@18.3.1: {} - react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1): + react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-platform-android': 13.6.6(encoding@0.1.13) '@react-native-community/cli-platform-ios': 13.6.6(encoding@0.1.13) '@react-native/assets-registry': 0.74.83 '@react-native/codegen': 0.74.83(@babel/preset-env@7.24.6(@babel/core@7.24.6)) - '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native/gradle-plugin': 0.74.83 '@react-native/js-polyfills': 0.74.83 '@react-native/normalize-colors': 0.74.83 - '@react-native/virtualized-lists': 0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@react-native/virtualized-lists': 0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -19851,14 +19872,14 @@ snapshots: pretty-format: 26.6.2 promise: 8.3.0 react: 18.3.1 - react-devtools-core: 5.2.0(bufferutil@4.0.8) + react-devtools-core: 5.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) react-refresh: 0.14.2 react-shallow-renderer: 16.15.0(react@18.3.1) regenerator-runtime: 0.13.11 scheduler: 0.24.0-canary-efb381bbf-20230505 stacktrace-parser: 0.1.10 whatwg-fetch: 3.6.20 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) yargs: 17.7.2 optionalDependencies: '@types/react': 18.3.1 @@ -21711,15 +21732,17 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.0.2 - ws@6.2.2(bufferutil@4.0.8): + ws@6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): dependencies: async-limiter: 1.0.1 optionalDependencies: bufferutil: 4.0.8 + utf-8-validate: 6.0.3 - ws@7.5.9(bufferutil@4.0.8): + ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3): optionalDependencies: bufferutil: 4.0.8 + utf-8-validate: 6.0.3 ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): optionalDependencies: