runwork 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/bin/runwork.js +2 -0
- package/bundled-types/agents.d.ts +3 -0
- package/bundled-types/ai.d.ts +2 -0
- package/bundled-types/app-registry.d.ts +74 -0
- package/bundled-types/channels.d.ts +2 -0
- package/bundled-types/components.d.ts +1 -0
- package/bundled-types/core-agent.d.ts +265 -0
- package/bundled-types/core-ai.d.ts +170 -0
- package/bundled-types/core-base-agent.d.ts +89 -0
- package/bundled-types/core-channels.d.ts +42 -0
- package/bundled-types/core-components.d.ts +21 -0
- package/bundled-types/core-endpoints.d.ts +198 -0
- package/bundled-types/core-entities.d.ts +276 -0
- package/bundled-types/core-entity-do.d.ts +119 -0
- package/bundled-types/core-events.d.ts +52 -0
- package/bundled-types/core-file-storage.d.ts +216 -0
- package/bundled-types/core-integration-entities.d.ts +147 -0
- package/bundled-types/core-integrations.d.ts +143 -0
- package/bundled-types/core-routes.d.ts +33 -0
- package/bundled-types/core-scheduler.d.ts +180 -0
- package/bundled-types/core-utils.d.ts +85 -0
- package/bundled-types/core-workflow-cloudflare.d.ts +85 -0
- package/bundled-types/core-workflow-config.d.ts +18 -0
- package/bundled-types/core-workflow-coordinator.d.ts +78 -0
- package/bundled-types/core-workflow-instance.d.ts +82 -0
- package/bundled-types/core-workflow-types.d.ts +251 -0
- package/bundled-types/core-workflows.d.ts +75 -0
- package/bundled-types/core-workspace.d.ts +454 -0
- package/bundled-types/create-app.d.ts +57 -0
- package/bundled-types/endpoints.d.ts +2 -0
- package/bundled-types/entities.d.ts +5 -0
- package/bundled-types/events.d.ts +2 -0
- package/bundled-types/index.d.ts +19 -0
- package/bundled-types/integrations.d.ts +2 -0
- package/bundled-types/routes.d.ts +2 -0
- package/bundled-types/scheduler.d.ts +2 -0
- package/bundled-types/storage.d.ts +2 -0
- package/bundled-types/test-entry.d.ts +21 -0
- package/bundled-types/types.d.ts +1 -0
- package/bundled-types/vite.d.ts +23 -0
- package/bundled-types/workflows.d.ts +14 -0
- package/bundled-types/workspace.d.ts +2 -0
- package/dist/__tests__/types-manager.test.d.ts +1 -0
- package/dist/__tests__/types-manager.test.js +85 -0
- package/dist/api/__tests__/client.test.d.ts +1 -0
- package/dist/api/__tests__/client.test.js +264 -0
- package/dist/api/client.d.ts +72 -0
- package/dist/api/client.js +144 -0
- package/dist/auth/__tests__/store.test.d.ts +1 -0
- package/dist/auth/__tests__/store.test.js +103 -0
- package/dist/auth/browser.d.ts +2 -0
- package/dist/auth/browser.js +7 -0
- package/dist/auth/store.d.ts +5 -0
- package/dist/auth/store.js +33 -0
- package/dist/commands/clone.d.ts +2 -0
- package/dist/commands/clone.js +90 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +43 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +188 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +87 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +32 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +13 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +166 -0
- package/dist/generated/bundled-types.d.ts +1 -0
- package/dist/generated/bundled-types.js +44 -0
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/git/__tests__/credentials.test.d.ts +1 -0
- package/dist/git/__tests__/credentials.test.js +128 -0
- package/dist/git/auto-commit.d.ts +3 -0
- package/dist/git/auto-commit.js +209 -0
- package/dist/git/credentials.d.ts +15 -0
- package/dist/git/credentials.js +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31 -0
- package/dist/logs/tailer.d.ts +13 -0
- package/dist/logs/tailer.js +135 -0
- package/dist/template/manifest.d.ts +17 -0
- package/dist/template/manifest.js +102 -0
- package/dist/types-manager.d.ts +1 -0
- package/dist/types-manager.js +35 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/dist/utils/__tests__/prompt.test.d.ts +1 -0
- package/dist/utils/__tests__/prompt.test.js +103 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +23 -0
- package/dist/utils/prompt.d.ts +4 -0
- package/dist/utils/prompt.js +34 -0
- package/dist/utils/zip.d.ts +1 -0
- package/dist/utils/zip.js +17 -0
- package/package.json +66 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Entity Durable Object - SQL Storage
|
|
3
|
+
* DO NOT MODIFY THIS FILE - You may break the entity functionality
|
|
4
|
+
*
|
|
5
|
+
* EntityDO is the per-app entity storage system backed by native SQLite.
|
|
6
|
+
* All entities of the same type share one DO instance, keyed by entity ID.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - SQL-backed document storage with versioning (CAS operations)
|
|
10
|
+
* - Server-side query, search, sort, count via json_extract()
|
|
11
|
+
* - Security: field name validation, LIKE escaping, input limits
|
|
12
|
+
* - Auto-migration from legacy KV storage
|
|
13
|
+
*
|
|
14
|
+
* NO SCHEDULING - Use SchedulerDO for scheduled jobs
|
|
15
|
+
*/
|
|
16
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
17
|
+
import type { Env } from './core-utils';
|
|
18
|
+
export declare const SAFE_FIELD_NAME: RegExp;
|
|
19
|
+
export declare function validateFieldName(field: string): string;
|
|
20
|
+
export declare function escapeLikePattern(input: string): string;
|
|
21
|
+
export declare const QUERY_LIMITS: {
|
|
22
|
+
MAX_SEARCH_LENGTH: number;
|
|
23
|
+
MAX_FILTER_COUNT: number;
|
|
24
|
+
MAX_SEARCH_FIELDS: number;
|
|
25
|
+
MAX_LIMIT: number;
|
|
26
|
+
MAX_DATA_SIZE: number;
|
|
27
|
+
};
|
|
28
|
+
interface QueryOptions {
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
cursor?: string | null;
|
|
32
|
+
filters?: Record<string, unknown>;
|
|
33
|
+
search?: {
|
|
34
|
+
query: string;
|
|
35
|
+
fields: string[];
|
|
36
|
+
};
|
|
37
|
+
sort?: {
|
|
38
|
+
field: string;
|
|
39
|
+
order: 'asc' | 'desc';
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface QueryResult {
|
|
43
|
+
items: Array<{
|
|
44
|
+
key: string;
|
|
45
|
+
v: number;
|
|
46
|
+
data: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
total: number;
|
|
49
|
+
hasMore: boolean;
|
|
50
|
+
next: string | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Versioned document type for entity storage
|
|
54
|
+
*/
|
|
55
|
+
export type Doc<T> = {
|
|
56
|
+
v: number;
|
|
57
|
+
data: T;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* EntityDO - Per-app entity storage with SQL queries
|
|
61
|
+
*
|
|
62
|
+
* This Durable Object provides:
|
|
63
|
+
* - Document storage with optimistic concurrency (CAS) via SQLite
|
|
64
|
+
* - Query with search, sort, filter, pagination via json_extract()
|
|
65
|
+
* - Count with optional filters
|
|
66
|
+
* - Security validation on all field names
|
|
67
|
+
*/
|
|
68
|
+
export declare class EntityDO extends DurableObject<Env> {
|
|
69
|
+
ctx: DurableObjectState;
|
|
70
|
+
env: Env;
|
|
71
|
+
private _tableReady;
|
|
72
|
+
private _migrationDone;
|
|
73
|
+
constructor(ctx: DurableObjectState, env: Env);
|
|
74
|
+
private ensureTable;
|
|
75
|
+
private migrateFromKV;
|
|
76
|
+
getDoc(key: string): {
|
|
77
|
+
v: number;
|
|
78
|
+
data: unknown;
|
|
79
|
+
} | null;
|
|
80
|
+
casPut(key: string, expectedV: number, data: unknown): {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
v: number;
|
|
83
|
+
};
|
|
84
|
+
del(key: string): boolean;
|
|
85
|
+
has(key: string): boolean;
|
|
86
|
+
queryEntities(options?: QueryOptions): QueryResult;
|
|
87
|
+
countEntities(filters?: Record<string, unknown>): number;
|
|
88
|
+
bulkCreate(items: Array<{
|
|
89
|
+
key: string;
|
|
90
|
+
data: unknown;
|
|
91
|
+
}>): Array<{
|
|
92
|
+
key: string;
|
|
93
|
+
v: number;
|
|
94
|
+
data: unknown;
|
|
95
|
+
}>;
|
|
96
|
+
bulkUpdate(updates: Array<{
|
|
97
|
+
key: string;
|
|
98
|
+
data: Record<string, unknown>;
|
|
99
|
+
}>): Array<{
|
|
100
|
+
key: string;
|
|
101
|
+
ok: boolean;
|
|
102
|
+
v: number;
|
|
103
|
+
data: unknown;
|
|
104
|
+
}>;
|
|
105
|
+
bulkDelete(keys: string[]): number;
|
|
106
|
+
exportAll(): Array<{
|
|
107
|
+
key: string;
|
|
108
|
+
v: number;
|
|
109
|
+
data: unknown;
|
|
110
|
+
}>;
|
|
111
|
+
importAll(items: Array<{
|
|
112
|
+
key: string;
|
|
113
|
+
v: number;
|
|
114
|
+
data: unknown;
|
|
115
|
+
}>): {
|
|
116
|
+
imported: number;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Events
|
|
3
|
+
* DO NOT MODIFY THIS FILE - You may break the project functionality
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* - emitEvent(): Fire-and-forget event emission to the workspace event stream
|
|
7
|
+
* - flog(): Structured logging that outputs JSON to sandbox stdout
|
|
8
|
+
*
|
|
9
|
+
* Events appear in the workspace activity feed and channel views.
|
|
10
|
+
* When running outside a workspace (standalone mode), calls are silently skipped.
|
|
11
|
+
*/
|
|
12
|
+
export interface EmitEventParams {
|
|
13
|
+
type: string;
|
|
14
|
+
entityType?: string;
|
|
15
|
+
entityId?: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
interface EventEnv {
|
|
20
|
+
WORKSPACE_API_URL?: string;
|
|
21
|
+
WORKSPACE_API_KEY?: string;
|
|
22
|
+
WORKSPACE_ID?: string;
|
|
23
|
+
APP_ID?: string;
|
|
24
|
+
APP_NAME?: string;
|
|
25
|
+
DEPLOYMENT_MODE?: 'preview' | 'production';
|
|
26
|
+
WorkspaceObject?: DurableObjectNamespace;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Any context that supports waitUntil - compatible with both
|
|
30
|
+
* ExecutionContext (Hono route handlers) and DurableObjectState (DOs).
|
|
31
|
+
*/
|
|
32
|
+
type WaitUntilContext = Pick<ExecutionContext, 'waitUntil'>;
|
|
33
|
+
/**
|
|
34
|
+
* Convert an app name to a kebab-case channel name.
|
|
35
|
+
* "My Cool App" -> "my-cool-app"
|
|
36
|
+
*/
|
|
37
|
+
export declare function toChannelName(appName: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Structured log entry for sandbox stdout.
|
|
40
|
+
* Outputs JSON with a consistent schema so the platform can parse and display
|
|
41
|
+
* these entries in the observability timeline.
|
|
42
|
+
*/
|
|
43
|
+
export declare function flog(level: 'info' | 'warn' | 'error', system: string, message: string, data?: Record<string, unknown>): void;
|
|
44
|
+
/**
|
|
45
|
+
* Emit an event to the workspace unified event stream.
|
|
46
|
+
* Fire-and-forget: uses ctx.waitUntil so it doesn't block the response.
|
|
47
|
+
* Silently skips if workspace env vars are not configured (standalone mode).
|
|
48
|
+
*
|
|
49
|
+
* Automatically routes events to a channel derived from APP_NAME when available.
|
|
50
|
+
*/
|
|
51
|
+
export declare function emitEvent(ctx: WaitUntilContext, env: EventEnv, event: EmitEventParams): void;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core File Storage Utilities for R2
|
|
3
|
+
* DO NOT MODIFY THIS FILE - You may break the project functionality
|
|
4
|
+
*
|
|
5
|
+
* This module provides the FileStorageClient class for R2 bucket operations.
|
|
6
|
+
* Use the FileStorageClient to upload, download, delete, and list files.
|
|
7
|
+
* Presigned URLs are generated via the platform API (vibe-apps don't have R2 credentials).
|
|
8
|
+
*/
|
|
9
|
+
import { type Env } from './core-utils';
|
|
10
|
+
import type { FileMetadata, UploadOptions, DownloadOptions, ListFilesOptions, ListFilesResult, PresignedUrlRequest, PresignedUrlResponse } from './shared/core-types';
|
|
11
|
+
export type { FileMetadata, UploadOptions, DownloadOptions, ListFilesOptions, ListFilesResult, PresignedUrlRequest, PresignedUrlResponse, };
|
|
12
|
+
/**
|
|
13
|
+
* FileStorageClient - Wrapper for R2 bucket operations
|
|
14
|
+
*
|
|
15
|
+
* Use this client to:
|
|
16
|
+
* - Upload files directly to R2 (for smaller files via worker)
|
|
17
|
+
* - Download files directly from R2
|
|
18
|
+
* - List and manage files in the bucket
|
|
19
|
+
* - Get presigned URLs for direct R2 access (via platform API)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const storage = createFileStorageClient(env);
|
|
24
|
+
*
|
|
25
|
+
* // Upload a file
|
|
26
|
+
* const metadata = await storage.upload(fileData, {
|
|
27
|
+
* name: 'document.pdf',
|
|
28
|
+
* contentType: 'application/pdf'
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Get presigned URL for large file download
|
|
32
|
+
* const { url } = await storage.getPresignedDownloadUrl('path/to/file.pdf');
|
|
33
|
+
*
|
|
34
|
+
* // List files
|
|
35
|
+
* const { files, cursor } = await storage.list({ prefix: 'uploads/' });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare class FileStorageClient {
|
|
39
|
+
private env;
|
|
40
|
+
private readonly bucket;
|
|
41
|
+
private readonly platformBaseUrl;
|
|
42
|
+
private readonly workspaceApiKey;
|
|
43
|
+
private readonly appId;
|
|
44
|
+
constructor(env: Env);
|
|
45
|
+
private extractOrigin;
|
|
46
|
+
/**
|
|
47
|
+
* Check if file storage is configured (R2 bucket bound)
|
|
48
|
+
*/
|
|
49
|
+
isConfigured(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check if presigned URL generation is available (platform API configured)
|
|
52
|
+
*/
|
|
53
|
+
canGeneratePresignedUrls(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Generate a unique key for file storage
|
|
56
|
+
*/
|
|
57
|
+
private generateKey;
|
|
58
|
+
/**
|
|
59
|
+
* Upload a file to R2
|
|
60
|
+
* For smaller files that can be processed through the worker.
|
|
61
|
+
* For large files, use getPresignedUploadUrl() for direct R2 upload.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const metadata = await storage.upload(fileBuffer, {
|
|
66
|
+
* name: 'report.pdf',
|
|
67
|
+
* contentType: 'application/pdf',
|
|
68
|
+
* customMetadata: { department: 'sales' }
|
|
69
|
+
* });
|
|
70
|
+
* console.log('Uploaded:', metadata.key);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
upload(data: ReadableStream | ArrayBuffer | string | Blob, options?: UploadOptions): Promise<FileMetadata>;
|
|
74
|
+
/**
|
|
75
|
+
* Download a file from R2
|
|
76
|
+
* Returns the R2ObjectBody which includes the body stream and metadata.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const object = await storage.download('path/to/file.pdf');
|
|
81
|
+
* if (object) {
|
|
82
|
+
* return new Response(object.body, {
|
|
83
|
+
* headers: { 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream' }
|
|
84
|
+
* });
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
download(key: string, options?: DownloadOptions): Promise<R2ObjectBody | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Delete a file from R2
|
|
91
|
+
* Returns true if the file existed, false otherwise.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const deleted = await storage.delete('path/to/file.pdf');
|
|
96
|
+
* console.log(deleted ? 'File deleted' : 'File did not exist');
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
delete(key: string): Promise<boolean>;
|
|
100
|
+
/**
|
|
101
|
+
* Delete multiple files from R2
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* await storage.deleteMany(['file1.pdf', 'file2.pdf', 'file3.pdf']);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
deleteMany(keys: string[]): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* List files in R2 with optional prefix and pagination
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* // List all files in uploads/ folder
|
|
115
|
+
* const { files, cursor, truncated } = await storage.list({
|
|
116
|
+
* prefix: 'uploads/',
|
|
117
|
+
* limit: 100
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* // Paginate through results
|
|
121
|
+
* if (truncated && cursor) {
|
|
122
|
+
* const nextPage = await storage.list({ prefix: 'uploads/', cursor });
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
list(options?: ListFilesOptions): Promise<ListFilesResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Get file metadata without downloading the file content
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const metadata = await storage.getMetadata('path/to/file.pdf');
|
|
133
|
+
* if (metadata) {
|
|
134
|
+
* console.log(`File size: ${metadata.size} bytes`);
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
getMetadata(key: string): Promise<FileMetadata | null>;
|
|
139
|
+
/**
|
|
140
|
+
* Check if a file exists in R2
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* if (await storage.exists('path/to/file.pdf')) {
|
|
145
|
+
* console.log('File exists');
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
exists(key: string): Promise<boolean>;
|
|
150
|
+
/**
|
|
151
|
+
* Get a presigned URL for uploading a file directly to R2
|
|
152
|
+
* The URL is generated by the platform API (vibe-apps don't have R2 credentials).
|
|
153
|
+
* Use this for large file uploads to avoid worker memory/CPU limits.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const { url, expiresAt } = await storage.getPresignedUploadUrl('uploads/large-video.mp4', {
|
|
158
|
+
* contentType: 'video/mp4',
|
|
159
|
+
* expiresIn: 3600 // 1 hour
|
|
160
|
+
* });
|
|
161
|
+
*
|
|
162
|
+
* // Client can now PUT directly to this URL
|
|
163
|
+
* await fetch(url, { method: 'PUT', body: fileData });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
getPresignedUploadUrl(key: string, options?: {
|
|
167
|
+
contentType?: string;
|
|
168
|
+
expiresIn?: number;
|
|
169
|
+
}): Promise<PresignedUrlResponse>;
|
|
170
|
+
/**
|
|
171
|
+
* Get a presigned URL for downloading a file directly from R2
|
|
172
|
+
* The URL is generated by the platform API (vibe-apps don't have R2 credentials).
|
|
173
|
+
* Use this for large file downloads to avoid worker memory/CPU limits.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const { url, expiresAt } = await storage.getPresignedDownloadUrl('path/to/large-file.zip');
|
|
178
|
+
*
|
|
179
|
+
* // Redirect user to presigned URL or return it to client
|
|
180
|
+
* return Response.redirect(url, 302);
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
getPresignedDownloadUrl(key: string, options?: {
|
|
184
|
+
expiresIn?: number;
|
|
185
|
+
}): Promise<PresignedUrlResponse>;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a FileStorageClient instance
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { createFileStorageClient } from './core-file-storage';
|
|
193
|
+
*
|
|
194
|
+
* app.post('/api/files/upload', async (c) => {
|
|
195
|
+
* const storage = createFileStorageClient(c.env);
|
|
196
|
+
* const formData = await c.req.formData();
|
|
197
|
+
* const file = formData.get('file') as File;
|
|
198
|
+
*
|
|
199
|
+
* const metadata = await storage.upload(file.stream(), {
|
|
200
|
+
* name: file.name,
|
|
201
|
+
* contentType: file.type
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* return c.json({ success: true, data: metadata });
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export declare function createFileStorageClient(env: Env): FileStorageClient;
|
|
209
|
+
import type { Hono } from 'hono';
|
|
210
|
+
/**
|
|
211
|
+
* Mount file storage routes on the Hono app
|
|
212
|
+
* Provides REST API for R2 bucket operations
|
|
213
|
+
*/
|
|
214
|
+
export declare function fileStorageRoutes(app: Hono<{
|
|
215
|
+
Bindings: Env;
|
|
216
|
+
}>): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Integration Entity Utilities
|
|
3
|
+
* DO NOT MODIFY THIS FILE - You may break the integration entity functionality
|
|
4
|
+
*
|
|
5
|
+
* This module provides the IntegrationEntity base class for entities that are backed
|
|
6
|
+
* by external integrations (HubSpot, GitHub, Salesforce, etc.) instead of local storage.
|
|
7
|
+
*
|
|
8
|
+
* IntegrationEntity implements the unified EntityClass interface with EntityContext.
|
|
9
|
+
* All CRUD operations take ctx as the first parameter, matching the Entity class pattern.
|
|
10
|
+
*/
|
|
11
|
+
import type { EntityContext, ListOptions, PaginatedResult, EntityClass } from './core-entities';
|
|
12
|
+
import type { Env } from './core-utils';
|
|
13
|
+
export interface OperationConfig {
|
|
14
|
+
/** API endpoint path (use :id for ID placeholder) */
|
|
15
|
+
endpoint: string;
|
|
16
|
+
}
|
|
17
|
+
export interface IntegrationEntityConfig {
|
|
18
|
+
/** Integration identifier: "hubspot", "github", "salesforce", etc. */
|
|
19
|
+
integrationId: string;
|
|
20
|
+
/** Nango provider config key: "hubspot", "github", etc. */
|
|
21
|
+
providerConfigKey: string;
|
|
22
|
+
operations: {
|
|
23
|
+
list?: OperationConfig;
|
|
24
|
+
get?: OperationConfig;
|
|
25
|
+
create?: OperationConfig;
|
|
26
|
+
update?: OperationConfig;
|
|
27
|
+
delete?: OperationConfig;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract the entity data type from an IntegrationEntity constructor.
|
|
32
|
+
* e.g., IntegrationEntityData<typeof ContactEntity> = HubSpotContact
|
|
33
|
+
*/
|
|
34
|
+
type IntegrationEntityData<T> = T extends abstract new (...args: unknown[]) => IntegrationEntity<infer D> ? D : never;
|
|
35
|
+
/**
|
|
36
|
+
* Base constructor type for IntegrationEntity subclasses
|
|
37
|
+
*/
|
|
38
|
+
type IntegrationEntityCtor = abstract new (...args: unknown[]) => IntegrationEntity<{
|
|
39
|
+
id: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Constructor with required static properties.
|
|
43
|
+
* This ensures the calling class has entityName, config, etc.
|
|
44
|
+
*/
|
|
45
|
+
type IntegrationEntityCtorWithConfig<TCtor> = TCtor & {
|
|
46
|
+
entityName: string;
|
|
47
|
+
config: IntegrationEntityConfig;
|
|
48
|
+
schema?: Record<string, unknown>;
|
|
49
|
+
getConnectionId(env: Env | Record<string, unknown>): string | undefined;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* IntegrationEntityClass type - for type checking integration entity classes
|
|
53
|
+
*/
|
|
54
|
+
export interface IntegrationEntityClass<T extends {
|
|
55
|
+
id: string;
|
|
56
|
+
} = {
|
|
57
|
+
id: string;
|
|
58
|
+
}> extends EntityClass<T> {
|
|
59
|
+
config: IntegrationEntityConfig;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* IntegrationEntity - Base class for entities backed by external integrations
|
|
63
|
+
*
|
|
64
|
+
* Implements the unified EntityClass interface with EntityContext as first parameter.
|
|
65
|
+
* This makes IntegrationEntity and Entity have identical CRUD interfaces.
|
|
66
|
+
*
|
|
67
|
+
* Extend this class to define entities that proxy CRUD operations to external services
|
|
68
|
+
* like HubSpot, GitHub, Salesforce, etc. via the IntegrationClient.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { IntegrationEntity, IntegrationEntityConfig } from './core-integration-entities';
|
|
73
|
+
*
|
|
74
|
+
* interface HubSpotContact {
|
|
75
|
+
* id: string;
|
|
76
|
+
* properties: { email: string; firstname: string; lastname: string };
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* export class ContactEntity extends IntegrationEntity<HubSpotContact> {
|
|
80
|
+
* static readonly entityName = 'hubspot_contact';
|
|
81
|
+
* static readonly config: IntegrationEntityConfig = {
|
|
82
|
+
* integrationId: 'hubspot',
|
|
83
|
+
* providerConfigKey: 'hubspot',
|
|
84
|
+
* operations: {
|
|
85
|
+
* list: { endpoint: '/crm/v3/objects/contacts' },
|
|
86
|
+
* get: { endpoint: '/crm/v3/objects/contacts/:id' },
|
|
87
|
+
* create: { endpoint: '/crm/v3/objects/contacts' },
|
|
88
|
+
* update: { endpoint: '/crm/v3/objects/contacts/:id' },
|
|
89
|
+
* delete: { endpoint: '/crm/v3/objects/contacts/:id' },
|
|
90
|
+
* },
|
|
91
|
+
* };
|
|
92
|
+
* static readonly schema = { fields: ['id', 'email', 'firstname', 'lastname'] };
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* // Usage (same pattern as Entity):
|
|
96
|
+
* const ctx = { env, client };
|
|
97
|
+
* const contacts = await ContactEntity.list(ctx, { limit: 50 });
|
|
98
|
+
* // contacts is PaginatedResult<HubSpotContact> - properly typed!
|
|
99
|
+
* const contact = await ContactEntity.get(ctx, 'contact-123');
|
|
100
|
+
* // contact is HubSpotContact | null - properly typed!
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare abstract class IntegrationEntity<T extends {
|
|
104
|
+
id: string;
|
|
105
|
+
}> {
|
|
106
|
+
static readonly entityName: string;
|
|
107
|
+
static readonly config: IntegrationEntityConfig;
|
|
108
|
+
static readonly schema?: Record<string, unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Get connection ID from env var if explicitly set.
|
|
111
|
+
* Returns undefined when not set -- the proxy resolves connections
|
|
112
|
+
* dynamically via workspaceId + provider, so this is optional.
|
|
113
|
+
*/
|
|
114
|
+
static getConnectionId(env: Env | Record<string, unknown>): string | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Get the IntegrationClient from context
|
|
117
|
+
* Throws if client is not available
|
|
118
|
+
*/
|
|
119
|
+
private static getClient;
|
|
120
|
+
/**
|
|
121
|
+
* List entities from the integration with optional filters
|
|
122
|
+
* Filters are passed through as query parameters to the external API
|
|
123
|
+
* Note: Entities needing POST-based filtering should override this method
|
|
124
|
+
*/
|
|
125
|
+
static list<TCtor extends IntegrationEntityCtor>(this: IntegrationEntityCtorWithConfig<TCtor>, ctx: EntityContext, options?: ListOptions): Promise<PaginatedResult<IntegrationEntityData<TCtor>>>;
|
|
126
|
+
/**
|
|
127
|
+
* Get a single entity by ID from the integration
|
|
128
|
+
*/
|
|
129
|
+
static get<TCtor extends IntegrationEntityCtor>(this: IntegrationEntityCtorWithConfig<TCtor>, ctx: EntityContext, id: string): Promise<IntegrationEntityData<TCtor> | null>;
|
|
130
|
+
/**
|
|
131
|
+
* Create a new entity in the integration
|
|
132
|
+
*/
|
|
133
|
+
static create<TCtor extends IntegrationEntityCtor>(this: IntegrationEntityCtorWithConfig<TCtor>, ctx: EntityContext, data: Partial<IntegrationEntityData<TCtor>>): Promise<IntegrationEntityData<TCtor>>;
|
|
134
|
+
/**
|
|
135
|
+
* Update an entity in the integration
|
|
136
|
+
*/
|
|
137
|
+
static update<TCtor extends IntegrationEntityCtor>(this: IntegrationEntityCtorWithConfig<TCtor>, ctx: EntityContext, id: string, data: Partial<IntegrationEntityData<TCtor>>): Promise<IntegrationEntityData<TCtor>>;
|
|
138
|
+
/**
|
|
139
|
+
* Delete an entity from the integration
|
|
140
|
+
*/
|
|
141
|
+
static delete<TCtor extends IntegrationEntityCtor>(this: IntegrationEntityCtorWithConfig<TCtor>, ctx: EntityContext, id: string): Promise<void>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Type guard to check if a class is an IntegrationEntity
|
|
145
|
+
*/
|
|
146
|
+
export declare function isIntegrationEntity(cls: unknown): cls is IntegrationEntityClass;
|
|
147
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Integration Utilities
|
|
3
|
+
* DO NOT MODIFY THIS FILE - You may break the integration functionality
|
|
4
|
+
*
|
|
5
|
+
* This module provides utilities for making API calls to third-party services
|
|
6
|
+
* through the platform's multi-provider integration system.
|
|
7
|
+
*
|
|
8
|
+
* Supported providers:
|
|
9
|
+
* - Nango: Self-hosted OAuth for 200+ APIs
|
|
10
|
+
* - Pipedream: Managed OAuth clients for business apps
|
|
11
|
+
* - AyrShare: Social media (Instagram, TikTok, Facebook, Twitter, etc.)
|
|
12
|
+
*
|
|
13
|
+
* Provider routing is automatic - the code is provider-agnostic.
|
|
14
|
+
* Use the IntegrationClient class to interact with connected third-party services.
|
|
15
|
+
*/
|
|
16
|
+
import { type Env } from './core-utils';
|
|
17
|
+
import type { NangoActionResponse, NangoProxyOptions } from './shared/core-types';
|
|
18
|
+
export type { NangoActionResponse, NangoProxyOptions, };
|
|
19
|
+
/**
|
|
20
|
+
* Integration requirement definition
|
|
21
|
+
* Used in integration-requirements.ts to declare which integrations your app needs
|
|
22
|
+
*/
|
|
23
|
+
export interface IntegrationRequirement {
|
|
24
|
+
/** Integration provider ID (e.g., 'github', 'slack', 'hubspot') */
|
|
25
|
+
integrationId: string;
|
|
26
|
+
/** Why this integration is needed */
|
|
27
|
+
reason: string;
|
|
28
|
+
/** Whether the app cannot function without this integration */
|
|
29
|
+
required: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* IntegrationClient - Wrapper for making API calls through Nango proxy
|
|
33
|
+
*
|
|
34
|
+
* Nango handles OAuth authentication and proxies requests to external APIs.
|
|
35
|
+
*
|
|
36
|
+
* For structured data access, prefer IntegrationEntity classes from './core-integration-entities'.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const client = new IntegrationClient(env);
|
|
41
|
+
*
|
|
42
|
+
* // GET request to HubSpot API
|
|
43
|
+
* const contacts = await client.proxy<{ results: Contact[] }>({
|
|
44
|
+
* method: 'GET',
|
|
45
|
+
* endpoint: '/crm/v3/objects/contacts',
|
|
46
|
+
* providerConfigKey: 'hubspot'
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* // POST request to create a GitHub issue
|
|
50
|
+
* const issue = await client.proxy({
|
|
51
|
+
* method: 'POST',
|
|
52
|
+
* endpoint: '/repos/owner/repo/issues',
|
|
53
|
+
* providerConfigKey: 'github',
|
|
54
|
+
* data: { title: 'New Issue', body: 'Issue description' }
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare class IntegrationClient {
|
|
59
|
+
private env;
|
|
60
|
+
private readonly proxyUrl;
|
|
61
|
+
private readonly proxyToken;
|
|
62
|
+
constructor(env: Env);
|
|
63
|
+
/**
|
|
64
|
+
* Fire-and-forget event emission for integration activity.
|
|
65
|
+
* Does not require waitUntil context - errors are silently swallowed.
|
|
66
|
+
*/
|
|
67
|
+
private fireEvent;
|
|
68
|
+
/**
|
|
69
|
+
* Check if integrations are configured
|
|
70
|
+
*/
|
|
71
|
+
isConfigured(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Check if a specific integration has a connection configured.
|
|
74
|
+
* The proxy resolves connections dynamically, so this returns true
|
|
75
|
+
* when the proxy is configured even without explicit env vars.
|
|
76
|
+
*/
|
|
77
|
+
hasConnection(provider: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Normalize integration ID to canonical form for env var lookup.
|
|
80
|
+
* MUST be identical to platform's normalizeIntegrationId() in provider-config.ts.
|
|
81
|
+
*
|
|
82
|
+
* Strips provider prefixes (pd_, nango_, ayrshare_), common suffixes (-demo, _v1),
|
|
83
|
+
* and removes all separators for consistent cross-provider matching.
|
|
84
|
+
*
|
|
85
|
+
* Examples:
|
|
86
|
+
* - "pd_sendgrid" -> "sendgrid"
|
|
87
|
+
* - "google-drive" -> "googledrive"
|
|
88
|
+
* - "pd_google_drive" -> "googledrive"
|
|
89
|
+
* - "hubspot-demo" -> "hubspot"
|
|
90
|
+
*/
|
|
91
|
+
private normalizeIntegrationId;
|
|
92
|
+
private getWorkspaceIntegrationId;
|
|
93
|
+
private getLegacyConnectionId;
|
|
94
|
+
/**
|
|
95
|
+
* Make a request to external API via platform proxy.
|
|
96
|
+
* Uses platformFetch which routes through WorkspaceObject DO in production
|
|
97
|
+
* to avoid 522 timeouts from WfP workers.
|
|
98
|
+
*/
|
|
99
|
+
private makeRequest;
|
|
100
|
+
/**
|
|
101
|
+
* Make a proxy request to an external API through the integration proxy.
|
|
102
|
+
* Use this for all API calls to connected third-party services.
|
|
103
|
+
* Supports multiple providers (Nango, Pipedream, AyrShare) - routing is automatic.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // GET request
|
|
108
|
+
* const result = await client.proxy<{ results: Contact[] }>({
|
|
109
|
+
* method: 'GET',
|
|
110
|
+
* endpoint: '/crm/v3/objects/contacts',
|
|
111
|
+
* providerConfigKey: 'hubspot'
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* // POST request with data
|
|
115
|
+
* const result = await client.proxy({
|
|
116
|
+
* method: 'POST',
|
|
117
|
+
* endpoint: '/repos/owner/repo/issues',
|
|
118
|
+
* providerConfigKey: 'github',
|
|
119
|
+
* data: { title: 'Bug Report', body: 'Description' }
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
proxy<T = unknown>(options: NangoProxyOptions): Promise<NangoActionResponse<T>>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create an integration client instance
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* import { createIntegrationClient } from './core-integrations';
|
|
131
|
+
*
|
|
132
|
+
* app.get('/api/hubspot/contacts', async (c) => {
|
|
133
|
+
* const client = createIntegrationClient(c.env);
|
|
134
|
+
* const result = await client.proxy<{ results: Contact[] }>({
|
|
135
|
+
* method: 'GET',
|
|
136
|
+
* endpoint: '/crm/v3/objects/contacts',
|
|
137
|
+
* providerConfigKey: 'hubspot'
|
|
138
|
+
* });
|
|
139
|
+
* return c.json(result);
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function createIntegrationClient(env: Env): IntegrationClient;
|