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
1 change: 1 addition & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ early_access: false
reviews:
auto_review:
enabled: true
sequence_diagrams: false
chat:
auto_reply: true
10 changes: 0 additions & 10 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@
"default": "./dist/index.cjs"
}
},
"./client": {
"import": {
"types": "./dist/client.d.ts",
"default": "./dist/client.js"
},
"require": {
"types": "./dist/client.d.cts",
"default": "./dist/client.cjs"
}
},
"./schema": {
"import": {
"types": "./dist/schema.d.ts",
Expand Down
27 changes: 18 additions & 9 deletions packages/runtime/src/client/crud/dialects/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,15 +416,24 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
return this.buildEnumFilter(eb, modelAlias, field, fieldDef, payload);
}

return match(fieldDef.type as BuiltinType)
.with('String', () => this.buildStringFilter(eb, modelAlias, field, payload))
.with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) =>
this.buildNumberFilter(eb, model, modelAlias, field, type, payload),
)
.with('Boolean', () => this.buildBooleanFilter(eb, modelAlias, field, payload))
.with('DateTime', () => this.buildDateTimeFilter(eb, modelAlias, field, payload))
.with('Bytes', () => this.buildBytesFilter(eb, modelAlias, field, payload))
.exhaustive();
return (
match(fieldDef.type as BuiltinType)
.with('String', () => this.buildStringFilter(eb, modelAlias, field, payload))
.with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) =>
this.buildNumberFilter(eb, model, modelAlias, field, type, payload),
)
.with('Boolean', () => this.buildBooleanFilter(eb, modelAlias, field, payload))
.with('DateTime', () => this.buildDateTimeFilter(eb, modelAlias, field, payload))
.with('Bytes', () => this.buildBytesFilter(eb, modelAlias, field, payload))
// TODO: JSON filters
.with('Json', () => {
throw new InternalError('JSON filters are not supported yet');
})
.with('Unsupported', () => {
throw new QueryError(`Unsupported field cannot be used in filters`);
})
.exhaustive()
);
}

private buildLiteralFilter(eb: ExpressionBuilder<any, any>, lhs: Expression<any>, type: BuiltinType, rhs: unknown) {
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/client/crud/dialects/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
.with('DateTime', () =>
value instanceof Date ? value : typeof value === 'string' ? new Date(value) : value,
)
.with('Decimal', () => (value !== null ? value.toString() : value))
.otherwise(() => value);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
.with('DateTime', () => (value instanceof Date ? value.toISOString() : value))
.with('Decimal', () => (value as Decimal).toString())
.with('Bytes', () => Buffer.from(value as Uint8Array))
.with('Json', () => JSON.stringify(value))
.otherwise(() => value);
}
}
Expand Down
23 changes: 14 additions & 9 deletions packages/runtime/src/client/crud/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,20 @@ export class InputValidator<Schema extends SchemaDef> {
}

private makePrimitiveFilterSchema(type: BuiltinType, optional: boolean) {
return match(type)
.with('String', () => this.makeStringFilterSchema(optional))
.with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) =>
this.makeNumberFilterSchema(this.makePrimitiveSchema(type), optional),
)
.with('Boolean', () => this.makeBooleanFilterSchema(optional))
.with('DateTime', () => this.makeDateTimeFilterSchema(optional))
.with('Bytes', () => this.makeBytesFilterSchema(optional))
.exhaustive();
return (
match(type)
.with('String', () => this.makeStringFilterSchema(optional))
.with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) =>
this.makeNumberFilterSchema(this.makePrimitiveSchema(type), optional),
)
.with('Boolean', () => this.makeBooleanFilterSchema(optional))
.with('DateTime', () => this.makeDateTimeFilterSchema(optional))
.with('Bytes', () => this.makeBytesFilterSchema(optional))
// TODO: JSON filters
.with('Json', () => z.any())
.with('Unsupported', () => z.never())
.exhaustive()
);
}

private makeDateTimeFilterSchema(optional: boolean): ZodType {
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/client/helpers/schema-db-pusher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
.with('Decimal', () => 'decimal')
.with('DateTime', () => 'timestamp')
.with('Bytes', () => (this.schema.provider.type === 'postgresql' ? 'bytea' : 'blob'))
.with('Json', () => 'jsonb')
.otherwise(() => {
throw new Error(`Unsupported field type: ${type}`);
});
Expand Down
11 changes: 11 additions & 0 deletions packages/runtime/src/client/result-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class ResultProcessor<Schema extends SchemaDef> {
.with('Bytes', () => this.transformBytes(value))
.with('Decimal', () => this.transformDecimal(value))
.with('BigInt', () => this.transformBigInt(value))
.with('Json', () => this.transformJson(value))
.otherwise(() => value);
}

Expand Down Expand Up @@ -156,4 +157,14 @@ export class ResultProcessor<Schema extends SchemaDef> {
}
}
}

private transformJson(value: unknown) {
return match(this.schema.provider.type)
.with('sqlite', () => {
// better-sqlite3 returns JSON as string
invariant(typeof value === 'string', 'Expected string, got ' + typeof value);
return JSON.parse(value as string);
})
.otherwise(() => value);
}
}
2 changes: 1 addition & 1 deletion packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ZenStackClient, type ClientContract } from './client';
export * from './client';
89 changes: 73 additions & 16 deletions packages/runtime/test/client-api/type-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ import Decimal from 'decimal.js';
import { describe, expect, it } from 'vitest';
import { createTestClient } from '../utils';

describe('zmodel type coverage tests', () => {
it('supports all types', async () => {
const db = await createTestClient(
`
const PG_DB_NAME = 'client-api-type-coverage-tests';

describe.each(['sqlite', 'postgresql'] as const)('zmodel type coverage tests', (provider) => {
it('supports all types - plain', async () => {
const date = new Date();
const data = {
id: '1',
String: 'string',
Int: 100,
BigInt: BigInt(9007199254740991),
DateTime: date,
Float: 1.23,
Decimal: new Decimal(1.2345),
Boolean: true,
Bytes: new Uint8Array([1, 2, 3, 4]),
Json: { foo: 'bar' },
};

let db: any;
try {
db = await createTestClient(
`
model Foo {
id String @id @default(cuid())

Expand All @@ -17,28 +35,67 @@ describe('zmodel type coverage tests', () => {
Decimal Decimal
Boolean Boolean
Bytes Bytes
Json Json

@@allow('all', true)
}
`,
);
{ provider, dbName: PG_DB_NAME },
);

await db.foo.create({ data });
await expect(db.foo.findUnique({ where: { id: '1' } })).resolves.toMatchObject(data);
} finally {
await db?.$disconnect();
}
});

it('supports all types - array', async () => {
if (provider === 'sqlite') {
return;
}

const date = new Date();
const data = {
id: '1',
String: 'string',
Int: 100,
BigInt: BigInt(9007199254740991),
DateTime: date,
Float: 1.23,
Decimal: new Decimal(1.2345),
Boolean: true,
Bytes: new Uint8Array([1, 2, 3, 4]),
String: ['string'],
Int: [100],
BigInt: [BigInt(9007199254740991)],
DateTime: [date],
Float: [1.23],
Decimal: [new Decimal(1.2345)],
Boolean: [true],
Bytes: [new Uint8Array([1, 2, 3, 4])],
Json: [{ foo: 'bar' }],
};

await db.foo.create({ data });
let db: any;
try {
db = await createTestClient(
`
model Foo {
id String @id @default(cuid())

String String[]
Int Int[]
BigInt BigInt[]
DateTime DateTime[]
Float Float[]
Decimal Decimal[]
Boolean Boolean[]
Bytes Bytes[]
Json Json[]

@@allow('all', true)
}
`,
{ provider, dbName: PG_DB_NAME },
);

const r = await db.foo.findUnique({ where: { id: '1' } });
expect(r.Bytes).toEqual(data.Bytes);
await db.foo.create({ data });
await expect(db.foo.findUnique({ where: { id: '1' } })).resolves.toMatchObject(data);
} finally {
await db?.$disconnect();
}
});
});
1 change: 0 additions & 1 deletion packages/runtime/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
"noEmit": true,
"noImplicitAny": false
},

"include": ["test/**/*.ts"]
}
1 change: 0 additions & 1 deletion packages/runtime/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
client: 'src/client/index.ts',
schema: 'src/schema/index.ts',
'plugins/policy': 'src/plugins/policy/index.ts',
},
Expand Down
12 changes: 11 additions & 1 deletion packages/sdk/src/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,17 @@ export type ProcedureDef = {
mutation?: boolean;
};

export type BuiltinType = 'String' | 'Boolean' | 'Int' | 'Float' | 'BigInt' | 'Decimal' | 'DateTime' | 'Bytes';
export type BuiltinType =
| 'String'
| 'Boolean'
| 'Int'
| 'Float'
| 'BigInt'
| 'Decimal'
| 'DateTime'
| 'Bytes'
| 'Json'
| 'Unsupported';

export type MappedBuiltinType = string | boolean | number | bigint | Decimal | Date;

Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/ts-schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,8 @@ export class TsSchemaGenerator {
? ts.factory.createStringLiteral(field.type.type)
: field.type.reference
? ts.factory.createStringLiteral(field.type.reference.$refText)
: ts.factory.createStringLiteral('unknown');
: // `Unsupported` type
ts.factory.createStringLiteral('Unsupported');
}

private createEnumObject(e: Enum) {
Expand Down
2 changes: 1 addition & 1 deletion packages/tanstack-query/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
UseQueryOptions,
UseQueryResult,
} from '@tanstack/react-query';
import type { CreateArgs, FindArgs, ModelResult, SelectSubset } from '@zenstackhq/runtime/client';
import type { CreateArgs, FindArgs, ModelResult, SelectSubset } from '@zenstackhq/runtime';
import type { GetModels, SchemaDef } from '@zenstackhq/runtime/schema';

export type toHooks<Schema extends SchemaDef> = {
Expand Down