@usebetterdev/tenant-drizzle 0.2.1-beta.1 → 0.4.0-beta.2
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.
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -137,6 +137,11 @@ function createGetTenantRepository(table = tenantsTable) {
|
|
|
137
137
|
(r) => rowToTenant(r)
|
|
138
138
|
);
|
|
139
139
|
},
|
|
140
|
+
async count() {
|
|
141
|
+
const result = await db.select({ value: (0, import_drizzle_orm3.count)() }).from(table);
|
|
142
|
+
const row = result[0];
|
|
143
|
+
return Number(row?.value ?? 0);
|
|
144
|
+
},
|
|
140
145
|
async delete(tenantId2) {
|
|
141
146
|
await db.delete(table).where((0, import_drizzle_orm3.eq)(table.id, tenantId2));
|
|
142
147
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/repository.ts","../src/schema.ts","../src/database.ts"],"sourcesContent":["export { drizzleDatabase } from \"./database.js\";\nexport { tenantsTable, tenantId } from \"./schema.js\";\n","import { sql } from \"drizzle-orm\";\nimport type { TenantAdapter } from \"@usebetterdev/tenant-core\";\n\n/**\n * Minimal shape for a Drizzle pg transaction: must support execute(sql).\n * The actual Drizzle transaction extends this with insert, select, etc.\n */\nexport interface DrizzlePgTransaction {\n execute(query: ReturnType<typeof sql> | unknown): Promise<unknown>;\n}\n\n/**\n * Minimal shape for a Drizzle pg database: must support transaction().\n * Generic over TTx so the full transaction type (with insert, select, etc.) is preserved.\n */\nexport interface DrizzlePgDatabase<TTx extends DrizzlePgTransaction = DrizzlePgTransaction> {\n transaction<T>(\n callback: (tx: TTx) => Promise<T>,\n ): Promise<T>;\n}\n\n/**\n * Creates a TenantAdapter for Drizzle (PostgreSQL).\n *\n * Uses a transaction per runWithTenant: BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n * The handle passed to fn is the transaction runner; use only this handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = true so RLS policies that allow\n * when current_setting('app.bypass_rls', true) = 'true' permit access (CLI generates this bypass policy).\n *\n * @param database - Drizzle pg database (from drizzle(pool) or similar)\n * @returns TenantAdapter implementation\n */\nexport function drizzleAdapter<TTx extends DrizzlePgTransaction>(\n database: DrizzlePgDatabase<TTx>,\n): TenantAdapter<TTx, TTx> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.current_tenant', ${tenantId}, true)`,\n );\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.bypass_rls', 'true', true)`,\n );\n return fn(tx);\n });\n },\n };\n}\n","import { eq } from \"drizzle-orm\";\nimport type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport { tenantsTable } from \"./schema.js\";\n\n/** Table shape expected by createGetTenantRepository: id, name, slug, createdAt (or created_at). */\nexport type TenantsTableLike = typeof tenantsTable;\n\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.createdAt ?? row.created_at;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n * Pass the tenants table (e.g. tenantsTable from this package or your own with id, name, slug, created_at).\n * The returned function is called by core with the system database handle (from adapter.runAsSystem).\n */\nexport function createGetTenantRepository(\n table: TenantsTableLike = tenantsTable,\n): (database: unknown) => TenantRepository {\n return (database: unknown) => {\n const db = database as {\n insert: (t: TenantsTableLike) => {\n values: (v: { name: string; slug: string }) => {\n returning: () => Promise<unknown[]>;\n };\n };\n update: (t: TenantsTableLike) => {\n set: (v: Partial<{ name: string; slug: string }>) => {\n where: (c: unknown) => { returning: () => Promise<unknown[]> };\n };\n };\n select: () => {\n from: (t: TenantsTableLike) => {\n where: (c: unknown) => { limit: (n: number) => Promise<unknown[]> };\n limit: (n: number) => { offset: (n: number) => Promise<unknown[]> };\n };\n };\n delete: (t: TenantsTableLike) => {\n where: (c: unknown) => Promise<unknown>;\n };\n };\n\n return {\n async create(data) {\n const rows = await db\n .insert(table)\n .values({\n name: data.name,\n slug: data.slug,\n })\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async update(tenantId, data) {\n const set: Partial<{ name: string; slug: string }> = {};\n if (data.name !== undefined) set.name = data.name;\n if (data.slug !== undefined) set.slug = data.slug;\n if (Object.keys(set).length === 0) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n }\n const rows = await db\n .update(table)\n .set(set)\n .where(eq(table.id, tenantId))\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getById(tenantId: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getBySlug(slug: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.slug, slug))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await db.select().from(table).limit(limit).offset(offset);\n return (Array.isArray(rows) ? rows : []).map((r) =>\n rowToTenant(r as Record<string, unknown>),\n );\n },\n\n async delete(tenantId) {\n await db.delete(table).where(eq(table.id, tenantId));\n },\n };\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\";\n\n/**\n * Standard tenants table schema matching the CLI-generated shape.\n * Columns: id (UUID), name, slug, created_at.\n * Use this table or provide your own with the same column contract for createGetTenantRepository.\n */\nexport const tenantsTable = pgTable(\"tenants\", {\n id: uuid(\"id\").primaryKey().defaultRandom(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull().unique(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).defaultNow().notNull(),\n}).enableRLS();\n\n/**\n * Pre-configured tenant_id column for use in tenant-scoped tables.\n * References tenants.id and auto-populates from the current session variable.\n *\n * Usage:\n * ```ts\n * import { tenantId } from \"@usebetterdev/tenant/drizzle\";\n *\n * export const projectsTable = pgTable(\"projects\", {\n * id: serial(\"id\").primaryKey(),\n * name: text(\"name\").notNull(),\n * ...tenantId,\n * }).enableRLS();\n * ```\n */\nexport const tenantId = {\n tenantId: uuid(\"tenant_id\")\n .notNull()\n .references(() => tenantsTable.id)\n .default(sql`(current_setting('app.current_tenant', true))::uuid`),\n} as const;\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { drizzleAdapter } from \"./adapter.js\";\nimport type { DrizzlePgDatabase, DrizzlePgTransaction } from \"./adapter.js\";\nimport { createGetTenantRepository, type TenantsTableLike } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Drizzle (PostgreSQL).\n *\n * Bundles drizzleAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: drizzleDatabase(db) })`.\n *\n * @param db - Drizzle pg database (from drizzle(pool) or similar)\n * @param options - Optional: custom tenants table\n */\nexport function drizzleDatabase<TTx extends DrizzlePgTransaction>(\n db: DrizzlePgDatabase<TTx>,\n options?: { table?: TenantsTableLike },\n): DatabaseProvider<TTx, TTx> {\n return {\n adapter: drizzleAdapter(db),\n getTenantRepository: createGetTenantRepository(options?.table),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAAoB;AAiCb,SAAS,eACd,UACyB;AACzB,SAAO;AAAA,IACL,MAAM,cACJA,WACA,IACY;AACZ,aAAO,SAAS,YAAY,OAAO,OAAO;AACxC,cAAM,GAAG;AAAA,UACP,iEAA8CA,SAAQ;AAAA,QACxD;AACA,eAAO,GAAG,EAAE;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,YACJ,IACY;AACZ,aAAO,SAAS,YAAY,OAAO,OAAO;AACxC,cAAM,GAAG;AAAA,UACP;AAAA,QACF;AACA,eAAO,GAAG,EAAE;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC5DA,IAAAC,sBAAmB;;;ACAnB,IAAAC,sBAAoB;AACpB,qBAA+C;AAOxC,IAAM,mBAAe,wBAAQ,WAAW;AAAA,EAC7C,QAAI,qBAAK,IAAI,EAAE,WAAW,EAAE,cAAc;AAAA,EAC1C,UAAM,qBAAK,MAAM,EAAE,QAAQ;AAAA,EAC3B,UAAM,qBAAK,MAAM,EAAE,QAAQ,EAAE,OAAO;AAAA,EACpC,eAAW,0BAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,WAAW,EAAE,QAAQ;AAClF,CAAC,EAAE,UAAU;AAiBN,IAAM,WAAW;AAAA,EACtB,cAAU,qBAAK,WAAW,EACvB,QAAQ,EACR,WAAW,MAAM,aAAa,EAAE,EAChC,QAAQ,4EAAwD;AACrE;;;AD5BA,SAAS,YAAY,KAAsC;AACzD,QAAM,YAAY,IAAI,aAAa,IAAI;AACvC,MAAI,cAAc,UAAa,cAAc,MAAM;AACjD,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,SAAO;AAAA,IACL,IAAI,OAAO,IAAI,EAAE;AAAA,IACjB,MAAM,OAAO,IAAI,IAAI;AAAA,IACrB,MAAM,OAAO,IAAI,IAAI;AAAA,IACrB;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAOO,SAAS,0BACd,QAA0B,cACe;AACzC,SAAO,CAAC,aAAsB;AAC5B,UAAM,KAAK;AAsBX,WAAO;AAAA,MACL,MAAM,OAAO,MAAM;AACjB,cAAM,OAAO,MAAM,GAChB,OAAO,KAAK,EACZ,OAAO;AAAA,UACN,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,QACb,CAAC,EACA,UAAU;AACb,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,gBAAM,IAAI,MAAM,kDAAkD;AAAA,QACpE;AACA,eAAO,YAAY,GAA8B;AAAA,MACnD;AAAA,MAEA,MAAM,OAAOC,WAAU,MAAM;AAC3B,cAAM,MAA+C,CAAC;AACtD,YAAI,KAAK,SAAS,OAAW,KAAI,OAAO,KAAK;AAC7C,YAAI,KAAK,SAAS,OAAW,KAAI,OAAO,KAAK;AAC7C,YAAI,OAAO,KAAK,GAAG,EAAE,WAAW,GAAG;AACjC,gBAAMC,QAAO,MAAM,GAChB,OAAO,EACP,KAAK,KAAK,EACV,UAAM,wBAAG,MAAM,IAAID,SAAQ,CAAC,EAC5B,MAAM,CAAC;AACV,gBAAME,OAAM,MAAM,QAAQD,KAAI,IAAIA,MAAK,CAAC,IAAI;AAC5C,cAAI,CAACC,QAAO,OAAOA,SAAQ,UAAU;AACnC,kBAAM,IAAI,MAAM,iCAAiC;AAAA,UACnD;AACA,iBAAO,YAAYA,IAA8B;AAAA,QACnD;AACA,cAAM,OAAO,MAAM,GAChB,OAAO,KAAK,EACZ,IAAI,GAAG,EACP,UAAM,wBAAG,MAAM,IAAIF,SAAQ,CAAC,EAC5B,UAAU;AACb,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,gBAAM,IAAI,MAAM,iCAAiC;AAAA,QACnD;AACA,eAAO,YAAY,GAA8B;AAAA,MACnD;AAAA,MAEA,MAAM,QAAQA,WAAkB;AAC9B,cAAM,OAAO,MAAM,GAChB,OAAO,EACP,KAAK,KAAK,EACV,UAAM,wBAAG,MAAM,IAAIA,SAAQ,CAAC,EAC5B,MAAM,CAAC;AACV,cAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI;AAC5C,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,iBAAO;AAAA,QACT;AAEA,eAAO,YAAY,GAA8B;AAAA,MACnD;AAAA,MAEA,MAAM,UAAU,MAAc;AAC5B,cAAM,OAAO,MAAM,GAChB,OAAO,EACP,KAAK,KAAK,EACV,UAAM,wBAAG,MAAM,MAAM,IAAI,CAAC,EAC1B,MAAM,CAAC;AACV,cAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI;AAC5C,YAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,iBAAO;AAAA,QACT;AACA,eAAO,YAAY,GAA8B;AAAA,MACnD;AAAA,MAEA,MAAM,KAAK,UAAU,CAAC,GAAG;AACvB,cAAM,QAAQ,QAAQ,SAAS;AAC/B,cAAM,SAAS,KAAK,IAAI,GAAG,QAAQ,UAAU,CAAC;AAC9C,cAAM,OAAO,MAAM,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,MAAM;AACrE,gBAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,GAAG;AAAA,UAAI,CAAC,MAC5C,YAAY,CAA4B;AAAA,QAC1C;AAAA,MACF;AAAA,MAEA,MAAM,OAAOA,WAAU;AACrB,cAAM,GAAG,OAAO,KAAK,EAAE,UAAM,wBAAG,MAAM,IAAIA,SAAQ,CAAC;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF;;;AE3HO,SAAS,gBACd,IACA,SAC4B;AAC5B,SAAO;AAAA,IACL,SAAS,eAAe,EAAE;AAAA,IAC1B,qBAAqB,0BAA0B,SAAS,KAAK;AAAA,EAC/D;AACF;","names":["tenantId","import_drizzle_orm","import_drizzle_orm","tenantId","rows","row"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/repository.ts","../src/schema.ts","../src/database.ts"],"sourcesContent":["export { drizzleDatabase } from \"./database.js\";\nexport { tenantsTable, tenantId } from \"./schema.js\";\n","import { sql } from \"drizzle-orm\";\nimport type { TenantAdapter } from \"@usebetterdev/tenant-core\";\n\n/**\n * Minimal shape for a Drizzle pg transaction: must support execute(sql).\n * The actual Drizzle transaction extends this with insert, select, etc.\n */\nexport interface DrizzlePgTransaction {\n execute(query: ReturnType<typeof sql> | unknown): Promise<unknown>;\n}\n\n/**\n * Minimal shape for a Drizzle pg database: must support transaction().\n * Generic over TTx so the full transaction type (with insert, select, etc.) is preserved.\n */\nexport interface DrizzlePgDatabase<TTx extends DrizzlePgTransaction = DrizzlePgTransaction> {\n transaction<T>(\n callback: (tx: TTx) => Promise<T>,\n ): Promise<T>;\n}\n\n/**\n * Creates a TenantAdapter for Drizzle (PostgreSQL).\n *\n * Uses a transaction per runWithTenant: BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n * The handle passed to fn is the transaction runner; use only this handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = true so RLS policies that allow\n * when current_setting('app.bypass_rls', true) = 'true' permit access (CLI generates this bypass policy).\n *\n * @param database - Drizzle pg database (from drizzle(pool) or similar)\n * @returns TenantAdapter implementation\n */\nexport function drizzleAdapter<TTx extends DrizzlePgTransaction>(\n database: DrizzlePgDatabase<TTx>,\n): TenantAdapter<TTx, TTx> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.current_tenant', ${tenantId}, true)`,\n );\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.bypass_rls', 'true', true)`,\n );\n return fn(tx);\n });\n },\n };\n}\n","import { eq, count } from \"drizzle-orm\";\nimport type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport { tenantsTable } from \"./schema.js\";\n\n/** Table shape expected by createGetTenantRepository: id, name, slug, createdAt (or created_at). */\nexport type TenantsTableLike = typeof tenantsTable;\n\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.createdAt ?? row.created_at;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n * Pass the tenants table (e.g. tenantsTable from this package or your own with id, name, slug, created_at).\n * The returned function is called by core with the system database handle (from adapter.runAsSystem).\n */\nexport function createGetTenantRepository(\n table: TenantsTableLike = tenantsTable,\n): (database: unknown) => TenantRepository {\n return (database: unknown) => {\n const db = database as {\n insert: (t: TenantsTableLike) => {\n values: (v: { name: string; slug: string }) => {\n returning: () => Promise<unknown[]>;\n };\n };\n update: (t: TenantsTableLike) => {\n set: (v: Partial<{ name: string; slug: string }>) => {\n where: (c: unknown) => { returning: () => Promise<unknown[]> };\n };\n };\n select: {\n (): {\n from: (t: TenantsTableLike) => {\n where: (c: unknown) => { limit: (n: number) => Promise<unknown[]> };\n limit: (n: number) => { offset: (n: number) => Promise<unknown[]> };\n };\n };\n (fields: Record<string, unknown>): {\n from: (t: TenantsTableLike) => Promise<unknown[]>;\n };\n };\n delete: (t: TenantsTableLike) => {\n where: (c: unknown) => Promise<unknown>;\n };\n };\n\n return {\n async create(data) {\n const rows = await db\n .insert(table)\n .values({\n name: data.name,\n slug: data.slug,\n })\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async update(tenantId, data) {\n const set: Partial<{ name: string; slug: string }> = {};\n if (data.name !== undefined) set.name = data.name;\n if (data.slug !== undefined) set.slug = data.slug;\n if (Object.keys(set).length === 0) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n }\n const rows = await db\n .update(table)\n .set(set)\n .where(eq(table.id, tenantId))\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getById(tenantId: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getBySlug(slug: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.slug, slug))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await db.select().from(table).limit(limit).offset(offset);\n return (Array.isArray(rows) ? rows : []).map((r) =>\n rowToTenant(r as Record<string, unknown>),\n );\n },\n\n async count() {\n const result = await db.select({ value: count() }).from(table);\n const row = result[0] as { value: number | string } | undefined;\n return Number(row?.value ?? 0);\n },\n\n async delete(tenantId) {\n await db.delete(table).where(eq(table.id, tenantId));\n },\n };\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\";\n\n/**\n * Standard tenants table schema matching the CLI-generated shape.\n * Columns: id (UUID), name, slug, created_at.\n * Use this table or provide your own with the same column contract for createGetTenantRepository.\n */\nexport const tenantsTable = pgTable(\"tenants\", {\n id: uuid(\"id\").primaryKey().defaultRandom(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull().unique(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).defaultNow().notNull(),\n}).enableRLS();\n\n/**\n * Pre-configured tenant_id column for use in tenant-scoped tables.\n * References tenants.id and auto-populates from the current session variable.\n *\n * Usage:\n * ```ts\n * import { tenantId } from \"@usebetterdev/tenant/drizzle\";\n *\n * export const projectsTable = pgTable(\"projects\", {\n * id: serial(\"id\").primaryKey(),\n * name: text(\"name\").notNull(),\n * ...tenantId,\n * }).enableRLS();\n * ```\n */\nexport const tenantId = {\n tenantId: uuid(\"tenant_id\")\n .notNull()\n .references(() => tenantsTable.id)\n .default(sql`(current_setting('app.current_tenant', true))::uuid`),\n} as const;\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { drizzleAdapter } from \"./adapter.js\";\nimport type { DrizzlePgDatabase, DrizzlePgTransaction } from \"./adapter.js\";\nimport { createGetTenantRepository, type TenantsTableLike } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Drizzle (PostgreSQL).\n *\n * Bundles drizzleAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: drizzleDatabase(db) })`.\n *\n * @param db - Drizzle pg database (from drizzle(pool) or similar)\n * @param options - Optional: custom tenants table\n */\nexport function drizzleDatabase<TTx extends DrizzlePgTransaction>(\n db: DrizzlePgDatabase<TTx>,\n options?: { table?: TenantsTableLike },\n): DatabaseProvider<TTx, TTx> {\n return {\n adapter: drizzleAdapter(db),\n getTenantRepository: createGetTenantRepository(options?.table),\n };\n}\n"],"mappings":"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","names":["tenantId","import_drizzle_orm","import_drizzle_orm","tenantId","rows","row"]}
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ function drizzleAdapter(database) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// src/repository.ts
|
|
25
|
-
import { eq } from "drizzle-orm";
|
|
25
|
+
import { eq, count } from "drizzle-orm";
|
|
26
26
|
|
|
27
27
|
// src/schema.ts
|
|
28
28
|
import { sql as sql2 } from "drizzle-orm";
|
|
@@ -109,6 +109,11 @@ function createGetTenantRepository(table = tenantsTable) {
|
|
|
109
109
|
(r) => rowToTenant(r)
|
|
110
110
|
);
|
|
111
111
|
},
|
|
112
|
+
async count() {
|
|
113
|
+
const result = await db.select({ value: count() }).from(table);
|
|
114
|
+
const row = result[0];
|
|
115
|
+
return Number(row?.value ?? 0);
|
|
116
|
+
},
|
|
112
117
|
async delete(tenantId2) {
|
|
113
118
|
await db.delete(table).where(eq(table.id, tenantId2));
|
|
114
119
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/repository.ts","../src/schema.ts","../src/database.ts"],"sourcesContent":["import { sql } from \"drizzle-orm\";\nimport type { TenantAdapter } from \"@usebetterdev/tenant-core\";\n\n/**\n * Minimal shape for a Drizzle pg transaction: must support execute(sql).\n * The actual Drizzle transaction extends this with insert, select, etc.\n */\nexport interface DrizzlePgTransaction {\n execute(query: ReturnType<typeof sql> | unknown): Promise<unknown>;\n}\n\n/**\n * Minimal shape for a Drizzle pg database: must support transaction().\n * Generic over TTx so the full transaction type (with insert, select, etc.) is preserved.\n */\nexport interface DrizzlePgDatabase<TTx extends DrizzlePgTransaction = DrizzlePgTransaction> {\n transaction<T>(\n callback: (tx: TTx) => Promise<T>,\n ): Promise<T>;\n}\n\n/**\n * Creates a TenantAdapter for Drizzle (PostgreSQL).\n *\n * Uses a transaction per runWithTenant: BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n * The handle passed to fn is the transaction runner; use only this handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = true so RLS policies that allow\n * when current_setting('app.bypass_rls', true) = 'true' permit access (CLI generates this bypass policy).\n *\n * @param database - Drizzle pg database (from drizzle(pool) or similar)\n * @returns TenantAdapter implementation\n */\nexport function drizzleAdapter<TTx extends DrizzlePgTransaction>(\n database: DrizzlePgDatabase<TTx>,\n): TenantAdapter<TTx, TTx> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.current_tenant', ${tenantId}, true)`,\n );\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.bypass_rls', 'true', true)`,\n );\n return fn(tx);\n });\n },\n };\n}\n","import { eq } from \"drizzle-orm\";\nimport type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport { tenantsTable } from \"./schema.js\";\n\n/** Table shape expected by createGetTenantRepository: id, name, slug, createdAt (or created_at). */\nexport type TenantsTableLike = typeof tenantsTable;\n\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.createdAt ?? row.created_at;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n * Pass the tenants table (e.g. tenantsTable from this package or your own with id, name, slug, created_at).\n * The returned function is called by core with the system database handle (from adapter.runAsSystem).\n */\nexport function createGetTenantRepository(\n table: TenantsTableLike = tenantsTable,\n): (database: unknown) => TenantRepository {\n return (database: unknown) => {\n const db = database as {\n insert: (t: TenantsTableLike) => {\n values: (v: { name: string; slug: string }) => {\n returning: () => Promise<unknown[]>;\n };\n };\n update: (t: TenantsTableLike) => {\n set: (v: Partial<{ name: string; slug: string }>) => {\n where: (c: unknown) => { returning: () => Promise<unknown[]> };\n };\n };\n select: () => {\n from: (t: TenantsTableLike) => {\n where: (c: unknown) => { limit: (n: number) => Promise<unknown[]> };\n limit: (n: number) => { offset: (n: number) => Promise<unknown[]> };\n };\n };\n delete: (t: TenantsTableLike) => {\n where: (c: unknown) => Promise<unknown>;\n };\n };\n\n return {\n async create(data) {\n const rows = await db\n .insert(table)\n .values({\n name: data.name,\n slug: data.slug,\n })\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async update(tenantId, data) {\n const set: Partial<{ name: string; slug: string }> = {};\n if (data.name !== undefined) set.name = data.name;\n if (data.slug !== undefined) set.slug = data.slug;\n if (Object.keys(set).length === 0) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n }\n const rows = await db\n .update(table)\n .set(set)\n .where(eq(table.id, tenantId))\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getById(tenantId: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getBySlug(slug: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.slug, slug))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await db.select().from(table).limit(limit).offset(offset);\n return (Array.isArray(rows) ? rows : []).map((r) =>\n rowToTenant(r as Record<string, unknown>),\n );\n },\n\n async delete(tenantId) {\n await db.delete(table).where(eq(table.id, tenantId));\n },\n };\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\";\n\n/**\n * Standard tenants table schema matching the CLI-generated shape.\n * Columns: id (UUID), name, slug, created_at.\n * Use this table or provide your own with the same column contract for createGetTenantRepository.\n */\nexport const tenantsTable = pgTable(\"tenants\", {\n id: uuid(\"id\").primaryKey().defaultRandom(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull().unique(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).defaultNow().notNull(),\n}).enableRLS();\n\n/**\n * Pre-configured tenant_id column for use in tenant-scoped tables.\n * References tenants.id and auto-populates from the current session variable.\n *\n * Usage:\n * ```ts\n * import { tenantId } from \"@usebetterdev/tenant/drizzle\";\n *\n * export const projectsTable = pgTable(\"projects\", {\n * id: serial(\"id\").primaryKey(),\n * name: text(\"name\").notNull(),\n * ...tenantId,\n * }).enableRLS();\n * ```\n */\nexport const tenantId = {\n tenantId: uuid(\"tenant_id\")\n .notNull()\n .references(() => tenantsTable.id)\n .default(sql`(current_setting('app.current_tenant', true))::uuid`),\n} as const;\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { drizzleAdapter } from \"./adapter.js\";\nimport type { DrizzlePgDatabase, DrizzlePgTransaction } from \"./adapter.js\";\nimport { createGetTenantRepository, type TenantsTableLike } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Drizzle (PostgreSQL).\n *\n * Bundles drizzleAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: drizzleDatabase(db) })`.\n *\n * @param db - Drizzle pg database (from drizzle(pool) or similar)\n * @param options - Optional: custom tenants table\n */\nexport function drizzleDatabase<TTx extends DrizzlePgTransaction>(\n db: DrizzlePgDatabase<TTx>,\n options?: { table?: TenantsTableLike },\n): DatabaseProvider<TTx, TTx> {\n return {\n adapter: drizzleAdapter(db),\n getTenantRepository: createGetTenantRepository(options?.table),\n };\n}\n"],"mappings":"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","names":["tenantId","sql","tenantId","rows","row"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/repository.ts","../src/schema.ts","../src/database.ts"],"sourcesContent":["import { sql } from \"drizzle-orm\";\nimport type { TenantAdapter } from \"@usebetterdev/tenant-core\";\n\n/**\n * Minimal shape for a Drizzle pg transaction: must support execute(sql).\n * The actual Drizzle transaction extends this with insert, select, etc.\n */\nexport interface DrizzlePgTransaction {\n execute(query: ReturnType<typeof sql> | unknown): Promise<unknown>;\n}\n\n/**\n * Minimal shape for a Drizzle pg database: must support transaction().\n * Generic over TTx so the full transaction type (with insert, select, etc.) is preserved.\n */\nexport interface DrizzlePgDatabase<TTx extends DrizzlePgTransaction = DrizzlePgTransaction> {\n transaction<T>(\n callback: (tx: TTx) => Promise<T>,\n ): Promise<T>;\n}\n\n/**\n * Creates a TenantAdapter for Drizzle (PostgreSQL).\n *\n * Uses a transaction per runWithTenant: BEGIN → SET LOCAL app.current_tenant = tenantId → fn(tx) → COMMIT.\n * The handle passed to fn is the transaction runner; use only this handle for tenant-scoped queries so RLS applies.\n *\n * runAsSystem runs in a transaction with SET LOCAL app.bypass_rls = true so RLS policies that allow\n * when current_setting('app.bypass_rls', true) = 'true' permit access (CLI generates this bypass policy).\n *\n * @param database - Drizzle pg database (from drizzle(pool) or similar)\n * @returns TenantAdapter implementation\n */\nexport function drizzleAdapter<TTx extends DrizzlePgTransaction>(\n database: DrizzlePgDatabase<TTx>,\n): TenantAdapter<TTx, TTx> {\n return {\n async runWithTenant<T>(\n tenantId: string,\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.current_tenant', ${tenantId}, true)`,\n );\n return fn(tx);\n });\n },\n\n async runAsSystem<T>(\n fn: (database: TTx) => Promise<T>,\n ): Promise<T> {\n return database.transaction(async (tx) => {\n await tx.execute(\n sql`SELECT set_config('app.bypass_rls', 'true', true)`,\n );\n return fn(tx);\n });\n },\n };\n}\n","import { eq, count } from \"drizzle-orm\";\nimport type { Tenant, TenantRepository } from \"@usebetterdev/tenant-core\";\nimport { tenantsTable } from \"./schema.js\";\n\n/** Table shape expected by createGetTenantRepository: id, name, slug, createdAt (or created_at). */\nexport type TenantsTableLike = typeof tenantsTable;\n\nfunction rowToTenant(row: Record<string, unknown>): Tenant {\n const createdAt = row.createdAt ?? row.created_at;\n if (createdAt === undefined || createdAt === null) {\n throw new Error(\"better-tenant: row missing created_at / createdAt\");\n }\n return {\n id: String(row.id),\n name: String(row.name),\n slug: String(row.slug),\n createdAt: createdAt as Date | string,\n ...row,\n };\n}\n\n/**\n * Creates getTenantRepository for use with betterTenant({ getTenantRepository }).\n * Pass the tenants table (e.g. tenantsTable from this package or your own with id, name, slug, created_at).\n * The returned function is called by core with the system database handle (from adapter.runAsSystem).\n */\nexport function createGetTenantRepository(\n table: TenantsTableLike = tenantsTable,\n): (database: unknown) => TenantRepository {\n return (database: unknown) => {\n const db = database as {\n insert: (t: TenantsTableLike) => {\n values: (v: { name: string; slug: string }) => {\n returning: () => Promise<unknown[]>;\n };\n };\n update: (t: TenantsTableLike) => {\n set: (v: Partial<{ name: string; slug: string }>) => {\n where: (c: unknown) => { returning: () => Promise<unknown[]> };\n };\n };\n select: {\n (): {\n from: (t: TenantsTableLike) => {\n where: (c: unknown) => { limit: (n: number) => Promise<unknown[]> };\n limit: (n: number) => { offset: (n: number) => Promise<unknown[]> };\n };\n };\n (fields: Record<string, unknown>): {\n from: (t: TenantsTableLike) => Promise<unknown[]>;\n };\n };\n delete: (t: TenantsTableLike) => {\n where: (c: unknown) => Promise<unknown>;\n };\n };\n\n return {\n async create(data) {\n const rows = await db\n .insert(table)\n .values({\n name: data.name,\n slug: data.slug,\n })\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: createTenant failed to return row\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async update(tenantId, data) {\n const set: Partial<{ name: string; slug: string }> = {};\n if (data.name !== undefined) set.name = data.name;\n if (data.slug !== undefined) set.slug = data.slug;\n if (Object.keys(set).length === 0) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n }\n const rows = await db\n .update(table)\n .set(set)\n .where(eq(table.id, tenantId))\n .returning();\n const row = rows[0];\n if (!row || typeof row !== \"object\") {\n throw new Error(\"better-tenant: tenant not found\");\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getById(tenantId: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.id, tenantId))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async getBySlug(slug: string) {\n const rows = await db\n .select()\n .from(table)\n .where(eq(table.slug, slug))\n .limit(1);\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || typeof row !== \"object\") {\n return null;\n }\n return rowToTenant(row as Record<string, unknown>);\n },\n\n async list(options = {}) {\n const limit = options.limit ?? 50;\n const offset = Math.max(0, options.offset ?? 0);\n const rows = await db.select().from(table).limit(limit).offset(offset);\n return (Array.isArray(rows) ? rows : []).map((r) =>\n rowToTenant(r as Record<string, unknown>),\n );\n },\n\n async count() {\n const result = await db.select({ value: count() }).from(table);\n const row = result[0] as { value: number | string } | undefined;\n return Number(row?.value ?? 0);\n },\n\n async delete(tenantId) {\n await db.delete(table).where(eq(table.id, tenantId));\n },\n };\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\";\n\n/**\n * Standard tenants table schema matching the CLI-generated shape.\n * Columns: id (UUID), name, slug, created_at.\n * Use this table or provide your own with the same column contract for createGetTenantRepository.\n */\nexport const tenantsTable = pgTable(\"tenants\", {\n id: uuid(\"id\").primaryKey().defaultRandom(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull().unique(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).defaultNow().notNull(),\n}).enableRLS();\n\n/**\n * Pre-configured tenant_id column for use in tenant-scoped tables.\n * References tenants.id and auto-populates from the current session variable.\n *\n * Usage:\n * ```ts\n * import { tenantId } from \"@usebetterdev/tenant/drizzle\";\n *\n * export const projectsTable = pgTable(\"projects\", {\n * id: serial(\"id\").primaryKey(),\n * name: text(\"name\").notNull(),\n * ...tenantId,\n * }).enableRLS();\n * ```\n */\nexport const tenantId = {\n tenantId: uuid(\"tenant_id\")\n .notNull()\n .references(() => tenantsTable.id)\n .default(sql`(current_setting('app.current_tenant', true))::uuid`),\n} as const;\n","import type { DatabaseProvider } from \"@usebetterdev/tenant-core\";\nimport { drizzleAdapter } from \"./adapter.js\";\nimport type { DrizzlePgDatabase, DrizzlePgTransaction } from \"./adapter.js\";\nimport { createGetTenantRepository, type TenantsTableLike } from \"./repository.js\";\n\n/**\n * Creates a DatabaseProvider for Drizzle (PostgreSQL).\n *\n * Bundles drizzleAdapter + createGetTenantRepository into a single config value\n * for use with `betterTenant({ database: drizzleDatabase(db) })`.\n *\n * @param db - Drizzle pg database (from drizzle(pool) or similar)\n * @param options - Optional: custom tenants table\n */\nexport function drizzleDatabase<TTx extends DrizzlePgTransaction>(\n db: DrizzlePgDatabase<TTx>,\n options?: { table?: TenantsTableLike },\n): DatabaseProvider<TTx, TTx> {\n return {\n adapter: drizzleAdapter(db),\n getTenantRepository: createGetTenantRepository(options?.table),\n };\n}\n"],"mappings":"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","names":["tenantId","sql","tenantId","rows","row"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebetterdev/tenant-drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-beta.2",
|
|
4
4
|
"repository": "github:usebetter-dev/usebetter",
|
|
5
5
|
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
6
|
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"drizzle-orm": "^0.36.0",
|
|
28
|
-
"@usebetterdev/tenant-core": "0.
|
|
28
|
+
"@usebetterdev/tenant-core": "0.4.0-beta.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"pg": ">=8.0.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"tsup": "^8.3.5",
|
|
48
48
|
"typescript": "~5.7.2",
|
|
49
49
|
"vitest": "^2.1.6",
|
|
50
|
-
"@usebetterdev/test-utils": "0.
|
|
50
|
+
"@usebetterdev/test-utils": "0.4.0-beta.2"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=22"
|