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.
- package/README.md +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +16 -75
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +4 -65
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +207 -5184
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2 -4
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.js +5 -1
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +5 -1
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +217 -11647
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +706 -42
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.js +7 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +41 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +47 -0
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.js +15 -0
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +2 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +2 -10522
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +4 -8085
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +3 -33554
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +5 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +1 -361
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +14 -406
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +96 -5117
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +23 -419
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +2 -613
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +78 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +22 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +102 -4
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +1 -411
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +13 -12293
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.js +3 -0
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -83
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -391
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +2 -391
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +2 -325
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +3 -1362
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1 -1054
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -1224
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +19 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +1 -514
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4 -4356
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.js +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +21 -0
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +21 -0
- package/dist/websocket/index.js.map +1 -1
- package/package.json +18 -15
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/cli/core/__tests__/init.spec.ts +208 -0
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +298 -20
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/TypeProvider.ts +1 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +2 -2
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- 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
|
|
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
|
-
|
|
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> = (
|
|
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 =
|
|
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" &&
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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";
|
package/src/orm/core/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
7
|
+
* Get the repository for the given entity.
|
|
9
8
|
*/
|
|
10
9
|
export const $repository = <T extends TObject>(
|
|
11
|
-
entity: EntityPrimitive<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 } =
|
|
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(
|
|
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
|
}
|