alepha 0.15.2 → 0.15.4
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 +68 -80
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +8 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +170 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +3 -0
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +1 -0
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.js +1 -0
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +260 -260
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +10 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +12 -1
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +18 -2
- package/dist/api/users/index.js.map +1 -1
- package/dist/batch/index.d.ts +4 -4
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +7 -2
- package/dist/bucket/index.js.map +1 -1
- package/dist/cli/index.d.ts +196 -74
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +234 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +10 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +67 -13
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +28 -21
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +28 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +28 -21
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.d.ts +21 -13
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +10561 -4
- package/dist/email/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +6 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +9 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -5
- package/dist/orm/index.bun.js +32 -16
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +4 -1
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +34 -22
- package/dist/orm/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +2 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js +2 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +3 -3
- package/dist/react/router/index.browser.js +9 -15
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +305 -407
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +581 -781
- package/dist/react/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +13 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +42 -4
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +42 -42
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -167
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +1 -0
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/links/index.d.ts +39 -39
- package/dist/server/links/index.js +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.js +7 -2
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +8 -0
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +7 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +8 -0
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -2
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +734 -12
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +8 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +7 -2
- package/dist/system/index.js.map +1 -1
- package/dist/vite/index.d.ts +3 -2
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +42 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +34 -34
- package/dist/websocket/index.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/api/audits/controllers/AdminAuditController.ts +8 -0
- package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
- package/src/api/jobs/controllers/AdminJobController.ts +3 -0
- package/src/api/logs/TODO.md +13 -10
- package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
- package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
- package/src/api/users/controllers/AdminIdentityController.ts +3 -0
- package/src/api/users/controllers/AdminSessionController.ts +3 -0
- package/src/api/users/controllers/AdminUserController.ts +5 -0
- package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -32
- package/src/cli/commands/db.ts +5 -7
- package/src/cli/commands/init.spec.ts +50 -6
- package/src/cli/commands/init.ts +28 -5
- package/src/cli/providers/ViteDevServerProvider.ts +31 -9
- package/src/cli/services/AlephaCliUtils.ts +16 -0
- package/src/cli/services/PackageManagerUtils.ts +2 -0
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +28 -6
- package/src/cli/templates/agentMd.ts +6 -1
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +18 -4
- package/src/cli/templates/webAppRouterTs.ts +25 -1
- package/src/cli/templates/webHelloComponentTsx.ts +15 -5
- package/src/command/helpers/Runner.spec.ts +135 -0
- package/src/command/helpers/Runner.ts +4 -1
- package/src/command/providers/CliProvider.spec.ts +325 -0
- package/src/command/providers/CliProvider.ts +117 -7
- package/src/core/Alepha.ts +32 -25
- package/src/email/index.workerd.ts +36 -0
- package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
- package/src/lock/core/primitives/$lock.ts +13 -1
- package/src/orm/index.bun.ts +1 -1
- package/src/orm/index.ts +2 -6
- package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/react/auth/services/ReactAuth.ts +3 -1
- package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
- package/src/react/router/hooks/useActive.ts +1 -1
- package/src/react/router/hooks/useRouter.ts +1 -1
- package/src/react/router/index.ts +4 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
- package/src/react/router/primitives/$page.spec.tsx +0 -32
- package/src/react/router/primitives/$page.ts +6 -14
- package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
- package/src/react/router/providers/ReactPageProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.ts +21 -82
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
- package/src/react/router/providers/SSRManifestProvider.ts +7 -0
- package/src/react/router/services/ReactRouter.ts +13 -13
- package/src/scheduler/index.workerd.ts +43 -0
- package/src/scheduler/providers/CronProvider.ts +53 -6
- package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
- package/src/security/providers/ServerSecurityProvider.ts +30 -22
- package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
- package/src/server/links/providers/ServerLinksProvider.ts +1 -1
- package/src/system/index.browser.ts +25 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/system/providers/FileSystemProvider.ts +8 -0
- package/src/system/providers/NodeFileSystemProvider.ts +11 -2
- package/src/vite/tasks/buildServer.ts +2 -12
- package/src/vite/tasks/generateCloudflare.ts +47 -8
- package/src/vite/tasks/generateDocker.ts +4 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha, t } from "alepha";
|
|
3
|
+
import { $issuer, AlephaSecurity } from "alepha/security";
|
|
4
|
+
import { $action, ServerProvider } from "alepha/server";
|
|
5
|
+
import { describe, it } from "vitest";
|
|
6
|
+
import { LinkProvider, ServerLinksProvider } from "../index.ts";
|
|
7
|
+
|
|
8
|
+
describe("ServerLinksProvider", () => {
|
|
9
|
+
describe("secured field in links", () => {
|
|
10
|
+
it("should set secured=undefined for public actions (no secure option)", async ({
|
|
11
|
+
expect,
|
|
12
|
+
}) => {
|
|
13
|
+
class App {
|
|
14
|
+
publicAction = $action({
|
|
15
|
+
handler: () => "PUBLIC",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const alepha = Alepha.create().with(App).with(ServerLinksProvider);
|
|
20
|
+
await alepha.start();
|
|
21
|
+
|
|
22
|
+
const links = alepha.inject(LinkProvider).getServerLinks();
|
|
23
|
+
const link = links.find((l) => l.name === "publicAction");
|
|
24
|
+
|
|
25
|
+
expect(link).toBeDefined();
|
|
26
|
+
expect(link?.secured).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should set secured=false for explicitly public actions", async ({
|
|
30
|
+
expect,
|
|
31
|
+
}) => {
|
|
32
|
+
class App {
|
|
33
|
+
publicAction = $action({
|
|
34
|
+
secure: false,
|
|
35
|
+
handler: () => "PUBLIC",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const alepha = Alepha.create().with(App).with(ServerLinksProvider);
|
|
40
|
+
await alepha.start();
|
|
41
|
+
|
|
42
|
+
const links = alepha.inject(LinkProvider).getServerLinks();
|
|
43
|
+
const link = links.find((l) => l.name === "publicAction");
|
|
44
|
+
|
|
45
|
+
expect(link).toBeDefined();
|
|
46
|
+
expect(link?.secured).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should set secured=true for secured actions", async ({ expect }) => {
|
|
50
|
+
class App {
|
|
51
|
+
securedAction = $action({
|
|
52
|
+
secure: true,
|
|
53
|
+
handler: () => "SECURED",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const alepha = Alepha.create().with(App).with(ServerLinksProvider);
|
|
58
|
+
await alepha.start();
|
|
59
|
+
|
|
60
|
+
const links = alepha.inject(LinkProvider).getServerLinks();
|
|
61
|
+
const link = links.find((l) => l.name === "securedAction");
|
|
62
|
+
|
|
63
|
+
expect(link).toBeDefined();
|
|
64
|
+
expect(link?.secured).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should set secured to object for realm-secured actions", async ({
|
|
68
|
+
expect,
|
|
69
|
+
}) => {
|
|
70
|
+
class App {
|
|
71
|
+
realmAction = $action({
|
|
72
|
+
secure: { realm: "admin" },
|
|
73
|
+
handler: () => "REALM",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const alepha = Alepha.create().with(App).with(ServerLinksProvider);
|
|
78
|
+
await alepha.start();
|
|
79
|
+
|
|
80
|
+
const links = alepha.inject(LinkProvider).getServerLinks();
|
|
81
|
+
const link = links.find((l) => l.name === "realmAction");
|
|
82
|
+
|
|
83
|
+
expect(link).toBeDefined();
|
|
84
|
+
expect(link?.secured).toEqual({ realm: "admin" });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("/_links endpoint with security", () => {
|
|
89
|
+
it("should return public actions to unauthenticated users", async ({
|
|
90
|
+
expect,
|
|
91
|
+
}) => {
|
|
92
|
+
class App {
|
|
93
|
+
publicAction = $action({
|
|
94
|
+
schema: { response: t.text() },
|
|
95
|
+
handler: () => "PUBLIC",
|
|
96
|
+
});
|
|
97
|
+
issuer = $issuer({
|
|
98
|
+
secret: "test",
|
|
99
|
+
roles: [{ name: "user", permissions: [{ name: "*" }] }],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const alepha = Alepha.create()
|
|
104
|
+
.with(App)
|
|
105
|
+
.with(ServerLinksProvider)
|
|
106
|
+
.with(AlephaSecurity);
|
|
107
|
+
await alepha.start();
|
|
108
|
+
|
|
109
|
+
const res = await fetch(
|
|
110
|
+
`${alepha.inject(ServerProvider).hostname}/api/_links`,
|
|
111
|
+
);
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
|
|
114
|
+
expect(data.links).toContainEqual(
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
name: "publicAction",
|
|
117
|
+
path: "/publicAction",
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should NOT return secured actions to unauthenticated users", async ({
|
|
123
|
+
expect,
|
|
124
|
+
}) => {
|
|
125
|
+
class App {
|
|
126
|
+
securedAction = $action({
|
|
127
|
+
secure: true,
|
|
128
|
+
schema: { response: t.text() },
|
|
129
|
+
handler: () => "SECURED",
|
|
130
|
+
});
|
|
131
|
+
issuer = $issuer({
|
|
132
|
+
secret: "test",
|
|
133
|
+
roles: [{ name: "user", permissions: [{ name: "*" }] }],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const alepha = Alepha.create()
|
|
138
|
+
.with(App)
|
|
139
|
+
.with(ServerLinksProvider)
|
|
140
|
+
.with(AlephaSecurity);
|
|
141
|
+
await alepha.start();
|
|
142
|
+
|
|
143
|
+
const res = await fetch(
|
|
144
|
+
`${alepha.inject(ServerProvider).hostname}/api/_links`,
|
|
145
|
+
);
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
|
|
148
|
+
expect(data.links).not.toContainEqual(
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
name: "securedAction",
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should return secured actions to authenticated users with permissions", async ({
|
|
156
|
+
expect,
|
|
157
|
+
}) => {
|
|
158
|
+
class App {
|
|
159
|
+
securedAction = $action({
|
|
160
|
+
secure: true,
|
|
161
|
+
schema: { response: t.text() },
|
|
162
|
+
handler: () => "SECURED",
|
|
163
|
+
});
|
|
164
|
+
issuer = $issuer({
|
|
165
|
+
secret: "test",
|
|
166
|
+
roles: [{ name: "user", permissions: [{ name: "*" }] }],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const alepha = Alepha.create()
|
|
171
|
+
.with(App)
|
|
172
|
+
.with(ServerLinksProvider)
|
|
173
|
+
.with(AlephaSecurity);
|
|
174
|
+
await alepha.start();
|
|
175
|
+
|
|
176
|
+
// Use HttpClient to get a token automatically in test mode
|
|
177
|
+
const app = alepha.inject(App);
|
|
178
|
+
const { data } = await app.securedAction.fetch(
|
|
179
|
+
{},
|
|
180
|
+
{
|
|
181
|
+
user: { id: randomUUID(), roles: ["user"] },
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
expect(data).toBe("SECURED");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should return both public and secured actions when user is authenticated", async ({
|
|
188
|
+
expect,
|
|
189
|
+
}) => {
|
|
190
|
+
class App {
|
|
191
|
+
publicAction = $action({
|
|
192
|
+
schema: { response: t.text() },
|
|
193
|
+
handler: () => "PUBLIC",
|
|
194
|
+
});
|
|
195
|
+
securedAction = $action({
|
|
196
|
+
secure: true,
|
|
197
|
+
schema: { response: t.text() },
|
|
198
|
+
handler: () => "SECURED",
|
|
199
|
+
});
|
|
200
|
+
issuer = $issuer({
|
|
201
|
+
secret: "test",
|
|
202
|
+
roles: [{ name: "user", permissions: [{ name: "*" }] }],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const alepha = Alepha.create()
|
|
207
|
+
.with(App)
|
|
208
|
+
.with(ServerLinksProvider)
|
|
209
|
+
.with(AlephaSecurity);
|
|
210
|
+
await alepha.start();
|
|
211
|
+
|
|
212
|
+
const linksProvider = alepha.inject(ServerLinksProvider);
|
|
213
|
+
const user = { id: randomUUID(), roles: ["user"] };
|
|
214
|
+
|
|
215
|
+
const { links } = await linksProvider.getUserApiLinks({ user });
|
|
216
|
+
|
|
217
|
+
expect(links).toContainEqual(
|
|
218
|
+
expect.objectContaining({ name: "publicAction" }),
|
|
219
|
+
);
|
|
220
|
+
expect(links).toContainEqual(
|
|
221
|
+
expect.objectContaining({ name: "securedAction" }),
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should filter secured actions based on user permissions", async ({
|
|
226
|
+
expect,
|
|
227
|
+
}) => {
|
|
228
|
+
class App {
|
|
229
|
+
adminOnly = $action({
|
|
230
|
+
secure: true,
|
|
231
|
+
group: "admin",
|
|
232
|
+
schema: { response: t.text() },
|
|
233
|
+
handler: () => "ADMIN",
|
|
234
|
+
});
|
|
235
|
+
userAction = $action({
|
|
236
|
+
secure: true,
|
|
237
|
+
group: "user",
|
|
238
|
+
schema: { response: t.text() },
|
|
239
|
+
handler: () => "USER",
|
|
240
|
+
});
|
|
241
|
+
issuer = $issuer({
|
|
242
|
+
secret: "test",
|
|
243
|
+
roles: [
|
|
244
|
+
{ name: "admin", permissions: [{ name: "*" }] },
|
|
245
|
+
{ name: "user", permissions: [{ name: "user:*" }] },
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const alepha = Alepha.create()
|
|
251
|
+
.with(App)
|
|
252
|
+
.with(ServerLinksProvider)
|
|
253
|
+
.with(AlephaSecurity);
|
|
254
|
+
await alepha.start();
|
|
255
|
+
|
|
256
|
+
const linksProvider = alepha.inject(ServerLinksProvider);
|
|
257
|
+
|
|
258
|
+
// User with "user" role should only see userAction
|
|
259
|
+
const userLinks = await linksProvider.getUserApiLinks({
|
|
260
|
+
user: { id: randomUUID(), roles: ["user"] },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(userLinks.links).toContainEqual(
|
|
264
|
+
expect.objectContaining({ name: "userAction" }),
|
|
265
|
+
);
|
|
266
|
+
expect(userLinks.links).not.toContainEqual(
|
|
267
|
+
expect.objectContaining({ name: "adminOnly" }),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// User with "admin" role should see both
|
|
271
|
+
const adminLinks = await linksProvider.getUserApiLinks({
|
|
272
|
+
user: { id: randomUUID(), roles: ["admin"] },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(adminLinks.links).toContainEqual(
|
|
276
|
+
expect.objectContaining({ name: "userAction" }),
|
|
277
|
+
);
|
|
278
|
+
expect(adminLinks.links).toContainEqual(
|
|
279
|
+
expect.objectContaining({ name: "adminOnly" }),
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("mixed public and secured actions", () => {
|
|
285
|
+
it("should correctly differentiate public from secured in server links", async ({
|
|
286
|
+
expect,
|
|
287
|
+
}) => {
|
|
288
|
+
class App {
|
|
289
|
+
getUsers = $action({
|
|
290
|
+
path: "/users",
|
|
291
|
+
schema: { response: t.array(t.text()) },
|
|
292
|
+
handler: () => ["user1", "user2"],
|
|
293
|
+
});
|
|
294
|
+
createUser = $action({
|
|
295
|
+
path: "/users",
|
|
296
|
+
secure: true,
|
|
297
|
+
schema: {
|
|
298
|
+
body: t.object({ name: t.text() }),
|
|
299
|
+
response: t.text(),
|
|
300
|
+
},
|
|
301
|
+
handler: ({ body }) => body.name,
|
|
302
|
+
});
|
|
303
|
+
deleteUser = $action({
|
|
304
|
+
method: "DELETE",
|
|
305
|
+
path: "/users/:id",
|
|
306
|
+
secure: true,
|
|
307
|
+
schema: {
|
|
308
|
+
params: t.object({ id: t.text() }),
|
|
309
|
+
response: t.void(),
|
|
310
|
+
},
|
|
311
|
+
handler: () => {},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const alepha = Alepha.create().with(App).with(ServerLinksProvider);
|
|
316
|
+
await alepha.start();
|
|
317
|
+
|
|
318
|
+
const links = alepha.inject(LinkProvider).getServerLinks();
|
|
319
|
+
|
|
320
|
+
const getUsers = links.find((l) => l.name === "getUsers");
|
|
321
|
+
const createUser = links.find((l) => l.name === "createUser");
|
|
322
|
+
const deleteUser = links.find((l) => l.name === "deleteUser");
|
|
323
|
+
|
|
324
|
+
// getUsers is public (no secure option)
|
|
325
|
+
expect(getUsers?.secured).toBeUndefined();
|
|
326
|
+
|
|
327
|
+
// createUser and deleteUser are secured
|
|
328
|
+
expect(createUser?.secured).toBe(true);
|
|
329
|
+
expect(deleteUser?.secured).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
@@ -48,7 +48,7 @@ export class ServerLinksProvider {
|
|
|
48
48
|
group: action.group,
|
|
49
49
|
schema: action.options.schema,
|
|
50
50
|
requestBodyType: action.getBodyContentType(),
|
|
51
|
-
secured: action.options.secure
|
|
51
|
+
secured: action.options.secure,
|
|
52
52
|
method: action.method === "GET" ? undefined : action.method,
|
|
53
53
|
prefix: action.prefix,
|
|
54
54
|
path: action.path,
|
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import { $module } from "alepha";
|
|
2
|
+
import { FileSystemProvider } from "./providers/FileSystemProvider.ts";
|
|
3
|
+
import { MemoryFileSystemProvider } from "./providers/MemoryFileSystemProvider.ts";
|
|
4
|
+
import { MemoryShellProvider } from "./providers/MemoryShellProvider.ts";
|
|
5
|
+
import { ShellProvider } from "./providers/ShellProvider.ts";
|
|
6
|
+
import { FileDetector } from "./services/FileDetector.ts";
|
|
2
7
|
|
|
3
8
|
export * from "./errors/FileError.ts";
|
|
4
9
|
export * from "./providers/FileSystemProvider.ts";
|
|
5
10
|
export * from "./providers/MemoryFileSystemProvider.ts";
|
|
6
11
|
export * from "./providers/MemoryShellProvider.ts";
|
|
7
12
|
export * from "./providers/ShellProvider.ts";
|
|
13
|
+
export * from "./services/FileDetector.ts";
|
|
8
14
|
|
|
9
15
|
export const AlephaSystem = $module({
|
|
10
16
|
name: "alepha.system",
|
|
17
|
+
services: [
|
|
18
|
+
FileDetector,
|
|
19
|
+
FileSystemProvider,
|
|
20
|
+
MemoryFileSystemProvider,
|
|
21
|
+
ShellProvider,
|
|
22
|
+
MemoryShellProvider,
|
|
23
|
+
],
|
|
24
|
+
register: (alepha) =>
|
|
25
|
+
alepha
|
|
26
|
+
.with({
|
|
27
|
+
optional: true,
|
|
28
|
+
provide: FileSystemProvider,
|
|
29
|
+
use: MemoryFileSystemProvider,
|
|
30
|
+
})
|
|
31
|
+
.with({
|
|
32
|
+
optional: true,
|
|
33
|
+
provide: ShellProvider,
|
|
34
|
+
use: MemoryShellProvider,
|
|
35
|
+
}),
|
|
11
36
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index.browser.ts";
|
|
@@ -196,8 +196,16 @@ export interface CpOptions {
|
|
|
196
196
|
export interface MkdirOptions {
|
|
197
197
|
/**
|
|
198
198
|
* If true, creates parent directories as needed
|
|
199
|
+
*
|
|
200
|
+
* @default true
|
|
199
201
|
*/
|
|
200
202
|
recursive?: boolean;
|
|
203
|
+
/**
|
|
204
|
+
* If true, does not throw an error if the directory already exists
|
|
205
|
+
*
|
|
206
|
+
* @default true
|
|
207
|
+
*/
|
|
208
|
+
force?: boolean;
|
|
201
209
|
/**
|
|
202
210
|
* File mode (permission and sticky bits)
|
|
203
211
|
*/
|
|
@@ -291,8 +291,17 @@ export class NodeFileSystemProvider implements FileSystemProvider {
|
|
|
291
291
|
* await fs.mkdir("/tmp/mydir", { mode: 0o755 });
|
|
292
292
|
* ```
|
|
293
293
|
*/
|
|
294
|
-
async mkdir(path: string, options
|
|
295
|
-
|
|
294
|
+
async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {
|
|
295
|
+
const p = fsMkdir(path, {
|
|
296
|
+
recursive: options.recursive ?? true,
|
|
297
|
+
mode: options.mode,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (options.force === false) {
|
|
301
|
+
await p;
|
|
302
|
+
} else {
|
|
303
|
+
await p.catch(() => {});
|
|
304
|
+
}
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
/**
|
|
@@ -23,7 +23,7 @@ export interface BuildServerOptions {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Optional client directory name (relative to distDir).
|
|
26
|
-
* If provided, the
|
|
26
|
+
* If provided, the SSR manifest will be embedded in the server output.
|
|
27
27
|
*/
|
|
28
28
|
clientDir?: string;
|
|
29
29
|
|
|
@@ -167,16 +167,6 @@ export async function buildServer(
|
|
|
167
167
|
|
|
168
168
|
const entryFile = extractEntryFromBundle(opts.entry, result);
|
|
169
169
|
|
|
170
|
-
// Embed client template if client was built
|
|
171
|
-
let template = "";
|
|
172
|
-
if (opts.clientDir) {
|
|
173
|
-
const index = await readFile(
|
|
174
|
-
`${opts.distDir}/${opts.clientDir}/index.html`,
|
|
175
|
-
"utf-8",
|
|
176
|
-
);
|
|
177
|
-
template = `__alepha.set("alepha.react.server.template", \`${index.replace(/>\s*</g, "><").trim()}\`);\n`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
170
|
// Embed SSR manifests if client was built
|
|
181
171
|
// This bundles all manifest data into index.js for serverless deployments
|
|
182
172
|
let manifest = "";
|
|
@@ -230,7 +220,7 @@ export async function buildServer(
|
|
|
230
220
|
|
|
231
221
|
await writeFile(
|
|
232
222
|
`${opts.distDir}/index.js`,
|
|
233
|
-
`${warning}\n${
|
|
223
|
+
`${warning}\n${manifest}import './server/${entryFile}';\n`.trim(),
|
|
234
224
|
);
|
|
235
225
|
|
|
236
226
|
return { entryFile, manifest: manifestData };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { access, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
|
+
import type { Alepha } from "alepha";
|
|
4
|
+
import type { CronProvider } from "alepha/scheduler";
|
|
5
|
+
import type { WorkerdCronProvider } from "../../scheduler/providers/WorkerdCronProvider.ts";
|
|
3
6
|
|
|
4
7
|
export interface GenerateCloudflareOptions {
|
|
5
8
|
/**
|
|
@@ -13,6 +16,8 @@ export interface GenerateCloudflareOptions {
|
|
|
13
16
|
* Additional Wrangler configuration options to merge into wrangler.jsonc.
|
|
14
17
|
*/
|
|
15
18
|
config?: WranglerConfig;
|
|
19
|
+
|
|
20
|
+
alepha: Alepha;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export interface WranglerConfig {
|
|
@@ -31,7 +36,7 @@ const WARNING_COMMENT =
|
|
|
31
36
|
* - worker.js entry point for Cloudflare Workers
|
|
32
37
|
*/
|
|
33
38
|
export async function generateCloudflare(
|
|
34
|
-
opts: GenerateCloudflareOptions
|
|
39
|
+
opts: GenerateCloudflareOptions,
|
|
35
40
|
): Promise<void> {
|
|
36
41
|
const distDir = opts.distDir ?? "dist";
|
|
37
42
|
const root = process.cwd();
|
|
@@ -40,6 +45,15 @@ export async function generateCloudflare(
|
|
|
40
45
|
.then(() => true)
|
|
41
46
|
.catch(() => false);
|
|
42
47
|
|
|
48
|
+
let workerdCronProvider: CronProvider | undefined;
|
|
49
|
+
try {
|
|
50
|
+
workerdCronProvider = opts.alepha.inject(
|
|
51
|
+
"CronProvider",
|
|
52
|
+
) as WorkerdCronProvider;
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
const crons = workerdCronProvider?.getCronJobs();
|
|
56
|
+
|
|
43
57
|
const wrangler: WranglerConfig = {
|
|
44
58
|
name,
|
|
45
59
|
main: "./main.cloudflare.js",
|
|
@@ -62,12 +76,15 @@ export async function generateCloudflare(
|
|
|
62
76
|
};
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
if (crons && crons.length > 0) {
|
|
80
|
+
const cronExpressions = [...new Set(crons.map((c) => c.expression))];
|
|
81
|
+
wrangler.triggers ??= {};
|
|
82
|
+
wrangler.triggers.crons = cronExpressions;
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
const url = process.env.DATABASE_URL;
|
|
66
|
-
if (url?.startsWith("
|
|
67
|
-
const [name, id] = url
|
|
68
|
-
.replace("cloudflare-d1://", "")
|
|
69
|
-
.replace("cloudflare-d1:", "")
|
|
70
|
-
.split(":");
|
|
86
|
+
if (url?.startsWith("d1:")) {
|
|
87
|
+
const [name, id] = url.replace("d1://", "").replace("d1:", "").split(":");
|
|
71
88
|
wrangler.d1_databases = wrangler.d1_databases || [];
|
|
72
89
|
wrangler.d1_databases.push({
|
|
73
90
|
binding: name,
|
|
@@ -75,7 +92,7 @@ export async function generateCloudflare(
|
|
|
75
92
|
database_id: id,
|
|
76
93
|
});
|
|
77
94
|
wrangler.vars ??= {};
|
|
78
|
-
wrangler.vars.DATABASE_URL = `
|
|
95
|
+
wrangler.vars.DATABASE_URL = `d1://${name}:${id}`;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
await writeFile(
|
|
@@ -102,11 +119,33 @@ export default {
|
|
|
102
119
|
|
|
103
120
|
__alepha.set("cloudflare.env", env);
|
|
104
121
|
|
|
105
|
-
|
|
122
|
+
try {
|
|
123
|
+
await __alepha.start();
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error("Failed to start Alepha for fetch event", err);
|
|
126
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
await __alepha.events.emit("web:request", ctx);
|
|
107
130
|
|
|
108
131
|
return ctx.res;
|
|
109
132
|
},
|
|
133
|
+
|
|
134
|
+
scheduled: async (event, env, ctx) => {
|
|
135
|
+
__alepha.set("cloudflare.env", env);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await __alepha.start();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error("Failed to start Alepha for scheduled event", err);
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await __alepha.events.emit("cloudflare:scheduled", {
|
|
145
|
+
cron: event.cron,
|
|
146
|
+
scheduledTime: event.scheduledTime,
|
|
147
|
+
});
|
|
148
|
+
},
|
|
110
149
|
};
|
|
111
150
|
`.trim();
|
|
112
151
|
|