Skip to content
Draft
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
4 changes: 2 additions & 2 deletions packages/client/src/schema/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ImageTransformations, transformImage } from '../files/transformations';
import { Buffer } from '../util/buffer';
import { compactObject, isDefined } from '../util/lang';
import { StringKeys } from '../util/types';
import { Identifiable, InputXataFile } from './record';
import { OldIdentifiable, InputXataFile } from './record';

export type XataFileEditableFields = Partial<Pick<XataArrayFile, keyof InputFileEntry>>;
export type XataFileFields = Partial<
Expand Down Expand Up @@ -195,7 +195,7 @@ export class XataFile {
}
}

export type XataArrayFile = Identifiable & XataFile;
export type XataArrayFile = OldIdentifiable & XataFile;

export const parseInputFileEntry = async (entry: InputXataFile): Promise<InputFileEntry | null> => {
if (!isDefined(entry)) return null;
Expand Down
162 changes: 74 additions & 88 deletions packages/client/src/schema/identifiable.spec.ts
Original file line number Diff line number Diff line change
@@ -1,119 +1,105 @@
import { test } from 'vitest';
import { NewIdentifiable, NewIdentifierKey, NewIndentifierValue } from './identifiable';
import { Identifiers } from './identifiable';

const tables = [

Check warning on line 4 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'tables' is assigned a value but only used as a type. Allowed unused vars must match /^_/u
{
name: 'teams',
primaryKey: ['xata_id'],
name: 'PrimaryKey',
primaryKey: ['pk'],
columns: [
{ name: 'xata_id', type: 'boolean', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true },
{ name: 'email', type: 'email', unique: true },
{ name: 'pet', type: 'link', link: { table: 'pets' } },
{ name: 'account_value', type: 'int' },
{ name: 'vector', type: 'vector', vector: { dimension: 4 } }
]
},
{
name: 'users',
primaryKey: ['userdefined'],
columns: [
{ name: 'userdefined', type: 'int', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true },
{ name: 'pk', type: 'int', notNull: true, unique: true },
{ name: 'email', type: 'email', unique: true }
]
},
{
name: 'pets',
primaryKey: [],
columns: [
{ name: 'xata_id', type: 'text', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true },
{ name: 'name', type: 'string', notNull: true, unique: true }
]
},
{
name: 'datetime',
primaryKey: ['xata_id'],
columns: [
{ name: 'xata_id', type: 'datetime', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
]
},
{
name: 'multiple',
primaryKey: ['xata_id'],
columns: [
{ name: 'xata_id', type: 'multiple', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
]
},
{
name: 'vector',
primaryKey: ['xata_id'],
name: 'NullablePrimaryKey',
primaryKey: ['pk'],
columns: [
{ name: 'xata_id', type: 'vector', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
{ name: 'pk', type: 'int', notNull: false, unique: true },
{ name: 'name', type: 'string' }
]
},
{
name: 'boolean[]',
primaryKey: ['xata_id'],
name: 'UniqueNotNull',
primaryKey: [],
columns: [
{ name: 'xata_id', type: 'boolean[]', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
{ name: 'foo', type: 'string', notNull: true, unique: true },
{ name: 'name', type: 'string' }
]
},
{
name: 'jsonb',
primaryKey: ['xata_id'],
name: 'CompositePrimaryKey',
primaryKey: ['a', 'b'],
columns: [
{ name: 'xata_id', type: 'json', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
{ name: 'a', type: 'int', notNull: true, unique: true },
{ name: 'b', type: 'string', notNull: true, unique: true },
{ name: 'name', type: 'string' }
]
},
{
name: 'unknown',
primaryKey: [],
name: 'Mixture',
primaryKey: ['a', 'b'],
columns: [
{ name: 'xata_id', type: 'text', notNull: true, unique: true },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
{ name: 'a', type: 'int', notNull: true, unique: true },
{ name: 'b', type: 'string', notNull: true, unique: true },
{ name: 'c', type: 'string', notNull: true, unique: true },
{ name: 'name', type: 'string' }
]
},
{
name: 'neither',
name: 'None',
primaryKey: [],
columns: [
{ name: 'xata_id', type: 'text' },
{ name: 'xata_version', type: 'int', notNull: true },
{ name: 'xata_createdat', type: 'datetime', notNull: true },
{ name: 'xata_updatedat', type: 'datetime', notNull: true }
]
columns: [{ name: 'name', type: 'string' }]
}
] as const;

type DbIndentifiable = NewIdentifiable<typeof tables>['users'];
type DbIndentifiableKey = NewIdentifierKey<DbIndentifiable>;
type DbIndentifiableValue = NewIndentifierValue<DbIndentifiable>;
test('PrimaryKey', () => {
type Type = Identifiers<typeof tables>['PrimaryKey'];

const user: Type = { pk: 1 };

Check warning on line 58 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user' is assigned a value but never used. Allowed unused vars must match /^_/u
const user1: Type = 1;

Check warning on line 59 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user1' is assigned a value but never used. Allowed unused vars must match /^_/u
// @ts-expect-error
const user2: Type = { pk: '1' };

Check warning on line 61 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user2' is assigned a value but never used. Allowed unused vars must match /^_/u
// @ts-expect-error
const user3: Type = { pk: 1, xata_version: 1 };

Check warning on line 63 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user3' is assigned a value but never used. Allowed unused vars must match /^_/u
});

test('NullablePrimaryKey', () => {
type Type = Identifiers<typeof tables>['NullablePrimaryKey'];

const user: Type = { pk: 1 };

Check warning on line 69 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user' is assigned a value but never used. Allowed unused vars must match /^_/u
const user1: Type = 1;

Check warning on line 70 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user1' is assigned a value but never used. Allowed unused vars must match /^_/u
// @ts-expect-error
const user2: Type = { pk: '1' };

Check warning on line 72 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user2' is assigned a value but never used. Allowed unused vars must match /^_/u
// @ts-expect-error
const user3: Type = { pk: 1, xata_version: 1 };

Check warning on line 74 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user3' is assigned a value but never used. Allowed unused vars must match /^_/u
});

test('UniqueNotNull', () => {
type Type = Identifiers<typeof tables>['UniqueNotNull'];

const user: Type = { foo: 'bar' };

Check warning on line 80 in packages/client/src/schema/identifiable.spec.ts

View workflow job for this annotation

GitHub Actions / test

'user' is assigned a value but never used. Allowed unused vars must match /^_/u
// @ts-expect-error
const user1: Type = { foo: 1 };
// @ts-expect-error
const user2: Type = { foo: 'bar', xata_version: 1 };
});

test('CompositePrimaryKey', () => {
type Type = Identifiers<typeof tables>['CompositePrimaryKey'];

const user: Type = { a: 1, b: '2' };
// @ts-expect-error
const user1: Type = { a: '1', b: '2' };
// @ts-expect-error
const user2: Type = { a: 1, b: '2', xata_version: 1 };
});

test('Mixture', () => {
type Type = Identifiers<typeof tables>['Mixture'];

test('fake test', () => {
// This is a fake test to make sure that the type definitions in this file are working
const user: Type = { a: 1, b: '2', c: '3' };
// @ts-expect-error
const user1: Type = { a: '1', b: '2', c: '3' };
// @ts-expect-error
const user2: Type = { a: 1, b: '2', c: '3', xata_version: 1 };
});
70 changes: 48 additions & 22 deletions packages/client/src/schema/identifiable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Values } from '../util/types';
import { XataFile } from './files';
import { TableSchema } from './inference';
import { InputXataFile, NumericOperator } from './record';
Expand All @@ -11,48 +10,81 @@ import { InputXataFile, NumericOperator } from './record';
*
* If neither found, never will be returned.
*/
export type NewIdentifiable<T extends readonly TableSchema[]> = T extends never[]
export type Identifiers<T extends readonly TableSchema[]> = T extends never[]
? never
: T extends readonly unknown[]
? T[number] extends { name: string; columns: readonly unknown[] }
? {
[K in T[number]['name']]: PrimaryKeyType<T[number], K>;
[K in T[number]['name']]:
| PrimaryKeyType<T[number], K>
| UniqueNotNullType<T[number], K>
| SinglePrimaryKeyType<T[number], K>;
}
: never
: never;

type PrimaryKeyType<Tables, TableName> = Tables & { name: TableName } extends infer Table
? Table extends { name: string; columns: infer Columns } & { primaryKey: infer primaryKey }
? Table extends { name: string; columns: infer Columns } & { primaryKey: infer PrimaryKey }
? Columns extends readonly unknown[]
? Columns[number] extends { name: string; type: string }
? primaryKey extends readonly string[]
? Values<{
[K in Columns[number]['name']]: K extends primaryKey[0]
? PropertyType<Columns[number], K>
: K extends 'xata_id'
? PropertyType<Columns[number], K>
: never;
}>
? PrimaryKey extends readonly string[] & { 0: string }
? {
[P in PrimaryKey[number]]: PropertyType<Columns[number], P>[P];
}
: never
: never
: never
: never
: never;

type SinglePrimaryKeyType<Tables, TableName> = Tables & { name: TableName } extends infer Table
? Table extends { name: string; columns: infer Columns } & { primaryKey: infer PrimaryKey }
? Columns extends readonly unknown[]
? Columns[number] extends { name: string; type: string }
? PrimaryKey extends readonly string[] & { length: 1 }
? NonNullable<
{
[P in PrimaryKey[number]]: PropertyType<Columns[number], P>[P];
}[PrimaryKey[number]]
>
: never
: never
: never
: never
: never;

type UniqueNotNullType<Tables, TableName> = Tables & { name: TableName } extends infer Table
? Table extends { name: string; columns: infer Columns }
? Columns extends readonly unknown[]
? Columns[number] extends { name: string; type: string; unique?: boolean; notNull?: boolean }
? {
[K in Columns[number]['name']]: Columns[number] & { name: K } extends infer Column
? Column extends { name: string; type: string; unique: true; notNull: true }
? { [T in K]: InnerType<Column['type']> }
: never
: never;
}[Columns[number]['name']]
: never
: never
: never
: never;

type PropertyType<Properties, PropertyName extends PropertyKey> = Properties & {
name: PropertyName;
} extends infer Property
? Property extends {
name: string;
type: infer Type;
link?: { table: infer LinkedTable };
link?: { table: string };
notNull?: infer NotNull;
}
? NotNull extends true
? {
[K in PropertyName]: InnerType<Type>;
}
: never
: {
[K in PropertyName]: InnerType<Type> | null;
}
: never
: never;

Expand All @@ -79,16 +111,10 @@ type InnerType<Type> = Type extends
| 'real'
| 'numeric'
? number
: Type extends 'bool' | 'boolean'
? boolean
: never;

export type NewIndentifierValue<T extends object> = {
[K in keyof T]: T[K] extends never ? never : T[K];
}[keyof T];

export type NewIdentifierKey<T extends object> = {
[K in keyof T]: T[K] extends never ? never : K;
}[keyof T];

type NewEditableDataFields<T> = T extends Date
? string | Date
: NonNullable<T> extends Date
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export * from './operators';
export * from './pagination';
export { Query } from './query';
export { RecordColumnTypes, isIdentifiable } from './record';
export type { BaseData, EditableData, Identifiable, JSONData, Link, XataRecord } from './record';
export type { BaseData, EditableData, JSONData, Link, XataRecord } from './record';
export { Repository, KyselyRepository } from './repository';
export * from './selection';

Expand Down
1 change: 0 additions & 1 deletion packages/client/src/schema/inference.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { UnionToIntersection, Values } from '../util/types';
import { XataArrayFile, XataFile } from './files';
import { JSONValue } from './json';
import { Identifiable, XataRecord } from './record';

export type DatabaseSchema = {
tables: readonly TableSchema[];
Expand Down
Loading
Loading