create-absolutejs 0.17.3 → 0.18.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +37 -4
  3. package/changelog.json +10 -0
  4. package/dist/data.d.ts +1 -1
  5. package/dist/data.js +0 -1
  6. package/dist/generators/angular/generateAngularPage.d.ts +5 -6
  7. package/dist/generators/angular/generateAngularPage.js +16 -33
  8. package/dist/generators/angular/scaffoldAngular.d.ts +1 -1
  9. package/dist/generators/angular/scaffoldAngular.js +8 -9
  10. package/dist/generators/configurations/generateDrizzleConfig.js +1 -1
  11. package/dist/generators/configurations/generateEslintConfig.js +4 -2
  12. package/dist/generators/configurations/generatePackageJson.js +3 -3
  13. package/dist/generators/db/generateDatabaseTypes.js +18 -25
  14. package/dist/generators/db/generateDrizzleSchema.js +13 -9
  15. package/dist/generators/db/generateHandlers.js +3 -2
  16. package/dist/generators/db/generateRelationalSchema.d.ts +1 -1
  17. package/dist/generators/db/generateRelationalSchema.js +1 -1
  18. package/dist/generators/db/handlerTemplates.d.ts +7 -1
  19. package/dist/generators/db/handlerTemplates.js +7 -2
  20. package/dist/generators/db/scaffoldDatabase.d.ts +2 -1
  21. package/dist/generators/db/scaffoldDatabase.js +8 -6
  22. package/dist/generators/db/scaffoldDocker.d.ts +2 -1
  23. package/dist/generators/db/scaffoldDocker.js +5 -3
  24. package/dist/generators/htmx/scaffoldHTMX.js +1 -0
  25. package/dist/generators/project/generateAbsoluteAuthConfig.d.ts +1 -1
  26. package/dist/generators/project/generateAbsoluteAuthConfig.js +11 -10
  27. package/dist/generators/project/generateDBBlock.js +5 -5
  28. package/dist/generators/project/generateIdentity.d.ts +1 -0
  29. package/dist/generators/project/generateIdentity.js +15 -0
  30. package/dist/generators/project/generateImportsBlock.js +4 -9
  31. package/dist/generators/project/generateRoutesBlock.d.ts +2 -1
  32. package/dist/generators/project/generateRoutesBlock.js +8 -13
  33. package/dist/generators/project/generateServer.js +25 -16
  34. package/dist/generators/project/scaffoldAgentic.d.ts +1 -1
  35. package/dist/generators/project/scaffoldAgentic.js +45 -45
  36. package/dist/generators/project/scaffoldBackend.js +4 -9
  37. package/dist/generators/react/scaffoldReact.js +2 -2
  38. package/dist/prompt.js +3 -2
  39. package/dist/questions/authOption.js +1 -1
  40. package/dist/questions/databaseEngine.d.ts +1 -1
  41. package/dist/questions/orm.js +2 -5
  42. package/dist/scaffold.d.ts +2 -1
  43. package/dist/scaffold.js +14 -3
  44. package/dist/templates/configurations/eslint.config.example.mjs +12 -11
  45. package/dist/templates/htmx/LICENSE +13 -0
  46. package/dist/templates/htmx/htmx.2.0.11.min.js +1 -0
  47. package/dist/utils/abort.d.ts +1 -1
  48. package/dist/utils/abort.js +2 -4
  49. package/dist/utils/getPackageVersion.js +7 -2
  50. package/dist/utils/parseCommandLineOptions.js +7 -15
  51. package/dist/utils/registryChannel.d.ts +2 -0
  52. package/dist/utils/registryChannel.js +20 -0
  53. package/dist/versions.d.ts +66 -65
  54. package/dist/versions.js +66 -65
  55. package/package.json +25 -22
  56. package/dist/templates/htmx/htmx.2.0.6.min.js +0 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.18.0 — 2026-09-23
10
+
11
+ ### Fixed
12
+
13
+ - **Update registry dependency pins and generate compatible Elysia 2, Drizzle 1, Angular and HTMX starters with persistent auth, typed JSON subapps, and executable starter checks**
14
+
9
15
  ## 0.17.3 — 2026-09-22
10
16
 
11
17
  ### Fixed
package/README.md CHANGED
@@ -74,13 +74,13 @@ Usage: create-absolute [project-name] [options]
74
74
  Output directory for build artifacts.
75
75
 
76
76
  - `--db <engine|none>`
77
- Database engine (`postgresql` | `mysql` | `sqlite` | `mongodb` | `redis` | `singlestore` | `cockroachdb` | `mssql`) or `none`.
77
+ Database engine (`postgresql` | `mysql` | `mariadb` | `sqlite` | `mongodb` | `gel` | `singlestore` | `cockroachdb` | `mssql`) or `none`.
78
78
 
79
79
  - `--db-dir <directory>`
80
80
  Directory name for your database files.
81
81
 
82
82
  - `--db-host <provider|none>`
83
- Database host provider (`neon` | `planetscale` | `supabase` | `turso` | `vercel` | `upstash` | `atlas`) or `none`.
83
+ Database host provider (`neon` | `planetscale` | `turso`) or `none`.
84
84
 
85
85
  - `--directory <default|custom>`
86
86
  Directory-naming strategy: `default` or `custom`.
@@ -112,8 +112,8 @@ Usage: create-absolute [project-name] [options]
112
112
  - `--lts`
113
113
  Use LTS versions of required packages.
114
114
 
115
- - `--orm <drizzle|prisma|none>`
116
- ORM to configure: `drizzle` | `prisma` | `none`.
115
+ - `--orm <drizzle|none>`
116
+ ORM to configure: `drizzle` | `none`. Prisma scaffolding is not implemented and is rejected before files are written. Drizzle 1 no longer includes the Gel dialect; choose no ORM for Gel.
117
117
 
118
118
  - `--plugin <plugin>`
119
119
  Elysia plugin(s) to include (repeatable); `none` skips plugin setup. Select
@@ -191,3 +191,36 @@ Contributions are welcome! Feel free to open issues or submit pull requests to i
191
191
  ## License
192
192
 
193
193
  **Business Source License 1.1 (BSL-1.1)** – see [`LICENSE`](./LICENSE) for details.
194
+
195
+ ## Scaffold compatibility and verification
196
+
197
+ The CLI pins published registry versions; it does not install local package links.
198
+ The compatibility policy in `src/utils/registryChannel.ts` also applies when
199
+ resolving versions at creation time, so npm's older `latest` tag cannot silently
200
+ switch a generated server back to Elysia 1.
201
+
202
+ - AbsoluteJS uses its current 0.20 beta channel.
203
+ - Elysia remains at 2.0.0-beta.6 and OpenAPI at 2.0.0-beta.2: the published
204
+ authentication/plugin contracts still rely on `ElysiaStatus.code`, which later
205
+ Elysia betas changed. Upgrading these requires a coordinated SDK migration.
206
+ - TypeScript 5.9.3 and Angular's current 21 LTS patches match the framework's
207
+ compiler peer dependencies. Newer incompatible compiler majors are not selected.
208
+ - Drizzle ORM and Kit use the coordinated 1.0 release-candidate channel.
209
+ - All other pins are checked against their latest published release channel.
210
+
211
+ Automatic authentication setup currently supplies a complete Google configuration
212
+ and requires a persistent database. Configure other login providers explicitly
213
+ using `@absolutejs/auth` after creation; the CLI rejects unsupported automatic
214
+ provider configurations instead of silently omitting them. Auth sessions are
215
+ in-memory by default; configure a shared session store for multi-instance hosting.
216
+
217
+ `bun run check-versions` checks every scaffold pin against that policy.
218
+ `bun run check:package` runs typechecking, unit tests, compilation and release
219
+ metadata validation. `bun run test:starters` scaffolds representative frontend,
220
+ plugin, database and agentic combinations into a temporary directory, installs
221
+ real registry dependencies, and checks TypeScript and ESLint. SQLite cases also
222
+ exercise generated HTTP validation and persistence using an isolated database.
223
+ The matrix creates Docker configuration files but does not start containers or
224
+ connect to paid providers. Use `SCAFFOLD_CHECK_CASES=sqlite-auth,all-frontends` to
225
+ select cases. Hosted database credentials and real OAuth acceptance are separate
226
+ integration checks, not implied by this matrix.
package/changelog.json CHANGED
@@ -2,6 +2,16 @@
2
2
  "contract": 1,
3
3
  "name": "create-absolutejs",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "kind": "fixed",
9
+ "summary": "Update registry dependency pins and generate compatible Elysia 2, Drizzle 1, Angular and HTMX starters with persistent auth, typed JSON subapps, and executable starter checks"
10
+ }
11
+ ],
12
+ "date": "2026-09-23",
13
+ "version": "0.18.0"
14
+ },
5
15
  {
6
16
  "changes": [
7
17
  {
package/dist/data.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare const availableCodeQualityTools: readonly ["eslint+prettier", "bi
5
5
  export declare const availableDatabaseEngines: readonly ["postgresql", "mysql", "sqlite", "mongodb", "mariadb", "gel", "singlestore", "cockroachdb", "mssql", "none"];
6
6
  export declare const availableDatabaseHosts: readonly ["neon", "planetscale", "turso", "none"];
7
7
  export declare const availableDirectoryConfigurations: readonly ["default", "custom"];
8
- export declare const availableDrizzleDialects: readonly ["gel", "mariadb", "mssql", "mysql", "postgresql", "singlestore", "sqlite"];
8
+ export declare const availableDrizzleDialects: readonly ["mariadb", "mssql", "mysql", "postgresql", "singlestore", "sqlite"];
9
9
  export declare const availableFrontends: readonly ["react", "html", "svelte", "vue", "htmx", "angular"];
10
10
  export declare const availableORMs: readonly ["drizzle", "prisma", "none"];
11
11
  export declare const availablePlugins: AvailableDependency[];
package/dist/data.js CHANGED
@@ -35,7 +35,6 @@ export const availableDatabaseHosts = [
35
35
  ];
36
36
  export const availableDirectoryConfigurations = ['default', 'custom'];
37
37
  export const availableDrizzleDialects = [
38
- 'gel',
39
38
  'mariadb',
40
39
  'mssql',
41
40
  'mysql',
@@ -1,11 +1,10 @@
1
1
  import { Frontend } from '../../types';
2
- export declare const generateAngularPage: (_frontends: Frontend[], includeExamples: boolean) => "import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { defineAngularPage } from '@absolutejs/absolute/angular';\n\ntype AngularPageProps = {\n\tinitialCount: number;\n};\n\n@Component({\n\timports: [CommonModule],\n\tselector: 'angular-page',\n\tstandalone: true,\n\ttemplateUrl: '../templates/angular-example.html'\n})\nexport class AngularExampleComponent {}\n\nexport const page = defineAngularPage<AngularPageProps>({\n\tcomponent: AngularExampleComponent\n});\n" | "import { Component, inject, InjectionToken } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { defineAngularPage } from '@absolutejs/absolute/angular';\nimport { DropdownComponent } from '../components/dropdown.component';\nimport { AppComponent } from '../components/app.component';\n\nexport const INITIAL_COUNT = new InjectionToken<number>('INITIAL_COUNT');\n\ntype AngularPageProps = {\n\tinitialCount: number;\n};\n\n@Component({\n\timports: [CommonModule, DropdownComponent, AppComponent],\n\tselector: 'angular-page',\n\tstandalone: true,\n\ttemplateUrl: '../templates/angular-example.html'\n})\nexport class AngularExampleComponent {\n\tinitialCount: number = 0;\n\n\tconstructor() {\n\t\tconst initialCountToken = inject(INITIAL_COUNT, { optional: true });\n\t\tthis.initialCount = initialCountToken ?? 0;\n\t}\n}\n\nexport const page = defineAngularPage<AngularPageProps>({\n\tcomponent: AngularExampleComponent\n});\n";
2
+ export declare const generateAngularPage: (includeExamples: boolean) => "import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nexport type Context = {\n\tinitialCount: number;\n};\n\n@Component({\n\timports: [CommonModule],\n\tselector: 'angular-page',\n\tstandalone: true,\n\ttemplateUrl: '../templates/angular-example.html'\n})\nexport class AngularExampleComponent {}\n\nexport const factory = () => AngularExampleComponent;\n" | "import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { usePageContext } from '@absolutejs/absolute/angular';\nimport { DropdownComponent } from '../components/dropdown.component';\nimport { AppComponent } from '../components/app.component';\n\n\nexport type Context = {\n\tinitialCount: number;\n};\n\n@Component({\n\timports: [CommonModule, DropdownComponent, AppComponent],\n\tselector: 'angular-page',\n\tstandalone: true,\n\ttemplateUrl: '../templates/angular-example.html'\n})\nexport class AngularExampleComponent {\n\tinitialCount = usePageContext<Context>().initialCount;\n}\n\nexport const factory = () => AngularExampleComponent;\n";
3
3
  export declare const generateAngularPageHtml: (includeExamples: boolean) => "<main></main>\n" | "<header>\n\t<a href=\"/\">AbsoluteJS</a>\n\t<app-dropdown></app-dropdown>\n</header>\n<app-root [initialCount]=\"initialCount\"></app-root>\n";
4
- export declare const generateAppComponent: (isSingleFrontend: boolean) => string;
5
- export declare const generateAppComponentCss: () => string;
4
+ export declare const generateAppComponent: () => string;
6
5
  export declare const generateAppComponentHtml: (frontends: Frontend[], editBasePath: string) => string;
7
- export declare const generateCounterComponent: (isSingleFrontend: boolean) => string;
8
- export declare const generateCounterComponentHtml: () => string;
6
+ export declare const generateCounterComponent: (stylesRelativeDirectory: string) => string;
9
7
  export declare const generateCounterComponentCss: () => string;
10
- export declare const generateDropdownComponent: (_frontends: Frontend[]) => string;
8
+ export declare const generateCounterComponentHtml: () => string;
9
+ export declare const generateDropdownComponent: () => string;
11
10
  export declare const generateDropdownComponentHtml: (frontends: Frontend[]) => string;
@@ -1,12 +1,10 @@
1
1
  import { formatNavLink } from '../../utils/formatNavLink';
2
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
- export const generateAngularPage = (_frontends, includeExamples) => {
2
+ export const generateAngularPage = (includeExamples) => {
4
3
  if (!includeExamples)
5
4
  return `import { Component } from '@angular/core';
6
5
  import { CommonModule } from '@angular/common';
7
- import { defineAngularPage } from '@absolutejs/absolute/angular';
8
6
 
9
- type AngularPageProps = {
7
+ export type Context = {
10
8
  initialCount: number;
11
9
  };
12
10
 
@@ -18,19 +16,16 @@ type AngularPageProps = {
18
16
  })
19
17
  export class AngularExampleComponent {}
20
18
 
21
- export const page = defineAngularPage<AngularPageProps>({
22
- component: AngularExampleComponent
23
- });
19
+ export const factory = () => AngularExampleComponent;
24
20
  `;
25
- return `import { Component, inject, InjectionToken } from '@angular/core';
21
+ return `import { Component } from '@angular/core';
26
22
  import { CommonModule } from '@angular/common';
27
- import { defineAngularPage } from '@absolutejs/absolute/angular';
23
+ import { usePageContext } from '@absolutejs/absolute/angular';
28
24
  import { DropdownComponent } from '../components/dropdown.component';
29
25
  import { AppComponent } from '../components/app.component';
30
26
 
31
- export const INITIAL_COUNT = new InjectionToken<number>('INITIAL_COUNT');
32
27
 
33
- type AngularPageProps = {
28
+ export type Context = {
34
29
  initialCount: number;
35
30
  };
36
31
 
@@ -41,17 +36,10 @@ type AngularPageProps = {
41
36
  templateUrl: '../templates/angular-example.html'
42
37
  })
43
38
  export class AngularExampleComponent {
44
- initialCount: number = 0;
45
-
46
- constructor() {
47
- const initialCountToken = inject(INITIAL_COUNT, { optional: true });
48
- this.initialCount = initialCountToken ?? 0;
49
- }
39
+ initialCount = usePageContext<Context>().initialCount;
50
40
  }
51
41
 
52
- export const page = defineAngularPage<AngularPageProps>({
53
- component: AngularExampleComponent
54
- });
42
+ export const factory = () => AngularExampleComponent;
55
43
  `;
56
44
  };
57
45
  export const generateAngularPageHtml = (includeExamples) => {
@@ -64,7 +52,7 @@ export const generateAngularPageHtml = (includeExamples) => {
64
52
  <app-root [initialCount]="initialCount"></app-root>
65
53
  `;
66
54
  };
67
- export const generateAppComponent = (isSingleFrontend) => `import { Component, Input, ViewEncapsulation } from '@angular/core';
55
+ export const generateAppComponent = () => `import { Component, Input, ViewEncapsulation } from '@angular/core';
68
56
  import { CommonModule } from '@angular/common';
69
57
  import { CounterComponent } from './counter.component';
70
58
 
@@ -73,14 +61,12 @@ import { CounterComponent } from './counter.component';
73
61
  standalone: true,
74
62
  imports: [CommonModule, CounterComponent],
75
63
  templateUrl: '../templates/app.component.html',
76
- styleUrl: '${isSingleFrontend ? '../' : '../../'}styles/app.component.css',
77
64
  encapsulation: ViewEncapsulation.None
78
65
  })
79
66
  export class AppComponent {
80
67
  @Input() initialCount: number = 0;
81
68
  }
82
69
  `;
83
- export const generateAppComponentCss = () => ``;
84
70
  export const generateAppComponentHtml = (frontends, editBasePath) => {
85
71
  const exploreBlock = frontends.length > 1
86
72
  ? `\n\t<p style="margin-top: 2rem">\n\t\tExplore the other pages to see multiple frameworks running\n\t\ttogether.\n\t</p>`
@@ -114,7 +100,7 @@ export const generateAppComponentHtml = (frontends, editBasePath) => {
114
100
  </main>
115
101
  `;
116
102
  };
117
- export const generateCounterComponent = (isSingleFrontend) => `import { Component, Input } from '@angular/core';
103
+ export const generateCounterComponent = (stylesRelativeDirectory) => `import { Component, Input } from '@angular/core';
118
104
  import { CommonModule } from '@angular/common';
119
105
 
120
106
  @Component({
@@ -122,7 +108,7 @@ import { CommonModule } from '@angular/common';
122
108
  standalone: true,
123
109
  imports: [CommonModule],
124
110
  templateUrl: '../templates/counter.component.html',
125
- styleUrl: '${isSingleFrontend ? '../' : '../../'}styles/counter.component.css'
111
+ styleUrl: '${stylesRelativeDirectory}/counter.component.css'
126
112
  })
127
113
  export class CounterComponent {
128
114
  @Input() initialCount: number = 0;
@@ -137,10 +123,6 @@ export class CounterComponent {
137
123
  }
138
124
  }
139
125
  `;
140
- export const generateCounterComponentHtml = () => `<button (click)="increment()">
141
- count is <span class="counter-value">{{ count }}</span>
142
- </button>
143
- `;
144
126
  export const generateCounterComponentCss = () => `button {
145
127
  background-color: #1a1a1a;
146
128
  border: 1px solid transparent;
@@ -168,9 +150,11 @@ button:focus-visible {
168
150
  }
169
151
  }
170
152
  `;
171
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
172
- export const generateDropdownComponent = (_frontends) => {
173
- return `import { Component } from '@angular/core';
153
+ export const generateCounterComponentHtml = () => `<button (click)="increment()">
154
+ count is <span class="counter-value">{{ count }}</span>
155
+ </button>
156
+ `;
157
+ export const generateDropdownComponent = () => `import { Component } from '@angular/core';
174
158
  import { CommonModule } from '@angular/common';
175
159
 
176
160
  @Component({
@@ -183,7 +167,6 @@ export class DropdownComponent {
183
167
  isOpen = false;
184
168
  }
185
169
  `;
186
- };
187
170
  export const generateDropdownComponentHtml = (frontends) => {
188
171
  const navLinks = frontends.map(formatNavLink).join('\n\t\t');
189
172
  return `<details
@@ -1,2 +1,2 @@
1
1
  import { ScaffoldFrontendProps } from '../../types';
2
- export declare const scaffoldAngular: ({ editBasePath, includeExamples, isSingleFrontend, targetDirectory, frontends, templatesDirectory, projectAssetsDirectory, stylesDirectory, stylesIndexesDirectory }: ScaffoldFrontendProps) => void;
2
+ export declare const scaffoldAngular: ({ editBasePath, includeExamples, targetDirectory, frontends, templatesDirectory, projectAssetsDirectory, stylesDirectory, stylesIndexesDirectory }: ScaffoldFrontendProps) => void;
@@ -1,14 +1,14 @@
1
1
  import { copyFileSync, mkdirSync, writeFileSync } from 'fs';
2
- import { join } from 'path';
2
+ import { join, relative } from 'path';
3
3
  import { generateMarkupCSS } from '../project/generateMarkupCSS';
4
- import { generateAngularPage, generateAngularPageHtml, generateAppComponent, generateAppComponentCss, generateAppComponentHtml, generateCounterComponent, generateCounterComponentCss, generateCounterComponentHtml, generateDropdownComponent, generateDropdownComponentHtml } from './generateAngularPage';
5
- export const scaffoldAngular = ({ editBasePath, includeExamples, isSingleFrontend, targetDirectory, frontends, templatesDirectory, projectAssetsDirectory, stylesDirectory, stylesIndexesDirectory }) => {
4
+ import { generateAngularPage, generateAngularPageHtml, generateAppComponent, generateAppComponentHtml, generateCounterComponent, generateCounterComponentCss, generateCounterComponentHtml, generateDropdownComponent, generateDropdownComponentHtml } from './generateAngularPage';
5
+ export const scaffoldAngular = ({ editBasePath, includeExamples, targetDirectory, frontends, templatesDirectory, projectAssetsDirectory, stylesDirectory, stylesIndexesDirectory }) => {
6
6
  const pagesDirectory = join(targetDirectory, 'pages');
7
7
  const templatesDir = join(targetDirectory, 'templates');
8
8
  if (!includeExamples) {
9
9
  mkdirSync(pagesDirectory, { recursive: true });
10
10
  mkdirSync(templatesDir, { recursive: true });
11
- writeFileSync(join(pagesDirectory, 'angular-example.ts'), generateAngularPage(frontends, false), 'utf-8');
11
+ writeFileSync(join(pagesDirectory, 'angular-example.ts'), generateAngularPage(false), 'utf-8');
12
12
  writeFileSync(join(templatesDir, 'angular-example.html'), generateAngularPageHtml(false), 'utf-8');
13
13
  writeFileSync(join(stylesIndexesDirectory, 'angular-example.css'), `@import url('../reset.css');\n\nangular-page {\n\tdisplay: flex;\n\tflex: 1;\n\tflex-direction: column;\n}\n`, 'utf-8');
14
14
  return;
@@ -18,14 +18,13 @@ export const scaffoldAngular = ({ editBasePath, includeExamples, isSingleFronten
18
18
  mkdirSync(componentsDirectory, { recursive: true });
19
19
  mkdirSync(pagesDirectory, { recursive: true });
20
20
  mkdirSync(templatesDir, { recursive: true });
21
- writeFileSync(join(pagesDirectory, 'angular-example.ts'), generateAngularPage(frontends, true), 'utf-8');
21
+ writeFileSync(join(pagesDirectory, 'angular-example.ts'), generateAngularPage(true), 'utf-8');
22
22
  writeFileSync(join(templatesDir, 'angular-example.html'), generateAngularPageHtml(true), 'utf-8');
23
- writeFileSync(join(componentsDirectory, 'dropdown.component.ts'), generateDropdownComponent(frontends), 'utf-8');
23
+ writeFileSync(join(componentsDirectory, 'dropdown.component.ts'), generateDropdownComponent(), 'utf-8');
24
24
  writeFileSync(join(templatesDir, 'dropdown.component.html'), generateDropdownComponentHtml(frontends), 'utf-8');
25
- writeFileSync(join(componentsDirectory, 'app.component.ts'), generateAppComponent(isSingleFrontend), 'utf-8');
25
+ writeFileSync(join(componentsDirectory, 'app.component.ts'), generateAppComponent(), 'utf-8');
26
26
  writeFileSync(join(templatesDir, 'app.component.html'), generateAppComponentHtml(frontends, editBasePath), 'utf-8');
27
- writeFileSync(join(stylesDirectory, 'app.component.css'), generateAppComponentCss(), 'utf-8');
28
- writeFileSync(join(componentsDirectory, 'counter.component.ts'), generateCounterComponent(isSingleFrontend), 'utf-8');
27
+ writeFileSync(join(componentsDirectory, 'counter.component.ts'), generateCounterComponent(relative(componentsDirectory, stylesDirectory).replaceAll('\\', '/')), 'utf-8');
29
28
  writeFileSync(join(templatesDir, 'counter.component.html'), generateCounterComponentHtml(), 'utf-8');
30
29
  writeFileSync(join(stylesDirectory, 'counter.component.css'), generateCounterComponentCss(), 'utf-8');
31
30
  const angularCSS = generateMarkupCSS('angular', '#dd0031');
@@ -12,7 +12,7 @@ export const createDrizzleConfig = ({ projectName, databaseDirectory, databaseEn
12
12
  dbCredentials: {
13
13
  url: env.DATABASE_URL
14
14
  },
15
- dialect: '${databaseEngine}',
15
+ dialect: '${databaseEngine === 'mariadb' ? 'mysql' : databaseEngine}',
16
16
  out: '${databaseDirectory}/migrations',
17
17
  schema: '${databaseDirectory}/schema.ts'
18
18
  });
@@ -6,7 +6,8 @@ import absolute from 'eslint-plugin-absolute';
6
6
  ${frontends.includes('react') ? "import reactHooks from 'eslint-plugin-react-hooks';" : ''}
7
7
 
8
8
  export default defineConfig([
9
- { ignores: ['node_modules/**', 'build/**', 'dist/**', '.absolutejs/**', '.data/**', '**/.absolutejs-hmr-*', 'drizzle/**'] },
9
+ // The copied HTMX distribution is third-party minified runtime code.
10
+ { ignores: ['src/frontend/**/htmx.min.js', 'node_modules/**', 'build/**', 'dist/**', '.absolutejs/**', '.data/**', '**/.absolutejs-hmr-*', 'drizzle/**'] },
10
11
  { linterOptions: { noInlineConfig: true, reportUnusedDisableDirectives: 'error' } },
11
12
  ...tseslint.configs.recommended,
12
13
  {
@@ -42,7 +43,8 @@ export default defineConfig([
42
43
  { files: ['**/*.{html,vue,svelte,gjs,gts}'], plugins: { absolute }, processor: 'absolute/template-source' },
43
44
  ${frontends.includes('react')
44
45
  ? `{
45
- files: ['src/frontend/**/*.{ts,tsx,js,jsx}'],
46
+ // React Hooks rules do not apply to Angular's unrelated usePageContext API.
47
+ files: ['src/frontend/**/*.{tsx,jsx}', 'src/frontend/**/hooks/**/*.{ts,js}'],
46
48
  plugins: { 'react-hooks': reactHooks },
47
49
  rules: { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'error' },
48
50
  },`
@@ -147,7 +147,7 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
147
147
  dependencies[absoluteAuthPlugin.value] = resolveVersion(absoluteAuthPlugin.value, absoluteAuthPlugin.latestVersion);
148
148
  }
149
149
  if (agentic) {
150
- for (const name of [
150
+ [
151
151
  '@absolutejs/a2a',
152
152
  '@absolutejs/agency',
153
153
  '@absolutejs/agent-discovery',
@@ -166,9 +166,9 @@ export const createPackageJson = async ({ agentic, projectName, authOption, plug
166
166
  '@absolutejs/secrets',
167
167
  '@absolutejs/sync-bus-pg',
168
168
  '@absolutejs/wallet'
169
- ]) {
169
+ ].forEach((name) => {
170
170
  dependencies[name] = resolveVersion(name, versions[name]);
171
- }
171
+ });
172
172
  devDependencies['@absolutejs/agent-conformance'] = resolveVersion('@absolutejs/agent-conformance', versions['@absolutejs/agent-conformance']);
173
173
  }
174
174
  for (const pluginValue of plugins) {
@@ -108,56 +108,49 @@ export const generateDatabaseTypes = ({ databaseEngine, databaseHost, authOption
108
108
  let dbImport = '';
109
109
  let dbTypeLine = '';
110
110
  if (databaseHost === 'neon') {
111
- dbImport = `import { NeonHttpDatabase } from 'drizzle-orm/neon-http';`;
112
- dbTypeLine = 'export type DatabaseType = NeonHttpDatabase<SchemaType>;';
111
+ dbImport = `import type { drizzle } from 'drizzle-orm/neon-http';`;
112
+ dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
113
113
  }
114
114
  else if (databaseHost === 'planetscale' && databaseEngine === 'mysql') {
115
- dbImport = `import { PlanetScaleDatabase } from 'drizzle-orm/planetscale-serverless';`;
116
- dbTypeLine =
117
- 'export type DatabaseType = PlanetScaleDatabase<SchemaType>;';
115
+ dbImport = `import type { drizzle } from 'drizzle-orm/planetscale-serverless';`;
116
+ dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
118
117
  }
119
118
  else if (databaseHost === 'planetscale' &&
120
119
  databaseEngine === 'postgresql') {
121
- dbImport = `import { NodePgDatabase } from 'drizzle-orm/node-postgres';`;
122
- dbTypeLine = 'export type DatabaseType = NodePgDatabase<SchemaType>;';
120
+ dbImport = `import type { drizzle } from 'drizzle-orm/node-postgres';`;
121
+ dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
123
122
  }
124
123
  else if (databaseHost === 'turso') {
125
- dbImport = `import { LibSQLDatabase } from 'drizzle-orm/libsql';`;
126
- dbTypeLine = 'export type DatabaseType = LibSQLDatabase<SchemaType>;';
124
+ dbImport = `import type { drizzle } from 'drizzle-orm/libsql';`;
125
+ dbTypeLine = 'export type DatabaseType = ReturnType<typeof drizzle>;';
127
126
  }
128
127
  if ((!databaseHost || databaseHost === 'none') &&
129
128
  isDrizzleDialect(databaseEngine)) {
130
129
  switch (databaseEngine) {
131
- case 'gel':
132
- dbImport = `import { GelJsDatabase } from 'drizzle-orm/gel';`;
133
- dbTypeLine =
134
- 'export type DatabaseType = GelJsDatabase<SchemaType>;';
135
- break;
136
130
  case 'mariadb':
137
131
  case 'mysql':
138
- dbImport = `import { Mysql2Database } from 'drizzle-orm/mysql2';`;
132
+ dbImport = `import type { drizzle } from 'drizzle-orm/mysql2';`;
139
133
  dbTypeLine =
140
- 'export type DatabaseType = Mysql2Database<SchemaType>;';
134
+ 'export type DatabaseType = ReturnType<typeof drizzle>;';
141
135
  break;
142
136
  case 'mssql':
143
- dbImport = `import { NodeMssqlDatabase } from 'drizzle-orm/node-mssql';`;
137
+ dbImport = `import type { NodeMsSqlDatabase } from 'drizzle-orm/node-mssql';`;
144
138
  dbTypeLine =
145
- 'export type DatabaseType = NodeMssqlDatabase<SchemaType>;';
139
+ 'export type DatabaseType = NodeMsSqlDatabase;';
146
140
  break;
147
141
  case 'postgresql':
148
- dbImport = `import { BunSQLDatabase } from 'drizzle-orm/bun-sql';`;
142
+ dbImport = `import type { drizzle } from 'drizzle-orm/bun-sql';`;
149
143
  dbTypeLine =
150
- 'export type DatabaseType = BunSQLDatabase<SchemaType>;';
144
+ 'export type DatabaseType = ReturnType<typeof drizzle>;';
151
145
  break;
152
146
  case 'singlestore':
153
- dbImport = `import { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
154
- dbTypeLine =
155
- 'export type DatabaseType = SingleStoreDriverDatabase<SchemaType>;';
147
+ dbImport = `import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';`;
148
+ dbTypeLine = 'export type DatabaseType = SingleStoreDriverDatabase;';
156
149
  break;
157
150
  case 'sqlite':
158
- dbImport = `import { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';`;
151
+ dbImport = `import type { drizzle } from 'drizzle-orm/bun-sqlite';`;
159
152
  dbTypeLine =
160
- 'export type DatabaseType = BunSQLiteDatabase<SchemaType>;';
153
+ 'export type DatabaseType = ReturnType<typeof drizzle>;';
161
154
  break;
162
155
  }
163
156
  }
@@ -53,7 +53,7 @@ const DIALECTS = {
53
53
  pkg: 'sqlite-core',
54
54
  string: 'text()',
55
55
  table: 'sqliteTable',
56
- time: "integer({ mode: 'timestamp' })"
56
+ time: "integer({ mode: 'timestamp_ms' })"
57
57
  }
58
58
  };
59
59
  const builder = (expr) => expr.split('(')[0];
@@ -72,13 +72,15 @@ export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
72
72
  ? [cfg.table, stringBuilder, timeBuilder, jsonBuilder]
73
73
  : [cfg.table, intBuilder, timeBuilder];
74
74
  const uniqueBuilders = Array.from(new Set(importBuilders));
75
- const builderImport = `import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`;
76
- const sqliteImports = databaseEngine === 'sqlite'
75
+ const builderImport = `${authOption === 'abs' ? "import type { UserIdentity } from '../src/types/userIdentity';\n" : ''}import { ${uniqueBuilders.join(', ')} } from 'drizzle-orm/${cfg.pkg}';`;
76
+ const sqliteImports = databaseEngine === 'sqlite' || databaseEngine === 'mssql'
77
77
  ? `import { sql } from 'drizzle-orm';\n`
78
78
  : '';
79
79
  let uidColumn;
80
- if (databaseEngine === 'mariadb' ||
81
- databaseEngine === 'mssql' ||
80
+ if (databaseEngine === 'mssql') {
81
+ uidColumn = `int('uid').identity().primaryKey()`;
82
+ }
83
+ else if (databaseEngine === 'mariadb' ||
82
84
  databaseEngine === 'mysql' ||
83
85
  databaseEngine === 'singlestore') {
84
86
  uidColumn = `${intBuilder}('uid').primaryKey().autoincrement()`;
@@ -93,14 +95,16 @@ export const generateDrizzleSchema = ({ databaseEngine, authOption }) => {
93
95
  ? `const JULIAN_DAY_UNIX_EPOCH_OFFSET = 2440587.5;
94
96
  const MILLIS_PER_DAY = 86400000;\n\n`
95
97
  : '';
96
- const timestampColumn = databaseEngine === 'sqlite'
97
- ? `${cfg.time}.notNull().default(sql\`(julianday('now') - \${JULIAN_DAY_UNIX_EPOCH_OFFSET}) * \${MILLIS_PER_DAY}\`)`
98
- : `${cfg.time}.notNull().defaultNow()`;
98
+ let timestampColumn = `${cfg.time}.notNull().defaultNow()`;
99
+ if (databaseEngine === 'sqlite')
100
+ timestampColumn = `${cfg.time}.notNull().default(sql\`(julianday('now') - \${JULIAN_DAY_UNIX_EPOCH_OFFSET}) * \${MILLIS_PER_DAY}\`)`;
101
+ if (databaseEngine === 'mssql')
102
+ timestampColumn = `${cfg.time}.notNull().default(sql\`sysdatetime()\`)`;
99
103
  const tableBlock = authOption === 'abs'
100
104
  ? `export const users = ${cfg.table}('users', {
101
105
  auth_sub: ${cfg.string}.primaryKey(),
102
106
  created_at: ${timestampColumn},
103
- metadata: ${cfg.json}.$type<Record<string, unknown>>().default({})
107
+ metadata: ${cfg.json}.$type<UserIdentity>().default({})
104
108
  });`
105
109
  : `export const countHistory = ${cfg.table}('count_history', {
106
110
  uid: ${uidColumn},
@@ -1,4 +1,4 @@
1
- import { getAuthTemplate, getCountTemplate } from './handlerTemplates';
1
+ import { getAuthTemplate, getCountTemplate, isDriverConfigurationKey } from './handlerTemplates';
2
2
  export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth }) => {
3
3
  if (databaseEngine === undefined || databaseEngine === 'none') {
4
4
  throw new Error('Internal Error: databaseEngine is undefined or "none".');
@@ -6,6 +6,7 @@ export const generateDBHandlers = ({ databaseEngine, databaseHost, orm, usesAuth
6
6
  const host = databaseHost && databaseHost !== 'none' ? databaseHost : 'local';
7
7
  const ormKey = orm === 'drizzle' ? 'drizzle' : 'sql';
8
8
  const key = `${databaseEngine}:${ormKey}:${host}`;
9
- // @ts-expect-error - TODO: Finish the other templates
9
+ if (!isDriverConfigurationKey(key))
10
+ throw new Error(`Unsupported database configuration: ${key}`);
10
11
  return usesAuth ? getAuthTemplate(key) : getCountTemplate(key);
11
12
  };
@@ -1,3 +1,3 @@
1
1
  import { AuthOption, DatabaseEngine } from '../../types';
2
- export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
3
2
  export declare const generateRelationalSchema: (databaseEngine: DatabaseEngine, authOption: AuthOption) => string;
3
+ export declare const supportsRelationalSchema: (engine: DatabaseEngine) => boolean;
@@ -30,7 +30,6 @@ const DIALECTS = {
30
30
  timestamp: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP'
31
31
  }
32
32
  };
33
- export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
34
33
  export const generateRelationalSchema = (databaseEngine, authOption) => {
35
34
  if (databaseEngine === undefined || !(databaseEngine in DIALECTS)) {
36
35
  throw new Error(`Internal error: no relational DDL for engine "${databaseEngine}"`);
@@ -50,3 +49,4 @@ export const generateRelationalSchema = (databaseEngine, authOption) => {
50
49
  );
51
50
  `;
52
51
  };
52
+ export const supportsRelationalSchema = (engine) => engine !== undefined && engine in DIALECTS;
@@ -31,7 +31,12 @@ declare const driverConfigurations: {
31
31
  };
32
32
  readonly 'mssql:drizzle:local': {
33
33
  readonly importLines: "import { eq } from 'drizzle-orm'\nimport { schema } from '../../../db/schema'";
34
- readonly queries: QueryOperations;
34
+ readonly queries: {
35
+ readonly insertHistory: string;
36
+ readonly insertUser: string;
37
+ readonly selectUser: string;
38
+ readonly selectHistory: string;
39
+ };
35
40
  };
36
41
  readonly 'mssql:sql:local': {
37
42
  readonly importLines: "";
@@ -105,4 +110,5 @@ declare const driverConfigurations: {
105
110
  type DriverConfigurationKey = keyof typeof driverConfigurations;
106
111
  export declare const getAuthTemplate: (key: DriverConfigurationKey) => string;
107
112
  export declare const getCountTemplate: (key: DriverConfigurationKey) => string;
113
+ export declare const isDriverConfigurationKey: (key: string) => key is DriverConfigurationKey;
108
114
  export {};
@@ -19,7 +19,7 @@ export const getUser = async (
19
19
  ((await selectUserRow(db, authSub)) as User | null) ?? null;`}
20
20
 
21
21
  export const createUser = async (db: DatabaseType, newUserData: NewUser) => {
22
- const { auth_sub: authSub, metadata: userIdentity } = newUserData;
22
+ ${rowsAreTyped ? '' : 'const { auth_sub: authSub, metadata: userIdentity } = newUserData;'}
23
23
  ${queries.insertUser}
24
24
  }`;
25
25
  /* Takes `db` as the `DatabaseType` alias rather than naming the driver type
@@ -367,7 +367,11 @@ import { schema } from '../../../db/schema'`,
367
367
  'mssql:drizzle:local': {
368
368
  importLines: `import { eq } from 'drizzle-orm'
369
369
  import { schema } from '../../../db/schema'`,
370
- queries: drizzleQueryOperations
370
+ queries: {
371
+ ...drizzleQueryOperations,
372
+ insertHistory: drizzleQueryOperations.insertHistory.replace('.values({ count }).returning()', '.output().values({ count })'),
373
+ insertUser: drizzleQueryOperations.insertUser.replace('.values(newUserData)\n\t\t.returning()', '.output()\n\t\t.values(newUserData)')
374
+ }
371
375
  },
372
376
  'mssql:sql:local': {
373
377
  importLines: ``,
@@ -462,3 +466,4 @@ export const getCountTemplate = (key) => {
462
466
  throw new Error(`Unsupported driver configuration: ${key}`);
463
467
  return buildSqlCountTemplate(configuration);
464
468
  };
469
+ export const isDriverConfigurationKey = (key) => Object.hasOwn(driverConfigurations, key);
@@ -1,10 +1,11 @@
1
1
  import type { CreateConfiguration } from '../../types';
2
2
  type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'databaseHost' | 'orm' | 'databaseDirectory' | 'authOption' | 'databaseEngine'> & {
3
3
  databaseDirectory: string;
4
+ verifyLocalDatabase?: boolean;
4
5
  backendDirectory: string;
5
6
  typesDirectory: string;
6
7
  };
7
- export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }: ScaffoldDatabaseProps) => Promise<{
8
+ export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory, verifyLocalDatabase }: ScaffoldDatabaseProps) => Promise<{
8
9
  dockerFreshInstall: boolean;
9
10
  }>;
10
11
  export {};