Skip to content

Commit

Permalink
Merge pull request #1677 from drizzle-team/beta
Browse files Browse the repository at this point in the history
Beta
  • Loading branch information
AndriiSherman authored Dec 25, 2023
2 parents 0a4e3b2 + d5c45a6 commit d535e0b
Show file tree
Hide file tree
Showing 38 changed files with 5,486 additions and 427 deletions.
138 changes: 138 additions & 0 deletions changelogs/drizzle-orm/0.29.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
## Fixes

- Added improvements to the planescale relational tests #1579 - thanks @Angelelz
- [Pg] FIX: correct string escaping for empty PgArrays #1640 - thanks @Angelelz
- Fix wrong syntax for exists fn in sqlite #1647 - thanks @Angelelz
- Properly handle dates in AWS Data API
- Fix Hermes mixins constructor issue

## ESLint Drizzle Plugin, v0.2.3

```
npm i eslint-plugin-drizzle@0.2.3
```

🎉 **[ESLint] Add support for functions and improve error messages #1586 - thanks @ngregrichardson**

- Allowed Drizzle object to be or to be retrieved from a function, e.g.
- Added better context to the suggestion in the error message.

## New Drivers

### 🎉 Expo SQLite Driver is available

For starting with Expo SQLite Driver, you need to install `expo-sqlite` and `drizzle-orm` packages.

```bash
npm install drizzle-orm expo-sqlite@next
```

Then, you can use it like this:

```ts
import { drizzle } from "drizzle-orm/expo-sqlite";
import { openDatabaseSync } from "expo-sqlite/next";

const expoDb = openDatabaseSync("db.db");

const db = drizzle(expoDb);

await db.select().from(...)...

// or

db.select().from(...).then(...);

// or

db.select().from(...).all();
```

If you want to use Drizzle Migrations, you need to update babel and metro configuration files.

1. Install `babel-plugin-inline-import` package.

```bash
npm install babel-plugin-inline-import
```

2. Update `babel.config.js` and `metro.config.js` files.

babel.config.js

```diff
module.exports = function(api) {
api.cache(true);

return {
presets: ['babel-preset-expo'],
+ plugins: [["inline-import", { "extensions": [".sql"] }]]
};
};
```

metro.config.js

```diff
const { getDefaultConfig } = require('expo/metro-config');

/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);

+config.resolver.sourceExts.push('sql');

module.exports = config;
```

3. Create `drizzle.config.ts` file in your project root folder.

```ts
import type { Config } from 'drizzle-kit';

export default {
schema: './db/schema.ts',
out: './drizzle',
driver: 'expo',
} satisfies Config;
```

After creating schema file and drizzle.config.ts file, you can generate migrations like this:

```bash
npx drizzle-kit generate:sqlite
```

Then you need to import `migrations.js` file in your `App.tsx` file from `./drizzle` folder and use hook `useMigrations` or `migrate` function.

```tsx
import { drizzle } from "drizzle-orm/expo-sqlite";
import { openDatabaseSync } from "expo-sqlite/next";
import { useMigrations } from 'drizzle-orm/expo-sqlite/migrator';
import migrations from './drizzle/migrations';

const expoDb = openDatabaseSync("db.db");

const db = drizzle(expoDb);

export default function App() {
const { success, error } = useMigrations(db, migrations);

if (error) {
return (
<View>
<Text>Migration error: {error.message}</Text>
</View>
);
}

if (!success) {
return (
<View>
<Text>Migration is in progress...</Text>
</View>
);
}

return ...your application component;
}
```
5 changes: 5 additions & 0 deletions changelogs/eslint-plugin-drizzle/0.2.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# eslint-plugin-drizzle 0.2.3

- Added better context to the suggestion in the error message
- fix: Correct detection of `drizzleObjectName` when it's retrieved from or is a function
- chore: Refactored duplicate code in `utils/options.ts` into `isDrizzleObjName` function
8 changes: 7 additions & 1 deletion drizzle-orm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "drizzle-orm",
"version": "0.29.1",
"version": "0.29.2",
"description": "Drizzle ORM package for SQL databases",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -51,15 +51,18 @@
"@planetscale/database": ">=1",
"@types/better-sqlite3": "*",
"@types/pg": "*",
"@types/react": ">=18",
"@types/sql.js": "*",
"@vercel/postgres": "*",
"better-sqlite3": ">=7",
"bun-types": "*",
"expo-sqlite": ">=13.2.0",
"knex": "*",
"kysely": "*",
"mysql2": ">=2",
"pg": ">=8",
"postgres": ">=3",
"react": ">=18",
"sql.js": ">=1",
"sqlite3": ">=5"
},
Expand Down Expand Up @@ -133,16 +136,19 @@
"@types/better-sqlite3": "^7.6.4",
"@types/node": "^20.2.5",
"@types/pg": "^8.10.1",
"@types/react": "^18.2.45",
"@types/sql.js": "^1.4.4",
"@vercel/postgres": "^0.3.0",
"better-sqlite3": "^8.4.0",
"bun-types": "^0.6.6",
"cpy": "^10.1.0",
"expo-sqlite": "^13.2.0",
"knex": "^2.4.2",
"kysely": "^0.25.0",
"mysql2": "^3.3.3",
"pg": "^8.11.0",
"postgres": "^3.3.5",
"react": "^18.2.0",
"sql.js": "^1.8.0",
"sqlite3": "^5.1.2",
"tslib": "^2.5.2",
Expand Down
18 changes: 15 additions & 3 deletions drizzle-orm/src/aws-data-api/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,28 @@ export function toValueParam(value: any, typings?: QueryTypingsValue): { value:
if (value === null) {
response.value = { isNull: true };
} else if (typeof value === 'string') {
response.value = response.typeHint === 'DATE'
? { stringValue: value.split('T')[0]! }
: { stringValue: value };
switch (response.typeHint) {
case TypeHint.DATE: {
response.value = { stringValue: value.split('T')[0]! };
break;
}
case TypeHint.TIMESTAMP: {
response.value = { stringValue: value.replace('T', ' ').replace('Z', '') };
break;
}
default: {
response.value = { stringValue: value };
break;
}
}
} else if (typeof value === 'number' && Number.isInteger(value)) {
response.value = { longValue: value };
} else if (typeof value === 'number' && !Number.isInteger(value)) {
response.value = { doubleValue: value };
} else if (typeof value === 'boolean') {
response.value = { booleanValue: value };
} else if (value instanceof Date) { // eslint-disable-line no-instanceof/no-instanceof
// TODO: check if this clause is needed? Seems like date value always comes as string
response.value = { stringValue: value.toISOString().replace('T', ' ').replace('Z', '') };
} else {
throw new Error(`Unknown type for ${value}`);
Expand Down
45 changes: 45 additions & 0 deletions drizzle-orm/src/expo-sqlite/driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { SQLiteDatabase, SQLiteRunResult } from 'expo-sqlite/next';
import { DefaultLogger } from '~/logger.ts';
import {
createTableRelationsHelpers,
extractTablesRelationalConfig,
type RelationalSchemaConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
import type { DrizzleConfig } from '~/utils.ts';
import { ExpoSQLiteSession } from './session.ts';

export type ExpoSQLiteDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
> = BaseSQLiteDatabase<'sync', SQLiteRunResult, TSchema>;

export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
client: SQLiteDatabase,
config: DrizzleConfig<TSchema> = {},
): ExpoSQLiteDatabase<TSchema> {
const dialect = new SQLiteSyncDialect();
let logger;
if (config.logger === true) {
logger = new DefaultLogger();
} else if (config.logger !== false) {
logger = config.logger;
}

let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
if (config.schema) {
const tablesConfig = extractTablesRelationalConfig(
config.schema,
createTableRelationsHelpers,
);
schema = {
fullSchema: config.schema,
schema: tablesConfig.tables,
tableNamesMap: tablesConfig.tableNamesMap,
};
}

const session = new ExpoSQLiteSession(client, dialect, schema, { logger });
return new BaseSQLiteDatabase('sync', dialect, session, schema) as ExpoSQLiteDatabase<TSchema>;
}
2 changes: 2 additions & 0 deletions drizzle-orm/src/expo-sqlite/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './driver.ts';
export * from './session.ts';
101 changes: 101 additions & 0 deletions drizzle-orm/src/expo-sqlite/migrator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { useEffect, useReducer } from "react";
import type { MigrationMeta } from '~/migrator.ts';
import type { ExpoSQLiteDatabase } from './driver.ts';

interface MigrationConfig {
journal: {
entries: { idx: number; when: number; tag: string; breakpoints: boolean }[];
};
migrations: Record<string, string>;
}

async function readMigrationFiles({ journal, migrations }: MigrationConfig): Promise<MigrationMeta[]> {
const migrationQueries: MigrationMeta[] = [];

for await (const journalEntry of journal.entries) {
const query = migrations[`m${journalEntry.idx.toString().padStart(4, '0')}`];

if (!query) {
throw new Error(`Missing migration: ${journalEntry.tag}`);
}

try {
const result = query.split('--> statement-breakpoint').map((it) => {
return it;
});

migrationQueries.push({
sql: result,
bps: journalEntry.breakpoints,
folderMillis: journalEntry.when,
hash: '',
});
} catch {
throw new Error(`Failed to parse migration: ${journalEntry.tag}`);
}
}

return migrationQueries;
}

export async function migrate<TSchema extends Record<string, unknown>>(
db: ExpoSQLiteDatabase<TSchema>,
config: MigrationConfig,
) {
const migrations = await readMigrationFiles(config);
return db.dialect.migrate(migrations, db.session);
}

interface State {
success: boolean;
error?: Error;
}

type Action =
| { type: 'migrating' }
| { type: 'migrated'; payload: true }
| { type: 'error'; payload: Error }

export const useMigrations = (db: ExpoSQLiteDatabase<any>, migrations: {
journal: {
entries: { idx: number; when: number; tag: string; breakpoints: boolean }[];
};
migrations: Record<string, string>;
}): State => {
const initialState: State = {
success: false,
error: undefined,
}

const fetchReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'migrating': {
return { ...initialState }
}
case 'migrated': {
return { ...initialState, success: action.payload }
}
case 'error': {
return { ...initialState, error: action.payload }
}
default: {
return state
}
}
}

const [state, dispatch] = useReducer(fetchReducer, initialState);

useEffect(() => {
dispatch({ type: 'migrating' })
try {
migrate(db, migrations as any).then(() => {
dispatch({ type: 'migrated', payload: true })
})
} catch (error) {
dispatch({ type: 'error', payload: error as Error })
}
}, []);

return state;
}
Loading

0 comments on commit d535e0b

Please sign in to comment.