@codmir/sdk 0.1.1 → 0.1.2
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 +63 -7
- package/dist/browser/index.cjs +24 -2
- package/dist/browser/index.js +1 -1
- package/dist/{chunk-T7OAAOG2.js → chunk-4W5YK72Y.js} +44 -2
- package/dist/council/index.cjs +2064 -0
- package/dist/council/index.js +2016 -0
- package/dist/index.cjs +40 -2
- package/dist/index.js +1 -1
- package/dist/nextjs/index.cjs +24 -2
- package/dist/nextjs/index.js +1 -1
- package/dist/overseer/index.cjs +44 -2
- package/dist/overseer/index.js +9 -1
- package/dist/react-native/index.cjs +24 -2
- package/dist/react-native/index.js +1 -1
- package/dist/serverless/index.cjs +1260 -0
- package/dist/serverless/index.js +1213 -0
- package/package.json +18 -3
- package/dist/browser/index.d.cts +0 -47
- package/dist/browser/index.d.ts +0 -47
- package/dist/client.d.cts +0 -52
- package/dist/client.d.ts +0 -52
- package/dist/index-BlgYnCLd.d.cts +0 -171
- package/dist/index-BlgYnCLd.d.ts +0 -171
- package/dist/index.d.cts +0 -4
- package/dist/index.d.ts +0 -4
- package/dist/nextjs/index.d.cts +0 -77
- package/dist/nextjs/index.d.ts +0 -77
- package/dist/overseer/index.d.cts +0 -1
- package/dist/overseer/index.d.ts +0 -1
- package/dist/react-native/index.d.cts +0 -95
- package/dist/react-native/index.d.ts +0 -95
- package/dist/replay/index.d.cts +0 -206
- package/dist/replay/index.d.ts +0 -206
- package/dist/types.d.cts +0 -281
- package/dist/types.d.ts +0 -281
|
@@ -0,0 +1,1213 @@
|
|
|
1
|
+
import "../chunk-MLKGABMK.js";
|
|
2
|
+
|
|
3
|
+
// src/serverless/execution.ts
|
|
4
|
+
var POOL_CONFIG = {
|
|
5
|
+
modal: {
|
|
6
|
+
maxTimeout: 3600,
|
|
7
|
+
// 1 hour
|
|
8
|
+
coldStartMs: 300,
|
|
9
|
+
costPerMinute: 2,
|
|
10
|
+
gpuSupport: true,
|
|
11
|
+
carbonScore: 60
|
|
12
|
+
},
|
|
13
|
+
fargate: {
|
|
14
|
+
maxTimeout: 86400,
|
|
15
|
+
// 24 hours
|
|
16
|
+
coldStartMs: 6e4,
|
|
17
|
+
costPerMinute: 1,
|
|
18
|
+
gpuSupport: false,
|
|
19
|
+
carbonScore: 50
|
|
20
|
+
},
|
|
21
|
+
github: {
|
|
22
|
+
maxTimeout: 21600,
|
|
23
|
+
// 6 hours
|
|
24
|
+
coldStartMs: 3e4,
|
|
25
|
+
costPerMinute: 0,
|
|
26
|
+
gpuSupport: false,
|
|
27
|
+
carbonScore: 70
|
|
28
|
+
},
|
|
29
|
+
community: {
|
|
30
|
+
maxTimeout: 3600,
|
|
31
|
+
coldStartMs: 5e3,
|
|
32
|
+
costPerMinute: 0.5,
|
|
33
|
+
gpuSupport: false,
|
|
34
|
+
carbonScore: 80
|
|
35
|
+
},
|
|
36
|
+
local: {
|
|
37
|
+
maxTimeout: Infinity,
|
|
38
|
+
coldStartMs: 0,
|
|
39
|
+
costPerMinute: 0,
|
|
40
|
+
gpuSupport: false,
|
|
41
|
+
carbonScore: 100
|
|
42
|
+
},
|
|
43
|
+
auto: {
|
|
44
|
+
maxTimeout: Infinity,
|
|
45
|
+
coldStartMs: 0,
|
|
46
|
+
costPerMinute: 0,
|
|
47
|
+
gpuSupport: true,
|
|
48
|
+
carbonScore: 50
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function selectPool(config, grace) {
|
|
52
|
+
if (config.pool && config.pool !== "auto") {
|
|
53
|
+
return config.pool;
|
|
54
|
+
}
|
|
55
|
+
const timeout = config.timeout || 300;
|
|
56
|
+
const needsGpu = !!config.gpu;
|
|
57
|
+
const preferGreen = grace?.preferGreen ?? false;
|
|
58
|
+
if (needsGpu) {
|
|
59
|
+
return "modal";
|
|
60
|
+
}
|
|
61
|
+
if (timeout > 3600) {
|
|
62
|
+
return "fargate";
|
|
63
|
+
}
|
|
64
|
+
if (grace?.maxBudget !== void 0 && grace.maxBudget < 10) {
|
|
65
|
+
return "github";
|
|
66
|
+
}
|
|
67
|
+
if (preferGreen) {
|
|
68
|
+
return timeout > 3600 ? "fargate" : "github";
|
|
69
|
+
}
|
|
70
|
+
return "modal";
|
|
71
|
+
}
|
|
72
|
+
async function execute(request) {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const pool = selectPool(request.config, request.grace);
|
|
75
|
+
const executionId = `exec_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
76
|
+
console.log(`[codmir] Executing ${request.functionName} on ${pool}`);
|
|
77
|
+
try {
|
|
78
|
+
let result;
|
|
79
|
+
switch (pool) {
|
|
80
|
+
case "local":
|
|
81
|
+
throw new Error("Use executeLocal for local execution");
|
|
82
|
+
case "modal":
|
|
83
|
+
result = await executeOnModal(request);
|
|
84
|
+
break;
|
|
85
|
+
case "fargate":
|
|
86
|
+
result = await executeOnFargate(request);
|
|
87
|
+
break;
|
|
88
|
+
case "github":
|
|
89
|
+
result = await executeOnGitHub(request);
|
|
90
|
+
break;
|
|
91
|
+
case "community":
|
|
92
|
+
result = await executeOnCommunity(request);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
result = await executeOnModal(request);
|
|
96
|
+
}
|
|
97
|
+
const durationMs = Date.now() - startTime;
|
|
98
|
+
const creditsUsed = Math.ceil(durationMs / 6e4) * POOL_CONFIG[pool].costPerMinute;
|
|
99
|
+
return {
|
|
100
|
+
id: executionId,
|
|
101
|
+
status: "completed",
|
|
102
|
+
result,
|
|
103
|
+
metrics: {
|
|
104
|
+
durationMs,
|
|
105
|
+
coldStartMs: POOL_CONFIG[pool].coldStartMs,
|
|
106
|
+
creditsUsed,
|
|
107
|
+
carbonFootprint: estimateCarbonFootprint(durationMs, pool),
|
|
108
|
+
pool
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
id: executionId,
|
|
114
|
+
status: "failed",
|
|
115
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
116
|
+
metrics: {
|
|
117
|
+
durationMs: Date.now() - startTime,
|
|
118
|
+
creditsUsed: 0,
|
|
119
|
+
pool
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function executeAsync(request) {
|
|
125
|
+
const executionId = `exec_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
126
|
+
const pool = selectPool(request.config, request.grace);
|
|
127
|
+
console.log(`[codmir] Spawning async execution ${executionId} on ${pool}`);
|
|
128
|
+
setImmediate(async () => {
|
|
129
|
+
try {
|
|
130
|
+
await execute(request);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`[codmir] Async execution ${executionId} failed:`, error);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return executionId;
|
|
136
|
+
}
|
|
137
|
+
async function executeLocal(handler, args) {
|
|
138
|
+
return handler(...args);
|
|
139
|
+
}
|
|
140
|
+
async function executeOnModal(request) {
|
|
141
|
+
const apiUrl = process.env.CODMIR_API_URL || "https://codmir.com";
|
|
142
|
+
const apiKey = process.env.CODMIR_API_KEY;
|
|
143
|
+
if (!apiKey) {
|
|
144
|
+
throw new Error("CODMIR_API_KEY is required for remote execution");
|
|
145
|
+
}
|
|
146
|
+
const response = await fetch(`${apiUrl}/api/cloud/jobs`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
"Authorization": `Bearer ${apiKey}`
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
pool: "modal",
|
|
154
|
+
appName: request.appName,
|
|
155
|
+
functionName: request.functionName,
|
|
156
|
+
args: request.args,
|
|
157
|
+
config: request.config,
|
|
158
|
+
grace: request.grace
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw new Error(`Modal execution failed: ${response.statusText}`);
|
|
163
|
+
}
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
if (request.wait !== false) {
|
|
166
|
+
return await pollForResult(data.jobId);
|
|
167
|
+
}
|
|
168
|
+
return data.jobId;
|
|
169
|
+
}
|
|
170
|
+
async function executeOnFargate(request) {
|
|
171
|
+
const apiUrl = process.env.CODMIR_API_URL || "https://codmir.com";
|
|
172
|
+
const apiKey = process.env.CODMIR_API_KEY;
|
|
173
|
+
if (!apiKey) {
|
|
174
|
+
throw new Error("CODMIR_API_KEY is required for remote execution");
|
|
175
|
+
}
|
|
176
|
+
const response = await fetch(`${apiUrl}/api/cloud/jobs`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"Authorization": `Bearer ${apiKey}`
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
pool: "fargate",
|
|
184
|
+
appName: request.appName,
|
|
185
|
+
functionName: request.functionName,
|
|
186
|
+
args: request.args,
|
|
187
|
+
config: request.config,
|
|
188
|
+
grace: request.grace
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new Error(`Fargate execution failed: ${response.statusText}`);
|
|
193
|
+
}
|
|
194
|
+
const data = await response.json();
|
|
195
|
+
if (request.wait !== false) {
|
|
196
|
+
return await pollForResult(data.jobId);
|
|
197
|
+
}
|
|
198
|
+
return data.jobId;
|
|
199
|
+
}
|
|
200
|
+
async function executeOnGitHub(request) {
|
|
201
|
+
const apiUrl = process.env.CODMIR_API_URL || "https://codmir.com";
|
|
202
|
+
const apiKey = process.env.CODMIR_API_KEY;
|
|
203
|
+
if (!apiKey) {
|
|
204
|
+
throw new Error("CODMIR_API_KEY is required for remote execution");
|
|
205
|
+
}
|
|
206
|
+
const response = await fetch(`${apiUrl}/api/cloud/jobs`, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: {
|
|
209
|
+
"Content-Type": "application/json",
|
|
210
|
+
"Authorization": `Bearer ${apiKey}`
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({
|
|
213
|
+
pool: "github",
|
|
214
|
+
appName: request.appName,
|
|
215
|
+
functionName: request.functionName,
|
|
216
|
+
args: request.args,
|
|
217
|
+
config: request.config,
|
|
218
|
+
grace: request.grace
|
|
219
|
+
})
|
|
220
|
+
});
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
throw new Error(`GitHub execution failed: ${response.statusText}`);
|
|
223
|
+
}
|
|
224
|
+
const data = await response.json();
|
|
225
|
+
if (request.wait !== false) {
|
|
226
|
+
return await pollForResult(data.jobId);
|
|
227
|
+
}
|
|
228
|
+
return data.jobId;
|
|
229
|
+
}
|
|
230
|
+
async function executeOnCommunity(request) {
|
|
231
|
+
return executeOnModal(request);
|
|
232
|
+
}
|
|
233
|
+
async function pollForResult(jobId) {
|
|
234
|
+
const apiUrl = process.env.CODMIR_API_URL || "https://codmir.com";
|
|
235
|
+
const apiKey = process.env.CODMIR_API_KEY;
|
|
236
|
+
const maxWait = 3e5;
|
|
237
|
+
const pollInterval = 1e3;
|
|
238
|
+
const startTime = Date.now();
|
|
239
|
+
while (Date.now() - startTime < maxWait) {
|
|
240
|
+
const response = await fetch(`${apiUrl}/api/cloud/jobs/${jobId}`, {
|
|
241
|
+
headers: {
|
|
242
|
+
"Authorization": `Bearer ${apiKey}`
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
if (!response.ok) {
|
|
246
|
+
throw new Error(`Failed to get job status: ${response.statusText}`);
|
|
247
|
+
}
|
|
248
|
+
const data = await response.json();
|
|
249
|
+
if (data.status === "COMPLETED") {
|
|
250
|
+
return data.output;
|
|
251
|
+
}
|
|
252
|
+
if (data.status === "FAILED") {
|
|
253
|
+
throw new Error(data.error || "Execution failed");
|
|
254
|
+
}
|
|
255
|
+
if (data.status === "CANCELLED") {
|
|
256
|
+
throw new Error("Execution was cancelled");
|
|
257
|
+
}
|
|
258
|
+
await sleep(pollInterval);
|
|
259
|
+
}
|
|
260
|
+
throw new Error("Execution timed out waiting for result");
|
|
261
|
+
}
|
|
262
|
+
function estimateCarbonFootprint(durationMs, pool) {
|
|
263
|
+
const baseEmission = 0.5;
|
|
264
|
+
const minutes = durationMs / 6e4;
|
|
265
|
+
const carbonScore = POOL_CONFIG[pool].carbonScore;
|
|
266
|
+
const multiplier = 2 - carbonScore / 100;
|
|
267
|
+
return baseEmission * minutes * multiplier;
|
|
268
|
+
}
|
|
269
|
+
function sleep(ms) {
|
|
270
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/serverless/function.ts
|
|
274
|
+
var CodmirFunction = class {
|
|
275
|
+
name;
|
|
276
|
+
config;
|
|
277
|
+
app;
|
|
278
|
+
handler;
|
|
279
|
+
executionCount = 0;
|
|
280
|
+
constructor(app2, config, handler) {
|
|
281
|
+
this.app = app2;
|
|
282
|
+
this.config = config;
|
|
283
|
+
this.handler = handler;
|
|
284
|
+
this.name = config.name || this.generateName();
|
|
285
|
+
}
|
|
286
|
+
generateName() {
|
|
287
|
+
return `${this.app.name}_fn_${Date.now().toString(36)}`;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Execute the function (auto-selects local/remote based on environment).
|
|
291
|
+
*/
|
|
292
|
+
async call(...args) {
|
|
293
|
+
if (this.shouldRunLocally()) {
|
|
294
|
+
return this.local(...args);
|
|
295
|
+
}
|
|
296
|
+
return this.remote(...args);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Execute locally (for development).
|
|
300
|
+
*/
|
|
301
|
+
async local(...args) {
|
|
302
|
+
const startTime = Date.now();
|
|
303
|
+
this.executionCount++;
|
|
304
|
+
try {
|
|
305
|
+
const result = await executeLocal(this.handler, args);
|
|
306
|
+
console.log(`[codmir] ${this.name} completed locally in ${Date.now() - startTime}ms`);
|
|
307
|
+
return result;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`[codmir] ${this.name} failed locally:`, error);
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Execute remotely in the cloud.
|
|
315
|
+
*/
|
|
316
|
+
async remote(...args) {
|
|
317
|
+
const result = await execute({
|
|
318
|
+
appName: this.app.name,
|
|
319
|
+
functionName: this.name,
|
|
320
|
+
args,
|
|
321
|
+
config: this.config,
|
|
322
|
+
grace: this.config.grace
|
|
323
|
+
});
|
|
324
|
+
if (result.status === "completed" && result.result !== void 0) {
|
|
325
|
+
return result.result;
|
|
326
|
+
}
|
|
327
|
+
if (result.status === "failed") {
|
|
328
|
+
throw new Error(result.error || "Remote execution failed");
|
|
329
|
+
}
|
|
330
|
+
throw new Error(`Unexpected execution status: ${result.status}`);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Execute asynchronously and return execution ID.
|
|
334
|
+
*/
|
|
335
|
+
async spawn(...args) {
|
|
336
|
+
const executionId = await executeAsync({
|
|
337
|
+
appName: this.app.name,
|
|
338
|
+
functionName: this.name,
|
|
339
|
+
args,
|
|
340
|
+
config: this.config,
|
|
341
|
+
grace: this.config.grace
|
|
342
|
+
});
|
|
343
|
+
return executionId;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Execute with streaming results.
|
|
347
|
+
*/
|
|
348
|
+
async *stream(...args) {
|
|
349
|
+
const executionId = await this.spawn(...args);
|
|
350
|
+
yield { type: "started", executionId };
|
|
351
|
+
yield { type: "progress", percent: 0, message: "Starting..." };
|
|
352
|
+
try {
|
|
353
|
+
const result = await this.remote(...args);
|
|
354
|
+
yield { type: "progress", percent: 100, message: "Completed" };
|
|
355
|
+
yield { type: "completed", result };
|
|
356
|
+
} catch (error) {
|
|
357
|
+
yield { type: "failed", error: error instanceof Error ? error.message : "Unknown error" };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Execute in batch with multiple inputs.
|
|
362
|
+
*/
|
|
363
|
+
async map(inputs, config = {}) {
|
|
364
|
+
const { maxConcurrency = 10, continueOnError = true } = config;
|
|
365
|
+
const successful = [];
|
|
366
|
+
const failed = [];
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
let totalCredits = 0;
|
|
369
|
+
for (let i = 0; i < inputs.length; i += maxConcurrency) {
|
|
370
|
+
const batch = inputs.slice(i, i + maxConcurrency);
|
|
371
|
+
const promises = batch.map(async (input, batchIndex) => {
|
|
372
|
+
const index = i + batchIndex;
|
|
373
|
+
try {
|
|
374
|
+
const result = await this.call(input);
|
|
375
|
+
successful.push({ index, result });
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (!continueOnError) throw error;
|
|
378
|
+
failed.push({
|
|
379
|
+
index,
|
|
380
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
await Promise.all(promises);
|
|
385
|
+
if (config.onProgress) {
|
|
386
|
+
config.onProgress(successful.length + failed.length, inputs.length);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
total: inputs.length,
|
|
391
|
+
successful,
|
|
392
|
+
failed,
|
|
393
|
+
metrics: {
|
|
394
|
+
totalDurationMs: Date.now() - startTime,
|
|
395
|
+
totalCreditsUsed: totalCredits,
|
|
396
|
+
totalCarbonFootprint: 0
|
|
397
|
+
// Would be calculated from execution metrics
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Execute with specific options.
|
|
403
|
+
*/
|
|
404
|
+
async withOptions(options, ...args) {
|
|
405
|
+
const mergedConfig = {
|
|
406
|
+
...this.config,
|
|
407
|
+
timeout: options.timeout ?? this.config.timeout,
|
|
408
|
+
pool: options.pool ?? this.config.pool,
|
|
409
|
+
grace: { ...this.config.grace, ...options.grace }
|
|
410
|
+
};
|
|
411
|
+
const result = await execute({
|
|
412
|
+
appName: this.app.name,
|
|
413
|
+
functionName: this.name,
|
|
414
|
+
args,
|
|
415
|
+
config: mergedConfig,
|
|
416
|
+
grace: mergedConfig.grace,
|
|
417
|
+
wait: options.wait ?? true,
|
|
418
|
+
callbackUrl: options.callbackUrl
|
|
419
|
+
});
|
|
420
|
+
if (result.status === "completed" && result.result !== void 0) {
|
|
421
|
+
return result.result;
|
|
422
|
+
}
|
|
423
|
+
throw new Error(result.error || `Execution ${result.status}`);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get execution status by ID.
|
|
427
|
+
*/
|
|
428
|
+
async getStatus(executionId) {
|
|
429
|
+
return {
|
|
430
|
+
id: executionId,
|
|
431
|
+
status: "pending",
|
|
432
|
+
metrics: {
|
|
433
|
+
durationMs: 0,
|
|
434
|
+
creditsUsed: 0,
|
|
435
|
+
pool: "auto"
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Cancel an execution.
|
|
441
|
+
*/
|
|
442
|
+
async cancel(executionId) {
|
|
443
|
+
console.log(`[codmir] Cancelling execution: ${executionId}`);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Check if function should run locally.
|
|
447
|
+
*/
|
|
448
|
+
shouldRunLocally() {
|
|
449
|
+
const env = process.env.NODE_ENV || "development";
|
|
450
|
+
const forceRemote = process.env.CODMIR_FORCE_REMOTE === "true";
|
|
451
|
+
const forceLocal = process.env.CODMIR_FORCE_LOCAL === "true";
|
|
452
|
+
if (forceRemote) return false;
|
|
453
|
+
if (forceLocal) return true;
|
|
454
|
+
if (env === "development") return true;
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Make the function callable directly.
|
|
459
|
+
*/
|
|
460
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.promisify.custom")](...args) {
|
|
461
|
+
return this.call(...args);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function createFunction(app2, config, handler) {
|
|
465
|
+
return new CodmirFunction(app2, config, handler);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/serverless/sandbox.ts
|
|
469
|
+
var CodmirSandbox = class {
|
|
470
|
+
name;
|
|
471
|
+
config;
|
|
472
|
+
app;
|
|
473
|
+
sandboxId;
|
|
474
|
+
status = "created";
|
|
475
|
+
tunnelUrl;
|
|
476
|
+
constructor(app2, config) {
|
|
477
|
+
this.app = app2;
|
|
478
|
+
this.config = config;
|
|
479
|
+
this.name = config.name || `sandbox_${Date.now().toString(36)}`;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Spawn the sandbox environment.
|
|
483
|
+
*/
|
|
484
|
+
async spawn() {
|
|
485
|
+
if (this.status !== "created") {
|
|
486
|
+
throw new Error(`Sandbox is already ${this.status}`);
|
|
487
|
+
}
|
|
488
|
+
this.status = "spawning";
|
|
489
|
+
console.log(`[codmir] Spawning sandbox: ${this.name}`);
|
|
490
|
+
this.sandboxId = `sb_${Date.now().toString(36)}`;
|
|
491
|
+
this.status = "running";
|
|
492
|
+
if (this.config.webAccess) {
|
|
493
|
+
this.tunnelUrl = `https://${this.sandboxId}.sandbox.codmir.run`;
|
|
494
|
+
console.log(`[codmir] Tunnel available at: ${this.tunnelUrl}`);
|
|
495
|
+
}
|
|
496
|
+
console.log(`[codmir] Sandbox ${this.name} is ready`);
|
|
497
|
+
return this;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Execute a command in the sandbox.
|
|
501
|
+
*/
|
|
502
|
+
async exec(command, options = {}) {
|
|
503
|
+
this.ensureRunning();
|
|
504
|
+
const startTime = Date.now();
|
|
505
|
+
console.log(`[codmir] exec: ${command}`);
|
|
506
|
+
return {
|
|
507
|
+
stdout: "",
|
|
508
|
+
stderr: "",
|
|
509
|
+
exitCode: 0,
|
|
510
|
+
durationMs: Date.now() - startTime
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Execute a command and stream output.
|
|
515
|
+
*/
|
|
516
|
+
async *execStream(command) {
|
|
517
|
+
this.ensureRunning();
|
|
518
|
+
yield { type: "started", executionId: `exec_${Date.now()}` };
|
|
519
|
+
yield { type: "log", message: `$ ${command}`, level: "info" };
|
|
520
|
+
yield { type: "completed", result: { exitCode: 0 } };
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Write a file to the sandbox.
|
|
524
|
+
*/
|
|
525
|
+
async writeFile(path, content) {
|
|
526
|
+
this.ensureRunning();
|
|
527
|
+
console.log(`[codmir] writeFile: ${path} (${content.length} bytes)`);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Read a file from the sandbox.
|
|
531
|
+
*/
|
|
532
|
+
async readFile(path) {
|
|
533
|
+
this.ensureRunning();
|
|
534
|
+
console.log(`[codmir] readFile: ${path}`);
|
|
535
|
+
return "";
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* List files in a directory.
|
|
539
|
+
*/
|
|
540
|
+
async listFiles(path = "/") {
|
|
541
|
+
this.ensureRunning();
|
|
542
|
+
const result = await this.exec(`ls -la ${path}`);
|
|
543
|
+
return result.stdout.split("\n").filter(Boolean);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Open a tunnel for web access.
|
|
547
|
+
*/
|
|
548
|
+
async tunnel(port) {
|
|
549
|
+
this.ensureRunning();
|
|
550
|
+
if (!this.config.webAccess) {
|
|
551
|
+
throw new Error("Web access not enabled for this sandbox");
|
|
552
|
+
}
|
|
553
|
+
const url = `https://${this.sandboxId}-${port}.sandbox.codmir.run`;
|
|
554
|
+
console.log(`[codmir] Tunnel opened: ${url} -> localhost:${port}`);
|
|
555
|
+
return url;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Get the tunnel URL (if web access enabled).
|
|
559
|
+
*/
|
|
560
|
+
getTunnelUrl() {
|
|
561
|
+
return this.tunnelUrl;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Check if sandbox is running.
|
|
565
|
+
*/
|
|
566
|
+
isRunning() {
|
|
567
|
+
return this.status === "running";
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get sandbox status.
|
|
571
|
+
*/
|
|
572
|
+
getStatus() {
|
|
573
|
+
return this.status;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Terminate the sandbox.
|
|
577
|
+
*/
|
|
578
|
+
async terminate() {
|
|
579
|
+
if (this.status === "terminated") {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
console.log(`[codmir] Terminating sandbox: ${this.name}`);
|
|
583
|
+
this.status = "terminated";
|
|
584
|
+
this.sandboxId = void 0;
|
|
585
|
+
this.tunnelUrl = void 0;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Keep sandbox alive (extend timeout).
|
|
589
|
+
*/
|
|
590
|
+
async keepAlive(durationSeconds = 300) {
|
|
591
|
+
this.ensureRunning();
|
|
592
|
+
console.log(`[codmir] Extending sandbox timeout by ${durationSeconds}s`);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Create a checkpoint of the current state.
|
|
596
|
+
*/
|
|
597
|
+
async checkpoint(name) {
|
|
598
|
+
this.ensureRunning();
|
|
599
|
+
const checkpointId = `cp_${Date.now().toString(36)}`;
|
|
600
|
+
console.log(`[codmir] Created checkpoint: ${checkpointId}`);
|
|
601
|
+
return checkpointId;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Restore from a checkpoint.
|
|
605
|
+
*/
|
|
606
|
+
async restore(checkpointId) {
|
|
607
|
+
this.ensureRunning();
|
|
608
|
+
console.log(`[codmir] Restoring from checkpoint: ${checkpointId}`);
|
|
609
|
+
}
|
|
610
|
+
ensureRunning() {
|
|
611
|
+
if (this.status !== "running") {
|
|
612
|
+
throw new Error(`Sandbox is not running (status: ${this.status})`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
function createSandbox(app2, config) {
|
|
617
|
+
return new CodmirSandbox(app2, config);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/serverless/app.ts
|
|
621
|
+
var CodmirApp = class {
|
|
622
|
+
name;
|
|
623
|
+
config;
|
|
624
|
+
functions = /* @__PURE__ */ new Map();
|
|
625
|
+
sandboxes = /* @__PURE__ */ new Map();
|
|
626
|
+
deployed = false;
|
|
627
|
+
constructor(config) {
|
|
628
|
+
this.name = config.name;
|
|
629
|
+
this.config = {
|
|
630
|
+
grace: { enabled: true, preferGreen: false },
|
|
631
|
+
...config
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Create a serverless function.
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```typescript
|
|
639
|
+
* const processData = app.function({ cpu: 2, timeout: 300 })(
|
|
640
|
+
* async (data: string) => {
|
|
641
|
+
* return data.toUpperCase();
|
|
642
|
+
* }
|
|
643
|
+
* );
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
function(config = {}) {
|
|
647
|
+
return (handler) => {
|
|
648
|
+
const mergedConfig = {
|
|
649
|
+
...config,
|
|
650
|
+
image: config.image || this.config.image,
|
|
651
|
+
secrets: [...this.config.secrets || [], ...config.secrets || []],
|
|
652
|
+
volumes: { ...this.config.volumes, ...config.volumes },
|
|
653
|
+
grace: { ...this.config.grace, ...config.grace }
|
|
654
|
+
};
|
|
655
|
+
const fn2 = createFunction(this, mergedConfig, handler);
|
|
656
|
+
this.functions.set(fn2.name, fn2);
|
|
657
|
+
return fn2;
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Create a sandbox for interactive execution.
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* ```typescript
|
|
665
|
+
* const sandbox = await app.sandbox({ image: 'python:3.11' }).spawn();
|
|
666
|
+
* await sandbox.exec('pip install numpy');
|
|
667
|
+
* const result = await sandbox.exec('python -c "import numpy; print(numpy.__version__)"');
|
|
668
|
+
* await sandbox.terminate();
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
sandbox(config) {
|
|
672
|
+
const mergedConfig = {
|
|
673
|
+
...config,
|
|
674
|
+
secrets: [...this.config.secrets || [], ...config.secrets || []],
|
|
675
|
+
volumes: { ...this.config.volumes, ...config.volumes },
|
|
676
|
+
grace: { ...this.config.grace, ...config.grace }
|
|
677
|
+
};
|
|
678
|
+
const sandbox = createSandbox(this, mergedConfig);
|
|
679
|
+
this.sandboxes.set(sandbox.name, sandbox);
|
|
680
|
+
return sandbox;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Create a scheduled function (cron).
|
|
684
|
+
*/
|
|
685
|
+
cron(schedule, config = {}) {
|
|
686
|
+
return this.function({ ...config, schedule });
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Create a web endpoint function.
|
|
690
|
+
*/
|
|
691
|
+
webEndpoint(config = {}) {
|
|
692
|
+
return this.function({ ...config });
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Deploy the app to Codmir cloud.
|
|
696
|
+
*/
|
|
697
|
+
async deploy() {
|
|
698
|
+
if (this.deployed) {
|
|
699
|
+
throw new Error(`App ${this.name} is already deployed`);
|
|
700
|
+
}
|
|
701
|
+
const manifest = {
|
|
702
|
+
name: this.name,
|
|
703
|
+
config: this.config,
|
|
704
|
+
functions: Array.from(this.functions.entries()).map(([name, fn2]) => ({
|
|
705
|
+
name,
|
|
706
|
+
config: fn2.config
|
|
707
|
+
}))
|
|
708
|
+
};
|
|
709
|
+
console.log(`[codmir] Deploying app: ${this.name}`);
|
|
710
|
+
console.log(`[codmir] Functions: ${this.functions.size}`);
|
|
711
|
+
this.deployed = true;
|
|
712
|
+
return {
|
|
713
|
+
url: `https://${this.name}.codmir.run`,
|
|
714
|
+
functions: Array.from(this.functions.keys())
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Get a registered function by name.
|
|
719
|
+
*/
|
|
720
|
+
getFunction(name) {
|
|
721
|
+
return this.functions.get(name);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* List all registered functions.
|
|
725
|
+
*/
|
|
726
|
+
listFunctions() {
|
|
727
|
+
return Array.from(this.functions.keys());
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Update Grace configuration.
|
|
731
|
+
*/
|
|
732
|
+
withGrace(grace) {
|
|
733
|
+
this.config.grace = { ...this.config.grace, ...grace };
|
|
734
|
+
return this;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Serve the app locally for development.
|
|
738
|
+
*/
|
|
739
|
+
async serve(port = 8e3) {
|
|
740
|
+
console.log(`[codmir] Starting local server on port ${port}`);
|
|
741
|
+
console.log(`[codmir] Functions available:`);
|
|
742
|
+
for (const name of this.functions.keys()) {
|
|
743
|
+
console.log(` - ${name}: http://localhost:${port}/${name}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
function createApp(config) {
|
|
748
|
+
return new CodmirApp(config);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/serverless/image.ts
|
|
752
|
+
var CodmirImage = class {
|
|
753
|
+
config;
|
|
754
|
+
constructor(config) {
|
|
755
|
+
if (typeof config === "string") {
|
|
756
|
+
this.config = { base: config };
|
|
757
|
+
} else {
|
|
758
|
+
this.config = config;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Install Python packages via pip.
|
|
763
|
+
*/
|
|
764
|
+
pip(...packages) {
|
|
765
|
+
this.config.pythonPackages = [
|
|
766
|
+
...this.config.pythonPackages || [],
|
|
767
|
+
...packages
|
|
768
|
+
];
|
|
769
|
+
return this;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Install Python packages from requirements.txt.
|
|
773
|
+
*/
|
|
774
|
+
pipRequirements(path) {
|
|
775
|
+
this.config.pipRequirements = path;
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Install system packages via apt-get.
|
|
780
|
+
*/
|
|
781
|
+
apt(...packages) {
|
|
782
|
+
this.config.systemPackages = [
|
|
783
|
+
...this.config.systemPackages || [],
|
|
784
|
+
...packages
|
|
785
|
+
];
|
|
786
|
+
return this;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Run a command during image build.
|
|
790
|
+
*/
|
|
791
|
+
run(command) {
|
|
792
|
+
this.config.runCommands = [
|
|
793
|
+
...this.config.runCommands || [],
|
|
794
|
+
command
|
|
795
|
+
];
|
|
796
|
+
return this;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Copy files into the image.
|
|
800
|
+
*/
|
|
801
|
+
copy(from, to) {
|
|
802
|
+
this.config.copyFiles = [
|
|
803
|
+
...this.config.copyFiles || [],
|
|
804
|
+
{ from, to }
|
|
805
|
+
];
|
|
806
|
+
return this;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Set environment variables.
|
|
810
|
+
*/
|
|
811
|
+
env(vars) {
|
|
812
|
+
this.config.env = {
|
|
813
|
+
...this.config.env,
|
|
814
|
+
...vars
|
|
815
|
+
};
|
|
816
|
+
return this;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Set working directory.
|
|
820
|
+
*/
|
|
821
|
+
workdir(path) {
|
|
822
|
+
this.config.workdir = path;
|
|
823
|
+
return this;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Get the image configuration.
|
|
827
|
+
*/
|
|
828
|
+
getConfig() {
|
|
829
|
+
return this.config;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Generate Dockerfile content.
|
|
833
|
+
*/
|
|
834
|
+
toDockerfile() {
|
|
835
|
+
const lines = [];
|
|
836
|
+
lines.push(`FROM ${this.config.base}`);
|
|
837
|
+
lines.push("");
|
|
838
|
+
if (this.config.systemPackages?.length) {
|
|
839
|
+
lines.push("RUN apt-get update && apt-get install -y \\");
|
|
840
|
+
lines.push(` ${this.config.systemPackages.join(" \\\n ")} \\`);
|
|
841
|
+
lines.push(" && rm -rf /var/lib/apt/lists/*");
|
|
842
|
+
lines.push("");
|
|
843
|
+
}
|
|
844
|
+
if (this.config.workdir) {
|
|
845
|
+
lines.push(`WORKDIR ${this.config.workdir}`);
|
|
846
|
+
lines.push("");
|
|
847
|
+
}
|
|
848
|
+
if (this.config.copyFiles?.length) {
|
|
849
|
+
for (const { from, to } of this.config.copyFiles) {
|
|
850
|
+
lines.push(`COPY ${from} ${to}`);
|
|
851
|
+
}
|
|
852
|
+
lines.push("");
|
|
853
|
+
}
|
|
854
|
+
if (this.config.pipRequirements) {
|
|
855
|
+
lines.push(`COPY ${this.config.pipRequirements} /tmp/requirements.txt`);
|
|
856
|
+
lines.push("RUN pip install --no-cache-dir -r /tmp/requirements.txt");
|
|
857
|
+
lines.push("");
|
|
858
|
+
}
|
|
859
|
+
if (this.config.pythonPackages?.length) {
|
|
860
|
+
lines.push(`RUN pip install --no-cache-dir ${this.config.pythonPackages.join(" ")}`);
|
|
861
|
+
lines.push("");
|
|
862
|
+
}
|
|
863
|
+
if (this.config.runCommands?.length) {
|
|
864
|
+
for (const cmd of this.config.runCommands) {
|
|
865
|
+
lines.push(`RUN ${cmd}`);
|
|
866
|
+
}
|
|
867
|
+
lines.push("");
|
|
868
|
+
}
|
|
869
|
+
if (this.config.env) {
|
|
870
|
+
for (const [key, value] of Object.entries(this.config.env)) {
|
|
871
|
+
lines.push(`ENV ${key}="${value}"`);
|
|
872
|
+
}
|
|
873
|
+
lines.push("");
|
|
874
|
+
}
|
|
875
|
+
return lines.join("\n");
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
function createImage(config) {
|
|
879
|
+
return new CodmirImage(config);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/serverless/volume.ts
|
|
883
|
+
var CodmirVolume = class {
|
|
884
|
+
name;
|
|
885
|
+
config;
|
|
886
|
+
constructor(config) {
|
|
887
|
+
this.name = config.name;
|
|
888
|
+
this.config = {
|
|
889
|
+
size: 10,
|
|
890
|
+
// Default 10GB
|
|
891
|
+
...config
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Get the volume mount path.
|
|
896
|
+
*/
|
|
897
|
+
getPath() {
|
|
898
|
+
return `/volumes/${this.name}`;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Create the volume in the cloud.
|
|
902
|
+
*/
|
|
903
|
+
async create() {
|
|
904
|
+
console.log(`[codmir] Creating volume: ${this.name} (${this.config.size}GB)`);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Delete the volume.
|
|
908
|
+
*/
|
|
909
|
+
async delete() {
|
|
910
|
+
console.log(`[codmir] Deleting volume: ${this.name}`);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Get volume usage statistics.
|
|
914
|
+
*/
|
|
915
|
+
async stats() {
|
|
916
|
+
return {
|
|
917
|
+
usedBytes: 0,
|
|
918
|
+
totalBytes: (this.config.size || 10) * 1024 * 1024 * 1024
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* List files in the volume.
|
|
923
|
+
*/
|
|
924
|
+
async listFiles(path = "/") {
|
|
925
|
+
return [];
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Upload a file to the volume.
|
|
929
|
+
*/
|
|
930
|
+
async upload(localPath, remotePath) {
|
|
931
|
+
console.log(`[codmir] Uploading ${localPath} to ${this.name}:${remotePath}`);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Download a file from the volume.
|
|
935
|
+
*/
|
|
936
|
+
async download(remotePath, localPath) {
|
|
937
|
+
console.log(`[codmir] Downloading ${this.name}:${remotePath} to ${localPath}`);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
function createVolume(config) {
|
|
941
|
+
return new CodmirVolume(config);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/serverless/secret.ts
|
|
945
|
+
var CodmirSecret = class {
|
|
946
|
+
name;
|
|
947
|
+
config;
|
|
948
|
+
constructor(config) {
|
|
949
|
+
if (typeof config === "string") {
|
|
950
|
+
this.name = config;
|
|
951
|
+
this.config = { name: config };
|
|
952
|
+
} else {
|
|
953
|
+
this.name = config.name;
|
|
954
|
+
this.config = config;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Get the environment variable name.
|
|
959
|
+
*/
|
|
960
|
+
getEnvVar() {
|
|
961
|
+
return this.config.envVar || this.name;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Set the secret value (for initial setup).
|
|
965
|
+
*/
|
|
966
|
+
async set(value) {
|
|
967
|
+
console.log(`[codmir] Setting secret: ${this.name}`);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Delete the secret.
|
|
971
|
+
*/
|
|
972
|
+
async delete() {
|
|
973
|
+
console.log(`[codmir] Deleting secret: ${this.name}`);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Check if the secret exists.
|
|
977
|
+
*/
|
|
978
|
+
async exists() {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get secret metadata (not the value).
|
|
983
|
+
*/
|
|
984
|
+
async metadata() {
|
|
985
|
+
return {
|
|
986
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
987
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
function createSecret(config) {
|
|
992
|
+
return new CodmirSecret(config);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/serverless/decorators.ts
|
|
996
|
+
function app(config) {
|
|
997
|
+
return createApp(config);
|
|
998
|
+
}
|
|
999
|
+
function fn(appInstance, config = {}) {
|
|
1000
|
+
return appInstance.function(config);
|
|
1001
|
+
}
|
|
1002
|
+
function method(appInstance, config = {}) {
|
|
1003
|
+
return function(target, propertyKey, descriptor) {
|
|
1004
|
+
const originalMethod = descriptor.value;
|
|
1005
|
+
const fnName = `${target.constructor.name}_${String(propertyKey)}`;
|
|
1006
|
+
const codmirFn = appInstance.function({
|
|
1007
|
+
...config,
|
|
1008
|
+
name: fnName
|
|
1009
|
+
})(originalMethod);
|
|
1010
|
+
descriptor.value = function(...args) {
|
|
1011
|
+
return codmirFn.call(...args);
|
|
1012
|
+
};
|
|
1013
|
+
return descriptor;
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function web_endpoint(appInstance, config = {}) {
|
|
1017
|
+
return appInstance.function({
|
|
1018
|
+
...config,
|
|
1019
|
+
name: config.name || `endpoint_${config.method || "GET"}_${Date.now().toString(36)}`
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
function cron(appInstance, schedule, config = {}) {
|
|
1023
|
+
return appInstance.function({
|
|
1024
|
+
...config,
|
|
1025
|
+
schedule,
|
|
1026
|
+
name: config.name || `cron_${schedule.replace(/\s+/g, "_")}_${Date.now().toString(36)}`
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/serverless/utils.ts
|
|
1031
|
+
var logs = {
|
|
1032
|
+
/**
|
|
1033
|
+
* Log a debug message.
|
|
1034
|
+
*/
|
|
1035
|
+
debug(message, metadata) {
|
|
1036
|
+
logWithLevel("debug", message, metadata);
|
|
1037
|
+
},
|
|
1038
|
+
/**
|
|
1039
|
+
* Log an info message.
|
|
1040
|
+
*/
|
|
1041
|
+
info(message, metadata) {
|
|
1042
|
+
logWithLevel("info", message, metadata);
|
|
1043
|
+
},
|
|
1044
|
+
/**
|
|
1045
|
+
* Log a warning message.
|
|
1046
|
+
*/
|
|
1047
|
+
warn(message, metadata) {
|
|
1048
|
+
logWithLevel("warn", message, metadata);
|
|
1049
|
+
},
|
|
1050
|
+
/**
|
|
1051
|
+
* Log an error message.
|
|
1052
|
+
*/
|
|
1053
|
+
error(message, metadata) {
|
|
1054
|
+
logWithLevel("error", message, metadata);
|
|
1055
|
+
},
|
|
1056
|
+
/**
|
|
1057
|
+
* Log with custom level.
|
|
1058
|
+
*/
|
|
1059
|
+
log(level, message, metadata) {
|
|
1060
|
+
logWithLevel(level, message, metadata);
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
function logWithLevel(level, message, metadata) {
|
|
1064
|
+
const entry = {
|
|
1065
|
+
level,
|
|
1066
|
+
message,
|
|
1067
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1068
|
+
metadata
|
|
1069
|
+
};
|
|
1070
|
+
const prefix = `[codmir:${level.toUpperCase()}]`;
|
|
1071
|
+
const metaStr = metadata ? ` ${JSON.stringify(metadata)}` : "";
|
|
1072
|
+
switch (level) {
|
|
1073
|
+
case "debug":
|
|
1074
|
+
console.debug(`${prefix} ${message}${metaStr}`);
|
|
1075
|
+
break;
|
|
1076
|
+
case "info":
|
|
1077
|
+
console.log(`${prefix} ${message}${metaStr}`);
|
|
1078
|
+
break;
|
|
1079
|
+
case "warn":
|
|
1080
|
+
console.warn(`${prefix} ${message}${metaStr}`);
|
|
1081
|
+
break;
|
|
1082
|
+
case "error":
|
|
1083
|
+
console.error(`${prefix} ${message}${metaStr}`);
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
var progress = {
|
|
1088
|
+
_total: 0,
|
|
1089
|
+
_current: 0,
|
|
1090
|
+
/**
|
|
1091
|
+
* Start progress tracking.
|
|
1092
|
+
*/
|
|
1093
|
+
start(total, message) {
|
|
1094
|
+
this._total = total;
|
|
1095
|
+
this._current = 0;
|
|
1096
|
+
this.report({ percent: 0, message: message || "Starting..." });
|
|
1097
|
+
},
|
|
1098
|
+
/**
|
|
1099
|
+
* Update progress.
|
|
1100
|
+
*/
|
|
1101
|
+
update(current, message) {
|
|
1102
|
+
this._current = current;
|
|
1103
|
+
const percent = this._total > 0 ? Math.round(current / this._total * 100) : 0;
|
|
1104
|
+
this.report({ percent, message });
|
|
1105
|
+
},
|
|
1106
|
+
/**
|
|
1107
|
+
* Increment progress by one.
|
|
1108
|
+
*/
|
|
1109
|
+
increment(message) {
|
|
1110
|
+
this.update(this._current + 1, message);
|
|
1111
|
+
},
|
|
1112
|
+
/**
|
|
1113
|
+
* Mark progress as complete.
|
|
1114
|
+
*/
|
|
1115
|
+
complete(message) {
|
|
1116
|
+
this.report({ percent: 100, message: message || "Completed" });
|
|
1117
|
+
},
|
|
1118
|
+
/**
|
|
1119
|
+
* Report progress update.
|
|
1120
|
+
*/
|
|
1121
|
+
report(update) {
|
|
1122
|
+
console.log(`[codmir:PROGRESS] ${update.percent}% - ${update.message || ""}`);
|
|
1123
|
+
},
|
|
1124
|
+
/**
|
|
1125
|
+
* Get current progress percentage.
|
|
1126
|
+
*/
|
|
1127
|
+
getPercent() {
|
|
1128
|
+
return this._total > 0 ? Math.round(this._current / this._total * 100) : 0;
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
var checkpoint = {
|
|
1132
|
+
_data: null,
|
|
1133
|
+
/**
|
|
1134
|
+
* Save a checkpoint.
|
|
1135
|
+
*/
|
|
1136
|
+
async save(data, name) {
|
|
1137
|
+
const id = `cp_${Date.now().toString(36)}`;
|
|
1138
|
+
const cp = {
|
|
1139
|
+
id,
|
|
1140
|
+
name,
|
|
1141
|
+
data,
|
|
1142
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1143
|
+
};
|
|
1144
|
+
this._data = cp;
|
|
1145
|
+
console.log(`[codmir:CHECKPOINT] Saved: ${id}${name ? ` (${name})` : ""}`);
|
|
1146
|
+
return id;
|
|
1147
|
+
},
|
|
1148
|
+
/**
|
|
1149
|
+
* Restore from the latest checkpoint.
|
|
1150
|
+
*/
|
|
1151
|
+
async restore() {
|
|
1152
|
+
return this._data;
|
|
1153
|
+
},
|
|
1154
|
+
/**
|
|
1155
|
+
* Restore from a specific checkpoint.
|
|
1156
|
+
*/
|
|
1157
|
+
async restoreById(id) {
|
|
1158
|
+
if (this._data?.id === id) {
|
|
1159
|
+
return this._data;
|
|
1160
|
+
}
|
|
1161
|
+
return null;
|
|
1162
|
+
},
|
|
1163
|
+
/**
|
|
1164
|
+
* List available checkpoints.
|
|
1165
|
+
*/
|
|
1166
|
+
async list() {
|
|
1167
|
+
if (this._data) {
|
|
1168
|
+
return [{ id: this._data.id, name: this._data.name, timestamp: this._data.timestamp }];
|
|
1169
|
+
}
|
|
1170
|
+
return [];
|
|
1171
|
+
},
|
|
1172
|
+
/**
|
|
1173
|
+
* Clear all checkpoints.
|
|
1174
|
+
*/
|
|
1175
|
+
async clear() {
|
|
1176
|
+
this._data = null;
|
|
1177
|
+
console.log("[codmir:CHECKPOINT] Cleared");
|
|
1178
|
+
},
|
|
1179
|
+
/**
|
|
1180
|
+
* Delete a specific checkpoint.
|
|
1181
|
+
*/
|
|
1182
|
+
async delete(id) {
|
|
1183
|
+
if (this._data?.id === id) {
|
|
1184
|
+
this._data = null;
|
|
1185
|
+
}
|
|
1186
|
+
console.log(`[codmir:CHECKPOINT] Deleted: ${id}`);
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
export {
|
|
1190
|
+
CodmirApp,
|
|
1191
|
+
CodmirFunction,
|
|
1192
|
+
CodmirImage,
|
|
1193
|
+
CodmirSandbox,
|
|
1194
|
+
CodmirSecret,
|
|
1195
|
+
CodmirVolume,
|
|
1196
|
+
app,
|
|
1197
|
+
checkpoint,
|
|
1198
|
+
createApp,
|
|
1199
|
+
createFunction,
|
|
1200
|
+
createImage,
|
|
1201
|
+
createSandbox,
|
|
1202
|
+
createSecret,
|
|
1203
|
+
createVolume,
|
|
1204
|
+
cron,
|
|
1205
|
+
execute,
|
|
1206
|
+
executeAsync,
|
|
1207
|
+
executeLocal,
|
|
1208
|
+
fn,
|
|
1209
|
+
logs,
|
|
1210
|
+
method,
|
|
1211
|
+
progress,
|
|
1212
|
+
web_endpoint
|
|
1213
|
+
};
|