better-auth-studio 1.0.58-beta.4 → 1.0.59-beta.1

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":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAyLhG;AAwBD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAmmKR"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAyLhG;AAwBD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAi8KR"}
package/dist/routes.js CHANGED
@@ -4331,6 +4331,316 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4331
4331
  res.status(500).json({ success: false, error: 'Failed to generate token' });
4332
4332
  }
4333
4333
  });
4334
+ router.post('/api/tools/plugin-generator', async (req, res) => {
4335
+ try {
4336
+ const { pluginName, description, clientFramework = 'react', tables = [], hooks = [], middleware = [], endpoints = [], rateLimit, } = req.body || {};
4337
+ if (!pluginName || typeof pluginName !== 'string' || pluginName.trim().length === 0) {
4338
+ return res.status(400).json({ success: false, error: 'Plugin name is required' });
4339
+ }
4340
+ const validNameRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
4341
+ if (!validNameRegex.test(pluginName.trim())) {
4342
+ return res.status(400).json({
4343
+ success: false,
4344
+ error: 'Plugin name must be a valid JavaScript identifier (letters, numbers, _, $)',
4345
+ });
4346
+ }
4347
+ const sanitizedName = pluginName.trim();
4348
+ const camelCaseName = sanitizedName.charAt(0).toLowerCase() + sanitizedName.slice(1);
4349
+ const pascalCaseName = sanitizedName.charAt(0).toUpperCase() + sanitizedName.slice(1);
4350
+ // Generate schema
4351
+ const schemaCode = tables.length > 0
4352
+ ? tables
4353
+ .map((table) => {
4354
+ const fields = table.fields
4355
+ ?.filter((f) => f.name.trim())
4356
+ .map((field) => {
4357
+ let attrStr = `type: "${field.type}"`;
4358
+ if (field.required)
4359
+ attrStr += ',\n required: true';
4360
+ if (field.unique)
4361
+ attrStr += ',\n unique: true';
4362
+ return ` ${field.name}: {\n ${attrStr}\n }`;
4363
+ })
4364
+ .join(',\n') || '';
4365
+ return ` ${table.name}: {
4366
+ fields: {
4367
+ ${fields}
4368
+ },
4369
+ }`;
4370
+ })
4371
+ .join(',\n')
4372
+ : '';
4373
+ // Generate hooks
4374
+ const beforeHooks = hooks
4375
+ .filter((h) => h.timing === 'before')
4376
+ .map((hook) => {
4377
+ let matcher = '';
4378
+ if (hook.action === 'sign-up') {
4379
+ matcher = `(ctx) => ctx.path.startsWith("/sign-up")`;
4380
+ }
4381
+ else if (hook.action === 'sign-in') {
4382
+ matcher = `(ctx) => ctx.path.startsWith("/sign-in")`;
4383
+ }
4384
+ else if (hook.action === 'custom' && hook.customPath) {
4385
+ matcher = `(ctx) => ctx.path === "${hook.customPath}"`;
4386
+ if (hook.customMatcher) {
4387
+ matcher = `(ctx) => ctx.path === "${hook.customPath}" && (${hook.customMatcher})`;
4388
+ }
4389
+ }
4390
+ else {
4391
+ matcher = `(ctx) => true`;
4392
+ }
4393
+ return ` {
4394
+ matcher: ${matcher},
4395
+ handler: createAuthMiddleware(async (ctx) => {
4396
+ // ${hook.name || `${hook.timing} ${hook.action} hook`}
4397
+ ${hook.hookLogic || '// Hook logic here'}
4398
+ }),
4399
+ }`;
4400
+ });
4401
+ const afterHooks = hooks
4402
+ .filter((h) => h.timing === 'after')
4403
+ .map((hook) => {
4404
+ let matcher = '';
4405
+ if (hook.action === 'sign-up') {
4406
+ matcher = `(ctx) => ctx.path.startsWith("/sign-up")`;
4407
+ }
4408
+ else if (hook.action === 'sign-in') {
4409
+ matcher = `(ctx) => ctx.path.startsWith("/sign-in")`;
4410
+ }
4411
+ else if (hook.action === 'custom' && hook.customPath) {
4412
+ matcher = `(ctx) => ctx.path === "${hook.customPath}"`;
4413
+ if (hook.customMatcher) {
4414
+ matcher = `(ctx) => ctx.path === "${hook.customPath}" && (${hook.customMatcher})`;
4415
+ }
4416
+ }
4417
+ else {
4418
+ matcher = `(ctx) => true`;
4419
+ }
4420
+ return ` {
4421
+ matcher: ${matcher},
4422
+ handler: createAuthMiddleware(async (ctx) => {
4423
+ // ${hook.name || `${hook.timing} ${hook.action} hook`}
4424
+ ${hook.hookLogic || '// Hook logic here'}
4425
+ }),
4426
+ }`;
4427
+ });
4428
+ const middlewareCode = middleware
4429
+ .map((mw) => {
4430
+ return ` {
4431
+ path: "${mw.path}",
4432
+ middleware: createAuthMiddleware(async (ctx) => {
4433
+ // ${mw.name || 'Middleware'}
4434
+ ${mw.middlewareLogic || '// Middleware logic here'}
4435
+ }),
4436
+ }`;
4437
+ })
4438
+ .join(',\n');
4439
+ // Generate endpoints
4440
+ const endpointsCode = endpoints.length > 0
4441
+ ? endpoints
4442
+ .map((endpoint) => {
4443
+ const endpointName = endpoint.name?.trim() || `endpoint${endpoints.indexOf(endpoint) + 1}`;
4444
+ const sanitizedName = endpointName.replace(/[^a-zA-Z0-9]/g, '');
4445
+ const endpointPath = endpoint.path?.trim() || `/${camelCaseName}/${sanitizedName}`;
4446
+ const handlerLogic = endpoint.handlerLogic ||
4447
+ '// Endpoint handler logic here\n return ctx.json({ success: true });';
4448
+ const formattedHandlerLogic = handlerLogic
4449
+ .split('\n')
4450
+ .map((line) => {
4451
+ const trimmed = line.trim();
4452
+ if (!trimmed)
4453
+ return '';
4454
+ if (!line.startsWith(' ')) {
4455
+ return ' ' + trimmed;
4456
+ }
4457
+ return line;
4458
+ })
4459
+ .filter(Boolean)
4460
+ .join('\n');
4461
+ return ` ${sanitizedName}: createAuthEndpoint(
4462
+ "${endpointPath}",
4463
+ {
4464
+ method: "${endpoint.method || 'POST'}",
4465
+ },
4466
+ async (ctx) => {
4467
+ // ${endpoint.name || sanitizedName}
4468
+ ${formattedHandlerLogic}
4469
+ },
4470
+ ),`;
4471
+ })
4472
+ .join('\n')
4473
+ : '';
4474
+ const rateLimitCode = rateLimit
4475
+ ? (() => {
4476
+ const rl = rateLimit;
4477
+ let pathMatcher = '';
4478
+ if (rl.pathType === 'exact') {
4479
+ pathMatcher = `(path: string) => path === "${rl.path}"`;
4480
+ }
4481
+ else if (rl.pathType === 'prefix') {
4482
+ pathMatcher = `(path: string) => path.startsWith("${rl.path}")`;
4483
+ }
4484
+ else if (rl.pathType === 'regex') {
4485
+ pathMatcher = `(path: string) => new RegExp("${rl.path.replace(/"/g, '\\"')}").test(path)`;
4486
+ }
4487
+ else {
4488
+ pathMatcher = `(path: string) => true`;
4489
+ }
4490
+ const windowValue = rl.window && rl.window > 0 ? rl.window : 15 * 60 * 1000;
4491
+ const maxValue = rl.max && rl.max > 0 ? rl.max : 100;
4492
+ return ` window: ${windowValue},
4493
+ max: ${maxValue},
4494
+ pathMatcher: ${pathMatcher}`;
4495
+ })()
4496
+ : '';
4497
+ const cleanCode = (code) => {
4498
+ return code
4499
+ .split('\n')
4500
+ .map((line) => line.trimEnd())
4501
+ .filter((line, index, arr) => {
4502
+ if (line === '' && arr[index + 1] === '')
4503
+ return false;
4504
+ return true;
4505
+ })
4506
+ .join('\n')
4507
+ .replace(/\n{3,}/g, '\n\n')
4508
+ .trim();
4509
+ };
4510
+ const pluginParts = [];
4511
+ if (schemaCode) {
4512
+ pluginParts.push(` schema: {\n${schemaCode}\n }`);
4513
+ }
4514
+ if (beforeHooks.length > 0 || afterHooks.length > 0) {
4515
+ const hooksParts = [];
4516
+ if (beforeHooks.length > 0) {
4517
+ hooksParts.push(` before: [\n${beforeHooks.join(',\n')}\n ]`);
4518
+ }
4519
+ if (afterHooks.length > 0) {
4520
+ hooksParts.push(` after: [\n${afterHooks.join(',\n')}\n ]`);
4521
+ }
4522
+ pluginParts.push(` hooks: {\n${hooksParts.join(',\n')}\n }`);
4523
+ }
4524
+ if (middlewareCode) {
4525
+ pluginParts.push(` middlewares: [\n${middlewareCode}\n ]`);
4526
+ }
4527
+ if (endpointsCode) {
4528
+ pluginParts.push(` endpoints: {\n${endpointsCode}\n }`);
4529
+ }
4530
+ if (rateLimitCode) {
4531
+ pluginParts.push(` rateLimit: {\n${rateLimitCode}\n }`);
4532
+ }
4533
+ // Generate server plugin code
4534
+ const imports = ['import type { BetterAuthPlugin } from "@better-auth/core"'];
4535
+ if (hooks.length > 0 || middleware.length > 0 || endpoints.length > 0) {
4536
+ imports.push('import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"');
4537
+ }
4538
+ const serverPluginBody = pluginParts.length > 0
4539
+ ? ` id: "${camelCaseName}",\n${pluginParts.join(',\n')}`
4540
+ : ` id: "${camelCaseName}"`;
4541
+ const serverPluginCode = cleanCode(`import type { BetterAuthPlugin } from "@better-auth/core";
4542
+ ${imports.join('\n')}
4543
+
4544
+ ${description ? `/**\n * ${description.replace(/\n/g, '\n * ')}\n */` : ''}
4545
+ export const ${camelCaseName} = (options?: Record<string, any>) => {
4546
+ return {
4547
+ ${serverPluginBody}
4548
+ } satisfies BetterAuthPlugin;
4549
+ };
4550
+ `);
4551
+ const pathMethods = endpoints.length > 0
4552
+ ? endpoints
4553
+ .map((endpoint) => {
4554
+ const endpointPath = endpoint.path?.trim() || '';
4555
+ const method = endpoint.method || 'POST';
4556
+ return ` "${endpointPath}": "${method}"`;
4557
+ })
4558
+ .join(',\n')
4559
+ : '';
4560
+ const sessionAffectingPaths = endpoints
4561
+ .filter((endpoint) => {
4562
+ const path = endpoint.path?.trim() || '';
4563
+ return path.includes('/sign-in') || path.includes('/sign-up');
4564
+ })
4565
+ .map((endpoint) => {
4566
+ const endpointPath = endpoint.path?.trim() || '';
4567
+ return ` {
4568
+ matcher: (path) => path === "${endpointPath}",
4569
+ signal: "$sessionSignal",
4570
+ }`;
4571
+ });
4572
+ const atomListenersCode = sessionAffectingPaths.length > 0
4573
+ ? `\n atomListeners: [\n${sessionAffectingPaths.join(',\n')}\n ],`
4574
+ : '';
4575
+ const clientPluginCode = cleanCode(`import type { BetterAuthClientPlugin } from "@better-auth/core";
4576
+ import type { ${camelCaseName} } from "..";
4577
+
4578
+ export const ${camelCaseName}Client = () => {
4579
+ return {
4580
+ id: "${camelCaseName}" as const,
4581
+ $InferServerPlugin: {} as ReturnType<typeof ${camelCaseName}>,${pathMethods ? `\n pathMethods: {\n${pathMethods}\n },` : ''}${atomListenersCode}
4582
+ } satisfies BetterAuthClientPlugin;
4583
+ };
4584
+ `);
4585
+ // Generate server setup code
4586
+ const serverSetupCode = cleanCode(`import { betterAuth } from "@better-auth/core";
4587
+ import { ${camelCaseName} } from "./plugin/${camelCaseName}";
4588
+
4589
+ export const auth = betterAuth({
4590
+ // ... your existing config
4591
+ plugins: [
4592
+ ${camelCaseName}(),
4593
+ ],
4594
+ });
4595
+ `);
4596
+ const frameworkImportMap = {
4597
+ react: 'better-auth/react',
4598
+ svelte: 'better-auth/svelte',
4599
+ solid: 'better-auth/solid',
4600
+ vue: 'better-auth/vue',
4601
+ };
4602
+ const frameworkImport = frameworkImportMap[clientFramework] || 'better-auth/react';
4603
+ // Get baseURL based on framework
4604
+ const baseURLMap = {
4605
+ react: 'process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"',
4606
+ svelte: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4607
+ solid: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4608
+ vue: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4609
+ };
4610
+ const baseURL = baseURLMap[clientFramework] ||
4611
+ 'process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"';
4612
+ const clientSetupCode = cleanCode(`import { createAuthClient } from "${frameworkImport}";
4613
+ import { ${camelCaseName}Client } from "./plugin/${camelCaseName}/client";
4614
+
4615
+ export const authClient = createAuthClient({
4616
+ baseURL: ${baseURL},
4617
+ plugins: [
4618
+ ${camelCaseName}Client(),
4619
+ ],
4620
+ });
4621
+ `);
4622
+ return res.json({
4623
+ success: true,
4624
+ plugin: {
4625
+ name: sanitizedName,
4626
+ server: serverPluginCode,
4627
+ client: clientPluginCode,
4628
+ serverSetup: serverSetupCode,
4629
+ clientSetup: clientSetupCode,
4630
+ filePaths: {
4631
+ server: `plugin/${camelCaseName}/index.ts`,
4632
+ client: `plugin/${camelCaseName}/client/index.ts`,
4633
+ serverSetup: 'auth.ts',
4634
+ clientSetup: 'auth-client.ts',
4635
+ },
4636
+ },
4637
+ });
4638
+ }
4639
+ catch (error) {
4640
+ const message = error instanceof Error ? error.message : 'Failed to generate plugin';
4641
+ res.status(500).json({ success: false, error: message });
4642
+ }
4643
+ });
4334
4644
  router.post('/api/tools/export', async (req, res) => {
4335
4645
  try {
4336
4646
  const { tables, format, limit } = req.body;
@@ -4344,7 +4654,6 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4344
4654
  return res.status(500).json({ success: false, error: 'Auth adapter not available' });
4345
4655
  }
4346
4656
  const exportData = {};
4347
- // Fetch data from each table
4348
4657
  for (const tableName of tables) {
4349
4658
  try {
4350
4659
  const data = await adapter.findMany({
@@ -4354,7 +4663,6 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4354
4663
  exportData[tableName] = data || [];
4355
4664
  }
4356
4665
  catch (error) {
4357
- // If table doesn't exist or can't be accessed, skip it
4358
4666
  exportData[tableName] = [];
4359
4667
  }
4360
4668
  }