gencow 0.1.110 → 0.1.111

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/core/index.js CHANGED
@@ -2027,7 +2027,8 @@ function crud(table, options) {
2027
2027
  const countResult = await db.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2028
2028
  return { data, total: Number(countResult[0]?.count ?? 0) };
2029
2029
  }
2030
- const listDef = query(`${prefix}.list`, {
2030
+ const enabledMethods = new Set(options?.methods ?? ["list", "get", "create", "update", "remove"]);
2031
+ const listDef = !enabledMethods.has("list") ? void 0 : query(`${prefix}.list`, {
2031
2032
  public: isPublic,
2032
2033
  args: {
2033
2034
  page: v.optional(v.number()),
@@ -2058,7 +2059,7 @@ function crud(table, options) {
2058
2059
  };
2059
2060
  }
2060
2061
  });
2061
- const getDef = query(`${prefix}.get`, {
2062
+ const getDef = !enabledMethods.has("get") ? void 0 : query(`${prefix}.get`, {
2062
2063
  public: isPublic,
2063
2064
  args: { id: idValidator },
2064
2065
  handler: async (ctx, args) => {
@@ -2072,7 +2073,7 @@ function crud(table, options) {
2072
2073
  return result ?? null;
2073
2074
  }
2074
2075
  });
2075
- const createDef = mutation(`${prefix}.create`, {
2076
+ const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
2076
2077
  public: isPublic,
2077
2078
  invalidates: [],
2078
2079
  handler: async (ctx, args) => {
@@ -2085,14 +2086,14 @@ function crud(table, options) {
2085
2086
  insertData = await options.hooks.beforeCreate(insertData);
2086
2087
  }
2087
2088
  const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
2088
- if (useRealtime) {
2089
+ if (useRealtime && enabledMethods.has("list")) {
2089
2090
  const listResult = await fetchListWithTotal(ctx.db);
2090
2091
  ctx.realtime.emit(`${prefix}.list`, listResult);
2091
2092
  }
2092
2093
  return result;
2093
2094
  }
2094
2095
  });
2095
- const updateDef = mutation(`${prefix}.update`, {
2096
+ const updateDef = !enabledMethods.has("update") ? void 0 : mutation(`${prefix}.update`, {
2096
2097
  public: isPublic,
2097
2098
  invalidates: [],
2098
2099
  handler: async (ctx, args) => {
@@ -2107,14 +2108,18 @@ function crud(table, options) {
2107
2108
  }
2108
2109
  const [result] = await ctx.db.update(anyTable).set(updateData).where(eq(pk, id)).returning();
2109
2110
  if (useRealtime) {
2110
- const listResult = await fetchListWithTotal(ctx.db);
2111
- ctx.realtime.emit(`${prefix}.list`, listResult);
2112
- ctx.realtime.emit(`${prefix}.get`, result);
2111
+ if (enabledMethods.has("list")) {
2112
+ const listResult = await fetchListWithTotal(ctx.db);
2113
+ ctx.realtime.emit(`${prefix}.list`, listResult);
2114
+ }
2115
+ if (enabledMethods.has("get")) {
2116
+ ctx.realtime.emit(`${prefix}.get`, result);
2117
+ }
2113
2118
  }
2114
2119
  return result;
2115
2120
  }
2116
2121
  });
2117
- const removeDef = mutation(`${prefix}.remove`, {
2122
+ const removeDef = !enabledMethods.has("remove") ? void 0 : mutation(`${prefix}.remove`, {
2118
2123
  public: isPublic,
2119
2124
  invalidates: [],
2120
2125
  handler: async (ctx, args) => {
@@ -2125,7 +2130,7 @@ function crud(table, options) {
2125
2130
  } else {
2126
2131
  await ctx.db.delete(anyTable).where(eq(pk, args.id));
2127
2132
  }
2128
- if (useRealtime) {
2133
+ if (useRealtime && enabledMethods.has("list")) {
2129
2134
  const listResult = await fetchListWithTotal(ctx.db);
2130
2135
  ctx.realtime.emit(`${prefix}.list`, listResult);
2131
2136
  }
@@ -223,8 +223,24 @@ describe("buildAiPrompt — AI Vibe-Coding 프롬프트", () => {
223
223
 
224
224
  it("mutation 제한 경고 포함", () => {
225
225
  const md = buildAiPrompt(SIMPLE_API, ["tasks"]);
226
- expect(md).toContain("mutation 10 이내");
227
- expect(md).toContain("scheduler.runAfter");
226
+ expect(md).toContain("mutation: 30초");
227
+ expect(md).toContain("FUNCTION_TIMEOUT");
228
+ expect(md).toContain("별도 mutation으로 분리");
229
+ });
230
+
231
+ it("mutation 간 공유 로직 패턴 포함", () => {
232
+ const md = buildAiPrompt(SIMPLE_API, ["tasks"]);
233
+ expect(md).toContain("mutation 간 로직 공유");
234
+ expect(md).toContain("일반 async 함수로 분리");
235
+ expect(md).toContain("mutation은 래핑만");
236
+ });
237
+
238
+ it("AI 우회 금지 목록 포함", () => {
239
+ const md = buildAiPrompt(SIMPLE_API, ["tasks"]);
240
+ expect(md).toContain("절대 금지");
241
+ expect(md).toContain("openai-direct.ts");
242
+ expect(md).toContain("ai.ts 수정 또는 우회 금지");
243
+ expect(md).toContain("SDK 직접 설치/사용 금지");
228
244
  });
229
245
 
230
246
  it("크론 잡 사용법 포함", () => {
@@ -300,15 +300,23 @@ export function buildAiPrompt(apiObj, namespaces) {
300
300
  md += `- gencow/index.ts의 re-export는 export * as moduleName from "./moduleName" 패턴을 써.\n`;
301
301
  md += ` Module, Mod 같은 접미사를 붙이지 마.\n`;
302
302
  md += `\n`;
303
- md += `⚠️ mutation 제한:\n`;
304
- md += `- mutation은 10초 이내에 완료되어야 해. 외부 API나 LLM 호출이 길면 단계별로 분리해.\n`;
303
+ md += `⚠️ mutation 제한 (타임아웃):\n`;
304
+ md += `- query: 30초, mutation: 30초, cron: 10분. 시간을 초과하면 FUNCTION_TIMEOUT 에러.\n`;
305
305
  md += `- ✅ 1순위: 각 단계를 별도 mutation으로 분리하고, 프론트엔드에서 순차 호출해.\n`;
306
306
  md += ` 예: 크롤링(Step1) → 필터링(Step2) → 요약(Step3) 각각 별도 mutation으로 분리.\n`;
307
307
  md += ` 프론트엔드: await crawl(); await filter(); await summarize();\n`;
308
- md += `- ⚠️ 보조: ctx.scheduler.runAfter(0, "module.nextStep", { sessionId }) — 로컬 dev에서만 안정적. 클라우드에서는 sleep 시 타이머가 소멸할 수 있어.\n`;
309
- md += `- 같은 서버의 다른 모듈 함수를 호출할 때는 fetch()가 아닌 직접 import해서 호출해.\n`;
310
- md += ` 예: import { fetchNews } from "./naverApi"; → const result = await fetchNews.handler(ctx, { keyword });\n`;
311
- md += ` HTTP self-fetch (fetch("/api/mutation")) 패턴은 불필요한 네트워크 우회이므로 사용하지 마.\n`;
308
+ md += `- ⚠️ 보조: ctx.scheduler.runAfter(0, "module.nextStep", { sessionId }) — 로컬 dev에서만 안정적. 클라우드에서는 앱이 idle 상태로 전환 시 타이머가 소멸돼.\n`;
309
+ md += `\n`;
310
+ md += `⚠️ mutation 로직 공유:\n`;
311
+ md += `- mutation().handler() 다른 mutation에서 직접 호출하면 돼.\n`;
312
+ md += `- ✅ 공유 로직은 일반 async 함수로 분리하고, 각 mutation에서 래핑해:\n`;
313
+ md += ` // 공유 함수 (일반 async 함수)\n`;
314
+ md += ` async function filterBatch(ctx, ids) { /* 실제 로직 */ }\n`;
315
+ md += ` // mutation은 래핑만\n`;
316
+ md += ` export const filter = mutation("pipeline.filter", {\n`;
317
+ md += ` handler: async (ctx, args) => filterBatch(ctx, args.ids),\n`;
318
+ md += ` });\n`;
319
+ md += `- ❌ fetch("/api/mutation") self-fetch 패턴은 사용하지 마.\n`;
312
320
 
313
321
  md += `\n`;
314
322
  md += `크론 잡 (예약 작업):\n`;
@@ -373,12 +381,18 @@ export function buildAiPrompt(apiObj, namespaces) {
373
381
  md += ` const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));\n`;
374
382
  md += ` const hex = [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join("");\n`;
375
383
  md += `- 직접 구현한 simpleHash 같은 32비트 해시는 충돌 위험이 있으니 SHA-256을 사용해.\n`;
376
- md += `- AI 호출은 import { ai } from "./ai" 를 사용해:\n`;
384
+ md += `⚠️ AI 호출 규칙 (반드시 준수):\n`;
385
+ md += `- ✅ AI 호출은 반드시 import { ai } from "./ai"를 사용해:\n`;
377
386
  md += ` import { ai } from "./ai";\n`;
378
387
  md += ` const result = await ai.chat({ messages: [{ role: "user", content: "안녕" }] });\n`;
379
- md += ` ❌ import OpenAI from "openai"; → 시크릿 관리, 비용 추적, 보안 문제 발생\n`;
380
- md += ` ❌ OpenAI/Anthropic SDK 직접 설치 금지\n`;
381
388
  md += ` ✅ ai.chat()은 로컬(OPENAI_API_KEY)과 클라우드(Gencow 프록시) 자동 전환\n`;
389
+ md += `- ❌ 절대 금지:\n`;
390
+ md += ` - import OpenAI from "openai" — SDK 직접 설치/사용 금지\n`;
391
+ md += ` - fetch("https://api.openai.com/...") — API 직접 호출 금지\n`;
392
+ md += ` - openai-direct.ts, ai-wrapper.ts 같은 별도 래퍼 파일 생성 금지\n`;
393
+ md += ` - ai.ts 수정 또는 우회 금지 (프록시 우회 시 크레딧 차감 불가 + 장애 위험)\n`;
394
+ md += `- AI 에러 발생 시: .env의 OPENAI_API_KEY 확인 또는 npx gencow add AI로 재설치\n`;
395
+ md += `- JSON 응답 필요 시: ai.generateObject({ schema: z.object({...}), prompt: "..." })\n`;
382
396
  md += `- AI 컴포넌트가 없으면: gencow add AI 로 설치해.\n`;
383
397
  md += `- RAG가 필요하면: gencow add RAG (AI 자동 포함).\n`;
384
398
  md += `- 메모리가 필요하면: gencow add Memory.\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -2076,7 +2076,8 @@ function crud(table, options) {
2076
2076
  const countResult = await db.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2077
2077
  return { data, total: Number(countResult[0]?.count ?? 0) };
2078
2078
  }
2079
- const listDef = query(`${prefix}.list`, {
2079
+ const enabledMethods = new Set(options?.methods ?? ["list", "get", "create", "update", "remove"]);
2080
+ const listDef = !enabledMethods.has("list") ? void 0 : query(`${prefix}.list`, {
2080
2081
  public: isPublic,
2081
2082
  args: {
2082
2083
  page: v.optional(v.number()),
@@ -2107,7 +2108,7 @@ function crud(table, options) {
2107
2108
  };
2108
2109
  }
2109
2110
  });
2110
- const getDef = query(`${prefix}.get`, {
2111
+ const getDef = !enabledMethods.has("get") ? void 0 : query(`${prefix}.get`, {
2111
2112
  public: isPublic,
2112
2113
  args: { id: idValidator },
2113
2114
  handler: async (ctx, args) => {
@@ -2121,7 +2122,7 @@ function crud(table, options) {
2121
2122
  return result ?? null;
2122
2123
  }
2123
2124
  });
2124
- const createDef = mutation(`${prefix}.create`, {
2125
+ const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
2125
2126
  public: isPublic,
2126
2127
  invalidates: [],
2127
2128
  handler: async (ctx, args) => {
@@ -2134,14 +2135,14 @@ function crud(table, options) {
2134
2135
  insertData = await options.hooks.beforeCreate(insertData);
2135
2136
  }
2136
2137
  const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
2137
- if (useRealtime) {
2138
+ if (useRealtime && enabledMethods.has("list")) {
2138
2139
  const listResult = await fetchListWithTotal(ctx.db);
2139
2140
  ctx.realtime.emit(`${prefix}.list`, listResult);
2140
2141
  }
2141
2142
  return result;
2142
2143
  }
2143
2144
  });
2144
- const updateDef = mutation(`${prefix}.update`, {
2145
+ const updateDef = !enabledMethods.has("update") ? void 0 : mutation(`${prefix}.update`, {
2145
2146
  public: isPublic,
2146
2147
  invalidates: [],
2147
2148
  handler: async (ctx, args) => {
@@ -2156,14 +2157,18 @@ function crud(table, options) {
2156
2157
  }
2157
2158
  const [result] = await ctx.db.update(anyTable).set(updateData).where(eq(pk, id)).returning();
2158
2159
  if (useRealtime) {
2159
- const listResult = await fetchListWithTotal(ctx.db);
2160
- ctx.realtime.emit(`${prefix}.list`, listResult);
2161
- ctx.realtime.emit(`${prefix}.get`, result);
2160
+ if (enabledMethods.has("list")) {
2161
+ const listResult = await fetchListWithTotal(ctx.db);
2162
+ ctx.realtime.emit(`${prefix}.list`, listResult);
2163
+ }
2164
+ if (enabledMethods.has("get")) {
2165
+ ctx.realtime.emit(`${prefix}.get`, result);
2166
+ }
2162
2167
  }
2163
2168
  return result;
2164
2169
  }
2165
2170
  });
2166
- const removeDef = mutation(`${prefix}.remove`, {
2171
+ const removeDef = !enabledMethods.has("remove") ? void 0 : mutation(`${prefix}.remove`, {
2167
2172
  public: isPublic,
2168
2173
  invalidates: [],
2169
2174
  handler: async (ctx, args) => {
@@ -2174,7 +2179,7 @@ function crud(table, options) {
2174
2179
  } else {
2175
2180
  await ctx.db.delete(anyTable).where(eq(pk, args.id));
2176
2181
  }
2177
- if (useRealtime) {
2182
+ if (useRealtime && enabledMethods.has("list")) {
2178
2183
  const listResult = await fetchListWithTotal(ctx.db);
2179
2184
  ctx.realtime.emit(`${prefix}.list`, listResult);
2180
2185
  }
@@ -82244,7 +82249,7 @@ async function executeWithTimeout(fn, type, functionName) {
82244
82249
  new Promise((_, reject) => {
82245
82250
  controller.signal.addEventListener("abort", () => {
82246
82251
  const err = new Error(
82247
- `Function "${functionName}" exceeded ${timeoutMs / 1e3}s time limit. Use ctx.scheduler.runAfter() for long-running tasks.`
82252
+ `Function "${functionName}" exceeded ${timeoutMs / 1e3}s time limit. Split into smaller mutations and call them sequentially from the frontend. Limits: query 30s, mutation 30s, httpAction 5min, cron 10min.`
82248
82253
  );
82249
82254
  err.code = "FUNCTION_TIMEOUT";
82250
82255
  reject(err);
@@ -83373,10 +83378,10 @@ async function main() {
83373
83378
  } : void 0),
83374
83379
  scheduler: scheduler ?? {
83375
83380
  runAfter: () => {
83376
- throw new Error("Scheduler not initialized");
83381
+ throw new Error("Scheduler not initialized \u2014 add gencow/crons.ts to enable. Note: In BaaS mode, scheduler.runAfter() is sleep-unsafe (timers are lost when app goes idle). Recommended: split into separate mutations and call sequentially from frontend.");
83377
83382
  },
83378
83383
  runAt: () => {
83379
- throw new Error("Scheduler not initialized");
83384
+ throw new Error("Scheduler not initialized \u2014 add gencow/crons.ts to enable. Note: In BaaS mode, scheduler.runAt() is sleep-unsafe.");
83380
83385
  },
83381
83386
  cancel: () => false,
83382
83387
  cron: () => {