@percepta/create 3.2.0 → 3.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percepta/create",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Scaffold a new Mosaic package",
5
5
  "keywords": [
6
6
  "cli",
@@ -38,7 +38,7 @@
38
38
  "@types/node": "^24.1.0",
39
39
  "@types/validate-npm-package-name": "^4.0.2",
40
40
  "vitest": "^4.0.0",
41
- "@percepta/build": "0.5.1"
41
+ "@percepta/build": "1.0.0"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18.0.0"
@@ -63,9 +63,10 @@ pnpm access:bootstrap-customer-admin -- --subject core/user:<user-id>
63
63
 
64
64
  ## Shared Auth
65
65
 
66
- The `auth/` workspace owns the customer-global Better Auth schema, including
67
- `users`, `groups`, and `group_members`. Apps should consume this shared
68
- identity layer instead of creating app-local users or groups.
66
+ The `auth/` workspace wires the customer-global Better Auth schema from
67
+ `@percepta/auth`, including `users`, `groups`, and `group_members`. Apps should
68
+ consume this shared identity layer instead of creating app-local users or
69
+ groups.
69
70
 
70
71
  ## Adding a new package
71
72
 
@@ -1,8 +1,9 @@
1
1
  # Shared Auth
2
2
 
3
- This workspace owns the customer-global Better Auth database schema. Apps in the
4
- customer monorepo should import this package for session validation and user /
5
- group table references instead of creating app-local auth tables.
3
+ This workspace wires the customer-global Better Auth database schema from
4
+ `@percepta/auth`. Apps in the customer monorepo should import this package for
5
+ session validation and user / group table references instead of creating
6
+ app-local auth tables.
6
7
 
7
8
  Import auth as `@__APP_NAME__/auth`, the database handle as
8
9
  `@__APP_NAME__/auth/db`, and table definitions as `@__APP_NAME__/auth/schema`
@@ -17,14 +17,11 @@
17
17
  "db:setup-and-migrate": "pnpm db:setup && pnpm db:migrate"
18
18
  },
19
19
  "dependencies": {
20
- "@percepta/access-control": "0.3.2",
21
- "better-auth": "^1.6.4",
22
- "drizzle-orm": "^0.45.2",
23
- "pg": "^8.16.3"
20
+ "@percepta/auth": "0.1.0",
21
+ "drizzle-orm": "^0.45.2"
24
22
  },
25
23
  "devDependencies": {
26
24
  "@types/node": "^24.1.0",
27
- "@types/pg": "^8.15.4",
28
25
  "drizzle-kit": "^0.31.4",
29
26
  "tsx": "^4.20.3",
30
27
  "typescript": "^5.8.3"
@@ -1,54 +1,8 @@
1
- import { Pool } from "pg";
1
+ import { setupAuthDatabase } from "@percepta/auth";
2
2
  import { getAuthDatabaseConfig } from "../src/config/database";
3
3
 
4
- const SHARED_DATABASES = new Set(["demos", "internal_apps"]);
5
-
6
4
  async function main(): Promise<void> {
7
- const { database, host, password, port, url, user } = getAuthDatabaseConfig();
8
-
9
- if (url != null && url.length > 0) {
10
- console.log("AUTH_DATABASE_URL is set; skipping CREATE DATABASE.");
11
- return;
12
- }
13
-
14
- console.log(`Setting up shared auth database: ${database}`);
15
- console.log(`Host: ${host}:${port}`);
16
- console.log(`User: ${user}`);
17
-
18
- if (SHARED_DATABASES.has(database)) {
19
- console.log(`${database} is a shared infra-managed database; skipping.`);
20
- return;
21
- }
22
-
23
- const adminClient = new Pool({
24
- database: "postgres",
25
- host,
26
- password,
27
- port,
28
- user,
29
- });
30
-
31
- try {
32
- const result = await adminClient.query(
33
- "SELECT 1 FROM pg_database WHERE datname = $1",
34
- [database],
35
- );
36
-
37
- if (result.rows.length === 0) {
38
- await adminClient.query(
39
- `CREATE DATABASE ${escapePgIdentifier(database)}`,
40
- );
41
- console.log(`Database ${database} created successfully.`);
42
- } else {
43
- console.log(`Database ${database} already exists.`);
44
- }
45
- } finally {
46
- await adminClient.end();
47
- }
48
- }
49
-
50
- function escapePgIdentifier(identifier: string): string {
51
- return `"${identifier.replaceAll('"', '""')}"`;
5
+ await setupAuthDatabase({ config: getAuthDatabaseConfig() });
52
6
  }
53
7
 
54
8
  void main().catch((error) => {
@@ -1,6 +1,4 @@
1
- import { betterAuth } from "better-auth";
2
- import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
- import { admin } from "better-auth/plugins";
1
+ import { createLazyAuth, createPerceptaAuth } from "@percepta/auth/better-auth";
4
2
  import { db } from "./drizzle/db";
5
3
  import { accounts } from "./drizzle/schema/auth/accounts";
6
4
  import { sessions } from "./drizzle/schema/auth/sessions";
@@ -27,51 +25,23 @@ function getSecret(): string {
27
25
  }
28
26
 
29
27
  function createAuth() {
30
- return betterAuth({
28
+ return createPerceptaAuth({
31
29
  baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
32
- secret: getSecret(),
33
- database: drizzleAdapter(db, {
34
- provider: "pg",
35
- schema: {
36
- user: users,
37
- session: sessions,
38
- account: accounts,
39
- verification: verifications,
40
- },
41
- }),
42
- emailAndPassword: {
43
- enabled: true,
44
- },
45
- plugins: [admin()],
46
- advanced: {
47
- database: {
48
- generateId: false,
49
- },
30
+ database: db,
31
+ schema: {
32
+ user: users,
33
+ session: sessions,
34
+ account: accounts,
35
+ verification: verifications,
50
36
  },
37
+ secret: getSecret(),
51
38
  });
52
39
  }
53
40
 
54
- type Auth = ReturnType<typeof createAuth>;
55
-
56
- let authInstance: Auth | undefined;
57
-
58
- function getAuth(): Auth {
59
- authInstance ??= createAuth();
60
- return authInstance;
61
- }
62
-
63
41
  /**
64
42
  * Lazy proxy so app builds can import the shared auth package without requiring
65
43
  * runtime secrets until Better Auth is actually used.
66
44
  */
67
- export const auth: Auth = new Proxy({} as Auth, {
68
- get(_target, prop, receiver) {
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
70
- return Reflect.get(getAuth(), prop, receiver);
71
- },
72
- has(_target, prop) {
73
- return Reflect.has(getAuth(), prop);
74
- },
75
- });
45
+ export const auth = createLazyAuth(createAuth);
76
46
 
77
47
  export type BetterAuthSession = typeof auth.$Infer.Session;
@@ -1,31 +1,15 @@
1
- export interface AuthDatabaseConfig {
2
- readonly database: string;
3
- readonly host: string;
4
- readonly password: string;
5
- readonly port: number;
6
- readonly url?: string;
7
- readonly user: string;
8
- }
1
+ import {
2
+ createAuthDatabaseConnectionString,
3
+ readAuthDatabaseConfig,
4
+ type AuthDatabaseConfig,
5
+ } from "@percepta/auth";
6
+
7
+ export type { AuthDatabaseConfig } from "@percepta/auth";
9
8
 
10
9
  export function getAuthDatabaseConfig(): AuthDatabaseConfig {
11
- return {
12
- database: process.env.AUTH_DATABASE_NAME ?? "__DB_NAME__",
13
- host: process.env.DATABASE_HOST ?? "localhost",
14
- password: process.env.DATABASE_PASSWORD ?? "postgres",
15
- port: Number(process.env.DATABASE_PORT ?? 5434),
16
- url: process.env.AUTH_DATABASE_URL,
17
- user: process.env.DATABASE_USERNAME ?? "postgres",
18
- };
10
+ return readAuthDatabaseConfig({ defaultDatabaseName: "__DB_NAME__" });
19
11
  }
20
12
 
21
13
  export function getAuthDatabaseConnectionString(): string {
22
- const { database, host, password, port, url, user } = getAuthDatabaseConfig();
23
- if (url != null && url.length > 0) {
24
- return url;
25
- }
26
-
27
- return (
28
- `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}` +
29
- `@${host}:${port}/${encodeURIComponent(database)}`
30
- );
14
+ return createAuthDatabaseConnectionString(getAuthDatabaseConfig());
31
15
  }
@@ -1,9 +1,8 @@
1
- import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres";
2
- import { Pool } from "pg";
1
+ import { createAuthDatabase } from "@percepta/auth/drizzle";
3
2
  import { getAuthDatabaseConnectionString } from "../config/database";
4
3
  import * as schema from "./schema";
5
4
 
6
- export const client = new Pool({
5
+ export const { client, db } = createAuthDatabase({
7
6
  connectionString: getAuthDatabaseConnectionString(),
7
+ schema,
8
8
  });
9
- export const db: NodePgDatabase<typeof schema> = drizzle(client, { schema });
@@ -1,28 +1,7 @@
1
- import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
1
+ import { createAccountsTable } from "@percepta/auth/drizzle";
2
2
  import { users } from "../users";
3
3
 
4
- export const accounts = pgTable("account", {
5
- id: text("id")
6
- .$defaultFn(() => crypto.randomUUID())
7
- .primaryKey(),
8
- userId: uuid("user_id")
9
- .notNull()
10
- .references(() => users.id, { onDelete: "cascade" }),
11
- accountId: text("account_id").notNull(),
12
- providerId: text("provider_id").notNull(),
13
- accessToken: text("access_token"),
14
- refreshToken: text("refresh_token"),
15
- expiresAt: integer("expires_at"),
16
- accessTokenExpiresAt: timestamp("access_token_expires_at", { mode: "date" }),
17
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
18
- mode: "date",
19
- }),
20
- scope: text("scope"),
21
- idToken: text("id_token"),
22
- password: text("password"),
23
- createdAt: timestamp("created_at", { mode: "date" }).notNull(),
24
- updatedAt: timestamp("updated_at", { mode: "date" }).notNull(),
25
- });
4
+ export const accounts = createAccountsTable({ usersTable: users });
26
5
 
27
6
  export type Account = typeof accounts.$inferSelect;
28
7
  export type NewAccount = typeof accounts.$inferInsert;
@@ -1,21 +1,7 @@
1
- import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
1
+ import { createSessionsTable } from "@percepta/auth/drizzle";
2
2
  import { users } from "../users";
3
3
 
4
- export const sessions = pgTable("session", {
5
- id: text("id")
6
- .$defaultFn(() => crypto.randomUUID())
7
- .primaryKey(),
8
- userId: uuid("user_id")
9
- .notNull()
10
- .references(() => users.id, { onDelete: "cascade" }),
11
- token: text("token").notNull().unique(),
12
- expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
13
- ipAddress: text("ip_address"),
14
- userAgent: text("user_agent"),
15
- impersonatedBy: text("impersonated_by"),
16
- createdAt: timestamp("created_at", { mode: "date" }).notNull(),
17
- updatedAt: timestamp("updated_at", { mode: "date" }).notNull(),
18
- });
4
+ export const sessions = createSessionsTable({ usersTable: users });
19
5
 
20
6
  export type Session = typeof sessions.$inferSelect;
21
7
  export type NewSession = typeof sessions.$inferInsert;
@@ -1,15 +1,6 @@
1
- import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
1
+ import { createVerificationsTable } from "@percepta/auth/drizzle";
2
2
 
3
- export const verifications = pgTable("verification", {
4
- id: text("id")
5
- .$defaultFn(() => crypto.randomUUID())
6
- .primaryKey(),
7
- identifier: text("identifier").notNull(),
8
- value: text("value").notNull(),
9
- expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
10
- createdAt: timestamp("created_at", { mode: "date" }),
11
- updatedAt: timestamp("updated_at", { mode: "date" }),
12
- });
3
+ export const verifications = createVerificationsTable();
13
4
 
14
5
  export type Verification = typeof verifications.$inferSelect;
15
6
  export type NewVerification = typeof verifications.$inferInsert;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createGroupMembersTable,
3
3
  createGroupsTable,
4
- } from "@percepta/access-control/drizzle";
4
+ } from "@percepta/auth/drizzle";
5
5
  import { users } from "./users";
6
6
 
7
7
  export const groups = createGroupsTable();
@@ -1,4 +1,4 @@
1
- import { createUsersTable } from "@percepta/access-control/drizzle";
1
+ import { createUsersTable } from "@percepta/auth/drizzle";
2
2
 
3
3
  export const users = createUsersTable();
4
4