alepha 0.20.3 → 0.20.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/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/jobs/index.d.ts +14 -14
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/keys/index.d.ts +4 -4
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +8 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +20 -4
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +6 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +5037 -139
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +58 -10
- package/dist/api/users/index.js.map +1 -1
- package/dist/bucket/index.d.ts +77 -107
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +148 -4
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +7 -1
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +26 -0
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +11 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +11 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cli/config/index.d.ts +7 -5
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -3
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +420 -13
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +22 -511
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +4 -8
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +13 -15
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +10 -13
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +18 -15
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +10 -13
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +16 -13
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +27 -3
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -3
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -3
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/datetime/index.d.ts +69 -10
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +135 -13
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/smtp/index.js +10636 -2
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +8085 -4
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +33554 -3
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +30 -2
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +35 -12
- package/dist/lock/core/index.js.map +1 -1
- package/dist/mcp/index.d.ts +238 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +198 -71
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +4 -3
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +4877 -9
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +4 -3
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +608 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/react/core/index.d.ts +102 -1
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +65 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +6 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +7 -7
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +7 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +6 -0
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/router/index.browser.js +20 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +36 -4
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +20 -2
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/chunk-6Ep1yQYe.js +16 -0
- package/dist/react/testing/index.d.ts +411 -1
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +12293 -13
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +195 -1
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +61 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +84 -3
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +390 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +390 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.d.ts +325 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1361 -2
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1054 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1223 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/core/index.browser.js +10 -3
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +28 -5
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4374 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +3 -4
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -5
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +21 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +671 -263
- package/src/api/parameters/services/ParameterProvider.ts +21 -4
- package/src/api/users/__tests__/SessionService.spec.ts +99 -0
- package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/jobs/UserJobs.ts +44 -17
- package/src/api/users/providers/RealmProvider.ts +4 -0
- package/src/api/users/services/SessionService.ts +27 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
- package/src/bucket/index.ts +19 -2
- package/src/bucket/primitives/$bucket.ts +9 -1
- package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
- package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
- package/src/cache/core/index.ts +29 -0
- package/src/cache/core/primitives/$cache.ts +14 -1
- package/src/cli/config/defineConfig.ts +13 -15
- package/src/cli/core/__tests__/init.spec.ts +6 -7
- package/src/cli/core/services/ProjectScaffolder.ts +18 -14
- package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
- package/src/cli/core/templates/agentMd.ts +2 -10
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +3 -3
- package/src/cli/devtools/index.ts +12 -26
- package/src/cli/platform/index.ts +15 -24
- package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
- package/src/cli/vendor/index.ts +14 -23
- package/src/core/Alepha.ts +11 -1
- package/src/core/helpers/ref.ts +18 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/providers/SchemaValidator.ts +9 -1
- package/src/core/providers/TypeProvider.ts +1 -2
- package/src/datetime/REFACTORING.md +118 -0
- package/src/datetime/providers/DateTimeProvider.ts +203 -24
- package/src/lock/core/index.ts +31 -0
- package/src/lock/core/primitives/$lock.ts +14 -1
- package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
- package/src/mcp/helpers/jsonrpc.ts +26 -1
- package/src/mcp/index.ts +10 -5
- package/src/mcp/interfaces/McpTypes.ts +83 -6
- package/src/mcp/primitives/$prompt.ts +18 -1
- package/src/mcp/primitives/$resource.ts +18 -1
- package/src/mcp/primitives/$tool.ts +83 -7
- package/src/mcp/providers/McpServerProvider.ts +74 -16
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
- package/src/orm/REFACTORING.md +330 -0
- package/src/orm/core/primitives/$transactional.ts +11 -0
- package/src/orm/core/schemas/updateSchema.ts +1 -1
- package/src/orm/core/services/PgRelationManager.ts +4 -2
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
- package/src/react/core/hooks/useQuery.ts +153 -0
- package/src/react/core/index.ts +1 -0
- package/src/react/form/services/FormModel.ts +15 -6
- package/src/react/form/services/parseField.ts +8 -0
- package/src/react/i18n/providers/I18nProvider.ts +8 -2
- package/src/react/router/__tests__/$page.spec.tsx +0 -16
- package/src/react/router/__tests__/ssr.spec.tsx +339 -0
- package/src/react/router/primitives/$page.ts +28 -4
- package/src/react/router/providers/ReactPageProvider.ts +27 -9
- package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
- package/src/react/ui/index.ts +6 -0
- package/src/react/ui/services/SchemaControl.ts +209 -0
- package/src/security/primitives/$issuer.ts +6 -3
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
- package/src/server/core/errors/ValidationError.ts +13 -1
- package/src/server/core/primitives/$action.ts +16 -5
- package/src/server/core/providers/ServerRouterProvider.ts +26 -4
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -7
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
- package/src/websocket/services/WebSocketClient.ts +11 -5
- package/src/mcp/transports/SseMcpTransport.ts +0 -182
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { R2Bucket } from "@cloudflare/workers-types";
|
|
1
2
|
import {
|
|
2
3
|
$env,
|
|
3
4
|
$hook,
|
|
@@ -14,142 +15,6 @@ import type { FileStorageProvider } from "./FileStorageProvider.ts";
|
|
|
14
15
|
|
|
15
16
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
* R2Bucket interface matching Cloudflare's R2 API.
|
|
19
|
-
*/
|
|
20
|
-
export interface R2Bucket {
|
|
21
|
-
put(
|
|
22
|
-
key: string,
|
|
23
|
-
value:
|
|
24
|
-
| ReadableStream
|
|
25
|
-
| ArrayBuffer
|
|
26
|
-
| ArrayBufferView
|
|
27
|
-
| string
|
|
28
|
-
| Blob
|
|
29
|
-
| null,
|
|
30
|
-
options?: R2PutOptions,
|
|
31
|
-
): Promise<R2Object | null>;
|
|
32
|
-
get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;
|
|
33
|
-
head(key: string): Promise<R2Object | null>;
|
|
34
|
-
delete(keys: string | string[]): Promise<void>;
|
|
35
|
-
list(options?: R2ListOptions): Promise<R2Objects>;
|
|
36
|
-
createMultipartUpload(
|
|
37
|
-
key: string,
|
|
38
|
-
options?: R2MultipartOptions,
|
|
39
|
-
): Promise<R2MultipartUpload>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface R2Object {
|
|
43
|
-
key: string;
|
|
44
|
-
version: string;
|
|
45
|
-
size: number;
|
|
46
|
-
etag: string;
|
|
47
|
-
httpEtag: string;
|
|
48
|
-
checksums: R2Checksums;
|
|
49
|
-
uploaded: Date;
|
|
50
|
-
httpMetadata?: R2HTTPMetadata;
|
|
51
|
-
customMetadata?: Record<string, string>;
|
|
52
|
-
range?: R2Range;
|
|
53
|
-
storageClass: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface R2ObjectBody extends R2Object {
|
|
57
|
-
body: ReadableStream;
|
|
58
|
-
bodyUsed: boolean;
|
|
59
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
60
|
-
text(): Promise<string>;
|
|
61
|
-
json<T>(): Promise<T>;
|
|
62
|
-
blob(): Promise<Blob>;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface R2PutOptions {
|
|
66
|
-
onlyIf?: R2Conditional;
|
|
67
|
-
httpMetadata?: R2HTTPMetadata;
|
|
68
|
-
customMetadata?: Record<string, string>;
|
|
69
|
-
md5?: ArrayBuffer | string;
|
|
70
|
-
sha1?: ArrayBuffer | string;
|
|
71
|
-
sha256?: ArrayBuffer | string;
|
|
72
|
-
sha384?: ArrayBuffer | string;
|
|
73
|
-
sha512?: ArrayBuffer | string;
|
|
74
|
-
storageClass?: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface R2GetOptions {
|
|
78
|
-
onlyIf?: R2Conditional;
|
|
79
|
-
range?: R2Range;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface R2ListOptions {
|
|
83
|
-
limit?: number;
|
|
84
|
-
prefix?: string;
|
|
85
|
-
cursor?: string;
|
|
86
|
-
delimiter?: string;
|
|
87
|
-
startAfter?: string;
|
|
88
|
-
include?: ("httpMetadata" | "customMetadata")[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface R2Objects {
|
|
92
|
-
objects: R2Object[];
|
|
93
|
-
truncated: boolean;
|
|
94
|
-
cursor?: string;
|
|
95
|
-
delimitedPrefixes: string[];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface R2Checksums {
|
|
99
|
-
md5?: ArrayBuffer;
|
|
100
|
-
sha1?: ArrayBuffer;
|
|
101
|
-
sha256?: ArrayBuffer;
|
|
102
|
-
sha384?: ArrayBuffer;
|
|
103
|
-
sha512?: ArrayBuffer;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface R2HTTPMetadata {
|
|
107
|
-
contentType?: string;
|
|
108
|
-
contentLanguage?: string;
|
|
109
|
-
contentDisposition?: string;
|
|
110
|
-
contentEncoding?: string;
|
|
111
|
-
cacheControl?: string;
|
|
112
|
-
cacheExpiry?: Date;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface R2Conditional {
|
|
116
|
-
etagMatches?: string;
|
|
117
|
-
etagDoesNotMatch?: string;
|
|
118
|
-
uploadedBefore?: Date;
|
|
119
|
-
uploadedAfter?: Date;
|
|
120
|
-
secondsGranularity?: boolean;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface R2Range {
|
|
124
|
-
offset?: number;
|
|
125
|
-
length?: number;
|
|
126
|
-
suffix?: number;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface R2MultipartOptions {
|
|
130
|
-
httpMetadata?: R2HTTPMetadata;
|
|
131
|
-
customMetadata?: Record<string, string>;
|
|
132
|
-
storageClass?: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface R2MultipartUpload {
|
|
136
|
-
key: string;
|
|
137
|
-
uploadId: string;
|
|
138
|
-
uploadPart(
|
|
139
|
-
partNumber: number,
|
|
140
|
-
value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,
|
|
141
|
-
): Promise<R2UploadedPart>;
|
|
142
|
-
abort(): Promise<void>;
|
|
143
|
-
complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface R2UploadedPart {
|
|
147
|
-
partNumber: number;
|
|
148
|
-
etag: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
152
|
-
|
|
153
18
|
/**
|
|
154
19
|
* Cloudflare R2 storage provider.
|
|
155
20
|
*
|
|
@@ -311,7 +176,7 @@ export class CloudflareR2Provider implements FileStorageProvider {
|
|
|
311
176
|
type: contentType,
|
|
312
177
|
size: object.size,
|
|
313
178
|
lastModified: object.uploaded.getTime(),
|
|
314
|
-
stream: () => object.body,
|
|
179
|
+
stream: () => object.body as unknown as ReadableStream,
|
|
315
180
|
arrayBuffer: () => object.arrayBuffer(),
|
|
316
181
|
text: () => object.text(),
|
|
317
182
|
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import {
|
|
3
|
+
$env,
|
|
4
|
+
$hook,
|
|
5
|
+
$inject,
|
|
6
|
+
Alepha,
|
|
7
|
+
AlephaError,
|
|
8
|
+
type FileLike,
|
|
9
|
+
type Static,
|
|
10
|
+
t,
|
|
11
|
+
} from "alepha";
|
|
12
|
+
import { $logger } from "alepha/logger";
|
|
13
|
+
import { FileDetector, FileSystemProvider } from "alepha/system";
|
|
14
|
+
import { S3mini } from "s3mini";
|
|
15
|
+
import { FileNotFoundError } from "../errors/FileNotFoundError.ts";
|
|
16
|
+
import { $bucket } from "../primitives/$bucket.ts";
|
|
17
|
+
import type { FileStorageProvider } from "./FileStorageProvider.ts";
|
|
18
|
+
|
|
19
|
+
const envSchema = t.object({
|
|
20
|
+
/**
|
|
21
|
+
* S3 endpoint URL. The bucket name is appended (path-style) per request.
|
|
22
|
+
*
|
|
23
|
+
* Examples:
|
|
24
|
+
* - AWS S3: `https://s3.us-east-1.amazonaws.com`
|
|
25
|
+
* - Cloudflare R2: `https://<account-id>.r2.cloudflarestorage.com`
|
|
26
|
+
* - MinIO: `http://localhost:9000`
|
|
27
|
+
* - DigitalOcean Spaces: `https://<region>.digitaloceanspaces.com`
|
|
28
|
+
*/
|
|
29
|
+
S3_ENDPOINT: t.string(),
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* AWS region or "auto" for R2.
|
|
33
|
+
*
|
|
34
|
+
* @default "auto"
|
|
35
|
+
*/
|
|
36
|
+
S3_REGION: t.optional(t.string()),
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Access key ID for S3 authentication.
|
|
40
|
+
*/
|
|
41
|
+
S3_ACCESS_KEY_ID: t.string(),
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Secret access key for S3 authentication.
|
|
45
|
+
*/
|
|
46
|
+
S3_SECRET_ACCESS_KEY: t.string(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
declare module "alepha" {
|
|
50
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* S3-compatible file storage provider for Node.js.
|
|
55
|
+
*
|
|
56
|
+
* Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2,
|
|
57
|
+
* MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service.
|
|
58
|
+
*
|
|
59
|
+
* Uses path-style addressing (`<endpoint>/<bucket>`).
|
|
60
|
+
*/
|
|
61
|
+
export class NodeS3BucketProvider implements FileStorageProvider {
|
|
62
|
+
protected readonly log = $logger();
|
|
63
|
+
protected readonly env = $env(envSchema);
|
|
64
|
+
protected readonly alepha = $inject(Alepha);
|
|
65
|
+
protected readonly fileSystem = $inject(FileSystemProvider);
|
|
66
|
+
protected readonly fileDetector = $inject(FileDetector);
|
|
67
|
+
protected readonly clients: Map<string, S3mini> = new Map();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert bucket name to S3-compatible format.
|
|
71
|
+
* S3 bucket names must be lowercase, 3-63 characters, no underscores.
|
|
72
|
+
*/
|
|
73
|
+
public convertName(name: string): string {
|
|
74
|
+
return name.replaceAll("/", "-").replaceAll("_", "-").toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected getClient(bucketName: string): S3mini {
|
|
78
|
+
const name = this.convertName(bucketName);
|
|
79
|
+
let client = this.clients.get(name);
|
|
80
|
+
if (!client) {
|
|
81
|
+
const endpoint = this.env.S3_ENDPOINT.replace(/\/+$/, "");
|
|
82
|
+
client = new S3mini({
|
|
83
|
+
accessKeyId: this.env.S3_ACCESS_KEY_ID,
|
|
84
|
+
secretAccessKey: this.env.S3_SECRET_ACCESS_KEY,
|
|
85
|
+
region: this.env.S3_REGION || "auto",
|
|
86
|
+
endpoint: `${endpoint}/${name}`,
|
|
87
|
+
});
|
|
88
|
+
this.clients.set(name, client);
|
|
89
|
+
}
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected readonly onStart = $hook({
|
|
94
|
+
on: "start",
|
|
95
|
+
handler: async () => {
|
|
96
|
+
for (const bucket of this.alepha.primitives($bucket)) {
|
|
97
|
+
if (bucket.provider !== this) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const name = this.convertName(bucket.name);
|
|
102
|
+
const client = this.getClient(bucket.name);
|
|
103
|
+
|
|
104
|
+
this.log.debug(`Preparing S3 bucket '${name}'...`);
|
|
105
|
+
|
|
106
|
+
const exists = await client.bucketExists();
|
|
107
|
+
if (!exists) {
|
|
108
|
+
this.log.debug(`Creating S3 bucket '${name}'...`);
|
|
109
|
+
const created = await client.createBucket();
|
|
110
|
+
if (!created) {
|
|
111
|
+
throw new AlephaError(`Failed to create S3 bucket '${name}'`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.log.info(`S3 bucket '${bucket.name}' OK`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
protected createId(mimeType: string): string {
|
|
121
|
+
const ext = this.fileDetector.getExtensionFromMimeType(mimeType);
|
|
122
|
+
return `${crypto.randomUUID()}.${ext}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async upload(
|
|
126
|
+
bucketName: string,
|
|
127
|
+
file: FileLike,
|
|
128
|
+
fileId?: string,
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
fileId ??= this.createId(file.type);
|
|
131
|
+
|
|
132
|
+
this.log.trace(
|
|
133
|
+
`Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const client = this.getClient(bucketName);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
140
|
+
await client.putObject(
|
|
141
|
+
fileId,
|
|
142
|
+
buffer,
|
|
143
|
+
file.type || "application/octet-stream",
|
|
144
|
+
undefined,
|
|
145
|
+
{ "x-amz-meta-name": encodeURIComponent(file.name) },
|
|
146
|
+
file.size,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
this.log.trace(`File uploaded successfully: ${fileId}`);
|
|
150
|
+
return fileId;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.log.error(`Failed to upload file: ${error}`);
|
|
153
|
+
if (error instanceof Error) {
|
|
154
|
+
throw new AlephaError(`Upload failed: ${error.message}`, {
|
|
155
|
+
cause: error,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public async download(bucketName: string, fileId: string): Promise<FileLike> {
|
|
163
|
+
this.log.trace(
|
|
164
|
+
`Downloading file '${fileId}' from bucket '${bucketName}'...`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const client = this.getClient(bucketName);
|
|
168
|
+
const response = await client.getObjectResponse(fileId);
|
|
169
|
+
|
|
170
|
+
if (!response) {
|
|
171
|
+
throw new FileNotFoundError(
|
|
172
|
+
`File '${fileId}' not found in bucket '${bucketName}'`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
177
|
+
|
|
178
|
+
const mimeType =
|
|
179
|
+
response.headers.get("content-type") ||
|
|
180
|
+
this.fileDetector.getContentType(fileId);
|
|
181
|
+
|
|
182
|
+
const metaName = response.headers.get("x-amz-meta-name");
|
|
183
|
+
const name = metaName ? decodeURIComponent(metaName) : fileId;
|
|
184
|
+
|
|
185
|
+
return this.fileSystem.createFile({
|
|
186
|
+
buffer,
|
|
187
|
+
name,
|
|
188
|
+
type: mimeType,
|
|
189
|
+
size: buffer.length,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public async exists(bucketName: string, fileId: string): Promise<boolean> {
|
|
194
|
+
this.log.trace(
|
|
195
|
+
`Checking existence of file '${fileId}' in bucket '${bucketName}'...`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const client = this.getClient(bucketName);
|
|
199
|
+
const result = await client.objectExists(fileId);
|
|
200
|
+
return result === true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public async delete(bucketName: string, fileId: string): Promise<void> {
|
|
204
|
+
this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);
|
|
205
|
+
|
|
206
|
+
const client = this.getClient(bucketName);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
await client.deleteObject(fileId);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.log.error(`Failed to delete file: ${error}`);
|
|
212
|
+
if (error instanceof Error) {
|
|
213
|
+
throw new FileNotFoundError("Error deleting file", { cause: error });
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
package/src/cache/core/index.ts
CHANGED
|
@@ -12,6 +12,35 @@ export * from "./providers/MemoryCacheProvider.ts";
|
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
14
14
|
|
|
15
|
+
declare module "alepha" {
|
|
16
|
+
interface Hooks {
|
|
17
|
+
/**
|
|
18
|
+
* Fires when a cache lookup finds a value.
|
|
19
|
+
*/
|
|
20
|
+
"cache:hit": {
|
|
21
|
+
container: string;
|
|
22
|
+
key: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Fires when a cache lookup does not find a value.
|
|
26
|
+
*/
|
|
27
|
+
"cache:miss": {
|
|
28
|
+
container: string;
|
|
29
|
+
key: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Fires when a value is written to the cache.
|
|
33
|
+
*/
|
|
34
|
+
"cache:set": {
|
|
35
|
+
container: string;
|
|
36
|
+
key: string;
|
|
37
|
+
ttlMs?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
43
|
+
|
|
15
44
|
/**
|
|
16
45
|
* Type-safe caching with TTL support.
|
|
17
46
|
*
|
|
@@ -239,6 +239,12 @@ export class CachePrimitive<
|
|
|
239
239
|
ttl: px > 0 ? px : undefined,
|
|
240
240
|
compress: this.options.compress,
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
await this.alepha.events.emit("cache:set", {
|
|
244
|
+
container: this.container,
|
|
245
|
+
key,
|
|
246
|
+
ttlMs: px > 0 ? px : undefined,
|
|
247
|
+
});
|
|
242
248
|
}
|
|
243
249
|
|
|
244
250
|
public async get(key: string): Promise<TReturn | undefined> {
|
|
@@ -250,7 +256,14 @@ export class CachePrimitive<
|
|
|
250
256
|
return undefined;
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
|
|
259
|
+
const value = await this.provider.getTyped<TReturn>(this.container, key);
|
|
260
|
+
|
|
261
|
+
await this.alepha.events.emit(
|
|
262
|
+
value === undefined ? "cache:miss" : "cache:hit",
|
|
263
|
+
{ container: this.container, key },
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return value;
|
|
254
267
|
}
|
|
255
268
|
|
|
256
269
|
protected $provider(): CacheProvider {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Alepha } from "alepha";
|
|
1
|
+
import type { Alepha, Service } from "alepha";
|
|
2
2
|
import {
|
|
3
3
|
type AppEntryOptions,
|
|
4
4
|
appEntryOptions,
|
|
@@ -10,15 +10,6 @@ import {
|
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
12
12
|
|
|
13
|
-
export type AlephaCliConfigPlugin = (
|
|
14
|
-
config: AlephaCliConfig,
|
|
15
|
-
alepha: Alepha,
|
|
16
|
-
) => void;
|
|
17
|
-
|
|
18
|
-
export const cliConfigPlugins: AlephaCliConfigPlugin[] = [];
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
21
|
-
|
|
22
13
|
export interface AlephaCliConfig {
|
|
23
14
|
/**
|
|
24
15
|
* Override entry paths.
|
|
@@ -28,7 +19,12 @@ export interface AlephaCliConfig {
|
|
|
28
19
|
/**
|
|
29
20
|
* Register more services to the Alepha CLI (enhancements, commands, etc.).
|
|
30
21
|
*/
|
|
31
|
-
services?: Array<
|
|
22
|
+
services?: Array<Service>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @alias services Register more services to the Alepha CLI (enhancements, commands, etc.).
|
|
26
|
+
*/
|
|
27
|
+
plugins?: Array<Service>;
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
30
|
* Configure Alepha build command.
|
|
@@ -58,6 +54,12 @@ export const defineConfig = (config: AlephaCliConfig) => {
|
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
|
|
57
|
+
if (config.plugins) {
|
|
58
|
+
for (const it of config.plugins) {
|
|
59
|
+
alepha.with(it);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
61
63
|
if (config.env) {
|
|
62
64
|
for (const [key, value] of Object.entries(config.env)) {
|
|
63
65
|
process.env[key] = String(value);
|
|
@@ -76,10 +78,6 @@ export const defineConfig = (config: AlephaCliConfig) => {
|
|
|
76
78
|
alepha.set(appEntryOptions, config.entry);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
for (const plugin of cliConfigPlugins) {
|
|
80
|
-
plugin(config, alepha);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
81
|
return {};
|
|
84
82
|
};
|
|
85
83
|
};
|
|
@@ -98,25 +98,24 @@ describe("alepha init", () => {
|
|
|
98
98
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
99
|
|
|
100
100
|
describe("AI agent files", () => {
|
|
101
|
-
it("should create
|
|
102
|
-
const { fs,
|
|
101
|
+
it("should create both AGENTS.md and CLAUDE.md", async () => {
|
|
102
|
+
const { fs, cli, cmd, json } = createTestEnv();
|
|
103
103
|
await setupProject(fs, json);
|
|
104
|
-
shell.installedCommands.add("claude");
|
|
105
104
|
|
|
106
105
|
await cli.run(cmd.init, { root: "/project" });
|
|
107
106
|
|
|
107
|
+
expect(fs.wasWritten("/project/AGENTS.md")).toBe(true);
|
|
108
108
|
expect(fs.wasWritten("/project/CLAUDE.md")).toBe(true);
|
|
109
|
-
expect(fs.wasWritten("/project/AGENTS.md")).toBe(false);
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
it("should
|
|
111
|
+
it("should write CLAUDE.md as a stub importing AGENTS.md", async () => {
|
|
113
112
|
const { fs, cli, cmd, json } = createTestEnv();
|
|
114
113
|
await setupProject(fs, json);
|
|
115
114
|
|
|
116
115
|
await cli.run(cmd.init, { root: "/project" });
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
expect(
|
|
117
|
+
const claude = await fs.readTextFile("/project/CLAUDE.md");
|
|
118
|
+
expect(claude.trim()).toBe("@AGENTS.md");
|
|
120
119
|
});
|
|
121
120
|
|
|
122
121
|
it("should include Alepha instructions in agent file", async () => {
|
|
@@ -3,7 +3,7 @@ import { $inject, AlephaError } from "alepha";
|
|
|
3
3
|
import type { RunnerMethod } from "alepha/command";
|
|
4
4
|
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
5
5
|
import { FileSystemProvider, ShellProvider } from "alepha/system";
|
|
6
|
-
import {
|
|
6
|
+
import { agentMd } from "../templates/agentMd.ts";
|
|
7
7
|
import { alephaConfigTs } from "../templates/alephaConfigTs.ts";
|
|
8
8
|
import { apiHelloControllerTs } from "../templates/apiHelloControllerTs.ts";
|
|
9
9
|
import { apiHelloResponseSchemaTs } from "../templates/apiHelloResponseSchemaTs.ts";
|
|
@@ -94,7 +94,7 @@ export class ProjectScaffolder {
|
|
|
94
94
|
tsconfigJson?: boolean | "local";
|
|
95
95
|
biomeJson?: boolean;
|
|
96
96
|
editorconfig?: boolean;
|
|
97
|
-
agentMd?:
|
|
97
|
+
agentMd?: boolean;
|
|
98
98
|
},
|
|
99
99
|
): Promise<void> {
|
|
100
100
|
const tasks: Promise<void>[] = [];
|
|
@@ -126,7 +126,7 @@ export class ProjectScaffolder {
|
|
|
126
126
|
tasks.push(this.ensureEditorConfig(root, { force, checkWorkspace }));
|
|
127
127
|
}
|
|
128
128
|
if (opts.agentMd) {
|
|
129
|
-
tasks.push(this.ensureAgentMd(root, {
|
|
129
|
+
tasks.push(this.ensureAgentMd(root, { force }));
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
await Promise.all(tasks);
|
|
@@ -214,12 +214,19 @@ export class ProjectScaffolder {
|
|
|
214
214
|
return true;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Ensure AGENTS.md (cross-tool standard, canonical source) exists, with a
|
|
219
|
+
* CLAUDE.md stub that imports it via Claude Code's `@` syntax. Single
|
|
220
|
+
* source of truth, cross-platform, no symlink needed.
|
|
221
|
+
*/
|
|
217
222
|
public async ensureAgentMd(
|
|
218
223
|
root: string,
|
|
219
|
-
options:
|
|
224
|
+
options: { force?: boolean } = {},
|
|
220
225
|
): Promise<void> {
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
await Promise.all([
|
|
227
|
+
this.ensureFile(root, "AGENTS.md", agentMd(), options.force),
|
|
228
|
+
this.ensureFile(root, "CLAUDE.md", "@AGENTS.md\n", options.force),
|
|
229
|
+
]);
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
/**
|
|
@@ -602,12 +609,9 @@ export class ProjectScaffolder {
|
|
|
602
609
|
// Detect workspace context (are we inside packages/ or apps/ of a monorepo?)
|
|
603
610
|
const workspace = await this.pm.getWorkspaceContext(root);
|
|
604
611
|
|
|
605
|
-
//
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const hasClaudeCli = await this.utils.isInstalledAsync("claude");
|
|
609
|
-
agentType = hasClaudeCli ? "claude" : "agents";
|
|
610
|
-
}
|
|
612
|
+
// Always emit both AGENTS.md and CLAUDE.md at project roots (skip for
|
|
613
|
+
// monorepo sub-packages where agent files live at workspace root).
|
|
614
|
+
const writeAgentMd = !workspace.isPackage;
|
|
611
615
|
|
|
612
616
|
const isExpo = await this.pm.hasExpo(root);
|
|
613
617
|
|
|
@@ -626,7 +630,7 @@ export class ProjectScaffolder {
|
|
|
626
630
|
tsconfigJson: f.shadcn ? "local" : !workspace.config.tsconfigJson,
|
|
627
631
|
biomeJson: true,
|
|
628
632
|
editorconfig: !workspace.config.editorconfig,
|
|
629
|
-
agentMd:
|
|
633
|
+
agentMd: writeAgentMd,
|
|
630
634
|
});
|
|
631
635
|
|
|
632
636
|
// Create alepha.config.ts with documented options
|
|
@@ -729,7 +733,7 @@ export class ProjectScaffolder {
|
|
|
729
733
|
// pulls its peer primitives + dependencies (sonner, etc.).
|
|
730
734
|
if (flags.saas) {
|
|
731
735
|
// Pull the public SaaS bundle in one shot — it aggregates control,
|
|
732
|
-
// auto-form, alepha-table, use-
|
|
736
|
+
// auto-form, alepha-table, use-dialog, app-shell, every auth-*, and
|
|
733
737
|
// every admin-* block. Definition lives at
|
|
734
738
|
// https://alepha.dev/r/saas.json (see @alepha/ui-registry).
|
|
735
739
|
// `--yes --overwrite` is the only combo that works non-interactively
|
|
@@ -73,6 +73,11 @@ export class BuildCloudflareTask extends BuildTask {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
wrangler.observability ??= {
|
|
77
|
+
enabled: true,
|
|
78
|
+
head_sampling_rate: 1,
|
|
79
|
+
};
|
|
80
|
+
|
|
76
81
|
this.enhanceDomain(wrangler);
|
|
77
82
|
this.enhanceCron(ctx, wrangler);
|
|
78
83
|
this.enhanceDatabase(wrangler);
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
export interface AgentMdOptions {
|
|
4
|
-
type: AgentMdType;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const agentMd = (options: AgentMdOptions): string => {
|
|
8
|
-
const header = options.type === "claude" ? `# CLAUDE.md` : `# AGENTS.md`;
|
|
9
|
-
|
|
10
|
-
return `${header}
|
|
1
|
+
export const agentMd = (): string => {
|
|
2
|
+
return `# AGENTS.md
|
|
11
3
|
|
|
12
4
|
This is an **Alepha** project.
|
|
13
5
|
|
|
@@ -11,7 +11,7 @@ export const saasAdminLayoutTsx = () =>
|
|
|
11
11
|
`import { AppShell } from "@/components/app-shell";
|
|
12
12
|
import { Toaster } from "@/components/ui/sonner";
|
|
13
13
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
14
|
-
import {
|
|
14
|
+
import { DialogProvider } from "@/components/use-dialog";
|
|
15
15
|
import { NestedView, useRouterState } from "alepha/react/router";
|
|
16
16
|
import { ShieldCheck, Users } from "lucide-react";
|
|
17
17
|
|
|
@@ -39,7 +39,7 @@ const AdminLayout = () => {
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<TooltipProvider>
|
|
42
|
-
<
|
|
42
|
+
<DialogProvider>
|
|
43
43
|
<AppShell
|
|
44
44
|
brand={
|
|
45
45
|
<a
|
|
@@ -68,7 +68,7 @@ const AdminLayout = () => {
|
|
|
68
68
|
<NestedView />
|
|
69
69
|
</AppShell>
|
|
70
70
|
<Toaster />
|
|
71
|
-
</
|
|
71
|
+
</DialogProvider>
|
|
72
72
|
</TooltipProvider>
|
|
73
73
|
);
|
|
74
74
|
};
|