gencow 0.1.78 → 0.1.80
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/bin/gencow.mjs +6 -10
- package/lib/__tests__/deploy-auditor.test.ts +15 -13
- package/lib/deploy-auditor.mjs +19 -30
- package/lib/readme-codegen.mjs +17 -21
- package/package.json +1 -1
- package/server/index.js +82 -27
- package/server/index.js.map +2 -2
package/bin/gencow.mjs
CHANGED
|
@@ -1738,17 +1738,15 @@ ${BOLD}Examples:${RESET}
|
|
|
1738
1738
|
process.exit(1);
|
|
1739
1739
|
}
|
|
1740
1740
|
|
|
1741
|
-
// 1-0. Pre-deploy dependency audit
|
|
1741
|
+
// 1-0. Pre-deploy dependency audit (informational — user deps auto-installed)
|
|
1742
1742
|
if (!forceDeploy) {
|
|
1743
1743
|
try {
|
|
1744
1744
|
const { auditDeployDependencies, formatAuditError } = await import("../lib/deploy-auditor.mjs");
|
|
1745
1745
|
const entryPoint = resolve(process.cwd(), "gencow", "index.ts");
|
|
1746
1746
|
if (existsSync(entryPoint)) {
|
|
1747
1747
|
const auditResult = await auditDeployDependencies(entryPoint);
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
process.exit(1);
|
|
1751
|
-
}
|
|
1748
|
+
const auditMsg = formatAuditError(auditResult);
|
|
1749
|
+
if (auditMsg) log(auditMsg);
|
|
1752
1750
|
}
|
|
1753
1751
|
} catch (auditErr) {
|
|
1754
1752
|
warn(`의존성 검사 스킵: ${auditErr.message}`);
|
|
@@ -2013,17 +2011,15 @@ ${BOLD}Examples:${RESET}
|
|
|
2013
2011
|
if (shouldDeployBackend) {
|
|
2014
2012
|
log(` ${BOLD}── 백엔드 배포 ──────────────────────${RESET}\n`);
|
|
2015
2013
|
|
|
2016
|
-
// 1-0. Pre-deploy dependency audit
|
|
2014
|
+
// 1-0. Pre-deploy dependency audit (informational — user deps auto-installed)
|
|
2017
2015
|
if (!forceDeploy) {
|
|
2018
2016
|
try {
|
|
2019
2017
|
const { auditDeployDependencies, formatAuditError } = await import("../lib/deploy-auditor.mjs");
|
|
2020
2018
|
const entryPoint = resolve(backendRoot, "gencow", "index.ts");
|
|
2021
2019
|
if (existsSync(entryPoint)) {
|
|
2022
2020
|
const auditResult = await auditDeployDependencies(entryPoint);
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
process.exit(1);
|
|
2026
|
-
}
|
|
2021
|
+
const auditMsg = formatAuditError(auditResult);
|
|
2022
|
+
if (auditMsg) log(auditMsg);
|
|
2027
2023
|
}
|
|
2028
2024
|
} catch (auditErr) {
|
|
2029
2025
|
warn(`의존성 검사 스킵: ${auditErr.message}`);
|
|
@@ -78,36 +78,38 @@ describe("isNodeBuiltin", () => {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
describe("formatAuditError", () => {
|
|
81
|
-
it("returns empty string when
|
|
82
|
-
expect(formatAuditError({ passed: true, unsupported: [] })).toBe("");
|
|
81
|
+
it("returns empty string when no user deps", () => {
|
|
82
|
+
expect(formatAuditError({ passed: true, unsupported: [], hasUserDependencies: false })).toBe("");
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it("formats
|
|
85
|
+
it("formats info message with user dependencies (auto-install)", () => {
|
|
86
86
|
const result = {
|
|
87
|
-
passed:
|
|
87
|
+
passed: true,
|
|
88
88
|
unsupported: [
|
|
89
89
|
{ packageName: "langfuse", importedFrom: "gencow/llmActions.ts" },
|
|
90
90
|
{ packageName: "cheerio", importedFrom: "gencow/crawlPipeline.ts" },
|
|
91
91
|
],
|
|
92
|
+
hasUserDependencies: true,
|
|
92
93
|
};
|
|
93
94
|
const output = formatAuditError(result);
|
|
94
|
-
|
|
95
|
+
// 이제 차단이 아닌 안내 메시지
|
|
95
96
|
expect(output).toContain("langfuse");
|
|
96
|
-
expect(output).toContain("gencow/llmActions.ts");
|
|
97
97
|
expect(output).toContain("cheerio");
|
|
98
|
-
expect(output).toContain("
|
|
99
|
-
|
|
98
|
+
expect(output).toContain("자동 설치");
|
|
99
|
+
// 차단 메시지가 없어야 함
|
|
100
|
+
expect(output).not.toContain("DEPLOY BLOCKED");
|
|
101
|
+
expect(output).not.toContain("--force");
|
|
100
102
|
});
|
|
101
103
|
|
|
102
|
-
it("
|
|
104
|
+
it("shows rollback info in message", () => {
|
|
103
105
|
const result = {
|
|
104
|
-
passed:
|
|
106
|
+
passed: true,
|
|
105
107
|
unsupported: [{ packageName: "moment", importedFrom: "gencow/utils.ts" }],
|
|
108
|
+
hasUserDependencies: true,
|
|
106
109
|
};
|
|
107
110
|
const output = formatAuditError(result);
|
|
108
|
-
expect(output).toContain("
|
|
109
|
-
expect(output).toContain("
|
|
110
|
-
expect(output).toContain("better-auth");
|
|
111
|
+
expect(output).toContain("moment");
|
|
112
|
+
expect(output).toContain("롤백");
|
|
111
113
|
});
|
|
112
114
|
});
|
|
113
115
|
|
package/lib/deploy-auditor.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deploy Auditor —
|
|
2
|
+
* Deploy Auditor — Pre-deploy dependency analysis (informational).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Detects user third-party dependencies that need auto-install on the
|
|
5
|
+
* Gencow cloud runtime. User deps are installed via `bun install` on
|
|
6
|
+
* the server during provisioning.
|
|
7
7
|
*
|
|
8
8
|
* How it works:
|
|
9
9
|
* 1. Uses esbuild metafile to extract all external imports
|
|
10
10
|
* 2. Filters out relative imports, Node built-ins, and platform packages
|
|
11
|
-
* 3. Returns list of
|
|
11
|
+
* 3. Returns list of user dependencies with source file info
|
|
12
12
|
*
|
|
13
13
|
* Usage:
|
|
14
14
|
* const result = await auditDeployDependencies("./gencow/index.ts");
|
|
15
|
-
* if (
|
|
15
|
+
* if (result.hasUserDependencies) { ... show info ... }
|
|
16
16
|
*/
|
|
17
17
|
import { build } from "esbuild";
|
|
18
18
|
|
|
@@ -61,8 +61,9 @@ const NODE_BUILTINS = new Set([
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* @typedef {Object} AuditResult
|
|
64
|
-
* @property {boolean} passed - true
|
|
65
|
-
* @property {UnsupportedDep[]} unsupported - List of
|
|
64
|
+
* @property {boolean} passed - Always true (audit is informational, not blocking)
|
|
65
|
+
* @property {UnsupportedDep[]} unsupported - List of user third-party packages
|
|
66
|
+
* @property {boolean} hasUserDependencies - true if user deps detected (will be auto-installed)
|
|
66
67
|
*/
|
|
67
68
|
|
|
68
69
|
/**
|
|
@@ -178,43 +179,31 @@ export async function auditDeployDependencies(entryPoint) {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
return {
|
|
181
|
-
passed:
|
|
182
|
+
passed: true, // Always pass — user deps are auto-installed on server
|
|
182
183
|
unsupported,
|
|
184
|
+
hasUserDependencies: unsupported.length > 0,
|
|
183
185
|
};
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
/**
|
|
187
|
-
* Format audit result as a human-readable
|
|
189
|
+
* Format audit result as a human-readable info message for CLI output.
|
|
190
|
+
* Shows which user dependencies will be auto-installed on the cloud.
|
|
188
191
|
*
|
|
189
192
|
* @param {AuditResult} result
|
|
190
|
-
* @returns {string} Formatted message (empty string if
|
|
193
|
+
* @returns {string} Formatted message (empty string if no user deps)
|
|
191
194
|
*/
|
|
192
195
|
export function formatAuditError(result) {
|
|
193
|
-
if (result.
|
|
196
|
+
if (!result.hasUserDependencies) return "";
|
|
194
197
|
|
|
198
|
+
const pkgNames = result.unsupported.map(d => d.packageName);
|
|
195
199
|
const lines = [
|
|
196
200
|
"",
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"",
|
|
201
|
-
" The following packages are NOT available in the Gencow cloud runtime:",
|
|
201
|
+
` 📦 서드파티 패키지 감지: ${pkgNames.join(", ")}`,
|
|
202
|
+
` → 클라우드 배포 시 자동 설치됩니다.`,
|
|
203
|
+
` → 설치 실패 시 배포가 롤백됩니다.`,
|
|
202
204
|
"",
|
|
203
205
|
];
|
|
204
206
|
|
|
205
|
-
for (const dep of result.unsupported) {
|
|
206
|
-
lines.push(` ✗ ${dep.packageName} (imported in ${dep.importedFrom})`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
lines.push("");
|
|
210
|
-
lines.push(" Gencow 클라우드에서 사용 가능한 패키지:");
|
|
211
|
-
lines.push(" @gencow/core, drizzle-orm, better-auth, postgres, hono, ai, zod");
|
|
212
|
-
lines.push("");
|
|
213
|
-
lines.push(" 해결 방법:");
|
|
214
|
-
lines.push(" 1. 해당 패키지 사용을 제거하거나 동적 import (try/catch)로 변경");
|
|
215
|
-
lines.push(" 2. gencow deploy --force 로 강제 배포 (서버 크래시 위험)");
|
|
216
|
-
lines.push("");
|
|
217
|
-
|
|
218
207
|
return lines.join("\n");
|
|
219
208
|
}
|
|
220
209
|
|
package/lib/readme-codegen.mjs
CHANGED
|
@@ -307,18 +307,16 @@ export function buildAiPrompt(apiObj, namespaces) {
|
|
|
307
307
|
md += `- .env 파일은 로컬 개발 전용이야. 클라우드에는 gencow env push로 올려.\n`;
|
|
308
308
|
md += `- 로컬 dev 서버에 설정하려면 \`gencow env set --local KEY=VALUE\`를 써.\n`;
|
|
309
309
|
md += `\n`;
|
|
310
|
-
md += `⚠️ 패키지
|
|
311
|
-
md += `-
|
|
312
|
-
md +=
|
|
313
|
-
md += `- npm install로 추가한 서드파티 패키지(langfuse, openai, axios, cheerio 등)는 배포 시 사용 불가.\n`;
|
|
314
|
-
md += ` 배포하면 "new version unhealthy" 에러가 발생해.\n`;
|
|
310
|
+
md += `⚠️ 패키지 정보:\n`;
|
|
311
|
+
md += `- npm install로 추가한 서드파티 패키지(langfuse, axios, cheerio 등)는 배포 시 자동 설치돼.\n`;
|
|
312
|
+
md += `- 단, child_process, vm, os, cluster, worker_threads 모듈은 보안상 차단됨.\n`;
|
|
315
313
|
md += `\n`;
|
|
316
314
|
md += `⚠️ 해싱/암호화:\n`;
|
|
317
|
-
md += `- node:crypto
|
|
315
|
+
md += `- node:crypto 모듈도 사용 가능하지만, 더 가볍게 Web Crypto API(crypto.subtle)도 추천:\n`;
|
|
318
316
|
md += ` const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));\n`;
|
|
319
317
|
md += ` const hex = [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join("");\n`;
|
|
320
318
|
md += `- 직접 구현한 simpleHash 같은 32비트 해시는 충돌 위험이 있으니 SHA-256을 사용해.\n`;
|
|
321
|
-
md += `- AI 호출이 필요하면 ctx.ai.chat()을 사용해. OpenAI SDK를 직접
|
|
319
|
+
md += `- AI 호출이 필요하면 ctx.ai.chat()을 사용해. OpenAI SDK를 직접 설치해도 되지만 ctx.ai.chat()이 더 간편해.\n`;
|
|
322
320
|
md += `\`\`\`\n\n`;
|
|
323
321
|
return md;
|
|
324
322
|
}
|
|
@@ -401,9 +399,10 @@ export function buildDeploySection() {
|
|
|
401
399
|
md += `\`\`\`\n\n`;
|
|
402
400
|
md += `> ⚠️ 프론트엔드에서 API를 호출하려면 빌드 시 \`VITE_API_URL\`을 반드시 설정하세요.\n\n`;
|
|
403
401
|
|
|
404
|
-
// 패키지
|
|
405
|
-
md += `###
|
|
406
|
-
md +=
|
|
402
|
+
// 서드파티 패키지 안내 (자동 설치)
|
|
403
|
+
md += `### 📦 서드파티 npm 패키지\n\n`;
|
|
404
|
+
md += `\`npm install\`로 추가한 서드파티 패키지는 배포 시 **자동으로 설치**됩니다.\n\n`;
|
|
405
|
+
md += `클라우드 기본 제공 패키지 (별도 설치 불필요):\n\n`;
|
|
407
406
|
md += `| 패키지 | 설명 |\n`;
|
|
408
407
|
md += `| :--- | :--- |\n`;
|
|
409
408
|
md += `| \`@gencow/core\` | Gencow 핵심 프레임워크 |\n`;
|
|
@@ -413,9 +412,8 @@ export function buildDeploySection() {
|
|
|
413
412
|
md += `| \`hono\` | HTTP 프레임워크 |\n`;
|
|
414
413
|
md += `| \`ai\`, \`@ai-sdk/*\` | AI SDK |\n`;
|
|
415
414
|
md += `| \`zod\` | 밸리데이션 |\n\n`;
|
|
416
|
-
md += `>
|
|
417
|
-
md += `>
|
|
418
|
-
md += `> AI 호출이 필요하면 \`ctx.ai.chat()\`을 사용하세요.\n\n`;
|
|
415
|
+
md += `> 📦 langfuse, axios, cheerio 등 추가 패키지도 \`npm install\` 후 \`gencow deploy\`하면 자동 설치됩니다.\n`;
|
|
416
|
+
md += `> ⛔ \`child_process\`, \`vm\`, \`os\`, \`cluster\`, \`worker_threads\` 모듈은 보안상 차단됩니다.\n\n`;
|
|
419
417
|
md += `### CORS 설정\n\n`;
|
|
420
418
|
md += `- \`*.gencow.app\` 서브도메인 간 요청은 **자동 허용**됩니다.\n`;
|
|
421
419
|
md += `- 커스텀 도메인에서 API를 호출하려면 환경변수를 설정하세요:\n\n`;
|
|
@@ -424,23 +422,21 @@ export function buildDeploySection() {
|
|
|
424
422
|
md += `\`\`\`\n\n`;
|
|
425
423
|
|
|
426
424
|
// 해싱/암호화 대안
|
|
427
|
-
md += `### 🔐
|
|
428
|
-
md +=
|
|
429
|
-
md += `해싱이 필요하면 **Web Crypto API** (\`crypto.subtle\`)를 사용하세요:\n\n`;
|
|
425
|
+
md += `### 🔐 해싱/암호화\n\n`;
|
|
426
|
+
md += `\`node:crypto\` 모듈 사용 가능합니다. Web Crypto API도 대안으로 사용할 수 있습니다:\n\n`;
|
|
430
427
|
md += `\`\`\`typescript\n`;
|
|
431
|
-
md += `// SHA-256 해싱 (
|
|
428
|
+
md += `// SHA-256 해싱 (Web Crypto API)
|
|
429
|
+
`;
|
|
432
430
|
md += `async function sha256(text: string): Promise<string> {\n`;
|
|
433
431
|
md += ` const data = new TextEncoder().encode(text);\n`;
|
|
434
432
|
md += ` const hash = await crypto.subtle.digest("SHA-256", data);\n`;
|
|
435
433
|
md += ` return [...new Uint8Array(hash)]\n`;
|
|
436
434
|
md += ` .map(b => b.toString(16).padStart(2, "0"))\n`;
|
|
437
435
|
md += ` .join("");\n`;
|
|
438
|
-
md += `}\n
|
|
439
|
-
md += `// 사용 예: 중복 탐지, 캐시 키, 콘텐츠 해싱\n`;
|
|
440
|
-
md += `const articleHash = await sha256(article.url + article.title);\n`;
|
|
436
|
+
md += `}\n`;
|
|
441
437
|
md += `\`\`\`\n\n`;
|
|
442
438
|
md += `> ⚠️ 직접 구현한 \`simpleHash()\` 같은 32비트 해시는 대량 데이터에서 충돌 위험이 있습니다.\n`;
|
|
443
|
-
md += `> 반드시 SHA-256을
|
|
439
|
+
md += `> 반드시 SHA-256을 사용하세요.\n\n`;
|
|
444
440
|
|
|
445
441
|
return md;
|
|
446
442
|
}
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -71681,16 +71681,8 @@ async function consumeInviteCode(code) {
|
|
|
71681
71681
|
// ../server/src/auditor.ts
|
|
71682
71682
|
var esbuild = __toESM(require_main(), 1);
|
|
71683
71683
|
var FORBIDDEN_MODULES = /* @__PURE__ */ new Set([
|
|
71684
|
-
"fs",
|
|
71685
|
-
"fs/promises",
|
|
71686
|
-
"node:fs",
|
|
71687
|
-
"node:fs/promises",
|
|
71688
71684
|
"child_process",
|
|
71689
71685
|
"node:child_process",
|
|
71690
|
-
"net",
|
|
71691
|
-
"node:net",
|
|
71692
|
-
"tls",
|
|
71693
|
-
"node:tls",
|
|
71694
71686
|
"dgram",
|
|
71695
71687
|
"node:dgram",
|
|
71696
71688
|
"os",
|
|
@@ -71699,14 +71691,6 @@ var FORBIDDEN_MODULES = /* @__PURE__ */ new Set([
|
|
|
71699
71691
|
"node:cluster",
|
|
71700
71692
|
"worker_threads",
|
|
71701
71693
|
"node:worker_threads",
|
|
71702
|
-
"http",
|
|
71703
|
-
"node:http",
|
|
71704
|
-
"https",
|
|
71705
|
-
"node:https",
|
|
71706
|
-
"http2",
|
|
71707
|
-
"node:http2",
|
|
71708
|
-
"readline",
|
|
71709
|
-
"node:readline",
|
|
71710
71694
|
"v8",
|
|
71711
71695
|
"node:v8",
|
|
71712
71696
|
"vm",
|
|
@@ -71766,6 +71750,15 @@ var ALLOWED_NODE_MODULES = /* @__PURE__ */ new Set([
|
|
|
71766
71750
|
"node:timers",
|
|
71767
71751
|
"node:timers/promises",
|
|
71768
71752
|
"node:zlib",
|
|
71753
|
+
// 서드파티 패키지 호환을 위해 허용 (nsjail이 보안 담당)
|
|
71754
|
+
"node:fs",
|
|
71755
|
+
"node:fs/promises",
|
|
71756
|
+
"node:http",
|
|
71757
|
+
"node:https",
|
|
71758
|
+
"node:http2",
|
|
71759
|
+
"node:net",
|
|
71760
|
+
"node:tls",
|
|
71761
|
+
"node:readline",
|
|
71769
71762
|
// non-prefixed equivalents
|
|
71770
71763
|
"crypto",
|
|
71771
71764
|
"path",
|
|
@@ -71778,7 +71771,15 @@ var ALLOWED_NODE_MODULES = /* @__PURE__ */ new Set([
|
|
|
71778
71771
|
"querystring",
|
|
71779
71772
|
"string_decoder",
|
|
71780
71773
|
"timers",
|
|
71781
|
-
"zlib"
|
|
71774
|
+
"zlib",
|
|
71775
|
+
"fs",
|
|
71776
|
+
"fs/promises",
|
|
71777
|
+
"http",
|
|
71778
|
+
"https",
|
|
71779
|
+
"http2",
|
|
71780
|
+
"net",
|
|
71781
|
+
"tls",
|
|
71782
|
+
"readline"
|
|
71782
71783
|
]);
|
|
71783
71784
|
function isAllowedNodeModule(moduleName) {
|
|
71784
71785
|
return ALLOWED_NODE_MODULES.has(moduleName);
|
|
@@ -72553,6 +72554,38 @@ function getOriginalEnvValue(key) {
|
|
|
72553
72554
|
}
|
|
72554
72555
|
|
|
72555
72556
|
// ../server/src/index.ts
|
|
72557
|
+
var FUNCTION_TIMEOUTS = {
|
|
72558
|
+
query: 3e4,
|
|
72559
|
+
// 30초
|
|
72560
|
+
mutation: 3e4,
|
|
72561
|
+
// 30초
|
|
72562
|
+
httpAction: 3e5,
|
|
72563
|
+
// 5분
|
|
72564
|
+
cron: 6e5
|
|
72565
|
+
// 10분
|
|
72566
|
+
};
|
|
72567
|
+
async function executeWithTimeout(fn, type, functionName) {
|
|
72568
|
+
const timeoutMs = FUNCTION_TIMEOUTS[type];
|
|
72569
|
+
const controller = new AbortController();
|
|
72570
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
72571
|
+
try {
|
|
72572
|
+
const result = await Promise.race([
|
|
72573
|
+
fn(),
|
|
72574
|
+
new Promise((_, reject) => {
|
|
72575
|
+
controller.signal.addEventListener("abort", () => {
|
|
72576
|
+
const err = new Error(
|
|
72577
|
+
`Function "${functionName}" exceeded ${timeoutMs / 1e3}s time limit. Use ctx.scheduler.runAfter() for long-running tasks.`
|
|
72578
|
+
);
|
|
72579
|
+
err.code = "FUNCTION_TIMEOUT";
|
|
72580
|
+
reject(err);
|
|
72581
|
+
});
|
|
72582
|
+
})
|
|
72583
|
+
]);
|
|
72584
|
+
return result;
|
|
72585
|
+
} finally {
|
|
72586
|
+
clearTimeout(timer);
|
|
72587
|
+
}
|
|
72588
|
+
}
|
|
72556
72589
|
var METERING_URL = process.env.GENCOW_METERING_URL;
|
|
72557
72590
|
var IS_BAAS = !!METERING_URL;
|
|
72558
72591
|
var INTERNAL_TOKEN = process.env.GENCOW_INTERNAL_TOKEN || "";
|
|
@@ -72930,7 +72963,9 @@ async function main() {
|
|
|
72930
72963
|
}
|
|
72931
72964
|
}
|
|
72932
72965
|
const headers = new Headers();
|
|
72933
|
-
|
|
72966
|
+
const rawType = file3.type || "application/octet-stream";
|
|
72967
|
+
const needsCharset = (rawType.startsWith("text/") || rawType === "application/json") && !rawType.includes("charset");
|
|
72968
|
+
headers.set("Content-Type", needsCharset ? `${rawType}; charset=utf-8` : rawType);
|
|
72934
72969
|
const ext = resolved.split(".").pop()?.toLowerCase() || "";
|
|
72935
72970
|
if (["js", "css", "png", "jpg", "jpeg", "gif", "svg", "ico", "woff", "woff2"].includes(ext)) {
|
|
72936
72971
|
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
@@ -73207,7 +73242,11 @@ async function main() {
|
|
|
73207
73242
|
for (const [name21, mut] of mutationMap) {
|
|
73208
73243
|
scheduler.registerAction(name21, async (args) => {
|
|
73209
73244
|
const ctx = buildCtx({ get: () => null });
|
|
73210
|
-
await
|
|
73245
|
+
await executeWithTimeout(
|
|
73246
|
+
() => mut.handler(ctx, args ?? {}),
|
|
73247
|
+
"mutation",
|
|
73248
|
+
name21
|
|
73249
|
+
);
|
|
73211
73250
|
});
|
|
73212
73251
|
}
|
|
73213
73252
|
console.log(`[scheduler] Registered ${mutationMap.size} mutation(s) as scheduler actions`);
|
|
@@ -73234,7 +73273,11 @@ async function main() {
|
|
|
73234
73273
|
if (mut) {
|
|
73235
73274
|
try {
|
|
73236
73275
|
const ctx = buildCtx({ get: () => null });
|
|
73237
|
-
await
|
|
73276
|
+
await executeWithTimeout(
|
|
73277
|
+
() => mut.handler(ctx, {}),
|
|
73278
|
+
"cron",
|
|
73279
|
+
actionName
|
|
73280
|
+
);
|
|
73238
73281
|
console.log(`[scheduler] Cron action "${actionName}" (mutation) completed`);
|
|
73239
73282
|
} catch (err) {
|
|
73240
73283
|
const msg = err instanceof Error ? err.message : JSON.stringify(err, null, 2);
|
|
@@ -73272,7 +73315,11 @@ async function main() {
|
|
|
73272
73315
|
}
|
|
73273
73316
|
try {
|
|
73274
73317
|
const validatedArgs = parseArgs(queryDef.argsSchema, args);
|
|
73275
|
-
const result = await
|
|
73318
|
+
const result = await executeWithTimeout(
|
|
73319
|
+
() => queryDef.handler(ctx, validatedArgs),
|
|
73320
|
+
"query",
|
|
73321
|
+
name21
|
|
73322
|
+
);
|
|
73276
73323
|
if (queryDef.isPublic && !IS_BAAS && Array.isArray(result) && result.length >= 100) {
|
|
73277
73324
|
if (!publicQueryWarned.has(name21)) {
|
|
73278
73325
|
publicQueryWarned.add(name21);
|
|
@@ -73282,8 +73329,8 @@ async function main() {
|
|
|
73282
73329
|
}
|
|
73283
73330
|
return c.json(result);
|
|
73284
73331
|
} catch (err) {
|
|
73285
|
-
const status = err instanceof GencowValidationError ? 400 : 500;
|
|
73286
|
-
return c.json({ error: err.message }, status);
|
|
73332
|
+
const status = err?.code === "FUNCTION_TIMEOUT" ? 408 : err instanceof GencowValidationError ? 400 : 500;
|
|
73333
|
+
return c.json({ error: err.message, ...err?.code ? { code: err.code } : {} }, status);
|
|
73287
73334
|
}
|
|
73288
73335
|
}).post("/mutation", async (c) => {
|
|
73289
73336
|
let name21, args;
|
|
@@ -73309,12 +73356,16 @@ async function main() {
|
|
|
73309
73356
|
}
|
|
73310
73357
|
try {
|
|
73311
73358
|
const validatedArgs = parseArgs(mut.argsSchema, args);
|
|
73312
|
-
const result = await
|
|
73359
|
+
const result = await executeWithTimeout(
|
|
73360
|
+
() => mut.handler(ctx, validatedArgs),
|
|
73361
|
+
"mutation",
|
|
73362
|
+
name21
|
|
73363
|
+
);
|
|
73313
73364
|
await invalidateQueries(mut.invalidates, ctx);
|
|
73314
73365
|
return c.json(result, 201);
|
|
73315
73366
|
} catch (err) {
|
|
73316
|
-
const status = err instanceof GencowValidationError ? 400 : 500;
|
|
73317
|
-
return c.json({ error: err.message }, status);
|
|
73367
|
+
const status = err?.code === "FUNCTION_TIMEOUT" ? 408 : err instanceof GencowValidationError ? 400 : 500;
|
|
73368
|
+
return c.json({ error: err.message, ...err?.code ? { code: err.code } : {} }, status);
|
|
73318
73369
|
}
|
|
73319
73370
|
}).get("/storage/:id", storageRoutes(storage, rawSql, storageDir));
|
|
73320
73371
|
const queryNames = getRegisteredQueries();
|
|
@@ -73379,7 +73430,11 @@ async function main() {
|
|
|
73379
73430
|
text: () => c.req.text()
|
|
73380
73431
|
};
|
|
73381
73432
|
try {
|
|
73382
|
-
const result = await
|
|
73433
|
+
const result = await executeWithTimeout(
|
|
73434
|
+
() => action.handler(ctx, req),
|
|
73435
|
+
"httpAction",
|
|
73436
|
+
`${action.method} ${action.path}`
|
|
73437
|
+
);
|
|
73383
73438
|
if (result instanceof Response) {
|
|
73384
73439
|
return result;
|
|
73385
73440
|
}
|