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
180 changes: 180 additions & 0 deletions docs/plans/drizzle-type-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Plan: Create `@deessejs/postgres` Package

**Date:** 2026-04-28
**Status:** Pending approval

## Problem

When `postgres` is exported from `packages/deesse`, Turbopack analyzes all exports client-side and attempts to bundle the `postgres` package (which uses Node.js built-ins like `fs`, `net`, `tls`) into client bundles, causing build failures.

## Solution

Create a separate package `@deessejs/postgres` that contains only the database adapter. This allows:

1. `packages/deesse` to remain client-safe (no Node.js dependencies)
2. `@deessejs/postgres` to contain server-only code with full `postgres` dependency
3. Users to install `@deessejs/postgres` as a server-only dependency

## Implementation

### Step 1: Create `packages/db-postgres`

```
packages/db-postgres/
├── package.json
├── tsconfig.json
└── src/
└── index.ts # exports postgres()
```

**package.json:**
```json
{
"name": "@deessejs/postgres",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"dependencies": {
"deesse": "workspace:*",
"postgres": "^3.4.9",
"drizzle-orm": "^0.38.0"
}
}
```

### Step 2: Move database adapter to `@deessejs/postgres`

Create `packages/db-postgres/src/index.ts`:
```typescript
import { postgres as createPostgres } from 'postgres';
import { drizzle } from 'drizzle-orm/postgres-js';
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import type { Config } from 'deesse';

export interface PostgresOptions {
connectionString: string;
schema?: Record<string, unknown>;
pool?: {
max?: number;
idleTimeout?: number;
connectTimeout?: number;
};
disablePreparedStatements?: boolean;
}

export function postgres<TSchema extends Record<string, unknown> = Record<string, never>>(
options: PostgresOptions
): PostgresJsDatabase<TSchema> {
const sql = createPostgres(options.connectionString, {
max: options.pool?.max,
idle_timeout: options.pool?.idleTimeout,
connect_timeout: options.pool?.connectTimeout,
prepare: options.disablePreparedStatements ? false : true,
});

return drizzle(sql, { schema: options.schema }) as PostgresJsDatabase<TSchema>;
}
```

### Step 3: Remove `postgres` from `packages/deesse`

Remove the export from `packages/deesse/src/index.ts`:
```typescript
// REMOVE this line:
// export { postgres, type PostgresOptions } from "./database/index.js";
```

Remove `postgres` dependency from `packages/deesse/package.json` if not needed elsewhere.

### Step 4: Create `withDeesse()` helper (Optional)

For easier Next.js integration, create `withDeesse()` in `packages/next`:

```javascript
// packages/next/src/withDeesse.js
export const withDeesse = (nextConfig = {}) => ({
...nextConfig,
serverExternalPackages: [
...(nextConfig.serverExternalPackages || []),
'@deessejs/postgres',
],
});
```

### Step 5: Update `examples/base`

Update `examples/base/src/deesse.config.tsx`:
```typescript
import { defineConfig } from 'deesse';
import { postgres } from '@deessejs/postgres'; // Changed import
import { deessePages } from './deesse.pages';
import { schema } from './db/schema/auth-schema';
import { ThemeToggle } from './components/theme-toggle';

export const config = defineConfig({
database: postgres({
connectionString: process.env.DATABASE_URL!,
schema,
}),
// ...
});
```

Update `examples/base/package.json`:
```json
{
"dependencies": {
"@deessejs/postgres": "workspace:*"
}
}
```

### Step 6: Document in `docs/plans/drizzle-type-compatibility.md`

Update the plan document to reflect the new approach.

## Files to Modify

| File | Change |
|------|--------|
| `packages/db-postgres/package.json` | Create new package |
| `packages/db-postgres/tsconfig.json` | Create new tsconfig |
| `packages/db-postgres/src/index.ts` | Create adapter |
| `packages/deesse/src/index.ts` | Remove postgres export |
| `packages/deesse/package.json` | Remove postgres dependency |
| `examples/base/src/deesse.config.tsx` | Update import |
| `examples/base/package.json` | Add @deessejs/postgres dependency |
| `docs/plans/drizzle-type-compatibility.md` | Update plan |

## Alternative: Keep Single Package with Workaround

If separate package is too complex, users can work around this by:

1. Creating database outside of config import
2. Using `react-server` directive to mark files server-only

But this degrades DX significantly.

## Validation

- `pnpm build` on `packages/deesse` succeeds without postgres in exports
- `pnpm build` on `packages/db-postgres` succeeds
- `pnpm build` on `examples/base` succeeds without Turbopack errors

## Breaking Changes

- Users must install `@deessejs/postgres` separately if they want the adapter
- Existing code using `import { postgres } from 'deesse'` will break

## Migration Path

1. Install `@deessejs/postgres` as a new dependency
2. Change import from `import { postgres } from 'deesse'` to `import { postgres } from '@deessejs/postgres'`
3. Optionally use `withDeesse()` in `next.config.mjs` to externalize the package
118 changes: 118 additions & 0 deletions docs/reports/issues/config-type-inference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Issue: Config Type Inference Causes TypeScript Errors in Examples

**Date:** 2026-04-28
**Updated:** 2026-04-28
**Project:** nesalia/deessejs
**Component:** deesse (type system)
**Severity:** Low
**Status:** FIXED — generic added to `getDeesse()` and `Deesse` types

## Problem Summary

When a schema is passed to `postgres()` from `@deessejs/postgres`, the inferred schema type `Record<string, unknown>` is incompatible with `InternalConfig`'s default `Record<string, never>`.

However, this issue is now **mitigated in practice** by using `@deessejs/postgres` instead of direct `drizzle()` calls. The examples build successfully.

## Resolution

The fix adds generic parameters to `getDeesse()`, `createDeesse()`, and `Deesse` types:

```typescript
// packages/deesse/src/index.ts
export const getDeesse = async <TSchema extends Record<string, unknown> = Record<string, never>>(
config?: InternalConfig<TSchema>
): Promise<Deesse<TSchema>> => {
// ...
}

// packages/deesse/src/server.ts
export const createDeesse = <TSchema extends Record<string, unknown> = Record<string, never>>(
config: InternalConfig<TSchema>
): Deesse<TSchema> => {
// ...
}

// packages/deesse/src/config/types.ts
export type Deesse<TSchema extends Record<string, unknown> = Record<string, never>> = {
auth: Auth;
database: PostgresJsDatabase<TSchema>;
};
```

This allows callers to pass their schema-bearing config without type errors, while preserving the database type safety throughout the call chain.

## Root Cause (Still Exists Theoretically)

### Type Hierarchy Issue

```typescript
// getDeesse signature:
export const getDeesse = async (config?: InternalConfig): Promise<Deesse>
// expects InternalConfig = InternalConfig<Record<string, never>>
```

When a schema is passed to `postgres()`:
```typescript
postgres({ connectionString: ..., schema })
// schema causes TSchema = Record<string, unknown>
```

`Record<string, unknown>` is not assignable to `Record<string, never>`:

| Type | Assignable to |
|------|---------------|
| `Record<string, never>` | `Record<string, unknown>` — (never is bottom type) |
| `Record<string, unknown>` | `Record<string, never>` — (unknown is top type) |

### Why Examples Work Now

The examples work because the actual schema type inference doesn't cause issues in practice — likely because:

1. The schema is defined inline and TypeScript infers a concrete object type, not `Record<string, unknown>`
2. The `as any` workaround was needed with `pg` driver, but `postgres-js` driver doesn't have the same type incompatibility

## Historical Context

This issue was initially observed when using `drizzle-orm/node-postgres` with the `pg` driver:

```typescript
// OLD problematic code
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

export const config = defineConfig({
database: drizzle({
client: new Pool({ connectionString: ... }),
schema,
}) as any, // Required workaround
});
```

The `as any` was needed for two reasons:
1. `pg` driver returns `NodePgDatabase` which is incompatible with `PostgresJsDatabase`
2. TypeScript schema inference issues

## Files Affected

- `examples/base/src/deesse.config.tsx` — uses `@deessejs/postgres` now
- `examples/without-admin/src/deesse.config.ts` — uses `@deessejs/postgres` now
- `packages/deesse/src/index.ts` — `getDeesse()` function
- `packages/deesse/src/config/types.ts` — type definitions
- `packages/db-postgres/src/index.ts` — new postgres adapter package

## Open Issue

The type inference issue could still surface if someone:
1. Uses `getDeesse(config)` with a schema-bearing config
2. Passes the config from a separate module

If this occurs, the workaround is:
```typescript
export const deesse = await getDeesse(config as InternalConfig);
```

## Environment

- Next.js 16.2.4 with Turbopack
- TypeScript strict checks enabled
- `@deessejs/postgres` 0.1.0 published to npm
Loading
Loading