@forinda/kickjs-prisma 3.0.5 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/prisma.adapter.ts","../src/prisma-tenant.adapter.ts","../src/query-adapter.ts"],"mappings":";;;;;cAGa,oBAAA;AAAA,UAEI,0BAAA;EAF2C;;;;EAO1D,UAAA,EAAY,GAAA;EAL6B;;;;;;;;;;;;EAmBzC,aAAA,GAAgB,QAAA,aAAqB,GAAA,GAAM,OAAA,CAAQ,GAAA;EAAnD;;;;EAMA,gBAAA,IAAoB,EAAA,EAAI,GAAA,EAAK,QAAA,aAAqB,YAAA;EAAlD;EAGA,OAAA;EAHoB;;;;EASpB,QAAA;AAAA;AAAA,UAGe,oBAAA;EAAA;;;;;AAgBjB;EATE,MAAA;;;;AA4BF;EAvBE,OAAA;AAAA;;cAIW,aAAA;;;;;;;;;;;;;;;;;;UAmBI,mBAAA;EACf,UAAA,CAAW,IAAA;IACT,KAAA,EAAO,MAAA;IACP,OAAA,GAAU,MAAA;EAAA,IACR,OAAA;EACJ,SAAA,EAAW,IAAA,GAAO,MAAA,oBAA0B,OAAA;EAC5C,QAAA,CAAS,IAAA,GAAO,MAAA,oBAA0B,OAAA;EAC1C,MAAA,CAAO,IAAA;IAAQ,IAAA,EAAM,MAAA;EAAA,IAA4B,OAAA;EACjD,MAAA,CAAO,IAAA;IAAQ,KAAA,EAAO,MAAA;IAAyB,IAAA,EAAM,MAAA;EAAA,IAA4B,OAAA;EACjF,MAAA,CAAO,IAAA;IAAQ,KAAA,EAAO,MAAA;EAAA,IAA4B,OAAA;EAClD,UAAA,CAAW,IAAA;IAAS,KAAA,GAAQ,MAAA;EAAA,IAA4B,OAAA;IAAU,KAAA;EAAA;EAClE,KAAA,CAAM,IAAA;IAAS,KAAA,GAAQ,MAAA;EAAA,IAA4B,OAAA;AAAA;;;;AArFrD;;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;cCgCa,aAAA,YAAyB,UAAA;EAAA,QAIhB,OAAA;EAHpB,IAAA;EAAA,QACQ,MAAA;cAEY,OAAA,EAAS,oBAAA;EDQ7B;ECHA,WAAA,CAAA;IAAc;EAAA,GAAa,cAAA;EDYhB;ECsBL,QAAA,CAAA,GAAY,OAAA;AAAA;;;;AD7EpB;;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA;;;;;AAgBA;cEba,mBAAA,2BAA8C,UAAA;EACzD,IAAA;EAAA,iBACiB,UAAA;EAAA,iBACA,aAAA;EAAA,iBACA,WAAA;EAAA,iBACA,YAAA;EAAA,iBACA,OAAA;EAAA,iBACA,aAAA;cAEL,OAAA,EAAS,0BAAA,CAA2B,GAAA;EF2B5C;;;;;EEVE,KAAA,CAAM,QAAA,mBAA2B,OAAA,CAAQ,GAAA;EFaE;EEQ3C,WAAA,CAAA;IAAc;EAAA,GAAa,cAAA,GAAiB,OAAA;EFPG;EAAA,QEqCvC,UAAA;EFpCQ;EE8DhB,QAAA,CAAA,GAAY,OAAA;EF7DU;EAAA,IEgFxB,eAAA,CAAA;AAAA;;;;;AFpKN;;;;;AAEA;;;;;;;;;;;;;UGiBiB,iBAAA,UAA2B,MAAA;EHE1C;EGAA,aAAA,UAAuB,MAAA;AAAA;;UAIR,iBAAA;EACf,KAAA,GAAQ,MAAA;EACR,OAAA,GAAU,MAAA;EACV,IAAA;EACA,IAAA;AAAA;;;;;AHUF;;;;;AAgBA;;;;;AAmBA;cG3Ba,kBAAA,YAA8B,mBAAA,CACzC,iBAAA,EACA,iBAAA;EAAA,SAES,IAAA;EAET,KAAA,UAAe,MAAA,cAAA,CACb,MAAA,EAAQ,WAAA,EACR,MAAA,GAAQ,iBAAA,CAAkB,MAAA,IACzB,iBAAA;EHqBS;EAAA,QGaJ,YAAA;EHXU;EAAA,QGiDV,SAAA;EHhDQ;EAAA,QGqDR,WAAA;EHpDa;EAAA,QGiEb,MAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/prisma.adapter.ts","../src/prisma-tenant.adapter.ts","../src/query-adapter.ts"],"mappings":";;;;;cAGa,oBAAA;AAAA,UAEI,0BAAA;EAF2C;;;;EAO1D,UAAA,EAAY,GAAA;EAL6B;;;;;;;;;;;;EAmBzC,aAAA,GAAgB,QAAA,aAAqB,GAAA,GAAM,OAAA,CAAQ,GAAA;EAAnD;;;;EAMA,gBAAA,IAAoB,EAAA,EAAI,GAAA,EAAK,QAAA,aAAqB,YAAA;EAAlD;EAGA,OAAA;EAHoB;;;;EASpB,QAAA;AAAA;AAAA,UAGe,oBAAA;EAAA;;;;;AAgBjB;EATE,MAAA;;;;AA4BF;EAvBE,OAAA;AAAA;;cAIW,aAAA;;;;;;;;;;;;;;;;;;UAmBI,mBAAA;EACf,UAAA,CAAW,IAAA;IACT,KAAA,EAAO,MAAA;IACP,OAAA,GAAU,MAAA;EAAA,IACR,OAAA;EACJ,SAAA,EAAW,IAAA,GAAO,MAAA,oBAA0B,OAAA;EAC5C,QAAA,CAAS,IAAA,GAAO,MAAA,oBAA0B,OAAA;EAC1C,MAAA,CAAO,IAAA;IAAQ,IAAA,EAAM,MAAA;EAAA,IAA4B,OAAA;EACjD,MAAA,CAAO,IAAA;IAAQ,KAAA,EAAO,MAAA;IAAyB,IAAA,EAAM,MAAA;EAAA,IAA4B,OAAA;EACjF,MAAA,CAAO,IAAA;IAAQ,KAAA,EAAO,MAAA;EAAA,IAA4B,OAAA;EAClD,UAAA,CAAW,IAAA;IAAS,KAAA,GAAQ,MAAA;EAAA,IAA4B,OAAA;IAAU,KAAA;EAAA;EAClE,KAAA,CAAM,IAAA;IAAS,KAAA,GAAQ,MAAA;EAAA,IAA4B,OAAA;AAAA;;;;AArFrD;;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;cCgCa,aAAA,YAAyB,UAAA;EAAA,QAIhB,OAAA;EAHpB,IAAA;EAAA,QACQ,MAAA;cAEY,OAAA,EAAS,oBAAA;EDQ7B;ECHA,WAAA,CAAA;IAAc;EAAA,GAAa,cAAA;EDYhB;ECsBL,QAAA,CAAA,GAAY,OAAA;AAAA;;;;AD7EpB;;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA;;;;;AAgBA;cEba,mBAAA,2BAA8C,UAAA;EACzD,IAAA;EAAA,iBACiB,UAAA;EAAA,iBACA,aAAA;EAAA,iBACA,WAAA;EAAA,iBACA,YAAA;EAAA,iBACA,OAAA;EAAA,iBACA,aAAA;cAEL,OAAA,EAAS,0BAAA,CAA2B,GAAA;EF2B5C;;;;;EEVE,KAAA,CAAM,QAAA,mBAA2B,OAAA,CAAQ,GAAA;EFaE;EEQ3C,WAAA,CAAA;IAAc;EAAA,GAAa,cAAA,GAAiB,OAAA;EFPG;EAAA,QEoCvC,UAAA;EFnCQ;EE6DhB,QAAA,CAAA,GAAY,OAAA;EF5DU;EAAA,IE+ExB,eAAA,CAAA;AAAA;;;;;AFnKN;;;;;AAEA;;;;;;;;;;;;;UGiBiB,iBAAA,UAA2B,MAAA;EHE1C;EGAA,aAAA,UAAuB,MAAA;AAAA;;UAIR,iBAAA;EACf,KAAA,GAAQ,MAAA;EACR,OAAA,GAAU,MAAA;EACV,IAAA;EACA,IAAA;AAAA;;;;;AHUF;;;;;AAgBA;;;;;AAmBA;cG3Ba,kBAAA,YAA8B,mBAAA,CACzC,iBAAA,EACA,iBAAA;EAAA,SAES,IAAA;EAET,KAAA,UAAe,MAAA,cAAA,CACb,MAAA,EAAQ,WAAA,EACR,MAAA,GAAQ,iBAAA,CAAkB,MAAA,IACzB,iBAAA;EHqBS;EAAA,QGaJ,YAAA;EHXU;EAAA,QGiDV,SAAA;EHhDQ;EAAA,QGqDR,WAAA;EHpDa;EAAA,QGiEb,MAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-prisma v3.0.5
2
+ * @forinda/kickjs-prisma v3.0.6
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["log"],"sources":["../src/types.ts","../src/prisma.adapter.ts","../src/prisma-tenant.adapter.ts","../src/query-adapter.ts"],"sourcesContent":["import type { MaybePromise } from '@forinda/kickjs'\n\n/** DI token for resolving the current tenant's PrismaClient (multi-tenant) */\nexport const PRISMA_TENANT_CLIENT = Symbol('PrismaTenantDB')\n\nexport interface PrismaTenantAdapterOptions<TDb = unknown> {\n /**\n * The provider (default) PrismaClient. Used when no tenant is\n * resolved or when accessing the tenant registry.\n */\n providerDb: TDb\n\n /**\n * Factory that creates a PrismaClient for a given tenant.\n * Called once per tenant — the result is cached for subsequent requests.\n *\n * @example\n * ```ts\n * tenantFactory: async (tenantId) => {\n * const url = await lookupTenantDbUrl(tenantId)\n * return new PrismaClient({ datasourceUrl: url })\n * }\n * ```\n */\n tenantFactory: (tenantId: string) => TDb | Promise<TDb>\n\n /**\n * Optional function to close a tenant PrismaClient connection.\n * Called for each cached connection during shutdown.\n */\n onTenantShutdown?: (db: TDb, tenantId: string) => MaybePromise<any>\n\n /** Enable query logging (default: false) */\n logging?: boolean\n\n /**\n * Cache TTL in milliseconds. Tenant connections idle beyond this\n * duration are evicted. Default: no eviction (connections live until shutdown).\n */\n cacheTtl?: number\n}\n\nexport interface PrismaAdapterOptions {\n /**\n * PrismaClient instance — typed as `any` to avoid version coupling.\n *\n * Prisma 5/6: `new PrismaClient()` from `@prisma/client`\n * Prisma 7+: `new PrismaClient({ adapter })` from your generated output path\n */\n client: any\n /**\n * Enable query logging (default: false).\n * Uses `$on('query', ...)` for Prisma 5/6 and `$extends` for Prisma 7+.\n */\n logging?: boolean\n}\n\n/** DI token for resolving the PrismaClient from the container */\nexport const PRISMA_CLIENT = Symbol('PrismaClient')\n\n/**\n * Common Prisma model delegate operations.\n * Use this to type-narrow the injected PrismaClient to a specific model\n * without needing `as any` casts in repositories.\n *\n * @example\n * ```ts\n * @Repository()\n * export class PrismaUserRepository {\n * @Inject(PRISMA_CLIENT) private prisma!: { user: PrismaModelDelegate }\n *\n * async findById(id: string) {\n * return this.prisma.user.findUnique({ where: { id } })\n * }\n * }\n * ```\n */\nexport interface PrismaModelDelegate {\n findUnique(args: {\n where: Record<string, unknown>\n include?: Record<string, unknown>\n }): Promise<unknown>\n findFirst?(args?: Record<string, unknown>): Promise<unknown>\n findMany(args?: Record<string, unknown>): Promise<unknown[]>\n create(args: { data: Record<string, unknown> }): Promise<unknown>\n update(args: { where: Record<string, unknown>; data: Record<string, unknown> }): Promise<unknown>\n delete(args: { where: Record<string, unknown> }): Promise<unknown>\n deleteMany(args?: { where?: Record<string, unknown> }): Promise<{ count: number }>\n count(args?: { where?: Record<string, unknown> }): Promise<number>\n}\n","import { Logger, type AppAdapter, type AdapterContext, Scope } from '@forinda/kickjs'\nimport { PRISMA_CLIENT, type PrismaAdapterOptions } from './types'\n\nconst log = Logger.for('PrismaAdapter')\n\n/**\n * Prisma adapter — registers a PrismaClient in the DI container and manages\n * its lifecycle (connection setup and teardown).\n *\n * Works with Prisma 5, 6, and 7+.\n *\n * @example Prisma 5/6\n * ```ts\n * import { PrismaClient } from '@prisma/client'\n *\n * new PrismaAdapter({ client: new PrismaClient(), logging: true })\n * ```\n *\n * @example Prisma 7+ (driver adapters)\n * ```ts\n * import { PrismaClient } from './generated/prisma'\n * import { PrismaPg } from '@prisma/adapter-pg'\n * import pg from 'pg'\n *\n * const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })\n * const client = new PrismaClient({ adapter: new PrismaPg(pool) })\n * new PrismaAdapter({ client, logging: true })\n * ```\n *\n * Inject the client in services:\n * ```ts\n * @Service()\n * class UserService {\n * @Inject(PRISMA_CLIENT) private prisma!: PrismaClient\n * }\n * ```\n */\nexport class PrismaAdapter implements AppAdapter {\n name = 'PrismaAdapter'\n private client: any\n\n constructor(private options: PrismaAdapterOptions) {\n this.client = options.client\n }\n\n /** Register the PrismaClient in the DI container */\n beforeStart({ container }: AdapterContext): void {\n // Set up query logging if requested\n if (this.options.logging) {\n if (typeof this.client.$on === 'function') {\n // Prisma 5/6: event-based logging\n this.client.$on('query', (event: any) => {\n log.debug(`Query: ${event.query}`)\n log.debug(`Params: ${event.params}`)\n log.debug(`Duration: ${event.duration}ms`)\n })\n } else if (typeof this.client.$extends === 'function') {\n // Prisma 7+: Client Extensions for logging ($on removed)\n this.client = this.client.$extends({\n query: {\n $allOperations({ operation, model, args, query }: any) {\n const start = performance.now()\n return query(args).then((result: any) => {\n const duration = Math.round(performance.now() - start)\n log.debug(`${model}.${operation} — ${duration}ms`)\n return result\n })\n },\n },\n })\n }\n }\n\n // Register the client instance as a singleton factory in the container\n container.registerFactory(PRISMA_CLIENT, () => this.client, Scope.SINGLETON)\n\n log.info('PrismaClient registered in DI container')\n }\n\n /** Disconnect the PrismaClient on shutdown */\n async shutdown(): Promise<void> {\n if (typeof this.client.$disconnect === 'function') {\n await this.client.$disconnect()\n log.info('PrismaClient disconnected')\n }\n }\n}\n","import { Logger, type AppAdapter, type AdapterContext, Scope } from '@forinda/kickjs'\nimport { PRISMA_TENANT_CLIENT, type PrismaTenantAdapterOptions } from './types'\n\nconst log = Logger.for('PrismaTenantAdapter')\n\n/**\n * Multi-tenant Prisma adapter — manages per-tenant PrismaClient connections\n * with automatic caching and lifecycle management.\n *\n * Registers `PRISMA_TENANT_CLIENT` as a TRANSIENT DI token that resolves\n * to the current tenant's PrismaClient using AsyncLocalStorage\n * (requires `TenantAdapter` to be configured).\n *\n * Works alongside `PrismaAdapter` — use `PRISMA_CLIENT` for the provider\n * database and `PRISMA_TENANT_CLIENT` for the current tenant's database.\n *\n * @example\n * ```ts\n * import { PrismaClient } from '@prisma/client'\n *\n * const providerDb = new PrismaClient({ datasourceUrl: PROVIDER_URL })\n *\n * bootstrap({\n * adapters: [\n * new TenantAdapter({ strategy: 'subdomain' }),\n * new PrismaTenantAdapter({\n * providerDb,\n * tenantFactory: async (tenantId) => {\n * const url = await lookupTenantDbUrl(tenantId)\n * return new PrismaClient({ datasourceUrl: url })\n * },\n * onTenantShutdown: (db) => db.$disconnect(),\n * }),\n * ],\n * })\n * ```\n *\n * Inject in services:\n * ```ts\n * @Service()\n * class ProjectService {\n * constructor(@Inject(PRISMA_TENANT_CLIENT) private prisma: PrismaClient) {}\n * }\n * ```\n */\nexport class PrismaTenantAdapter<TDb = unknown> implements AppAdapter {\n name = 'PrismaTenantAdapter'\n private readonly providerDb: TDb\n private readonly tenantFactory: (tenantId: string) => TDb | Promise<TDb>\n private readonly connections = new Map<string, TDb>()\n private readonly lastAccessed = new Map<string, number>()\n private readonly options: PrismaTenantAdapterOptions<TDb>\n private readonly evictionTimer?: ReturnType<typeof setInterval>\n\n constructor(options: PrismaTenantAdapterOptions<TDb>) {\n this.options = options\n this.providerDb = options.providerDb\n this.tenantFactory = options.tenantFactory\n\n if (options.cacheTtl && options.cacheTtl > 0) {\n const interval = Math.min(options.cacheTtl, 60_000)\n this.evictionTimer = setInterval(() => this.evictStale(), interval)\n this.evictionTimer.unref()\n }\n }\n\n /**\n * Get the PrismaClient for a specific tenant.\n * Creates and caches the connection on first access.\n * Returns the provider DB when tenantId is undefined/null.\n */\n async getDb(tenantId?: string | null): Promise<TDb> {\n if (!tenantId) return this.providerDb\n\n const cached = this.connections.get(tenantId)\n if (cached) {\n this.lastAccessed.set(tenantId, Date.now())\n return cached\n }\n\n const db = await this.tenantFactory(tenantId)\n this.connections.set(tenantId, db)\n this.lastAccessed.set(tenantId, Date.now())\n\n if (this.options.logging) {\n log.info(`Tenant DB created: ${tenantId} (${this.connections.size} total)`)\n }\n\n return db\n }\n\n /** Register PRISMA_TENANT_CLIENT as a transient factory in DI */\n async beforeStart({ container }: AdapterContext): Promise<void> {\n // Dynamically import getCurrentTenant to avoid hard dep on multi-tenant package\n let getCurrentTenant: (() => { id: string } | undefined) | undefined\n\n try {\n // @ts-expect-error optional peer dependency\n const mt = await import('@forinda/kickjs-multi-tenant')\n getCurrentTenant = mt.getCurrentTenant\n } catch {\n log.warn(\n 'PrismaTenantAdapter: @forinda/kickjs-multi-tenant not found. ' +\n 'PRISMA_TENANT_CLIENT will always resolve to the provider database.',\n )\n }\n\n container.registerFactory(\n PRISMA_TENANT_CLIENT,\n () => {\n const tenant = getCurrentTenant?.()\n return this.getDb(tenant?.id)\n },\n Scope.TRANSIENT,\n )\n\n log.info(\n `Prisma tenant DB registered (${getCurrentTenant ? 'multi-tenant mode' : 'provider-only mode'})`,\n )\n }\n\n /** Evict connections that haven't been accessed within cacheTtl */\n private async evictStale(): Promise<void> {\n const ttl = this.options.cacheTtl\n if (!ttl) return\n\n const now = Date.now()\n for (const [tenantId, lastTime] of this.lastAccessed) {\n if (now - lastTime > ttl) {\n const db = this.connections.get(tenantId)\n if (db && this.options.onTenantShutdown) {\n try {\n await this.options.onTenantShutdown(db, tenantId)\n } catch (err) {\n log.error(`Failed to evict tenant DB ${tenantId}: ${err}`)\n }\n }\n this.connections.delete(tenantId)\n this.lastAccessed.delete(tenantId)\n\n if (this.options.logging) {\n log.info(`Tenant DB evicted (idle): ${tenantId}`)\n }\n }\n }\n }\n\n /** Close all tenant connections on shutdown */\n async shutdown(): Promise<void> {\n if (this.evictionTimer) clearInterval(this.evictionTimer)\n\n if (this.options.onTenantShutdown) {\n for (const [tenantId, db] of this.connections) {\n try {\n await this.options.onTenantShutdown(db, tenantId)\n } catch (err) {\n log.error(`Failed to close tenant DB ${tenantId}: ${err}`)\n }\n }\n }\n\n this.connections.clear()\n this.lastAccessed.clear()\n log.info('All tenant DB connections closed')\n }\n\n /** Number of cached tenant connections (useful for monitoring) */\n get connectionCount(): number {\n return this.connections.size\n }\n}\n","import type { QueryBuilderAdapter, ParsedQuery, FilterItem, SortItem } from '@forinda/kickjs'\n\n/**\n * Configuration for the Prisma query builder adapter.\n *\n * Use the generic parameter to constrain `searchColumns` to actual model field names:\n *\n * @example\n * ```ts\n * import type { User } from '@prisma/client'\n *\n * // Type-safe — only User field names are accepted\n * const config: PrismaQueryConfig<User> = {\n * searchColumns: ['name', 'email'], // ✓ valid User fields\n * }\n *\n * // Without generic — accepts any string (backward compatible)\n * const config: PrismaQueryConfig = {\n * searchColumns: ['name', 'email'],\n * }\n * ```\n */\nexport interface PrismaQueryConfig<TModel = Record<string, any>> {\n /** Columns to search across when a search string is provided */\n searchColumns?: (keyof TModel & string)[]\n}\n\n/** Result shape matching Prisma's findMany arguments */\nexport interface PrismaQueryResult {\n where?: Record<string, any>\n orderBy?: Record<string, 'asc' | 'desc'>[]\n skip?: number\n take?: number\n}\n\n/**\n * Translates a ParsedQuery into Prisma-compatible `findMany` arguments.\n *\n * @example\n * ```ts\n * import type { User } from '@prisma/client'\n *\n * const adapter = new PrismaQueryAdapter()\n * const parsed = parseQuery(req.query)\n *\n * // Type-safe — only User fields allowed in searchColumns\n * const args = adapter.build(parsed, { searchColumns: ['name', 'email'] } satisfies PrismaQueryConfig<User>)\n * const users = await prisma.user.findMany(args)\n * ```\n */\nexport class PrismaQueryAdapter implements QueryBuilderAdapter<\n PrismaQueryResult,\n PrismaQueryConfig<any>\n> {\n readonly name = 'PrismaQueryAdapter'\n\n build<TModel = Record<string, any>>(\n parsed: ParsedQuery,\n config: PrismaQueryConfig<TModel> = {},\n ): PrismaQueryResult {\n const result: PrismaQueryResult = {}\n\n // Build where clause from filters and search\n const whereConditions = this.buildFilters(parsed.filters)\n const searchCondition = this.buildSearch(parsed.search, config.searchColumns)\n\n if (whereConditions.length > 0 || searchCondition) {\n const andClauses: any[] = []\n\n if (whereConditions.length > 0) {\n andClauses.push(...whereConditions)\n }\n if (searchCondition) {\n andClauses.push(searchCondition)\n }\n\n result.where = andClauses.length === 1 ? andClauses[0] : { AND: andClauses }\n }\n\n // Build orderBy\n const orderBy = this.buildSort(parsed.sort)\n if (orderBy.length > 0) {\n result.orderBy = orderBy\n }\n\n // Build pagination\n result.skip = parsed.pagination.offset\n result.take = parsed.pagination.limit\n\n return result\n }\n\n /** Map FilterItem[] to Prisma where conditions */\n private buildFilters(filters: FilterItem[]): Record<string, any>[] {\n return filters.map((filter) => {\n const { field, operator, value } = filter\n\n switch (operator) {\n case 'eq':\n return { [field]: { equals: this.coerce(value) } }\n case 'neq':\n return { [field]: { not: this.coerce(value) } }\n case 'gt':\n return { [field]: { gt: this.coerce(value) } }\n case 'gte':\n return { [field]: { gte: this.coerce(value) } }\n case 'lt':\n return { [field]: { lt: this.coerce(value) } }\n case 'lte':\n return { [field]: { lte: this.coerce(value) } }\n case 'contains':\n return { [field]: { contains: value, mode: 'insensitive' } }\n case 'starts':\n return { [field]: { startsWith: value, mode: 'insensitive' } }\n case 'ends':\n return { [field]: { endsWith: value, mode: 'insensitive' } }\n case 'in': {\n const values = value.split(',').map((v) => this.coerce(v.trim()))\n return { [field]: { in: values } }\n }\n case 'between': {\n const [min, max] = value.split(',').map((v) => this.coerce(v.trim()))\n return { [field]: { gte: min, lte: max } }\n }\n default:\n return { [field]: { equals: this.coerce(value) } }\n }\n })\n }\n\n /** Build Prisma orderBy from SortItem[] */\n private buildSort(sort: SortItem[]): Record<string, 'asc' | 'desc'>[] {\n return sort.map((item) => ({ [item.field]: item.direction }))\n }\n\n /** Build a search condition using OR + contains across multiple columns */\n private buildSearch(search: string, searchColumns?: string[]): Record<string, any> | null {\n if (!search || !searchColumns || searchColumns.length === 0) {\n return null\n }\n\n return {\n OR: searchColumns.map((column) => ({\n [column]: { contains: search, mode: 'insensitive' },\n })),\n }\n }\n\n /** Attempt to coerce a string value to a number or boolean if appropriate */\n private coerce(value: string): string | number | boolean {\n if (value === 'true') return true\n if (value === 'false') return false\n const num = Number(value)\n if (!Number.isNaN(num) && value.trim() !== '') return num\n return value\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["log"],"sources":["../src/types.ts","../src/prisma.adapter.ts","../src/prisma-tenant.adapter.ts","../src/query-adapter.ts"],"sourcesContent":["import type { MaybePromise } from '@forinda/kickjs'\n\n/** DI token for resolving the current tenant's PrismaClient (multi-tenant) */\nexport const PRISMA_TENANT_CLIENT = Symbol('PrismaTenantDB')\n\nexport interface PrismaTenantAdapterOptions<TDb = unknown> {\n /**\n * The provider (default) PrismaClient. Used when no tenant is\n * resolved or when accessing the tenant registry.\n */\n providerDb: TDb\n\n /**\n * Factory that creates a PrismaClient for a given tenant.\n * Called once per tenant — the result is cached for subsequent requests.\n *\n * @example\n * ```ts\n * tenantFactory: async (tenantId) => {\n * const url = await lookupTenantDbUrl(tenantId)\n * return new PrismaClient({ datasourceUrl: url })\n * }\n * ```\n */\n tenantFactory: (tenantId: string) => TDb | Promise<TDb>\n\n /**\n * Optional function to close a tenant PrismaClient connection.\n * Called for each cached connection during shutdown.\n */\n onTenantShutdown?: (db: TDb, tenantId: string) => MaybePromise<any>\n\n /** Enable query logging (default: false) */\n logging?: boolean\n\n /**\n * Cache TTL in milliseconds. Tenant connections idle beyond this\n * duration are evicted. Default: no eviction (connections live until shutdown).\n */\n cacheTtl?: number\n}\n\nexport interface PrismaAdapterOptions {\n /**\n * PrismaClient instance — typed as `any` to avoid version coupling.\n *\n * Prisma 5/6: `new PrismaClient()` from `@prisma/client`\n * Prisma 7+: `new PrismaClient({ adapter })` from your generated output path\n */\n client: any\n /**\n * Enable query logging (default: false).\n * Uses `$on('query', ...)` for Prisma 5/6 and `$extends` for Prisma 7+.\n */\n logging?: boolean\n}\n\n/** DI token for resolving the PrismaClient from the container */\nexport const PRISMA_CLIENT = Symbol('PrismaClient')\n\n/**\n * Common Prisma model delegate operations.\n * Use this to type-narrow the injected PrismaClient to a specific model\n * without needing `as any` casts in repositories.\n *\n * @example\n * ```ts\n * @Repository()\n * export class PrismaUserRepository {\n * @Inject(PRISMA_CLIENT) private prisma!: { user: PrismaModelDelegate }\n *\n * async findById(id: string) {\n * return this.prisma.user.findUnique({ where: { id } })\n * }\n * }\n * ```\n */\nexport interface PrismaModelDelegate {\n findUnique(args: {\n where: Record<string, unknown>\n include?: Record<string, unknown>\n }): Promise<unknown>\n findFirst?(args?: Record<string, unknown>): Promise<unknown>\n findMany(args?: Record<string, unknown>): Promise<unknown[]>\n create(args: { data: Record<string, unknown> }): Promise<unknown>\n update(args: { where: Record<string, unknown>; data: Record<string, unknown> }): Promise<unknown>\n delete(args: { where: Record<string, unknown> }): Promise<unknown>\n deleteMany(args?: { where?: Record<string, unknown> }): Promise<{ count: number }>\n count(args?: { where?: Record<string, unknown> }): Promise<number>\n}\n","import { Logger, type AppAdapter, type AdapterContext, Scope } from '@forinda/kickjs'\nimport { PRISMA_CLIENT, type PrismaAdapterOptions } from './types'\n\nconst log = Logger.for('PrismaAdapter')\n\n/**\n * Prisma adapter — registers a PrismaClient in the DI container and manages\n * its lifecycle (connection setup and teardown).\n *\n * Works with Prisma 5, 6, and 7+.\n *\n * @example Prisma 5/6\n * ```ts\n * import { PrismaClient } from '@prisma/client'\n *\n * new PrismaAdapter({ client: new PrismaClient(), logging: true })\n * ```\n *\n * @example Prisma 7+ (driver adapters)\n * ```ts\n * import { PrismaClient } from './generated/prisma'\n * import { PrismaPg } from '@prisma/adapter-pg'\n * import pg from 'pg'\n *\n * const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })\n * const client = new PrismaClient({ adapter: new PrismaPg(pool) })\n * new PrismaAdapter({ client, logging: true })\n * ```\n *\n * Inject the client in services:\n * ```ts\n * @Service()\n * class UserService {\n * @Inject(PRISMA_CLIENT) private prisma!: PrismaClient\n * }\n * ```\n */\nexport class PrismaAdapter implements AppAdapter {\n name = 'PrismaAdapter'\n private client: any\n\n constructor(private options: PrismaAdapterOptions) {\n this.client = options.client\n }\n\n /** Register the PrismaClient in the DI container */\n beforeStart({ container }: AdapterContext): void {\n // Set up query logging if requested\n if (this.options.logging) {\n if (typeof this.client.$on === 'function') {\n // Prisma 5/6: event-based logging\n this.client.$on('query', (event: any) => {\n log.debug(`Query: ${event.query}`)\n log.debug(`Params: ${event.params}`)\n log.debug(`Duration: ${event.duration}ms`)\n })\n } else if (typeof this.client.$extends === 'function') {\n // Prisma 7+: Client Extensions for logging ($on removed)\n this.client = this.client.$extends({\n query: {\n $allOperations({ operation, model, args, query }: any) {\n const start = performance.now()\n return query(args).then((result: any) => {\n const duration = Math.round(performance.now() - start)\n log.debug(`${model}.${operation} — ${duration}ms`)\n return result\n })\n },\n },\n })\n }\n }\n\n // Register the client instance as a singleton factory in the container\n container.registerFactory(PRISMA_CLIENT, () => this.client, Scope.SINGLETON)\n\n log.info('PrismaClient registered in DI container')\n }\n\n /** Disconnect the PrismaClient on shutdown */\n async shutdown(): Promise<void> {\n if (typeof this.client.$disconnect === 'function') {\n await this.client.$disconnect()\n log.info('PrismaClient disconnected')\n }\n }\n}\n","import { Logger, type AppAdapter, type AdapterContext, Scope } from '@forinda/kickjs'\nimport { PRISMA_TENANT_CLIENT, type PrismaTenantAdapterOptions } from './types'\n\nconst log = Logger.for('PrismaTenantAdapter')\n\n/**\n * Multi-tenant Prisma adapter — manages per-tenant PrismaClient connections\n * with automatic caching and lifecycle management.\n *\n * Registers `PRISMA_TENANT_CLIENT` as a TRANSIENT DI token that resolves\n * to the current tenant's PrismaClient using AsyncLocalStorage\n * (requires `TenantAdapter` to be configured).\n *\n * Works alongside `PrismaAdapter` — use `PRISMA_CLIENT` for the provider\n * database and `PRISMA_TENANT_CLIENT` for the current tenant's database.\n *\n * @example\n * ```ts\n * import { PrismaClient } from '@prisma/client'\n *\n * const providerDb = new PrismaClient({ datasourceUrl: PROVIDER_URL })\n *\n * bootstrap({\n * adapters: [\n * new TenantAdapter({ strategy: 'subdomain' }),\n * new PrismaTenantAdapter({\n * providerDb,\n * tenantFactory: async (tenantId) => {\n * const url = await lookupTenantDbUrl(tenantId)\n * return new PrismaClient({ datasourceUrl: url })\n * },\n * onTenantShutdown: (db) => db.$disconnect(),\n * }),\n * ],\n * })\n * ```\n *\n * Inject in services:\n * ```ts\n * @Service()\n * class ProjectService {\n * constructor(@Inject(PRISMA_TENANT_CLIENT) private prisma: PrismaClient) {}\n * }\n * ```\n */\nexport class PrismaTenantAdapter<TDb = unknown> implements AppAdapter {\n name = 'PrismaTenantAdapter'\n private readonly providerDb: TDb\n private readonly tenantFactory: (tenantId: string) => TDb | Promise<TDb>\n private readonly connections = new Map<string, TDb>()\n private readonly lastAccessed = new Map<string, number>()\n private readonly options: PrismaTenantAdapterOptions<TDb>\n private readonly evictionTimer?: ReturnType<typeof setInterval>\n\n constructor(options: PrismaTenantAdapterOptions<TDb>) {\n this.options = options\n this.providerDb = options.providerDb\n this.tenantFactory = options.tenantFactory\n\n if (options.cacheTtl && options.cacheTtl > 0) {\n const interval = Math.min(options.cacheTtl, 60_000)\n this.evictionTimer = setInterval(() => this.evictStale(), interval)\n this.evictionTimer.unref()\n }\n }\n\n /**\n * Get the PrismaClient for a specific tenant.\n * Creates and caches the connection on first access.\n * Returns the provider DB when tenantId is undefined/null.\n */\n async getDb(tenantId?: string | null): Promise<TDb> {\n if (!tenantId) return this.providerDb\n\n const cached = this.connections.get(tenantId)\n if (cached) {\n this.lastAccessed.set(tenantId, Date.now())\n return cached\n }\n\n const db = await this.tenantFactory(tenantId)\n this.connections.set(tenantId, db)\n this.lastAccessed.set(tenantId, Date.now())\n\n if (this.options.logging) {\n log.info(`Tenant DB created: ${tenantId} (${this.connections.size} total)`)\n }\n\n return db\n }\n\n /** Register PRISMA_TENANT_CLIENT as a transient factory in DI */\n async beforeStart({ container }: AdapterContext): Promise<void> {\n // Dynamically import getCurrentTenant to avoid hard dep on multi-tenant package\n let getCurrentTenant: (() => { id: string } | undefined) | undefined\n\n try {\n const mt: any = await import('@forinda/kickjs-multi-tenant')\n getCurrentTenant = mt.getCurrentTenant\n } catch {\n log.warn(\n 'PrismaTenantAdapter: @forinda/kickjs-multi-tenant not found. ' +\n 'PRISMA_TENANT_CLIENT will always resolve to the provider database.',\n )\n }\n\n container.registerFactory(\n PRISMA_TENANT_CLIENT,\n () => {\n const tenant = getCurrentTenant?.()\n return this.getDb(tenant?.id)\n },\n Scope.TRANSIENT,\n )\n\n log.info(\n `Prisma tenant DB registered (${getCurrentTenant ? 'multi-tenant mode' : 'provider-only mode'})`,\n )\n }\n\n /** Evict connections that haven't been accessed within cacheTtl */\n private async evictStale(): Promise<void> {\n const ttl = this.options.cacheTtl\n if (!ttl) return\n\n const now = Date.now()\n for (const [tenantId, lastTime] of this.lastAccessed) {\n if (now - lastTime > ttl) {\n const db = this.connections.get(tenantId)\n if (db && this.options.onTenantShutdown) {\n try {\n await this.options.onTenantShutdown(db, tenantId)\n } catch (err) {\n log.error(`Failed to evict tenant DB ${tenantId}: ${err}`)\n }\n }\n this.connections.delete(tenantId)\n this.lastAccessed.delete(tenantId)\n\n if (this.options.logging) {\n log.info(`Tenant DB evicted (idle): ${tenantId}`)\n }\n }\n }\n }\n\n /** Close all tenant connections on shutdown */\n async shutdown(): Promise<void> {\n if (this.evictionTimer) clearInterval(this.evictionTimer)\n\n if (this.options.onTenantShutdown) {\n for (const [tenantId, db] of this.connections) {\n try {\n await this.options.onTenantShutdown(db, tenantId)\n } catch (err) {\n log.error(`Failed to close tenant DB ${tenantId}: ${err}`)\n }\n }\n }\n\n this.connections.clear()\n this.lastAccessed.clear()\n log.info('All tenant DB connections closed')\n }\n\n /** Number of cached tenant connections (useful for monitoring) */\n get connectionCount(): number {\n return this.connections.size\n }\n}\n","import type { QueryBuilderAdapter, ParsedQuery, FilterItem, SortItem } from '@forinda/kickjs'\n\n/**\n * Configuration for the Prisma query builder adapter.\n *\n * Use the generic parameter to constrain `searchColumns` to actual model field names:\n *\n * @example\n * ```ts\n * import type { User } from '@prisma/client'\n *\n * // Type-safe — only User field names are accepted\n * const config: PrismaQueryConfig<User> = {\n * searchColumns: ['name', 'email'], // ✓ valid User fields\n * }\n *\n * // Without generic — accepts any string (backward compatible)\n * const config: PrismaQueryConfig = {\n * searchColumns: ['name', 'email'],\n * }\n * ```\n */\nexport interface PrismaQueryConfig<TModel = Record<string, any>> {\n /** Columns to search across when a search string is provided */\n searchColumns?: (keyof TModel & string)[]\n}\n\n/** Result shape matching Prisma's findMany arguments */\nexport interface PrismaQueryResult {\n where?: Record<string, any>\n orderBy?: Record<string, 'asc' | 'desc'>[]\n skip?: number\n take?: number\n}\n\n/**\n * Translates a ParsedQuery into Prisma-compatible `findMany` arguments.\n *\n * @example\n * ```ts\n * import type { User } from '@prisma/client'\n *\n * const adapter = new PrismaQueryAdapter()\n * const parsed = parseQuery(req.query)\n *\n * // Type-safe — only User fields allowed in searchColumns\n * const args = adapter.build(parsed, { searchColumns: ['name', 'email'] } satisfies PrismaQueryConfig<User>)\n * const users = await prisma.user.findMany(args)\n * ```\n */\nexport class PrismaQueryAdapter implements QueryBuilderAdapter<\n PrismaQueryResult,\n PrismaQueryConfig<any>\n> {\n readonly name = 'PrismaQueryAdapter'\n\n build<TModel = Record<string, any>>(\n parsed: ParsedQuery,\n config: PrismaQueryConfig<TModel> = {},\n ): PrismaQueryResult {\n const result: PrismaQueryResult = {}\n\n // Build where clause from filters and search\n const whereConditions = this.buildFilters(parsed.filters)\n const searchCondition = this.buildSearch(parsed.search, config.searchColumns)\n\n if (whereConditions.length > 0 || searchCondition) {\n const andClauses: any[] = []\n\n if (whereConditions.length > 0) {\n andClauses.push(...whereConditions)\n }\n if (searchCondition) {\n andClauses.push(searchCondition)\n }\n\n result.where = andClauses.length === 1 ? andClauses[0] : { AND: andClauses }\n }\n\n // Build orderBy\n const orderBy = this.buildSort(parsed.sort)\n if (orderBy.length > 0) {\n result.orderBy = orderBy\n }\n\n // Build pagination\n result.skip = parsed.pagination.offset\n result.take = parsed.pagination.limit\n\n return result\n }\n\n /** Map FilterItem[] to Prisma where conditions */\n private buildFilters(filters: FilterItem[]): Record<string, any>[] {\n return filters.map((filter) => {\n const { field, operator, value } = filter\n\n switch (operator) {\n case 'eq':\n return { [field]: { equals: this.coerce(value) } }\n case 'neq':\n return { [field]: { not: this.coerce(value) } }\n case 'gt':\n return { [field]: { gt: this.coerce(value) } }\n case 'gte':\n return { [field]: { gte: this.coerce(value) } }\n case 'lt':\n return { [field]: { lt: this.coerce(value) } }\n case 'lte':\n return { [field]: { lte: this.coerce(value) } }\n case 'contains':\n return { [field]: { contains: value, mode: 'insensitive' } }\n case 'starts':\n return { [field]: { startsWith: value, mode: 'insensitive' } }\n case 'ends':\n return { [field]: { endsWith: value, mode: 'insensitive' } }\n case 'in': {\n const values = value.split(',').map((v) => this.coerce(v.trim()))\n return { [field]: { in: values } }\n }\n case 'between': {\n const [min, max] = value.split(',').map((v) => this.coerce(v.trim()))\n return { [field]: { gte: min, lte: max } }\n }\n default:\n return { [field]: { equals: this.coerce(value) } }\n }\n })\n }\n\n /** Build Prisma orderBy from SortItem[] */\n private buildSort(sort: SortItem[]): Record<string, 'asc' | 'desc'>[] {\n return sort.map((item) => ({ [item.field]: item.direction }))\n }\n\n /** Build a search condition using OR + contains across multiple columns */\n private buildSearch(search: string, searchColumns?: string[]): Record<string, any> | null {\n if (!search || !searchColumns || searchColumns.length === 0) {\n return null\n }\n\n return {\n OR: searchColumns.map((column) => ({\n [column]: { contains: search, mode: 'insensitive' },\n })),\n }\n }\n\n /** Attempt to coerce a string value to a number or boolean if appropriate */\n private coerce(value: string): string | number | boolean {\n if (value === 'true') return true\n if (value === 'false') return false\n const num = Number(value)\n if (!Number.isNaN(num) && value.trim() !== '') return num\n return value\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-prisma",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "Prisma ORM adapter with DI integration, transaction support, and query building for KickJS",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -90,7 +90,7 @@
90
90
  "devDependencies": {
91
91
  "@types/node": "^25.0.0",
92
92
  "typescript": "^5.9.2",
93
- "@forinda/kickjs": "3.0.5"
93
+ "@forinda/kickjs": "3.0.6"
94
94
  },
95
95
  "publishConfig": {
96
96
  "access": "public"