appflare 0.2.38 → 0.2.40

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/cli/generate.ts CHANGED
@@ -36,6 +36,46 @@ function extractRolesFromConfig(config: LoadedAppflareConfig["config"]): string[
36
36
  return [];
37
37
  }
38
38
 
39
+ type AdditionalField = { name: string; tsType: string };
40
+
41
+ function extractAdditionalFields(config: LoadedAppflareConfig["config"]): AdditionalField[] {
42
+ const options = config.auth.options as Record<string, unknown> | undefined;
43
+ const userSection = options?.user as Record<string, unknown> | undefined;
44
+ const raw = userSection?.additionalFields as Record<string, { type: string }> | undefined;
45
+ if (!raw || typeof raw !== "object") return [];
46
+
47
+ const typeMap: Record<string, string> = {
48
+ string: "string",
49
+ number: "number",
50
+ boolean: "boolean",
51
+ date: "Date",
52
+ };
53
+
54
+ return Object.entries(raw)
55
+ .filter(([, v]) => v && typeof v === "object" && "type" in v)
56
+ .map(([name, v]) => ({
57
+ name,
58
+ tsType: typeMap[(v as { type: string }).type] || "unknown",
59
+ }));
60
+ }
61
+
62
+ async function patchAuthSchemaRole(schemaPath: string, roles: string[]): Promise<void> {
63
+ const content = await Bun.file(schemaPath).text();
64
+ const roleType = roles.map((r) => `"${r}"`).join(" | ");
65
+
66
+ const updated = content
67
+ .replace(
68
+ /(import.*?from\s+["']drizzle-orm\/sqlite-core["'])/,
69
+ "$1\nimport { customType } from \"drizzle-orm/sqlite-core\"",
70
+ )
71
+ .replace(
72
+ /role:\s*text\(["']role["']\)/,
73
+ `role: customType<{ data: ${roleType}; dataNotNull: ${roleType} }>({ dataType: () => "text" })("role")`,
74
+ );
75
+
76
+ await Bun.write(schemaPath, updated);
77
+ }
78
+
39
79
  function toConfigRelativePath(configDir: string, absolutePath: string): string {
40
80
  const relativePath = relative(configDir, absolutePath).replace(/\\/g, "/");
41
81
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
@@ -96,6 +136,7 @@ export async function generateArtifacts(
96
136
  );
97
137
 
98
138
  const roles = extractRolesFromConfig(config);
139
+ const additionalFields = extractAdditionalFields(config);
99
140
 
100
141
  const serverSource = generateServerSource(
101
142
  config.auth.basePath,
@@ -118,6 +159,7 @@ export async function generateArtifacts(
118
159
  discoveredHandlers,
119
160
  config.r2[0]?.binding,
120
161
  roles,
162
+ additionalFields,
121
163
  );
122
164
  const authConfigSource = generateAuthConfigSource(configImport);
123
165
  const drizzleSchemaPaths = compiledSchema
@@ -201,6 +243,11 @@ export async function generateArtifacts(
201
243
  }
202
244
  process.stdout.write(`🔐 Auth schema (${(performance.now() - t0).toFixed(0)}ms)\n`);
203
245
 
246
+ if (roles.length > 0) {
247
+ await patchAuthSchemaRole(authSchemaPath, roles);
248
+ process.stdout.write(`🔧 Patched role type (${(performance.now() - t0).toFixed(0)}ms)\n`);
249
+ }
250
+
204
251
  function generatedTsconfig(overrides: Record<string, unknown> = {}) {
205
252
  return {
206
253
  compilerOptions: {
@@ -41,11 +41,11 @@ export const createAuth = (
41
41
  : undefined,
42
42
  kv: env?.KV,
43
43
  },
44
- config.auth.options as any,
44
+ config.auth.options,
45
45
  );
46
46
 
47
47
  return betterAuth({
48
- ...(cloudflareOptions as any),
48
+ ...cloudflareOptions,
49
49
  ...(env
50
50
  ? {}
51
51
  : {
@@ -1,6 +1,7 @@
1
1
  import { generateTypes } from "../types";
2
+ import type { AdditionalField } from "../types";
2
3
 
3
- export function generateHandlersSource(schemaImportPath: string, roles: string[] = []): string {
4
+ export function generateHandlersSource(schemaImportPath: string, roles: string[] = [], additionalFields: AdditionalField[] = []): string {
4
5
  return `import type { Context } from "hono";
5
6
  import type { D1Database } from "@cloudflare/workers-types";
6
7
  import { drizzle } from "drizzle-orm/d1";
@@ -8,6 +9,6 @@ import { z, type ZodRawShape } from "zod";
8
9
  import * as authSchema from "./auth.schema";
9
10
  import * as schema from "${schemaImportPath}";
10
11
 
11
- ${generateTypes(roles)}
12
+ ${generateTypes(roles, additionalFields)}
12
13
  `;
13
14
  }
@@ -1,13 +1,36 @@
1
- export function generateTypesContextSection(roles: string[] = []): string {
2
- const userTypeExtension =
3
- roles.length > 0
4
- ? `type UserRole = ${roles.map((r) => `"${r}"`).join(" | ")};
5
- type User = Omit<AuthSession['user'], 'role'> & { role: UserRole };`
6
- : `type User = AuthSession['user']`;
1
+ export function generateTypesContextSection(
2
+ roles: string[] = [],
3
+ additionalFields: Array<{ name: string; tsType: string }> = [],
4
+ ): string {
5
+ const additionalFieldLines = additionalFields.map(
6
+ (f) => `\t${f.name}?: ${f.tsType} | null;`,
7
+ ).join("\n");
8
+
9
+ const adminFieldLines = roles.length > 0
10
+ ? [
11
+ "\trole: UserRole;",
12
+ "\tbanned: boolean | null;",
13
+ "\tbanReason?: string | null | undefined;",
14
+ "\tbanExpires?: Date | null | undefined;",
15
+ ].join("\n")
16
+ : "";
17
+
18
+ const userFields = [adminFieldLines, additionalFieldLines].filter(Boolean).join("\n");
19
+
20
+ const userType = roles.length > 0
21
+ ? `export type UserRole = ${roles.map((r) => `"${r}"`).join(" | ")};
22
+ type User = import("better-auth").User & {
23
+ ${userFields}
24
+ };`
25
+ : additionalFields.length > 0
26
+ ? `type User = import("better-auth").User & {
27
+ ${userFields}
28
+ };`
29
+ : "type User = import(\"better-auth\").User;";
7
30
 
8
31
  return `type AuthSession = typeof auth.$Infer.Session;
9
32
  type AuthAdapter = Awaited<typeof auth.$context>["internalAdapter"];
10
- ${userTypeExtension}
33
+ ${userType}
11
34
  type Session = AuthSession['session']
12
35
 
13
36
  export type StoragePutArgs = {
@@ -4,6 +4,8 @@ import { generateExecutionSource } from "./generators/execution";
4
4
  import { generateHandlersSource } from "./generators/handlers";
5
5
  import { generateRegistration } from "./registration";
6
6
 
7
+ export type AdditionalField = { name: string; tsType: string };
8
+
7
9
  export type GeneratedHandlerArtifact = {
8
10
  relativePath: string;
9
11
  source: string;
@@ -14,8 +16,9 @@ export function generateHandlersArtifacts(
14
16
  operations: DiscoveredHandlerOperation[],
15
17
  defaultR2Binding?: string,
16
18
  roles: string[] = [],
19
+ additionalFields: AdditionalField[] = [],
17
20
  ): GeneratedHandlerArtifact[] {
18
- const handlersSource = generateHandlersSource(schemaImportPath, roles);
21
+ const handlersSource = generateHandlersSource(schemaImportPath, roles, additionalFields);
19
22
 
20
23
  const contextSource = generateContextSource(defaultR2Binding);
21
24
 
@@ -4,12 +4,14 @@ import { generateTypesQueryRuntimeSection } from "./generators/types/query-runti
4
4
  import { generateTypesContextSection } from "./generators/types/context";
5
5
  import { generateTypesOperationsSection } from "./generators/types/operations";
6
6
 
7
- export function generateTypes(roles: string[] = []): string {
7
+ export type AdditionalField = { name: string; tsType: string };
8
+
9
+ export function generateTypes(roles: string[] = [], additionalFields: AdditionalField[] = []): string {
8
10
  return [
9
11
  generateTypesCoreSection(),
10
12
  generateTypesQueryDefinitionsSection(),
11
13
  generateTypesQueryRuntimeSection(),
12
- generateTypesContextSection(roles),
14
+ generateTypesContextSection(roles, additionalFields),
13
15
  generateTypesOperationsSection(),
14
16
  ].join("\n\n");
15
17
  }