create-absolutejs 0.15.3 → 0.15.5

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/data.js CHANGED
@@ -7,7 +7,7 @@ export const absoluteAuthPlugin = {
7
7
  providersConfiguration: {}
8
8
  },
9
9
  isPlugin: true,
10
- packageName: 'absoluteAuth'
10
+ packageName: 'auth'
11
11
  }
12
12
  ],
13
13
  latestVersion: versions['@absolutejs/auth'],
@@ -134,6 +134,9 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
134
134
  const dependencies = {};
135
135
  const devDependencies = {};
136
136
  devDependencies['typescript'] = resolveVersion('typescript', versions['typescript']);
137
+ /* Every scaffolded project runs on Bun, and the local database drivers type
138
+ against its built-in modules (`bun:sqlite`, `SQL` from `bun`). */
139
+ devDependencies['@types/bun'] = resolveVersion('@types/bun', versions['@types/bun']);
137
140
  for (const p of defaultPlugins) {
138
141
  dependencies[p.value] = resolveVersion(p.value, p.latestVersion);
139
142
  }
@@ -289,6 +292,8 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
289
292
  if (orm === 'drizzle') {
290
293
  scripts['db:studio'] = 'drizzle-kit studio';
291
294
  scripts['db:push'] = 'drizzle-kit push';
295
+ /* `drizzle.config.ts` imports it and the scripts above shell out to it. */
296
+ devDependencies['drizzle-kit'] = resolveVersion('drizzle-kit', versions['drizzle-kit']);
292
297
  }
293
298
  const packageJson = {
294
299
  dependencies,
@@ -163,7 +163,7 @@ export const generateDatabaseTypes = ({ databaseEngine, databaseHost, authOption
163
163
  }
164
164
  const schemaImport = authOption === 'abs'
165
165
  ? `import { users, schema } from '../../db/schema';`
166
- : `import { countHistory } from '../../db/schema';`;
166
+ : `import { countHistory, schema } from '../../db/schema';`;
167
167
  const extraTypes = authOption === 'abs'
168
168
  ? `export type User = typeof users.$inferSelect;
169
169
  export type NewUser = typeof users.$inferInsert;`
@@ -6,122 +6,98 @@ type QueryOperations = {
6
6
  };
7
7
  declare const driverConfigurations: {
8
8
  readonly 'cockroachdb:sql:local': {
9
- readonly dbType: "SQL";
10
9
  readonly importLines: "";
11
10
  readonly queries: QueryOperations;
12
11
  };
13
12
  readonly 'gel:drizzle:local': {
14
- readonly dbType: "GelJsDatabase<SchemaType>";
15
13
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'\n";
16
14
  readonly queries: QueryOperations;
17
15
  };
18
16
  readonly 'gel:sql:local': {
19
- readonly dbType: "Client";
20
17
  readonly importLines: "";
21
18
  readonly queries: QueryOperations;
22
19
  };
23
20
  readonly 'mariadb:drizzle:local': {
24
- readonly dbType: "MySql2Database<SchemaType>";
25
21
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
26
22
  readonly queries: QueryOperations;
27
23
  };
28
24
  readonly 'mariadb:sql:local': {
29
- readonly dbType: "SQL";
30
25
  readonly importLines: "";
31
26
  readonly queries: QueryOperations;
32
27
  };
33
28
  readonly 'mongodb:sql:local': {
34
- readonly dbType: "Db";
35
29
  readonly importLines: "";
36
30
  readonly queries: QueryOperations;
37
31
  };
38
32
  readonly 'mssql:drizzle:local': {
39
- readonly dbType: "NodeMssqlDatabase<SchemaType>";
40
33
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
41
34
  readonly queries: QueryOperations;
42
35
  };
43
36
  readonly 'mssql:sql:local': {
44
- readonly dbType: "ConnectionPool";
45
37
  readonly importLines: "";
46
38
  readonly queries: QueryOperations;
47
39
  };
48
40
  readonly 'mysql:drizzle:local': {
49
- readonly dbType: "MySql2Database<SchemaType>";
50
41
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
51
42
  readonly queries: QueryOperations;
52
43
  };
53
44
  readonly 'mysql:drizzle:planetscale': {
54
- readonly dbType: "PlanetScaleDatabase<SchemaType>";
55
45
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
56
46
  readonly queries: QueryOperations;
57
47
  };
58
48
  readonly 'mysql:sql:local': {
59
- readonly dbType: "SQL";
60
49
  readonly importLines: "";
61
50
  readonly queries: QueryOperations;
62
51
  };
63
52
  readonly 'mysql:sql:planetscale': {
64
- readonly dbType: "Client";
65
53
  readonly importLines: "";
66
54
  readonly queries: QueryOperations;
67
55
  };
68
56
  readonly 'postgresql:drizzle:local': {
69
- readonly dbType: "BunSQLDatabase<SchemaType>";
70
57
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
71
58
  readonly queries: QueryOperations;
72
59
  };
73
60
  readonly 'postgresql:drizzle:neon': {
74
- readonly dbType: "NeonDatabase<SchemaType>";
75
61
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
76
62
  readonly queries: QueryOperations;
77
63
  };
78
64
  readonly 'postgresql:drizzle:planetscale': {
79
- readonly dbType: "NodePgDatabase<SchemaType>";
80
65
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
81
66
  readonly queries: QueryOperations;
82
67
  };
83
68
  readonly 'postgresql:sql:local': {
84
- readonly dbType: "SQL";
85
69
  readonly importLines: "";
86
70
  readonly queries: QueryOperations;
87
71
  };
88
72
  readonly 'postgresql:sql:neon': {
89
- readonly dbType: "Pool";
90
73
  readonly importLines: "";
91
74
  readonly queries: QueryOperations;
92
75
  };
93
76
  readonly 'postgresql:sql:planetscale': {
94
- readonly dbType: "Pool";
95
77
  readonly importLines: "";
96
78
  readonly queries: QueryOperations;
97
79
  };
98
80
  readonly 'singlestore:drizzle:local': {
99
- readonly dbType: "SingleStoreDriverDatabase<SchemaType>";
100
81
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
101
82
  readonly queries: QueryOperations;
102
83
  };
103
84
  readonly 'singlestore:sql:local': {
104
- readonly dbType: "Pool";
105
85
  readonly importLines: "import { RowDataPacket } from 'mysql2/promise'\n";
106
86
  readonly queries: QueryOperations;
107
87
  };
108
88
  readonly 'sqlite:drizzle:local': {
109
- readonly dbType: "BunSQLiteDatabase<SchemaType>";
110
89
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
111
90
  readonly queries: QueryOperations;
112
91
  };
113
92
  readonly 'sqlite:drizzle:turso': {
114
- readonly dbType: "LibSQLDatabase<SchemaType>";
115
93
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
116
94
  readonly queries: QueryOperations;
117
95
  };
118
96
  readonly 'sqlite:sql:local': {
119
- readonly dbType: "Database";
120
97
  readonly importLines: "";
121
98
  readonly queries: QueryOperations;
122
99
  };
123
100
  readonly 'sqlite:sql:turso': {
124
- readonly dbType: "Client";
125
101
  readonly importLines: "";
126
102
  readonly queries: QueryOperations;
127
103
  };
@@ -1,22 +1,38 @@
1
- const buildSqlAuthTemplate = ({ importLines, queries }) => `
2
- import { DatabaseType, NewUser } from '../../types/databaseTypes';
1
+ const buildSqlAuthTemplate = ({ importLines, queries, rowsAreTyped }) => `
2
+ import { DatabaseType, NewUser${rowsAreTyped ? '' : ', User'} } from '../../types/databaseTypes';
3
3
  ${importLines}
4
4
 
5
- export const getUser = async (db: DatabaseType, authSub: string) => {
5
+ ${rowsAreTyped
6
+ ? `export const getUser = async (db: DatabaseType, authSub: string) => {
7
+ ${queries.selectUser}
8
+ };`
9
+ : `const selectUserRow = async (db: DatabaseType, authSub: string) => {
6
10
  ${queries.selectUser}
7
11
  };
8
12
 
13
+ /* The driver returns the row untyped, so it is asserted to the shape the query
14
+ above selects. */
15
+ export const getUser = async (
16
+ db: DatabaseType,
17
+ authSub: string
18
+ ): Promise<User | null> =>
19
+ ((await selectUserRow(db, authSub)) as User | null) ?? null;`}
20
+
9
21
  export const createUser = async (db: DatabaseType, newUserData: NewUser) => {
10
22
  const { auth_sub: authSub, metadata: userIdentity } = newUserData;
11
23
  ${queries.insertUser}
12
24
  }`;
13
- const buildSqlCountTemplate = ({ importLines, dbType, queries }) => `
25
+ /* Takes `db` as the `DatabaseType` alias rather than naming the driver type
26
+ inline, matching the auth template — the driver type is only ever spelled out
27
+ in types/databaseTypes, so it does not need importing at every call site. */
28
+ const buildSqlCountTemplate = ({ importLines, queries }) => `
29
+ import { DatabaseType } from '../../types/databaseTypes';
14
30
  ${importLines}
15
- export const getCountHistory = async (db: ${dbType}, uid: number) => {
31
+ export const getCountHistory = async (db: DatabaseType, uid: number) => {
16
32
  ${queries.selectHistory}
17
33
  }
18
34
 
19
- export const createCountHistory = async (db: ${dbType}, count: number) => {
35
+ export const createCountHistory = async (db: DatabaseType, count: number) => {
20
36
  ${queries.insertHistory}
21
37
  }
22
38
  `;
@@ -322,135 +338,111 @@ const mysqlPlanetScaleQueryOperations = {
322
338
  };
323
339
  const driverConfigurations = {
324
340
  'cockroachdb:sql:local': {
325
- dbType: 'SQL',
326
341
  importLines: ``,
327
342
  queries: postgresSqlQueryOperations
328
343
  },
329
344
  'gel:drizzle:local': {
330
- dbType: 'GelJsDatabase<SchemaType>',
331
345
  importLines: `import { eq } from 'drizzle-orm'
332
346
  import { schema } from '../../../db/schema'
333
347
  `,
334
348
  queries: drizzleQueryOperations
335
349
  },
336
350
  'gel:sql:local': {
337
- dbType: 'Client',
338
351
  importLines: ``,
339
352
  queries: gelClientQueryOperations
340
353
  },
341
354
  'mariadb:drizzle:local': {
342
- dbType: 'MySql2Database<SchemaType>',
343
355
  importLines: `import { eq } from 'drizzle-orm'
344
356
  import { schema } from '../../../db/schema'`,
345
357
  queries: mysqlDrizzleQueryOperations
346
358
  },
347
359
  'mariadb:sql:local': {
348
- dbType: 'SQL',
349
360
  importLines: ``,
350
361
  queries: mysqlSqlQueryOperations
351
362
  },
352
363
  'mongodb:sql:local': {
353
- dbType: 'Db',
354
364
  importLines: ``,
355
365
  queries: mongodbQueryOperations
356
366
  },
357
367
  'mssql:drizzle:local': {
358
- dbType: 'NodeMssqlDatabase<SchemaType>',
359
368
  importLines: `import { eq } from 'drizzle-orm'
360
369
  import { schema } from '../../../db/schema'`,
361
370
  queries: drizzleQueryOperations
362
371
  },
363
372
  'mssql:sql:local': {
364
- dbType: 'ConnectionPool',
365
373
  importLines: ``,
366
374
  queries: mssqlSqlQueryOperations
367
375
  },
368
376
  'mysql:drizzle:local': {
369
- dbType: 'MySql2Database<SchemaType>',
370
377
  importLines: `import { eq } from 'drizzle-orm'
371
378
  import { schema } from '../../../db/schema'`,
372
379
  queries: mysqlDrizzleQueryOperations
373
380
  },
374
381
  'mysql:drizzle:planetscale': {
375
- dbType: 'PlanetScaleDatabase<SchemaType>',
376
382
  importLines: `import { eq } from 'drizzle-orm'
377
383
  import { schema } from '../../../db/schema'`,
378
384
  queries: mysqlDrizzleQueryOperations
379
385
  },
380
386
  'mysql:sql:local': {
381
- dbType: 'SQL',
382
387
  importLines: ``,
383
388
  queries: mysqlSqlQueryOperations
384
389
  },
385
390
  'mysql:sql:planetscale': {
386
- dbType: 'Client',
387
391
  importLines: ``,
388
392
  queries: mysqlPlanetScaleQueryOperations
389
393
  },
390
394
  'postgresql:drizzle:local': {
391
- dbType: 'BunSQLDatabase<SchemaType>',
392
395
  importLines: `import { eq } from 'drizzle-orm'
393
396
  import { schema } from '../../../db/schema'`,
394
397
  queries: drizzleQueryOperations
395
398
  },
396
399
  'postgresql:drizzle:neon': {
397
- dbType: 'NeonDatabase<SchemaType>',
398
400
  importLines: `import { eq } from 'drizzle-orm'
399
401
  import { schema } from '../../../db/schema'`,
400
402
  queries: drizzleQueryOperations
401
403
  },
402
404
  'postgresql:drizzle:planetscale': {
403
- dbType: 'NodePgDatabase<SchemaType>',
404
405
  importLines: `import { eq } from 'drizzle-orm'
405
406
  import { schema } from '../../../db/schema'`,
406
407
  queries: drizzleQueryOperations
407
408
  },
408
409
  'postgresql:sql:local': {
409
- dbType: 'SQL',
410
410
  importLines: ``,
411
411
  queries: postgresSqlQueryOperations
412
412
  },
413
413
  'postgresql:sql:neon': {
414
- dbType: 'Pool',
415
414
  importLines: ``,
416
415
  queries: postgresQueryOperations
417
416
  },
418
417
  'postgresql:sql:planetscale': {
419
- dbType: 'Pool',
420
418
  importLines: ``,
421
419
  queries: postgresQueryOperations
422
420
  },
423
421
  'singlestore:drizzle:local': {
424
- dbType: 'SingleStoreDriverDatabase<SchemaType>',
425
422
  importLines: `import { eq } from 'drizzle-orm'
426
423
  import { schema } from '../../../db/schema'`,
427
424
  queries: mysqlDrizzleQueryOperations
428
425
  },
429
426
  'singlestore:sql:local': {
430
- dbType: 'Pool',
431
427
  importLines: `import { RowDataPacket } from 'mysql2/promise'
432
428
  `,
433
429
  queries: singlestoreSqlQueryOperations
434
430
  },
435
431
  'sqlite:drizzle:local': {
436
- dbType: 'BunSQLiteDatabase<SchemaType>',
437
432
  importLines: `import { eq } from 'drizzle-orm'
438
433
  import { schema } from '../../../db/schema'`,
439
434
  queries: drizzleQueryOperations
440
435
  },
441
436
  'sqlite:drizzle:turso': {
442
- dbType: 'LibSQLDatabase<SchemaType>',
443
437
  importLines: `import { eq } from 'drizzle-orm'
444
438
  import { schema } from '../../../db/schema'`,
445
439
  queries: drizzleQueryOperations
446
440
  },
447
441
  'sqlite:sql:local': {
448
- dbType: 'Database',
449
442
  importLines: ``,
450
443
  queries: bunSqliteQueryOperations
451
444
  },
452
445
  'sqlite:sql:turso': {
453
- dbType: 'Client',
454
446
  importLines: ``,
455
447
  queries: libsqlQueryOperations
456
448
  }
@@ -459,7 +451,10 @@ export const getAuthTemplate = (key) => {
459
451
  const configuration = driverConfigurations[key];
460
452
  if (!configuration)
461
453
  throw new Error(`Unsupported driver configuration: ${key}`);
462
- return buildSqlAuthTemplate(configuration);
454
+ return buildSqlAuthTemplate({
455
+ ...configuration,
456
+ rowsAreTyped: key.includes(':drizzle:')
457
+ });
463
458
  };
464
459
  export const getCountTemplate = (key) => {
465
460
  const configuration = driverConfigurations[key];
@@ -26,12 +26,12 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
26
26
  usesAuth
27
27
  });
28
28
  writeFileSync(join(handlerDirectory, handlerFileName), dbHandlers, 'utf-8');
29
- // Raw-SQL (no-ORM) auth handlers import `DatabaseType`/`NewUser` from
30
- // types/databaseTypes (so do the auth config and the example page), but
31
- // the drizzle-backed type module below is only written on the drizzle
32
- // path. Generate a self-contained, driver-typed version here so the
33
- // non-drizzle auth scaffold actually type-checks and builds.
34
- if (usesAuth && orm !== 'drizzle') {
29
+ // Raw-SQL (no-ORM) handlers import `DatabaseType` (and `NewUser` on the auth
30
+ // path) from types/databaseTypes (so do the auth config and the example
31
+ // page), but the drizzle-backed type module below is only written on the
32
+ // drizzle path. Generate a self-contained, driver-typed version here so the
33
+ // non-drizzle scaffold actually type-checks and builds.
34
+ if (orm !== 'drizzle') {
35
35
  mkdirSync(typesDirectory, { recursive: true });
36
36
  const sqlTypes = generateDatabaseTypes({
37
37
  authOption,
@@ -1,2 +1,3 @@
1
1
  import { ProviderOption } from '@absolutejs/auth';
2
+ export declare const generateSessionUserType: () => string;
2
3
  export declare const generateAbsoluteAuthConfig: (absProviders: ProviderOption[] | undefined, hasDatabase: boolean) => string;
@@ -12,6 +12,15 @@ const defaultProviderConfigurations = {
12
12
  searchParams: [['access_type', 'offline']]
13
13
  }
14
14
  };
15
+ /* Mirrors the `User` shape the raw-SQL path writes into types/databaseTypes, so
16
+ an auth scaffold without a database resolves the same type from the same
17
+ module the example page already imports. */
18
+ export const generateSessionUserType = () => `export type User = {
19
+ auth_sub: string;
20
+ created_at: Date;
21
+ metadata: Record<string, unknown>;
22
+ };
23
+ `;
15
24
  export const generateAbsoluteAuthConfig = (absProviders, hasDatabase) => {
16
25
  const providerConfigs = (absProviders ?? [])
17
26
  .map((provider) => {
@@ -45,38 +54,46 @@ ${credentialsLines}
45
54
  if (!hasDatabase) {
46
55
  return `import { getEnv } from '@absolutejs/absolute';
47
56
  import {
48
- AbsoluteAuthProps,
49
- createInMemoryAuthSessionStore
57
+ createInMemoryAuthSessionStore,
58
+ defineAuthConfig
50
59
  } from '@absolutejs/auth';
60
+ import { User } from '../../types/databaseTypes';
51
61
 
52
- export const absoluteAuthConfig = (): AbsoluteAuthProps => ({
53
- authSessionStore: createInMemoryAuthSessionStore(),
54
- providersConfiguration: {
62
+ export const absoluteAuthConfig = () =>
63
+ defineAuthConfig<User>({
64
+ authSessionStore: createInMemoryAuthSessionStore(),
65
+ /* Without a database there is nowhere to persist users, so no user is
66
+ ever registered and this resolver has nothing to look a subject up in.
67
+ Scaffold with a database to persist users and resolve them here. */
68
+ getUser: () => null,
69
+ providersConfiguration: {
55
70
  ${providerConfigs}
56
- }
57
- });
71
+ }
72
+ });
58
73
  `;
59
74
  }
60
75
  return `import { getEnv } from '@absolutejs/absolute';
61
76
  import {
62
- AbsoluteAuthProps,
63
77
  createInMemoryAuthSessionStore,
78
+ defineAuthConfig,
64
79
  extractPropFromIdentity,
65
- instantiateUserSession,
66
- providers
80
+ instantiateUserSession
67
81
  } from '@absolutejs/auth';
68
82
  import { DatabaseType, User } from '../../types/databaseTypes';
69
83
  import { createUser, getUser } from '../handlers/userHandlers';
70
84
 
71
- export const absoluteAuthConfig = (
72
- db: DatabaseType
73
- ): AbsoluteAuthProps<User> => ({
85
+ export const absoluteAuthConfig = (db: DatabaseType) =>
86
+ defineAuthConfig<User>({
74
87
  authSessionStore: createInMemoryAuthSessionStore(),
88
+ getUser: async (sub) => (await getUser(db, sub)) ?? null,
75
89
  providersConfiguration: {
76
90
  ${providerConfigs}
77
91
  },
92
+ /* \`providerConfiguration\` is handed to the callback rather than looked up by
93
+ name, so identity extraction also works for custom providers. */
78
94
  onCallbackSuccess: async ({
79
95
  authProvider,
96
+ providerConfiguration,
80
97
  providerInstance,
81
98
  tokenResponse,
82
99
  unregisteredSession,
@@ -86,6 +103,7 @@ ${providerConfigs}
86
103
  }) =>
87
104
  instantiateUserSession({
88
105
  authProvider,
106
+ providerConfiguration,
89
107
  providerInstance,
90
108
  session,
91
109
  tokenResponse,
@@ -93,7 +111,6 @@ ${providerConfigs}
93
111
  user_session_id,
94
112
  getUser: async (userIdentity) => {
95
113
  const provider = authProvider.toUpperCase();
96
- const providerConfiguration = providers[authProvider];
97
114
 
98
115
  const subject = extractPropFromIdentity(
99
116
  userIdentity,
@@ -116,7 +133,6 @@ ${providerConfigs}
116
133
  },
117
134
  onNewUser: async (userIdentity) => {
118
135
  const provider = authProvider.toUpperCase();
119
- const providerConfiguration = providers[authProvider];
120
136
 
121
137
  const subject = extractPropFromIdentity(
122
138
  userIdentity,
@@ -10,8 +10,9 @@ export const generateRoutesBlock = ({ databaseEngine, frontendDirectories, authO
10
10
  return status(error.code, error.message);
11
11
  }
12
12
 
13
- const providerConfiguration =
14
- auth_client.value && providers[auth_client.value as ProviderOption];
13
+ const providerConfiguration = auth_client.value
14
+ ? providers[auth_client.value as ProviderOption]
15
+ : undefined;
15
16
 
16
17
  return ${handlerCall};
17
18
  }`
@@ -36,11 +36,8 @@ export const generateServerFile = ({ tailwind, authOption, plugins, buildDirecto
36
36
  .filter((pluginImport) => pluginImport.isPlugin &&
37
37
  pluginImport.packageName !== 'networking')
38
38
  .map((pluginImport) => {
39
- if (pluginImport.packageName === 'absoluteAuth') {
40
- const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
41
- return hasDatabase
42
- ? `.use(absoluteAuth(absoluteAuthConfig(db)))`
43
- : `.use(absoluteAuth(absoluteAuthConfig()))`;
39
+ if (pluginImport.packageName === 'auth') {
40
+ return `.use(authPlugin)`;
44
41
  }
45
42
  if (pluginImport.config === undefined) {
46
43
  return `.use(${pluginImport.packageName})`;
@@ -63,10 +60,16 @@ export const generateServerFile = ({ tailwind, authOption, plugins, buildDirecto
63
60
  databaseEngine,
64
61
  frontendDirectories
65
62
  });
63
+ const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
64
+ /* `auth()` is async, so it is hoisted out of the `.use()` chain rather than
65
+ inlined — Elysia accepts the promise as a plugin. */
66
+ const authBlock = authOption === 'abs'
67
+ ? `const authPlugin = auth(absoluteAuthConfig(${hasDatabase ? 'db' : ''}))\n`
68
+ : '';
66
69
  const content = `${importsBlock}
67
70
 
68
71
  ${manifestBlock}
69
- ${dbBlock ? `${dbBlock}\n` : ''}
72
+ ${dbBlock ? `${dbBlock}\n` : ''}${authBlock}
70
73
  const server = new Elysia()
71
74
  .use(absolutejs)
72
75
  ${useBlock}${authOption === 'abs' ? `\n${guardBlock}` : ''}
@@ -2,6 +2,7 @@ import { CreateConfiguration } from '../../types';
2
2
  type ScaffoldBackendProps = Pick<CreateConfiguration, 'assetsDirectory' | 'absProviders' | 'authOption' | 'buildDirectory' | 'databaseEngine' | 'databaseHost' | 'frontendDirectories' | 'orm' | 'plugins' | 'tailwind'> & {
3
3
  backendDirectory: string;
4
4
  publicDirectory: string;
5
+ typesDirectory: string;
5
6
  };
6
- export declare const scaffoldBackend: ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, publicDirectory, tailwind }: ScaffoldBackendProps) => void;
7
+ export declare const scaffoldBackend: ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, publicDirectory, tailwind, typesDirectory }: ScaffoldBackendProps) => void;
7
8
  export {};
@@ -1,8 +1,8 @@
1
1
  import { mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
- import { generateAbsoluteAuthConfig } from './generateAbsoluteAuthConfig';
3
+ import { generateAbsoluteAuthConfig, generateSessionUserType } from './generateAbsoluteAuthConfig';
4
4
  import { generateServerFile } from './generateServer';
5
- export const scaffoldBackend = ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, publicDirectory, tailwind }) => {
5
+ export const scaffoldBackend = ({ assetsDirectory, authOption, absProviders, backendDirectory, buildDirectory, databaseEngine, databaseHost, frontendDirectories, orm, plugins, publicDirectory, tailwind, typesDirectory }) => {
6
6
  generateServerFile({
7
7
  assetsDirectory,
8
8
  authOption,
@@ -21,5 +21,13 @@ export const scaffoldBackend = ({ assetsDirectory, authOption, absProviders, bac
21
21
  const hasDatabase = databaseEngine !== undefined && databaseEngine !== 'none';
22
22
  const absoluteAuthConfig = generateAbsoluteAuthConfig(absProviders, hasDatabase);
23
23
  writeFileSync(join(backendDirectory, 'utils', 'absoluteAuthConfig.ts'), absoluteAuthConfig, 'utf-8');
24
+ /* The auth config and the example page both import `User` from
25
+ types/databaseTypes, which scaffoldDatabase only writes when there is
26
+ a database. Without one, emit the session user shape on its own so the
27
+ auth scaffold still type-checks. */
28
+ if (!hasDatabase) {
29
+ mkdirSync(typesDirectory, { recursive: true });
30
+ writeFileSync(join(typesDirectory, 'databaseTypes.ts'), generateSessionUserType(), 'utf-8');
31
+ }
24
32
  }
25
33
  };
@@ -40,6 +40,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
40
40
  absProviders,
41
41
  assetsDirectory,
42
42
  authOption,
43
+ directory,
43
44
  editBasePath,
44
45
  frontends,
45
46
  includeExamples,
@@ -56,6 +57,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
56
57
  absProviders,
57
58
  assetsDirectory,
58
59
  authOption,
60
+ directory,
59
61
  editBasePath,
60
62
  frontends,
61
63
  includeExamples,
@@ -72,6 +74,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
72
74
  absProviders,
73
75
  assetsDirectory,
74
76
  authOption,
77
+ directory,
75
78
  editBasePath,
76
79
  frontends,
77
80
  includeExamples,
@@ -88,6 +91,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
88
91
  absProviders,
89
92
  assetsDirectory,
90
93
  authOption,
94
+ directory,
91
95
  editBasePath,
92
96
  frontends,
93
97
  includeExamples,
@@ -104,6 +108,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
104
108
  absProviders,
105
109
  assetsDirectory,
106
110
  authOption,
111
+ directory,
107
112
  editBasePath,
108
113
  frontends,
109
114
  includeExamples,
@@ -121,6 +126,7 @@ export const scaffoldFrontends = ({ frontendDirectory, assetsDirectory, absProvi
121
126
  absProviders,
122
127
  assetsDirectory,
123
128
  authOption,
129
+ directory,
124
130
  editBasePath,
125
131
  frontends,
126
132
  includeExamples,
@@ -2,5 +2,5 @@ import { ProviderOption } from '@absolutejs/auth';
2
2
  import { AuthOption, Frontend } from '../../types';
3
3
  export declare const generateAppComponent: (frontends: Frontend[], editBasePath: string) => string;
4
4
  export declare const generateDropdownComponent: (frontends: Frontend[]) => string;
5
- export declare const generateReactExamplePage: (authOption: AuthOption, includeExamples: boolean) => string;
5
+ export declare const generateReactExamplePage: (authOption: AuthOption, includeExamples: boolean, reactDirectory: string) => string;
6
6
  export declare const generateSignInComponent: (absProviders: ProviderOption[] | undefined) => string;
@@ -73,10 +73,14 @@ export const generateDropdownComponent = (frontends) => {
73
73
  );
74
74
  `;
75
75
  };
76
- export const generateReactExamplePage = (authOption, includeExamples) => {
76
+ /* Pages live at `src/frontend/<reactDirectory>/pages`, but `reactDirectory` is
77
+ empty when React is the only frontend, which moves the page one level up. */
78
+ const buildDatabaseTypesPath = (reactDirectory) => `${reactDirectory ? '../../../' : '../../'}types/databaseTypes`;
79
+ export const generateReactExamplePage = (authOption, includeExamples, reactDirectory) => {
80
+ const databaseTypesPath = buildDatabaseTypesPath(reactDirectory);
77
81
  if (!includeExamples) {
78
82
  const authImports = authOption === 'abs'
79
- ? `import type { User } from '../../../types/databaseTypes';\nimport type { ProviderConfiguration } from '@absolutejs/auth';\n`
83
+ ? `import type { User } from '${databaseTypesPath}';\nimport type { ProviderConfiguration } from '@absolutejs/auth';\n`
80
84
  : '';
81
85
  const authProps = authOption === 'abs'
82
86
  ? '\n\tuser: User | null;\n\tproviderConfiguration: ProviderConfiguration | undefined;'
@@ -146,7 +150,7 @@ export const ReactExample = ({ cssPath }: ReactExampleProps) => (
146
150
  : `<Dropdown />`;
147
151
  const closing = authOption === 'abs' ? `};` : `);`;
148
152
  return `
149
- ${authOption === 'abs' ? `import type { User } from '../../../types/databaseTypes';\nimport { extractPropFromIdentity } from '@absolutejs/auth';\nimport type { ProviderConfiguration } from '@absolutejs/auth';` : ''}
153
+ ${authOption === 'abs' ? `import type { User } from '${databaseTypesPath}';\nimport { extractPropFromIdentity } from '@absolutejs/auth';\nimport type { ProviderConfiguration } from '@absolutejs/auth';` : ''}
150
154
  import { App } from '../components/App';
151
155
  import { Dropdown } from '../components/Dropdown';
152
156
  import { Head } from '../components/Head';
@@ -1,2 +1,2 @@
1
1
  import { ScaffoldFrontendProps } from '../../types';
2
- export declare const scaffoldReact: ({ authOption, editBasePath, includeExamples, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory, absProviders, stylesIndexesDirectory }: ScaffoldFrontendProps) => void;
2
+ export declare const scaffoldReact: ({ authOption, directory, editBasePath, includeExamples, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory, absProviders, stylesIndexesDirectory }: ScaffoldFrontendProps) => void;
@@ -1,20 +1,25 @@
1
1
  import { copyFileSync, cpSync, mkdirSync, writeFileSync } from 'fs';
2
- import { join } from 'path';
2
+ import { basename, join } from 'path';
3
3
  import { generateMarkupCSS } from '../project/generateMarkupCSS';
4
4
  import { generateAppComponent, generateDropdownComponent, generateReactExamplePage, generateSignInComponent } from './generateReactComponents';
5
- export const scaffoldReact = ({ authOption, editBasePath, includeExamples, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory, absProviders, stylesIndexesDirectory }) => {
5
+ export const scaffoldReact = ({ authOption, directory, editBasePath, includeExamples, targetDirectory, templatesDirectory, frontends, projectAssetsDirectory, absProviders, stylesIndexesDirectory }) => {
6
6
  if (!includeExamples) {
7
7
  mkdirSync(join(targetDirectory, 'components'), { recursive: true });
8
8
  copyFileSync(join(templatesDirectory, 'react', 'components', 'Head.tsx'), join(targetDirectory, 'components', 'Head.tsx'));
9
9
  mkdirSync(join(targetDirectory, 'pages'), { recursive: true });
10
- writeFileSync(join(targetDirectory, 'pages', 'ReactExample.tsx'), generateReactExamplePage(authOption, false), 'utf-8');
10
+ writeFileSync(join(targetDirectory, 'pages', 'ReactExample.tsx'), generateReactExamplePage(authOption, false, directory), 'utf-8');
11
11
  writeFileSync(join(stylesIndexesDirectory, 'react-example.css'), `@import url('../reset.css');`, 'utf-8');
12
12
  return;
13
13
  }
14
14
  mkdirSync(join(projectAssetsDirectory, 'svg'), { recursive: true });
15
15
  copyFileSync(join(templatesDirectory, 'assets', 'svg', 'react.svg'), join(projectAssetsDirectory, 'svg', 'react.svg'));
16
16
  copyFileSync(join(templatesDirectory, 'assets', 'svg', 'google-logo.svg'), join(projectAssetsDirectory, 'svg', 'google-logo.svg'));
17
+ /* `OAuthLink` is only rendered by the generated `SignIn` component, and it
18
+ types its provider against `citra`, which only ships as a transitive
19
+ dependency of `@absolutejs/auth`. Without Absolute Auth the import cannot
20
+ resolve, so the component is left out entirely. */
17
21
  cpSync(join(templatesDirectory, 'react'), targetDirectory, {
22
+ filter: (source) => authOption === 'abs' || basename(source) !== 'OAuthLink.tsx',
18
23
  recursive: true
19
24
  });
20
25
  const appComponent = generateAppComponent(frontends, editBasePath);
@@ -25,7 +30,7 @@ export const scaffoldReact = ({ authOption, editBasePath, includeExamples, targe
25
30
  const signInComponent = generateSignInComponent(absProviders);
26
31
  writeFileSync(join(targetDirectory, 'components', 'SignIn.tsx'), signInComponent, 'utf-8');
27
32
  }
28
- const pageComponent = generateReactExamplePage(authOption, true);
33
+ const pageComponent = generateReactExamplePage(authOption, true, directory);
29
34
  mkdirSync(join(targetDirectory, 'pages'), { recursive: true });
30
35
  writeFileSync(join(targetDirectory, 'pages', 'ReactExample.tsx'), pageComponent, 'utf-8');
31
36
  const cssOutputFile = join(stylesIndexesDirectory, 'react-example.css');
package/dist/scaffold.js CHANGED
@@ -53,7 +53,8 @@ export const scaffold = async ({ response: { agentic, projectName, codeQualityTo
53
53
  orm,
54
54
  plugins,
55
55
  publicDirectory: 'public',
56
- tailwind
56
+ tailwind,
57
+ typesDirectory
57
58
  });
58
59
  if (agentic)
59
60
  scaffoldAgentic({ backendDirectory, projectName });
package/dist/types.d.ts CHANGED
@@ -4,6 +4,9 @@ export type ScaffoldFrontendProps = {
4
4
  absProviders: ProviderOption[] | undefined;
5
5
  assetsDirectory: string;
6
6
  authOption: AuthOption;
7
+ /** Frontend's directory under `src/frontend`; empty when it is the only
8
+ * frontend and no directory was configured for it. */
9
+ directory: string;
7
10
  editBasePath: string;
8
11
  includeExamples: boolean;
9
12
  targetDirectory: string;
@@ -4,7 +4,7 @@
4
4
  * Run `bun run check-versions` to compare against latest npm versions.
5
5
  */
6
6
  export declare const versions: {
7
- readonly '@absolutejs/absolute': "0.19.0-beta.1099";
7
+ readonly '@absolutejs/absolute': "0.19.0-beta.1108";
8
8
  readonly '@absolutejs/a2a': "0.1.0";
9
9
  readonly '@absolutejs/agency': "0.4.0";
10
10
  readonly '@absolutejs/agent-conformance': "0.3.0";
@@ -42,11 +42,13 @@ export declare const versions: {
42
42
  readonly '@planetscale/database': "1.19.0";
43
43
  readonly '@stylistic/eslint-plugin': "5.9.0";
44
44
  readonly '@tailwindcss/cli': "4.2.0";
45
+ readonly '@types/bun': "1.3.14";
45
46
  readonly '@types/mssql': "9.1.9";
46
47
  readonly '@types/pg': "8.16.0";
47
48
  readonly '@types/react': "19.2.14";
48
49
  readonly '@typescript-eslint/parser': "8.56.0";
49
50
  readonly autoprefixer: "10.4.24";
51
+ readonly 'drizzle-kit': "0.31.10";
50
52
  readonly 'drizzle-orm': "0.45.1";
51
53
  readonly elysia: "1.4.28";
52
54
  readonly 'elysia-rate-limit': "4.5.0";
package/dist/versions.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export const versions = {
7
7
  /* ── Core ─────────────────────────────────────────────── */
8
- '@absolutejs/absolute': '0.19.0-beta.1099',
8
+ '@absolutejs/absolute': '0.19.0-beta.1108',
9
9
  '@absolutejs/a2a': '0.1.0',
10
10
  '@absolutejs/agency': '0.4.0',
11
11
  '@absolutejs/agent-conformance': '0.3.0',
@@ -48,6 +48,8 @@ export const versions = {
48
48
  '@stylistic/eslint-plugin': '5.9.0',
49
49
  /* ── Tailwind CSS ─────────────────────────────────────── */
50
50
  '@tailwindcss/cli': '4.2.0',
51
+ /* ── Bun ──────────────────────────────────────────────── */
52
+ '@types/bun': '1.3.14',
51
53
  /* ── Database Drivers ─────────────────────────────────── */
52
54
  '@types/mssql': '9.1.9',
53
55
  '@types/pg': '8.16.0',
@@ -56,6 +58,7 @@ export const versions = {
56
58
  '@typescript-eslint/parser': '8.56.0',
57
59
  autoprefixer: '10.4.24',
58
60
  /* ── ORM ──────────────────────────────────────────────── */
61
+ 'drizzle-kit': '0.31.10',
59
62
  'drizzle-orm': '0.45.1',
60
63
  elysia: '1.4.28',
61
64
  'elysia-rate-limit': '4.5.0',
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "description": "A CLI tool to create a new AbsoluteJS project",
12
12
  "devDependencies": {
13
- "@absolutejs/absolute": "0.19.0-beta.1081",
13
+ "@absolutejs/absolute": "0.19.0-beta.1108",
14
14
  "@eslint/compat": "2.0.2",
15
15
  "@stylistic/eslint-plugin": "5.9.0",
16
16
  "@types/bun": "1.3.8",
@@ -54,5 +54,5 @@
54
54
  "typecheck": "bun run tsc --noEmit"
55
55
  },
56
56
  "type": "module",
57
- "version": "0.15.3"
57
+ "version": "0.15.5"
58
58
  }