@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 +2 -2
- package/templates/monorepo/README.md +4 -3
- package/templates/monorepo/auth/README.md +4 -3
- package/templates/monorepo/auth/package.json +2 -5
- package/templates/monorepo/auth/scripts/setup-database.ts +2 -48
- package/templates/monorepo/auth/src/auth.ts +10 -40
- package/templates/monorepo/auth/src/config/database.ts +9 -25
- package/templates/monorepo/auth/src/drizzle/db.ts +3 -4
- package/templates/monorepo/auth/src/drizzle/schema/auth/accounts.ts +2 -23
- package/templates/monorepo/auth/src/drizzle/schema/auth/sessions.ts +2 -16
- package/templates/monorepo/auth/src/drizzle/schema/auth/verifications.ts +2 -11
- package/templates/monorepo/auth/src/drizzle/schema/groups.ts +1 -1
- package/templates/monorepo/auth/src/drizzle/schema/users.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percepta/create",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
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
|
|
67
|
-
`users`, `groups`, and `group_members`. Apps should
|
|
68
|
-
identity layer instead of creating app-local users or
|
|
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
|
|
4
|
-
customer monorepo should import this package for
|
|
5
|
-
group table references instead of creating
|
|
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/
|
|
21
|
-
"
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
|
28
|
+
return createPerceptaAuth({
|
|
31
29
|
baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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 {
|
|
1
|
+
import { createAccountsTable } from "@percepta/auth/drizzle";
|
|
2
2
|
import { users } from "../users";
|
|
3
3
|
|
|
4
|
-
export const accounts =
|
|
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 {
|
|
1
|
+
import { createSessionsTable } from "@percepta/auth/drizzle";
|
|
2
2
|
import { users } from "../users";
|
|
3
3
|
|
|
4
|
-
export const sessions =
|
|
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 {
|
|
1
|
+
import { createVerificationsTable } from "@percepta/auth/drizzle";
|
|
2
2
|
|
|
3
|
-
export const verifications =
|
|
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;
|