Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions integration/generated/migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ CREATE TABLE "ArrayTypes" (
"id" TEXT NOT NULL,
"strings" TEXT[],
"ints" INTEGER[],
"floats" DOUBLE PRECISION[],
"bools" BOOLEAN[],
"dateTimes" TIMESTAMP(3)[],
"bigInts" BIGINT[],
"decimals" DECIMAL(65,30)[],
"enums" "Role"[],
"jsonArray" JSONB[],

Expand Down Expand Up @@ -232,6 +236,58 @@ CREATE TABLE "NativeTypes" (
CONSTRAINT "NativeTypes_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "PostgresNativeTypes" (
"id" UUID NOT NULL,
"text" TEXT NOT NULL,
"varchar255" VARCHAR(255) NOT NULL,
"char10" CHAR(10) NOT NULL,
"xml" XML NOT NULL,
"inet" INET NOT NULL,
"bit8" BIT(8) NOT NULL,
"varbit" VARBIT NOT NULL,
"integer" INTEGER NOT NULL,
"smallint" SMALLINT NOT NULL,
"oid" OID NOT NULL,
"bigint" BIGINT NOT NULL,
"doublePrecision" DOUBLE PRECISION NOT NULL,
"real" REAL NOT NULL,
"decimal102" DECIMAL(10,2) NOT NULL,
"money" MONEY NOT NULL,
"timestamp6" TIMESTAMP(6) NOT NULL,
"timestamptz6" TIMESTAMPTZ(6) NOT NULL,
"date" DATE NOT NULL,
"time6" TIME(6) NOT NULL,
"timetz6" TIMETZ(6) NOT NULL,
"json" JSON NOT NULL,
"jsonb" JSONB NOT NULL,
"boolean" BOOLEAN NOT NULL,

CONSTRAINT "PostgresNativeTypes_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "TimestampModel" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "TimestampModel_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "DefaultFunctions" (
"id" TEXT NOT NULL,
"cuidField" TEXT NOT NULL,
"nowField" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"intDefault" INTEGER NOT NULL DEFAULT 0,
"boolDefault" BOOLEAN NOT NULL DEFAULT false,
"strDefault" TEXT NOT NULL DEFAULT 'default',

CONSTRAINT "DefaultFunctions_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "ExcludedModel" (
"id" TEXT NOT NULL,
Expand Down
57 changes: 56 additions & 1 deletion integration/generated/zero/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const scalarTypesTable = table('ScalarTypes')
json: json(),
bigInt: number(),
decimal: number(),
bytes: string(),
})
.primaryKey('id');

Expand All @@ -48,7 +47,11 @@ export const arrayTypesTable = table('ArrayTypes')
id: string(),
strings: json<string[]>(),
ints: json<number[]>(),
floats: json<number[]>(),
bools: json<boolean[]>(),
dateTimes: json<number[]>(),
bigInts: json<number[]>(),
decimals: json<number[]>(),
enums: json<Role[]>(),
jsonArray: json<any[]>(),
})
Expand Down Expand Up @@ -220,6 +223,55 @@ export const nativeTypesTable = table('NativeTypes')
})
.primaryKey('id');

export const postgresNativeTypesTable = table('PostgresNativeTypes')
.columns({
id: string(),
text: string(),
varchar255: string(),
char10: string(),
xml: string(),
inet: string(),
bit8: string(),
varbit: string(),
integer: number(),
smallint: number(),
oid: number(),
bigint: number(),
doublePrecision: number(),
real: number(),
decimal102: number(),
money: number(),
timestamp6: number(),
timestamptz6: number(),
date: number(),
time6: number(),
timetz6: number(),
json: json(),
jsonb: json(),
boolean: boolean(),
})
.primaryKey('id');

export const timestampModelTable = table('TimestampModel')
.columns({
id: string(),
name: string(),
createdAt: number(),
updatedAt: number(),
})
.primaryKey('id');

export const defaultFunctionsTable = table('DefaultFunctions')
.columns({
id: string(),
cuidField: string(),
nowField: number(),
intDefault: number(),
boolDefault: boolean(),
strDefault: string(),
})
.primaryKey('id');

export const minimalModelTable = table('MinimalModel')
.columns({
id: string(),
Expand Down Expand Up @@ -512,6 +564,9 @@ export const schema = createSchema({
memberTable,
enumFieldsTable,
nativeTypesTable,
postgresNativeTypesTable,
timestampModelTable,
defaultFunctionsTable,
minimalModelTable,
reservedWordsTable,
_articleToTagTable,
Expand Down
60 changes: 59 additions & 1 deletion integration/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ enum Status {
/// - Boolean → boolean()
/// - DateTime → number() (timestamp)
/// - Json → json()
/// - Bytes → string() (fallback)
/// - Bytes → excluded (unsupported currently)
model ScalarTypes {
id String @id @default(uuid())
str String
Expand Down Expand Up @@ -69,7 +69,11 @@ model ArrayTypes {
id String @id @default(uuid())
strings String[]
ints Int[]
floats Float[]
bools Boolean[]
dateTimes DateTime[]
bigInts BigInt[]
decimals Decimal[]
enums Role[]
jsonArray Json[]
}
Expand Down Expand Up @@ -292,6 +296,60 @@ model NativeTypes {
jsonb Json @db.JsonB
}

/// TEST: Comprehensive PostgreSQL native types → all map to base Zero types
model PostgresNativeTypes {
id String @id @default(uuid()) @db.Uuid
// String native types
text String @db.Text
varchar255 String @db.VarChar(255)
char10 String @db.Char(10)
xml String @db.Xml
inet String @db.Inet
bit8 String @db.Bit(8)
varbit String @db.VarBit
// Integer native types
integer Int @db.Integer
smallint Int @db.SmallInt
oid Int @db.Oid
// BigInt native types
bigint BigInt @db.BigInt
// Float native types
doublePrecision Float @db.DoublePrecision
real Float @db.Real
// Decimal native types
decimal102 Decimal @db.Decimal(10, 2)
money Decimal @db.Money
// DateTime native types
timestamp6 DateTime @db.Timestamp(6)
timestamptz6 DateTime @db.Timestamptz(6)
date DateTime @db.Date
time6 DateTime @db.Time(6)
timetz6 DateTime @db.Timetz(6)
// Json native types
json Json @db.Json
jsonb Json @db.JsonB
// Boolean native type
boolean Boolean @db.Boolean
}

/// TEST: @updatedAt attribute → DateTime field auto-updated
model TimestampModel {
id String @id @default(uuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

/// TEST: Various @default functions
model DefaultFunctions {
id String @id @default(uuid()) // uuid() default
cuidField String @default(cuid()) // cuid() default
nowField DateTime @default(now()) // now() default
intDefault Int @default(0) // static default
boolDefault Boolean @default(false) // static default
strDefault String @default("default") // static default
}

// ============================================================================
// EXCLUDED MODEL - Tests excludeTables config
// ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prisma-zero",
"version": "0.1.1",
"version": "0.1.2",
"description": "Generate Zero schemas from Prisma ORM schemas",
"type": "module",
"scripts": {
Expand Down
77 changes: 72 additions & 5 deletions src/mappers/schema-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ZeroRelationship,
TransformedSchema,
Config,
ZeroTypeMapping,
} from '../types';
import {mapPrismaTypeToZero} from './type-mapper';
import camelCase from 'camelcase';
Expand Down Expand Up @@ -107,6 +108,13 @@ function createImplicitManyToManyModel(
const columnAType = mapPrismaTypeToZero(idFieldA);
const columnBType = mapPrismaTypeToZero(idFieldB);

if (!columnAType || !columnBType) {
const unsupportedModel = !columnAType ? modelA : modelB;
throw new Error(
`Implicit relation ${relationName ?? 'unknown'}: Model ${unsupportedModel.name} has an unsupported @id field.`,
);
}

return {
tableName,
originalTableName,
Expand Down Expand Up @@ -141,6 +149,20 @@ function mapRelationships(
): Record<string, ZeroRelationship> {
const relationships: Record<string, ZeroRelationship> = {};

const isSupportedField = (target: DMMF.Model, fieldName: string): boolean => {
const field = target.fields.find(f => f.name === fieldName);
if (!field) {
return true;
}
return mapPrismaTypeToZero(field) !== null;
};

const areFieldsSupported = (
target: DMMF.Model,
fieldNames: string[],
): boolean =>
fieldNames.every(fieldName => isSupportedField(target, fieldName));

model.fields
.filter(field => field.relationName)
.forEach(field => {
Expand Down Expand Up @@ -189,18 +211,31 @@ function mapRelationships(
: true
: model.name === modelA.name;

const sourceField = [model.fields.find(f => f.isId)?.name || 'id'];
const destField = [isModelA ? 'A' : 'B'];
const targetDestField = [
targetModel.fields.find(f => f.isId)?.name || 'id',
];

if (
!areFieldsSupported(model, sourceField) ||
!areFieldsSupported(targetModel, targetDestField)
) {
return;
}

// Create a chained relationship through the join table
relationships[field.name] = {
type: 'many',
chain: [
{
sourceField: [model.fields.find(f => f.isId)?.name || 'id'],
destField: [isModelA ? 'A' : 'B'],
sourceField,
destField,
destSchema: getZeroTableName(joinTableName),
},
{
sourceField: [isModelA ? 'B' : 'A'],
destField: [targetModel.fields.find(f => f.isId)?.name || 'id'],
destField: targetDestField,
destSchema: getZeroTableName(targetModel.name),
},
],
Expand All @@ -216,6 +251,13 @@ function mapRelationships(
? ensureStringArray(backReference.relationFromFields)
: [];

if (
!areFieldsSupported(model, sourceFields) ||
!areFieldsSupported(targetModel, destFields)
) {
return;
}

relationships[field.name] = {
sourceField: sourceFields,
destField: destFields,
Expand All @@ -240,6 +282,13 @@ function mapRelationships(
destFields = ensureStringArray(backReference.relationFromFields);
}

if (
!areFieldsSupported(model, sourceFields) ||
!areFieldsSupported(targetModel, destFields)
) {
return;
}

relationships[field.name] = {
sourceField: sourceFields,
destField: destFields,
Expand All @@ -257,12 +306,16 @@ function mapModel(
dmmf: DMMF.Document,
config: Config,
): ZeroModel {
const columns: Record<string, ReturnType<typeof mapPrismaTypeToZero>> = {};
const columns: Record<string, ZeroTypeMapping> = {};

model.fields
.filter(field => !field.relationName)
.forEach(field => {
columns[field.name] = mapPrismaTypeToZero(field);
const mapping = mapPrismaTypeToZero(field);
if (!mapping) {
return;
}
columns[field.name] = mapping;
});

const idField = model.fields.find(f => f.isId)?.name;
Expand All @@ -271,6 +324,20 @@ function mapModel(
throw new Error(`No primary key found for ${model.name}`);
}

const unsupportedPrimaryKeys = primaryKey.filter(fieldName => {
const field = model.fields.find(f => f.name === fieldName);
if (!field) {
return false;
}
return mapPrismaTypeToZero(field) === null;
});

if (unsupportedPrimaryKeys.length > 0) {
throw new Error(
`Primary key field(s) ${unsupportedPrimaryKeys.join(', ')} in ${model.name} are not supported by Zero.`,
);
}

// Use the Prisma model name (optionally camelCased) for the Zero table name.
// If the Prisma model is mapped to a different DB table (@@map) or camelCase
// changes the casing, capture the DB table name in originalTableName so we
Expand Down
Loading