alepha 0.20.2 → 0.20.3

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 (208) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts.map +1 -1
  7. package/dist/api/audits/index.js +49 -0
  8. package/dist/api/audits/index.js.map +1 -1
  9. package/dist/api/files/index.d.ts.map +1 -1
  10. package/dist/api/files/index.js.map +1 -1
  11. package/dist/api/jobs/index.d.ts +16 -75
  12. package/dist/api/jobs/index.d.ts.map +1 -1
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.js.map +1 -1
  15. package/dist/api/notifications/index.d.ts +1 -10
  16. package/dist/api/notifications/index.d.ts.map +1 -1
  17. package/dist/api/organizations/index.d.ts.map +1 -1
  18. package/dist/api/parameters/index.browser.js +37 -0
  19. package/dist/api/parameters/index.browser.js.map +1 -1
  20. package/dist/api/parameters/index.d.ts +4 -65
  21. package/dist/api/parameters/index.d.ts.map +1 -1
  22. package/dist/api/parameters/index.js +37 -0
  23. package/dist/api/parameters/index.js.map +1 -1
  24. package/dist/api/payments/index.d.ts.map +1 -1
  25. package/dist/api/payments/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +207 -5184
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2 -4
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts.map +1 -1
  31. package/dist/api/verifications/index.js +2 -1
  32. package/dist/api/verifications/index.js.map +1 -1
  33. package/dist/bucket/index.js +5 -1
  34. package/dist/bucket/index.js.map +1 -1
  35. package/dist/bucket/index.workerd.js +5 -1
  36. package/dist/bucket/index.workerd.js.map +1 -1
  37. package/dist/cache/core/index.js.map +1 -1
  38. package/dist/cache/core/index.workerd.js.map +1 -1
  39. package/dist/captcha/index.js.map +1 -1
  40. package/dist/cli/core/index.d.ts +217 -11647
  41. package/dist/cli/core/index.d.ts.map +1 -1
  42. package/dist/cli/core/index.js +706 -42
  43. package/dist/cli/core/index.js.map +1 -1
  44. package/dist/cli/devtools/index.js +7 -1
  45. package/dist/cli/devtools/index.js.map +1 -1
  46. package/dist/cli/platform/index.d.ts +41 -64
  47. package/dist/cli/platform/index.d.ts.map +1 -1
  48. package/dist/cli/platform/index.js +47 -0
  49. package/dist/cli/platform/index.js.map +1 -1
  50. package/dist/cli/vendor/index.js +15 -0
  51. package/dist/cli/vendor/index.js.map +1 -1
  52. package/dist/command/index.js +1 -1
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js.map +1 -1
  55. package/dist/core/index.d.ts +2 -8
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js.map +1 -1
  58. package/dist/core/index.native.js.map +1 -1
  59. package/dist/core/index.workerd.js.map +1 -1
  60. package/dist/crypto/index.js.map +1 -1
  61. package/dist/datetime/index.js.map +1 -1
  62. package/dist/email/core/index.js.map +1 -1
  63. package/dist/email/smtp/index.js +2 -10522
  64. package/dist/email/smtp/index.js.map +1 -1
  65. package/dist/fake/index.d.ts +4 -8085
  66. package/dist/fake/index.d.ts.map +1 -1
  67. package/dist/fake/index.js +3 -33554
  68. package/dist/fake/index.js.map +1 -1
  69. package/dist/lock/core/index.js.map +1 -1
  70. package/dist/lock/redis/index.js.map +1 -1
  71. package/dist/logger/index.js +32 -1
  72. package/dist/logger/index.js.map +1 -1
  73. package/dist/mcp/index.js +5 -1
  74. package/dist/mcp/index.js.map +1 -1
  75. package/dist/orm/core/index.browser.js +1 -361
  76. package/dist/orm/core/index.browser.js.map +1 -1
  77. package/dist/orm/core/index.bun.js +14 -406
  78. package/dist/orm/core/index.bun.js.map +1 -1
  79. package/dist/orm/core/index.d.ts +96 -5117
  80. package/dist/orm/core/index.d.ts.map +1 -1
  81. package/dist/orm/core/index.js +23 -419
  82. package/dist/orm/core/index.js.map +1 -1
  83. package/dist/orm/postgres/index.bun.js +17 -20
  84. package/dist/orm/postgres/index.bun.js.map +1 -1
  85. package/dist/orm/postgres/index.d.ts +2 -613
  86. package/dist/orm/postgres/index.d.ts.map +1 -1
  87. package/dist/orm/postgres/index.js +17 -20
  88. package/dist/orm/postgres/index.js.map +1 -1
  89. package/dist/react/core/index.js.map +1 -1
  90. package/dist/react/i18n/index.js.map +1 -1
  91. package/dist/react/intro/index.js +22 -17
  92. package/dist/react/intro/index.js.map +1 -1
  93. package/dist/react/router/index.browser.js +78 -2
  94. package/dist/react/router/index.browser.js.map +1 -1
  95. package/dist/react/router/index.d.ts +22 -1
  96. package/dist/react/router/index.d.ts.map +1 -1
  97. package/dist/react/router/index.js +102 -4
  98. package/dist/react/router/index.js.map +1 -1
  99. package/dist/react/testing/index.d.ts +1 -411
  100. package/dist/react/testing/index.d.ts.map +1 -1
  101. package/dist/react/testing/index.js +13 -12293
  102. package/dist/react/testing/index.js.map +1 -1
  103. package/dist/react/ui/index.js +3 -0
  104. package/dist/react/ui/index.js.map +1 -1
  105. package/dist/react/websocket/index.js.map +1 -1
  106. package/dist/redis/index.js.map +1 -1
  107. package/dist/scheduler/index.d.ts +1 -83
  108. package/dist/scheduler/index.d.ts.map +1 -1
  109. package/dist/scheduler/index.js +2 -391
  110. package/dist/scheduler/index.js.map +1 -1
  111. package/dist/scheduler/index.workerd.js +2 -391
  112. package/dist/scheduler/index.workerd.js.map +1 -1
  113. package/dist/security/index.browser.js.map +1 -1
  114. package/dist/security/index.d.ts +2 -325
  115. package/dist/security/index.d.ts.map +1 -1
  116. package/dist/security/index.js +3 -1362
  117. package/dist/security/index.js.map +1 -1
  118. package/dist/server/auth/index.d.ts +1 -1054
  119. package/dist/server/auth/index.d.ts.map +1 -1
  120. package/dist/server/auth/index.js +16 -1224
  121. package/dist/server/auth/index.js.map +1 -1
  122. package/dist/server/cookies/index.js.map +1 -1
  123. package/dist/server/core/index.browser.js.map +1 -1
  124. package/dist/server/core/index.d.ts +1 -4
  125. package/dist/server/core/index.d.ts.map +1 -1
  126. package/dist/server/core/index.js +19 -4
  127. package/dist/server/core/index.js.map +1 -1
  128. package/dist/server/links/index.browser.js.map +1 -1
  129. package/dist/server/links/index.js.map +1 -1
  130. package/dist/server/metrics/index.d.ts +1 -514
  131. package/dist/server/metrics/index.d.ts.map +1 -1
  132. package/dist/server/metrics/index.js +4 -4356
  133. package/dist/server/metrics/index.js.map +1 -1
  134. package/dist/server/rate-limit/index.js.map +1 -1
  135. package/dist/server/static/index.js.map +1 -1
  136. package/dist/server/swagger/index.js +1 -1
  137. package/dist/server/swagger/index.js.map +1 -1
  138. package/dist/sms/index.js.map +1 -1
  139. package/dist/system/index.browser.js.map +1 -1
  140. package/dist/system/index.js.map +1 -1
  141. package/dist/system/index.workerd.js.map +1 -1
  142. package/dist/topic/core/index.js.map +1 -1
  143. package/dist/websocket/index.browser.js +21 -0
  144. package/dist/websocket/index.browser.js.map +1 -1
  145. package/dist/websocket/index.js +21 -0
  146. package/dist/websocket/index.js.map +1 -1
  147. package/package.json +18 -15
  148. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  149. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  150. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  151. package/src/api/users/services/UserService.ts +1 -5
  152. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  153. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  154. package/src/api/verifications/services/VerificationService.ts +1 -0
  155. package/src/cli/core/__tests__/init.spec.ts +208 -0
  156. package/src/cli/core/commands/init.ts +12 -0
  157. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  158. package/src/cli/core/services/ProjectScaffolder.ts +298 -20
  159. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  160. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  161. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  162. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  163. package/src/cli/core/templates/mainCss.ts +1 -0
  164. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  165. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  166. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  167. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  168. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  169. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  170. package/src/cli/core/templates/webIndexTs.ts +23 -1
  171. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  172. package/src/command/providers/CliProvider.ts +1 -1
  173. package/src/core/interfaces/Service.ts +3 -1
  174. package/src/core/providers/TypeProvider.ts +1 -1
  175. package/src/logger/services/Logger.ts +1 -1
  176. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  177. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  178. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  179. package/src/orm/__tests__/$repository-tests.ts +1 -0
  180. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  181. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  182. package/src/orm/core/index.shared.ts +0 -2
  183. package/src/orm/core/index.ts +1 -2
  184. package/src/orm/core/primitives/$repository.ts +3 -6
  185. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  186. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  187. package/src/orm/core/services/ModelBuilder.ts +1 -13
  188. package/src/orm/core/services/Repository.ts +1 -42
  189. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  190. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  191. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  192. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  193. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  194. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  195. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  196. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  197. package/src/scheduler/providers/CronProvider.ts +1 -1
  198. package/src/security/primitives/$basicAuth.ts +1 -1
  199. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  200. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  201. package/src/server/core/providers/ServerProvider.ts +1 -1
  202. package/src/server/core/providers/ServerRouterProvider.ts +2 -2
  203. package/src/server/core/services/HttpClient.ts +1 -1
  204. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  205. package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
  206. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  207. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  208. package/src/orm/core/primitives/$view.ts +0 -88
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Admin pages — each is a thin wrapper around the matching `admin-*`
3
+ * registry component (placed at `src/components/admin/*`). The starter
4
+ * ships with Users + Sessions; add more by `shadcn add @alepha/admin-…`
5
+ * and a matching `$page(...)` in AppRouter.
6
+ */
7
+
8
+ export const saasAdminUsersTsx = () =>
9
+ `import { AdminUsers } from "@/components/admin/admin-users";
10
+
11
+ const AdminUsersPage = () => {
12
+ return <AdminUsers />;
13
+ };
14
+
15
+ export default AdminUsersPage;
16
+ `;
17
+
18
+ export const saasAdminSessionsTsx = () =>
19
+ `import { AdminSessions } from "@/components/admin/admin-sessions";
20
+
21
+ const AdminSessionsPage = () => {
22
+ return <AdminSessions />;
23
+ };
24
+
25
+ export default AdminSessionsPage;
26
+ `;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SaaS auth layout — wraps every /auth/* page with a centered card.
3
+ * Routes (login, register, reset-password, verify-email) are mounted as
4
+ * children so they share this shell.
5
+ */
6
+ export const saasAuthLayoutTsx = () =>
7
+ `import { NestedView } from "alepha/react/router";
8
+
9
+ const AuthLayout = () => {
10
+ return (
11
+ <div className="bg-background flex min-h-screen items-center justify-center p-4">
12
+ <div className="w-full max-w-md">
13
+ <NestedView />
14
+ </div>
15
+ </div>
16
+ );
17
+ };
18
+
19
+ export default AuthLayout;
20
+ `;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Per-page wrapper around the registry components. The registry component
3
+ * receives the realm config from the page loader; the page itself stays a
4
+ * thin shell so apps can layer their branding around it.
5
+ *
6
+ * Registry components land at `src/components/auth/*` (shadcn defaults).
7
+ */
8
+
9
+ export const saasAuthLoginTsx = () =>
10
+ `import { AuthLogin } from "@/components/auth/auth-login";
11
+ import type { RealmConfig } from "alepha/api/users";
12
+
13
+ export interface AuthLoginPageProps {
14
+ realmConfig: RealmConfig;
15
+ }
16
+
17
+ const AuthLoginPage = (props: AuthLoginPageProps) => {
18
+ return <AuthLogin realmConfig={props.realmConfig} />;
19
+ };
20
+
21
+ export default AuthLoginPage;
22
+ `;
23
+
24
+ export const saasAuthRegisterTsx = () =>
25
+ `import { AuthRegister } from "@/components/auth/auth-register";
26
+ import type { RealmConfig } from "alepha/api/users";
27
+
28
+ export interface AuthRegisterPageProps {
29
+ realmConfig: RealmConfig;
30
+ }
31
+
32
+ const AuthRegisterPage = (props: AuthRegisterPageProps) => {
33
+ return <AuthRegister realmConfig={props.realmConfig} />;
34
+ };
35
+
36
+ export default AuthRegisterPage;
37
+ `;
38
+
39
+ export const saasAuthResetPasswordTsx = () =>
40
+ `import { AuthResetPassword } from "@/components/auth/auth-reset-password";
41
+ import type { RealmConfig } from "alepha/api/users";
42
+
43
+ export interface AuthResetPasswordPageProps {
44
+ realmConfig: RealmConfig;
45
+ }
46
+
47
+ const AuthResetPasswordPage = (props: AuthResetPasswordPageProps) => {
48
+ return <AuthResetPassword realmConfig={props.realmConfig} />;
49
+ };
50
+
51
+ export default AuthResetPasswordPage;
52
+ `;
53
+
54
+ export const saasAuthVerifyEmailTsx = () =>
55
+ `import { AuthVerifyEmail } from "@/components/auth/auth-verify-email";
56
+
57
+ const AuthVerifyEmailPage = () => {
58
+ return <AuthVerifyEmail />;
59
+ };
60
+
61
+ export default AuthVerifyEmailPage;
62
+ `;
@@ -0,0 +1,46 @@
1
+ export interface SaasRealmProviderOptions {
2
+ /**
3
+ * Email seeded as the default admin. Detected from `git config user.email`
4
+ * at init time; falls through to `admin@example.com` if git isn't configured.
5
+ */
6
+ adminEmail?: string;
7
+ }
8
+
9
+ /**
10
+ * Realm provider scaffolded by `alepha init --saas`.
11
+ *
12
+ * Minimal hello-world setup: credentials login with email, one admin seeded
13
+ * with the developer's git email at scaffold time, and an `admin:ui`
14
+ * permission used by the AppRouter to gate `/admin/*`. The default `admin`
15
+ * role grants `*` (so it inherits `admin:ui`); the default `user` role
16
+ * excludes `admin:*` (so non-admins get a 403 before the shell renders).
17
+ *
18
+ * Add `$env`, more permissions, or stricter settings as the project grows.
19
+ */
20
+ export const saasRealmProviderTs = (options: SaasRealmProviderOptions = {}) => {
21
+ const adminEmail = options.adminEmail ?? "admin@example.com";
22
+ return `import { $realm } from "alepha/api/users";
23
+ import { $permission } from "alepha/security";
24
+
25
+ export class RealmProvider {
26
+ /**
27
+ * Permission required to open the admin UI. Wired into AppRouter.adminLayout
28
+ * via \`$secure({ permissions: ["admin:ui"] })\`.
29
+ */
30
+ adminUi = $permission({
31
+ group: "admin",
32
+ name: "ui",
33
+ description: "Access to the admin UI shell",
34
+ });
35
+
36
+ realm = $realm({
37
+ settings: {
38
+ adminEmails: [${JSON.stringify(adminEmail)}],
39
+ },
40
+ identities: {
41
+ credentials: true,
42
+ },
43
+ });
44
+ }
45
+ `;
46
+ };
@@ -1,4 +1,16 @@
1
- export const webAppRouterTs = (options: { api?: boolean }) => {
1
+ export interface WebAppRouterOptions {
2
+ api?: boolean;
3
+ saas?: boolean;
4
+ }
5
+
6
+ export const webAppRouterTs = (options: WebAppRouterOptions) => {
7
+ if (options.saas) {
8
+ return saasAppRouterTs();
9
+ }
10
+ return basicAppRouterTs(options);
11
+ };
12
+
13
+ const basicAppRouterTs = (options: WebAppRouterOptions) => {
2
14
  const imports: string[] = ['import { $page } from "alepha/react/router";'];
3
15
  const classMembers: string[] = [];
4
16
 
@@ -27,3 +39,94 @@ export class AppRouter {
27
39
  ${classMembers.join("\n\n")}
28
40
  }`;
29
41
  };
42
+
43
+ /**
44
+ * SaaS router wires three trees onto the app:
45
+ * / → Home
46
+ * /auth/* → AuthLayout + login / register / reset / verify
47
+ * /admin/* → AdminLayout + users / sessions / api-keys / parameters / audits
48
+ *
49
+ * Each auth page resolves the realm config from its loader, so the registry
50
+ * components render with everything they need on first paint.
51
+ */
52
+ const saasAppRouterTs =
53
+ () => `import type { RealmController } from "alepha/api/users";
54
+ import { $page, NotFound } from "alepha/react/router";
55
+ import { $secure } from "alepha/security";
56
+ import { $client } from "alepha/server/links";
57
+ import type { HelloController } from "../api/controllers/HelloController.ts";
58
+
59
+ export class AppRouter {
60
+ protected readonly api = $client<HelloController>();
61
+ protected readonly realmApi = $client<RealmController>();
62
+
63
+ home = $page({
64
+ path: "/",
65
+ lazy: () => import("./components/Home.tsx"),
66
+ loader: () => this.api.hello(),
67
+ });
68
+
69
+ // ── /auth — login, register, reset, verify ─────────────────────────────
70
+ authLayout = $page({
71
+ path: "/auth",
72
+ lazy: () => import("./components/auth/AuthLayout.tsx"),
73
+ });
74
+
75
+ login = $page({
76
+ parent: this.authLayout,
77
+ path: "/login",
78
+ name: "login",
79
+ head: { title: "Sign in" },
80
+ lazy: () => import("./components/auth/Login.tsx"),
81
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
82
+ });
83
+
84
+ register = $page({
85
+ parent: this.authLayout,
86
+ path: "/register",
87
+ name: "register",
88
+ head: { title: "Sign up" },
89
+ lazy: () => import("./components/auth/Register.tsx"),
90
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
91
+ });
92
+
93
+ resetPassword = $page({
94
+ parent: this.authLayout,
95
+ path: "/reset-password",
96
+ head: { title: "Reset password" },
97
+ lazy: () => import("./components/auth/ResetPassword.tsx"),
98
+ loader: async () => ({ realmConfig: await this.realmApi.getRealmConfig() }),
99
+ });
100
+
101
+ verifyEmail = $page({
102
+ parent: this.authLayout,
103
+ path: "/verify-email",
104
+ head: { title: "Verify email" },
105
+ lazy: () => import("./components/auth/VerifyEmail.tsx"),
106
+ });
107
+
108
+ // ── /admin — gated by 'admin:ui' permission, declared in RealmProvider.
109
+ // Children inherit the gate via the parent chain.
110
+ adminLayout = $page({
111
+ path: "/admin",
112
+ use: [$secure({ permissions: ["admin:ui"] })],
113
+ lazy: () => import("./components/admin/AdminLayout.tsx"),
114
+ });
115
+
116
+ adminUsers = $page({
117
+ parent: this.adminLayout,
118
+ path: "/users",
119
+ head: { title: "Users" },
120
+ lazy: () => import("./components/admin/Users.tsx"),
121
+ });
122
+
123
+ adminSessions = $page({
124
+ parent: this.adminLayout,
125
+ path: "/sessions",
126
+ head: { title: "Sessions" },
127
+ lazy: () => import("./components/admin/Sessions.tsx"),
128
+ });
129
+
130
+ notFound = $page({ path: "/*", component: NotFound });
131
+ }
132
+ `;
@@ -1,9 +1,31 @@
1
1
  export interface WebIndexTsOptions {
2
2
  appName?: string;
3
+ /**
4
+ * SaaS bundle: auth pages need `useAuth()` (ReactAuth) which lives in the
5
+ * `alepha.react.auth` module. Importing it here registers ReactAuth in the
6
+ * container so the auth-* registry components can inject it.
7
+ */
8
+ saas?: boolean;
3
9
  }
4
10
 
5
11
  export const webIndexTs = (options: WebIndexTsOptions = {}) => {
6
- const { appName = "app" } = options;
12
+ const { appName = "app", saas = false } = options;
13
+
14
+ if (saas) {
15
+ return `
16
+ import { $module } from "alepha";
17
+ import { AlephaReactAuth } from "alepha/react/auth";
18
+ import { AlephaReactI18n } from "alepha/react/i18n";
19
+ import { AppRouter } from "./AppRouter.ts";
20
+
21
+ export const WebModule = $module({
22
+ name: "${appName}.web",
23
+ services: [AppRouter],
24
+ imports: [AlephaReactAuth, AlephaReactI18n],
25
+ });
26
+ `.trim();
27
+ }
28
+
7
29
  return `
8
30
  import { $module } from "alepha";
9
31
  import { AppRouter } from "./AppRouter.ts";
@@ -252,8 +252,10 @@ describe("SecretsCommand", () => {
252
252
 
253
253
  const output = writes.join("");
254
254
  expect(output).toContain("env:");
255
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: GitHub Actions `${{ ... }}` syntax in plain string
255
256
  expect(output).toContain("API_KEY: ${{ secrets.API_KEY }}");
256
257
  expect(output).toContain(
258
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: GitHub Actions `${{ ... }}` syntax in plain string
257
259
  "GITHUB_CLIENT_ID: ${{ secrets.APP_GITHUB_CLIENT_ID }}",
258
260
  );
259
261
  });
@@ -555,7 +555,7 @@ export class CliProvider {
555
555
  const parsed = this.parseFlags(argv, flagDefs);
556
556
 
557
557
  // Remove the mode flag from parsed result (it's handled separately)
558
- delete parsed.__mode__;
558
+ parsed.__mode__ = undefined;
559
559
 
560
560
  // apply manually defaults for optional properties that have defaults
561
561
  for (const [key, value] of Object.entries(schema.properties)) {
@@ -8,7 +8,9 @@ export type Service<T extends object = any> =
8
8
  | AbstractClass<T>
9
9
  | RunFunction<T>;
10
10
 
11
- export type RunFunction<T extends object = any> = (...args: any[]) => T | void;
11
+ export type RunFunction<T extends object = any> = (
12
+ ...args: any[]
13
+ ) => T | undefined;
12
14
 
13
15
  export type InstantiableClass<T extends object = any> = new (
14
16
  ...args: any[]
@@ -447,7 +447,7 @@ export class TypeProvider {
447
447
  /**
448
448
  * Create a schema that maps all properties of an object schema to nullable.
449
449
  */
450
- public nullify = <T extends TSchema>(schema: T, options?: TObjectOptions) =>
450
+ public nullify = (schema: TSchema, options?: TObjectOptions): TSchema =>
451
451
  Type.Mapped(
452
452
  Type.Identifier("K"),
453
453
  Type.KeyOf(schema),
@@ -146,7 +146,7 @@ export class Logger implements LoggerInterface {
146
146
  }
147
147
 
148
148
  let _data: object | Error | undefined;
149
- if (typeof data === "object" && !!data) {
149
+ if (typeof data === "object" && data) {
150
150
  _data = data;
151
151
  } else if (typeof message === "object" && message) {
152
152
  _data = message;
@@ -364,7 +364,7 @@ describe("$resource primitive", () => {
364
364
  uri: "protected://resource",
365
365
  handler: async ({ context }) => {
366
366
  const authHeader = context?.headers?.authorization;
367
- if (!authHeader || !authHeader.toString().startsWith("Bearer ")) {
367
+ if (!authHeader?.toString().startsWith("Bearer ")) {
368
368
  throw new Error("Unauthorized");
369
369
  }
370
370
  return { text: "Secret content" };
@@ -379,7 +379,7 @@ describe("$tool primitive", () => {
379
379
  },
380
380
  handler: async ({ context }) => {
381
381
  const authHeader = context?.headers?.authorization;
382
- if (!authHeader || !authHeader.toString().startsWith("Bearer ")) {
382
+ if (!authHeader?.toString().startsWith("Bearer ")) {
383
383
  throw new Error("Unauthorized");
384
384
  }
385
385
  return "Access granted";
@@ -751,7 +751,7 @@ describe("McpServerProvider", () => {
751
751
  schema: { result: t.text() },
752
752
  handler: async ({ context }) => {
753
753
  const auth = context?.headers?.authorization;
754
- if (!auth || !auth.toString().startsWith("Bearer ")) {
754
+ if (!auth?.toString().startsWith("Bearer ")) {
755
755
  throw new Error("Unauthorized");
756
756
  }
757
757
  return "Access granted";
@@ -1014,6 +1014,7 @@ export const testTransactionThrowsWhenUnsupported = async (alepha: Alepha) => {
1014
1014
  original,
1015
1015
  );
1016
1016
  } else {
1017
+ // biome-ignore lint/performance/noDelete: setting to undefined fails because the prototype has a getter
1017
1018
  delete provider.supportsTransactions;
1018
1019
  }
1019
1020
  }
@@ -1,8 +1,8 @@
1
- import { type Alepha, AlephaError, t } from "alepha";
1
+ import { type Alepha, t } from "alepha";
2
2
  import { sql } from "drizzle-orm";
3
3
  import { expect } from "vitest";
4
4
  import { PG_GENERATED } from "../core/constants/PG_SYMBOLS.ts";
5
- import { $entity, $repository, $view, db, pgAttr } from "../core/index.ts";
5
+ import { $entity, $repository, db, pgAttr } from "../core/index.ts";
6
6
 
7
7
  // ============================================================================
8
8
  // Shared entity definitions
@@ -425,68 +425,3 @@ export const testQueryCacheCustomKey = async (alepha: Alepha) => {
425
425
  );
426
426
  expect(cached).toHaveLength(1);
427
427
  };
428
-
429
- // ============================================================================
430
- // Feature 8: Database Views
431
- // ============================================================================
432
-
433
- export const testViewReadOnly = async (alepha: Alepha) => {
434
- // Create the underlying table
435
- const itemEntity = $entity({
436
- name: "view_items",
437
- schema: t.object({
438
- id: db.primaryKey(),
439
- name: t.text(),
440
- price: t.number(),
441
- }),
442
- });
443
-
444
- // Create a view
445
- const itemView = $view({
446
- name: "view_items_summary",
447
- schema: t.object({
448
- id: t.integer(),
449
- name: t.text(),
450
- price: t.number(),
451
- }),
452
- query: sql`SELECT id, name, price FROM view_items`,
453
- });
454
-
455
- class App {
456
- items = $repository(itemEntity);
457
- summary = $repository(itemView);
458
- }
459
-
460
- const app = alepha.inject(App);
461
- await alepha.start();
462
-
463
- // Verify the repository detects it's a view
464
- expect(app.summary.isReadOnly).toBe(true);
465
- expect(app.items.isReadOnly).toBe(false);
466
-
467
- // Write operations should throw on views
468
- await expect(
469
- app.summary.create({ id: 1, name: "test", price: 10 } as any),
470
- ).rejects.toThrow(AlephaError);
471
- };
472
-
473
- export const testViewRefreshThrowsForNonMaterialized = async (
474
- alepha: Alepha,
475
- ) => {
476
- const view = $view({
477
- name: "non_mat_view",
478
- schema: t.object({
479
- id: t.integer(),
480
- }),
481
- query: sql`SELECT 1 as id`,
482
- });
483
-
484
- class App {
485
- repo = $repository(view);
486
- }
487
-
488
- const app = alepha.inject(App);
489
- await alepha.start();
490
-
491
- await expect(app.repo.refresh()).rejects.toThrow(AlephaError);
492
- };
@@ -14,8 +14,6 @@ import {
14
14
  testPartialIndex,
15
15
  testQueryCache,
16
16
  testQueryCacheCustomKey,
17
- testViewReadOnly,
18
- testViewRefreshThrowsForNonMaterialized,
19
17
  } from "./orm-next-tests.ts";
20
18
 
21
19
  const sqlite = () =>
@@ -119,22 +117,3 @@ describe("query caching", () => {
119
117
  await testQueryCacheCustomKey(postgres());
120
118
  });
121
119
  });
122
-
123
- // =============================================================================
124
- // Feature 8: Database Views
125
- // =============================================================================
126
-
127
- describe("database views", () => {
128
- it("should block writes on view repositories (sqlite)", async () => {
129
- await testViewReadOnly(sqlite());
130
- });
131
- it("should block writes on view repositories (postgres)", async () => {
132
- await testViewReadOnly(postgres());
133
- });
134
- it("should throw on refresh for non-materialized view (sqlite)", async () => {
135
- await testViewRefreshThrowsForNonMaterialized(sqlite());
136
- });
137
- it("should throw on refresh for non-materialized view (postgres)", async () => {
138
- await testViewRefreshThrowsForNonMaterialized(postgres());
139
- });
140
- });
@@ -12,13 +12,11 @@ export * from "./errors/DbEntityNotFoundError.ts";
12
12
  export * from "./errors/DbForeignKeyError.ts";
13
13
  export * from "./errors/DbNotNullError.ts";
14
14
  export * from "./errors/DbTableNotFoundError.ts";
15
- export * from "./helpers/parseQueryString.ts";
16
15
  export * from "./helpers/pgAttr.ts";
17
16
  export * from "./interfaces/AggregateQuery.ts";
18
17
  export * from "./interfaces/FilterOperators.ts";
19
18
  export * from "./interfaces/PgQuery.ts";
20
19
  export * from "./interfaces/PgQueryWhere.ts";
21
20
  export * from "./primitives/$entity.ts";
22
- export * from "./primitives/$view.ts";
23
21
  export * from "./providers/DatabaseTypeProvider.ts";
24
22
  export * from "./schemas/legacyIdSchema.ts";
@@ -4,7 +4,6 @@ import type { PgTransaction } from "drizzle-orm/pg-core";
4
4
  import { DbMigrationMode } from "./modes/DbMigrationMode.ts";
5
5
  import { $entity } from "./primitives/$entity.ts";
6
6
  import { $sequence } from "./primitives/$sequence.ts";
7
- import { $view } from "./primitives/$view.ts";
8
7
  import { DrizzleKitProvider } from "./providers/DrizzleKitProvider.ts";
9
8
  import { BunSqliteProvider } from "./providers/drivers/BunSqliteProvider.ts";
10
9
  import { CloudflareD1Provider } from "./providers/drivers/CloudflareD1Provider.ts";
@@ -102,7 +101,7 @@ export const SqliteProvider = NodeSqliteProvider;
102
101
 
103
102
  export const AlephaOrm = $module({
104
103
  name: "alepha.orm",
105
- primitives: [$sequence, $entity, $view],
104
+ primitives: [$sequence, $entity],
106
105
  imports: [AlephaDateTime],
107
106
  services: [
108
107
  SqliteModelBuilder,
@@ -2,17 +2,14 @@ import { $context, $inject, type TObject } from "alepha";
2
2
  import { RepositoryProvider } from "../providers/RepositoryProvider.ts";
3
3
  import type { Repository } from "../services/Repository.ts";
4
4
  import type { EntityPrimitive } from "./$entity.ts";
5
- import type { ViewPrimitive } from "./$view.ts";
6
5
 
7
6
  /**
8
- * Get the repository for the given entity or view.
7
+ * Get the repository for the given entity.
9
8
  */
10
9
  export const $repository = <T extends TObject>(
11
- entity: EntityPrimitive<T> | ViewPrimitive<T>,
10
+ entity: EntityPrimitive<T>,
12
11
  ): Repository<T> => {
13
12
  const { alepha } = $context();
14
13
  const repositoryProvider = alepha.inject(RepositoryProvider);
15
- return $inject(
16
- repositoryProvider.createClassRepository(entity as EntityPrimitive<T>),
17
- );
14
+ return $inject(repositoryProvider.createClassRepository(entity));
18
15
  };
@@ -22,7 +22,6 @@ import type {
22
22
  SchemaToTableConfig,
23
23
  } from "../../primitives/$entity.ts";
24
24
  import type { SequencePrimitive } from "../../primitives/$sequence.ts";
25
- import type { ViewPrimitive } from "../../primitives/$view.ts";
26
25
  import { databaseEnvSchema } from "../../schemas/databaseEnvSchema.ts";
27
26
  import type { ModelBuilder } from "../../services/ModelBuilder.ts";
28
27
  import { DrizzleKitProvider } from "../DrizzleKitProvider.ts";
@@ -156,10 +155,6 @@ export abstract class DatabaseProvider {
156
155
  this.builder.buildTable(entity, this);
157
156
  }
158
157
 
159
- public registerView(view: ViewPrimitive) {
160
- this.builder.buildView(view, this);
161
- }
162
-
163
158
  public registerSequence(sequence: SequencePrimitive) {
164
159
  this.sequencePrimitives.push(sequence);
165
160
  this.builder.buildSequence(sequence, this);
@@ -1,5 +1,4 @@
1
1
  import { mkdir } from "node:fs/promises";
2
- import { createRequire } from "node:module";
3
2
  import { dirname } from "node:path";
4
3
  import type { DatabaseSync } from "node:sqlite";
5
4
  import {
@@ -12,7 +11,11 @@ import {
12
11
  type Static,
13
12
  t,
14
13
  } from "alepha";
14
+ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
15
+ import { BetterSQLiteSession } from "drizzle-orm/better-sqlite3/session";
15
16
  import type { PgDatabase } from "drizzle-orm/pg-core";
17
+ import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core/db";
18
+ import { SQLiteSyncDialect } from "drizzle-orm/sqlite-core/dialect";
16
19
  import { databaseEnvSchema } from "../../schemas/databaseEnvSchema.ts";
17
20
  import { SqliteModelBuilder } from "../../services/SqliteModelBuilder.ts";
18
21
  import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";
@@ -162,7 +165,7 @@ export class NodeSqliteProvider extends DatabaseProvider {
162
165
  protected readonly onStart = $hook({
163
166
  on: "start",
164
167
  handler: async () => {
165
- const { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
168
+ const { DatabaseSync } = await import("node:sqlite");
166
169
 
167
170
  const filepath = this.url.replace("sqlite://", "").replace("sqlite:", "");
168
171
 
@@ -323,13 +326,6 @@ export class NodeSqliteProvider extends DatabaseProvider {
323
326
  protected initDrizzle(): void {
324
327
  this.shimDatabaseSync();
325
328
 
326
- const require = createRequire(import.meta.url);
327
- const {
328
- BetterSQLiteSession,
329
- } = require("drizzle-orm/better-sqlite3/session");
330
- const { SQLiteSyncDialect } = require("drizzle-orm/sqlite-core/dialect");
331
- const { BaseSQLiteDatabase } = require("drizzle-orm/sqlite-core/db");
332
-
333
329
  const dialect = new SQLiteSyncDialect();
334
330
  const session = new BetterSQLiteSession(this.sqlite, dialect, undefined, {
335
331
  logger: {
@@ -339,14 +335,16 @@ export class NodeSqliteProvider extends DatabaseProvider {
339
335
  },
340
336
  });
341
337
 
342
- this.drizzleDb = new BaseSQLiteDatabase("sync", dialect, session);
338
+ this.drizzleDb = new BaseSQLiteDatabase(
339
+ "sync",
340
+ dialect,
341
+ session,
342
+ undefined,
343
+ );
343
344
  this.log.debug("Using node:sqlite with sync driver");
344
345
  }
345
346
 
346
347
  protected async executeMigrations(migrationsFolder: string): Promise<void> {
347
- const { migrate } = createRequire(import.meta.url)(
348
- "drizzle-orm/better-sqlite3/migrator",
349
- );
350
348
  migrate(this.drizzleDb, { migrationsFolder });
351
349
  }
352
350
  }