diff --git a/docs/plans/drizzle-type-compatibility.md b/docs/plans/drizzle-type-compatibility.md new file mode 100644 index 0000000..0d5ac5d --- /dev/null +++ b/docs/plans/drizzle-type-compatibility.md @@ -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; + pool?: { + max?: number; + idleTimeout?: number; + connectTimeout?: number; + }; + disablePreparedStatements?: boolean; +} + +export function postgres = Record>( + options: PostgresOptions +): PostgresJsDatabase { + 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; +} +``` + +### 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 \ No newline at end of file diff --git a/docs/reports/issues/config-type-inference.md b/docs/reports/issues/config-type-inference.md new file mode 100644 index 0000000..e032828 --- /dev/null +++ b/docs/reports/issues/config-type-inference.md @@ -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` is incompatible with `InternalConfig`'s default `Record`. + +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 = Record>( + config?: InternalConfig +): Promise> => { + // ... +} + +// packages/deesse/src/server.ts +export const createDeesse = = Record>( + config: InternalConfig +): Deesse => { + // ... +} + +// packages/deesse/src/config/types.ts +export type Deesse = Record> = { + auth: Auth; + database: PostgresJsDatabase; +}; +``` + +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 +// expects InternalConfig = InternalConfig> +``` + +When a schema is passed to `postgres()`: +```typescript +postgres({ connectionString: ..., schema }) +// schema causes TSchema = Record +``` + +`Record` is not assignable to `Record`: + +| Type | Assignable to | +|------|---------------| +| `Record` | `Record` — (never is bottom type) | +| `Record` | `Record` — (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` +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 \ No newline at end of file diff --git a/docs/reports/issues/drizzle-type-incompatibility.md b/docs/reports/issues/drizzle-type-incompatibility.md new file mode 100644 index 0000000..7c65aad --- /dev/null +++ b/docs/reports/issues/drizzle-type-incompatibility.md @@ -0,0 +1,330 @@ +# Drizzle-ORM Type Incompatibility Investigation + +**Date:** 2026-04-28 +**Issue:** Type mismatch between `postgres-js` and `node-postgres` drivers in DeesseJS +**Status:** Open + +## Problem Summary + +When a project uses `drizzle-orm/node-postgres` with the `pg` driver, TypeScript reports type incompatibilities with DeesseJS's `Config.database` type which expects `PostgresJsDatabase`. + +## Error Messages + +```typescript +// src/deesse.config.ts:8:3 - error TS2322: Type 'NodePgDatabase<...>' is not assignable to type 'PostgresJsDatabase<...>' + +// The types of '_.session' are incompatible between these types. +// Property 'dialect' is protected but type 'PgSession<...>' is not a class derived from 'PgSession<...>'. +``` + +## Actual Code That Causes the Error + +```typescript +// examples/base/src/deesse.config.tsx +import { defineConfig } from 'deesse'; +import { drizzle } from 'drizzle-orm/node-postgres'; // Uses pg driver +import { Pool } from 'pg'; +import { schema } from './db/schema/auth-schema'; + +export const config = defineConfig({ + name: "DeesseJS App", + database: drizzle({ + client: new Pool({ + connectionString: process.env.DATABASE_URL, + }), + schema, + }), // ❌ Type 'NodePgDatabase' is not assignable to 'PostgresJsDatabase' + secret: process.env.DEESSE_SECRET!, + auth: { baseURL: 'http://localhost:3000' }, +}); +``` + +The `drizzle()` call with `pg`'s Pool returns a `NodePgDatabase`, but DeesseJS's `Config` type expects `PostgresJsDatabase`. Both are valid Drizzle database instances, but TypeScript sees them as incompatible types. + +## Root Cause + +### Drizzle-ORM Type Hierarchy + +Both database types extend `PgDatabase` but with different HKT (Higher-Kinded Types): + +``` +PgDatabase +├── PostgresJsDatabase extends PgDatabase +└── NodePgDatabase extends PgDatabase +``` + +### Why Union Doesn't Solve the Generic Propagation Problem + +When we tried: +```typescript +type CompatibleDatabase = PostgresJsDatabase | NodePgDatabase; + +type Config = { + database: CompatibleDatabase; +}; +``` + +TypeScript error: `TSchema is declared but its value is never read` because the union doesn't actually use the generic - both types use it independently and TypeScript can't "match" them. + +### Why PgDatabase Base Class Doesn't Work + +Even though both extend `PgDatabase`, each specialization has different HKT parameters: +```typescript +PgDatabase +PgDatabase +``` + +These are structurally incompatible in TypeScript because the HKT is part of the type identity. + +## What Works at Runtime + +Better-auth's `drizzleAdapter` accepts any database type because it uses: +```typescript +db: { [key: string]: any } +``` + +This means both drivers work at runtime - the problem is only compile-time type checking. + +## Solution Approaches Considered + +### 1. Union Type Without Generics (Current Attempt) +```typescript +type CompatibleDatabase = PostgresJsDatabase | NodePgDatabase; +type Config = { database: CompatibleDatabase; }; +``` + +**Problem:** Loses schema generic propagation. When a user passes `Config<{ user: UserTable }>`, the internal `_.schema` type becomes incompatible with `Config>`. + +**Error seen:** +``` +Type 'InternalConfig<{ user: PgTableWithColumns<...> }>' is not assignable to type 'InternalConfig'. +``` + +### 2. Using `as any` Workaround (Current State in examples/base) +```typescript +database: drizzle({ client: new Pool(...) }) as any +``` + +**Problem:** Bypasses type safety, hard to maintain. + +### 3. Accept Both Drivers with `unknown` Pool Extraction +```typescript +const extractPool = (db: PostgresJsDatabase | NodePgDatabase): unknown => { + return (db as unknown as { $client?: unknown }).$client; +} +``` + +This part works - the pool extraction handles both types. + +## The Real Issue + +The incompatibility happens at the **schema generic level** not the driver level: + +1. User defines: `database: NodePgDatabase<{ user: UserTable, session: SessionTable }>` +2. Deesse internal type expects: `database: PostgresJsDatabase<{ ...schema... }>` +3. Even if drivers match, the HKT QueryResult types differ + +## Files Involved + +| File | Role | +|------|------| +| `packages/deesse/src/config/types.ts` | Defines `Config` and `InternalConfig` | +| `packages/deesse/src/config/define.ts` | Creates config via `defineConfig()` | +| `packages/next/src/root-page.tsx` | Uses `InternalConfig` as prop type | +| `examples/base/src/deesse.config.tsx` | User config using `pg` driver | + +## Possible Solutions + +1. **Keep generics but make database type accept union** - Still causes the TSchema issue +2. **Use two separate config types** - One for postgres-js, one for pg +3. **Accept the schema generic is lost** - Document that database type is intentionally loose +4. **Deep generic solution with conditional types** - Complex, may not be possible + +## Drizzle-ORM Native PostgreSQL Support + +Drizzle has native support for PostgreSQL via two drivers: + +### node-postgres (`drizzle-orm/node-postgres`) +```typescript +import { drizzle } from 'drizzle-orm/node-postgres'; +import { Pool } from 'pg'; + +const db = drizzle({ client: new Pool({ connectionString: ... }) }); +``` +- Can use `pg-native` for ~10% speed boost +- Supports per-query type parsers without global patching +- Returns `NodePgDatabase` + +### postgres.js (`drizzle-orm/postgres-js`) +```typescript +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; + +const db = drizzle(postgres(process.env.DATABASE_URL)); +``` +- Uses prepared statements by default (may need to opt out for AWS) +- Simpler API, no native addon support +- Returns `PostgresJsDatabase` + +### Key Differences + +| Feature | node-postgres | postgres.js | +|---------|---------------|-------------| +| Speed | pg-native boost (~10%) | Standard | +| Type parsers | Per-query support | Global only | +| Prepared statements | No | Yes (default) | +| Native addon | pg-native | No | +| Returns | `NodePgDatabase` | `PostgresJsDatabase` | + +**Important:** Both are valid Drizzle database clients that work at runtime. The problem is purely TypeScript's type system not recognizing them as compatible. + +## Proposed Solution: Database Adapter Pattern (PayloadCMS-inspired) + +Inspired by [PayloadCMS's postgresAdapter](https://payloadcms.com/docs/database/postgres), we could create a first-party database adapter that abstracts away the driver complexity. + +### How PayloadCMS Does It + +```typescript +import { postgresAdapter } from '@payloadcms/db-postgres' + +export default buildConfig({ + db: postgresAdapter({ + pool: { connectionString: process.env.DATABASE_URL }, + }), +}) +``` + +The adapter handles: +- Creating the database client internally +- Managing the pool lifecycle +- Exposing Drizzle for direct access +- Handling schema migrations + +### Proposed DeesseJS Approach + +Create a `postgres()` function in `packages/deesse` that: + +1. **Uses `postgres-js` driver** (returns `PostgresJsDatabase` - the native type Deesse expects) +2. **Accepts connection options** including pool config +3. **Returns properly typed database instance** + +```typescript +// packages/deesse/src/database/postgres.ts +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; + +export interface PostgresOptions { + connectionString: string; + schema?: Record; + pool?: { + max?: number; + idleTimeout?: number; + connectTimeout?: number; + }; +} + +export const postgres = = Record>( + options: PostgresOptions +): PostgresJsDatabase => { + const client = postgres(options.connectionString, { + max: options.pool?.max, + idle_timeout: options.pool?.idleTimeout, + connect_timeout: options.pool?.connectTimeout, + }); + + return drizzle(client, { schema: options.schema }) as PostgresJsDatabase; +} +``` + +### User Experience (Before vs After) + +**Before (current workaround):** +```typescript +import { defineConfig } from 'deesse'; +import { drizzle } from 'drizzle-orm/node-postgres'; // pg driver +import { Pool } from 'pg'; +import { schema } from './db/schema/auth-schema'; + +export const config = defineConfig({ + database: drizzle({ + client: new Pool({ connectionString: process.env.DATABASE_URL }), + schema, + }) as any, // Workaround needed + secret: process.env.DEESSE_SECRET!, + auth: { baseURL: 'http://localhost:3000' }, +}); +``` + +**After (with postgres adapter):** +```typescript +import { defineConfig, postgres } from 'deesse'; +import { schema } from './db/schema/auth-schema'; + +export const config = defineConfig({ + database: postgres({ + connectionString: process.env.DATABASE_URL, + schema, + }), + secret: process.env.DEESSE_SECRET!, + auth: { baseURL: 'http://localhost:3000' }, +}); +``` + +### Advantages + +1. **Type safety preserved** - Uses `PostgresJsDatabase` directly, no casting needed +2. **Single driver** - Uses `postgres-js` which is already supported by Deesse +3. **Better DX** - Users don't need to know about Drizzle drivers +4. **Consistent API** - Matches patterns like PayloadCMS, Prisma adapters +5. **Pool management** - Handles connection lifecycle internally + +### Implementation Location + +``` +packages/deesse/src/database/ +├── index.ts # Exports postgres(), postgresJs() +├── postgres.ts # postgres-js driver adapter +└── types.ts # Database adapter types +``` + +### Changes to Config Type + +The `Config.database` type would accept `PostgresJsDatabase` directly (no change needed), while users using the `postgres()` helper get seamless integration. + +### Alternative: Keep pg Support + +If we want to keep `pg` driver support, we could offer both: +```typescript +import { postgres, postgresPg } from 'deesse'; + +// Uses postgres-js (recommended) +postgres({ connectionString: ... }) + +// Uses pg driver +postgresPg({ pool: new Pool({ connectionString: ... }) }) +``` + +But this adds complexity and the original HKT problem remains. + +## Implementation Notes from Drizzle Docs + +When using `postgres-js`: +- Connection string: `postgres(connectionString, options)` +- Pool options: `max`, `idle_timeout`, `connect_timeout` +- Prepared statements are enabled by default (may need to disable for AWS Lambda) + +When using `node-postgres`: +- Pool: `new Pool({ connectionString: ... })` +- Can use `pg-native` module for performance boost +- Supports per-query type parsers + +## Current Status + +Attempted to fix by removing generics from `Config` and `InternalConfig`. Build of `packages/deesse` succeeds. Build of `examples/base` fails because `RootPage` props type is too strict. + +## Next Steps + +1. Evaluate whether the schema generic propagation is actually required +2. Consider if `RootPage` should accept a looser config type +3. Determine acceptable trade-off between type safety and driver compatibility \ No newline at end of file diff --git a/packages/db-postgres/.gitignore b/packages/db-postgres/.gitignore new file mode 100644 index 0000000..f71b69b --- /dev/null +++ b/packages/db-postgres/.gitignore @@ -0,0 +1,5 @@ +# Dependencies +node_modules/ + +# Build output +dist/ \ No newline at end of file diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json new file mode 100644 index 0000000..f573bb4 --- /dev/null +++ b/packages/db-postgres/package.json @@ -0,0 +1,28 @@ +{ + "name": "@deessejs/postgres", + "version": "0.1.1", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf dist" + }, + "dependencies": { + "deesse": "^0.2.13", + "postgres": "^3.4.9", + "drizzle-orm": "^0.38.0" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts new file mode 100644 index 0000000..720455a --- /dev/null +++ b/packages/db-postgres/src/index.ts @@ -0,0 +1,58 @@ +// Database adapter using postgres-js driver +// This package is server-only and should be added to serverExternalPackages + +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgresJs from 'postgres'; +import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'; + +/** + * Options for creating a postgres-js database connection. + */ +export interface PostgresOptions { + /** Database connection string */ + connectionString: string; + /** Drizzle schema for type-safe queries */ + schema?: Record; + /** Pool configuration options */ + pool?: { + /** Maximum number of connections in the pool */ + max?: number; + /** Idle connection timeout in milliseconds */ + idleTimeout?: number; + /** Connection timeout in milliseconds */ + connectTimeout?: number; + }; + /** Disable prepared statements (useful for AWS Lambda) */ + disablePreparedStatements?: boolean; +} + +/** + * Create a PostgresJsDatabase instance using the postgres-js driver. + * + * @example + * ```typescript + * import { defineConfig } from 'deesse'; + * import { postgres } from '@deessejs/postgres'; + * import { schema } from './db/schema'; + * + * export const config = defineConfig({ + * database: postgres({ + * connectionString: process.env.DATABASE_URL, + * schema, + * }), + * // ... + * }); + * ``` + */ +export function postgres = Record>( + options: PostgresOptions +): PostgresJsDatabase { + const sql = postgresJs(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; +} diff --git a/packages/db-postgres/tsconfig.json b/packages/db-postgres/tsconfig.json new file mode 100644 index 0000000..9ce63c2 --- /dev/null +++ b/packages/db-postgres/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": false, + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/deesse/src/config/define.ts b/packages/deesse/src/config/define.ts index f2fef99..a432f1b 100644 --- a/packages/deesse/src/config/define.ts +++ b/packages/deesse/src/config/define.ts @@ -56,8 +56,8 @@ export const defineConfig = >( auth: { ...config.auth, plugins: mergedAuth.plugins, - emailAndPassword: mergedAuth.emailAndPassword as InternalConfig['auth']['emailAndPassword'], - session: mergedAuth.session as InternalConfig['auth']['session'], + emailAndPassword: mergedAuth.emailAndPassword as Config['auth']['emailAndPassword'], + session: mergedAuth.session as Config['auth']['session'], trustedOrigins: mergedAuth.trustedOrigins, }, }; diff --git a/packages/deesse/src/config/types.ts b/packages/deesse/src/config/types.ts index 2dfe266..3a70b8b 100644 --- a/packages/deesse/src/config/types.ts +++ b/packages/deesse/src/config/types.ts @@ -30,8 +30,9 @@ export type InternalConfig = Record = Record> = { auth: Auth; - database: PostgresJsDatabase; + database: PostgresJsDatabase; }; diff --git a/packages/deesse/src/index.ts b/packages/deesse/src/index.ts index d6f29c6..8ff97ea 100644 --- a/packages/deesse/src/index.ts +++ b/packages/deesse/src/index.ts @@ -1,10 +1,10 @@ // @deessejs/deesse core package -import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import { createDeesse } from "./server.js"; import type { Deesse } from "./config/types.js"; import { getGlobalConfig } from "./config/define.js"; import type { InternalConfig } from "./config/types.js"; +import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; export { defineConfig } from "./config/index.js"; export type { Config, InternalConfig } from "./config/index.js"; @@ -29,15 +29,15 @@ export type { DeesseClientOptions } from "./client.js"; */ const DEESSE_GLOBAL_KEY = Symbol.for("@deessejs/core.instance"); -interface GlobalDeesseCache { - instance: Deesse | undefined; - config: InternalConfig | undefined; +interface GlobalDeesseCache = Record> { + instance: Deesse | undefined; + config: InternalConfig | undefined; pool: unknown | undefined; } -const getGlobalCache = (): GlobalDeesseCache => { +const getGlobalCache = = Record>(): GlobalDeesseCache => { const g = global as typeof global & { - [DEESSE_GLOBAL_KEY]?: GlobalDeesseCache; + [DEESSE_GLOBAL_KEY]?: GlobalDeesseCache; }; if (!g[DEESSE_GLOBAL_KEY]) { g[DEESSE_GLOBAL_KEY] = { @@ -55,7 +55,10 @@ const getGlobalCache = (): GlobalDeesseCache => { * Note: We do NOT compare database pools - the pool reference from $client * may return new wrapper objects on each access, causing false positives. */ -const isConfigEqual = (a: InternalConfig, b: InternalConfig): boolean => { +const isConfigEqual = >( + a: InternalConfig, + b: InternalConfig +): boolean => { if (a.secret !== b.secret) return false; if (a.name !== b.name) return false; if (a.auth.baseURL !== b.auth.baseURL) return false; @@ -75,7 +78,7 @@ const isConfigEqual = (a: InternalConfig, b: InternalConfig): boolean => { // Compare optional top-level fields if (JSON.stringify(a.plugins) !== JSON.stringify(b.plugins)) return false; - if (JSON.stringify(a.pages) !== JSON.stringify(b.pages)) return false; + if (JSON.stringify(a.pages) !== JSON.stringify(a.pages)) return false; if (JSON.stringify(a.admin) !== JSON.stringify(b.admin)) return false; return true; @@ -83,9 +86,9 @@ const isConfigEqual = (a: InternalConfig, b: InternalConfig): boolean => { /** * Extract pool reference from database. - * For pg Pool passed to drizzle-orm/node-postgres, the pool is stored in $client. + * The pool reference is stored in $client for both postgres-js and node-postgres. */ -const extractPool = (db: PostgresJsDatabase): unknown => { +const extractPool = >(db: PostgresJsDatabase): unknown => { return (db as unknown as { $client?: unknown }).$client; } @@ -96,11 +99,13 @@ const extractPool = (db: PostgresJsDatabase): unknown => { * Can be called without arguments if defineConfig() was called first, * or with a config for explicit instantiation. */ -export const getDeesse = async ( - config?: InternalConfig -): Promise => { - const effectiveConfig = config ?? getGlobalConfig(); - const cache = getGlobalCache(); +export const getDeesse = async < + TSchema extends Record = Record +>( + config?: InternalConfig +): Promise> => { + const effectiveConfig = config ?? (getGlobalConfig() as InternalConfig); + const cache = getGlobalCache(); // Case 1: Instance exists and config is semantically equal if (cache.instance && cache.config && isConfigEqual(cache.config, effectiveConfig)) { diff --git a/packages/deesse/tsconfig.json b/packages/deesse/tsconfig.json index 9ce63c2..aa5a487 100644 --- a/packages/deesse/tsconfig.json +++ b/packages/deesse/tsconfig.json @@ -5,7 +5,8 @@ "module": "ESNext", "moduleResolution": "bundler", "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + "verbatimModuleSyntax": false }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]