gencow 0.1.63 → 0.1.65

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 CHANGED
@@ -343,9 +343,26 @@ process.exit(0);
343
343
  let out = `/**\n * Generated by Gencow\n * Do not edit this file manually.\n */\n`;
344
344
  out += `import { defineQuery, defineMutation } from "@gencow/react";\n\n`;
345
345
 
346
- // Add import statements for each namespace file
346
+ // Add import statements 실제 파일 존재 여부 확인 후 import 경로 결정
347
+ // 네임스페이스별 파일(e.g. products.ts)이 있으면 개별 import,
348
+ // 없으면 단일 파일(index.ts) 프로젝트로 판단하여 index.ts에서 import
349
+ const nsFileMap = new Map();
347
350
  for (const ns of Object.keys(apiObj)) {
348
- out += `import type * as ${ns} from "./${ns}.ts";\n`;
351
+ const nsFile = resolve(absoluteFunctions, `${ns}.ts`);
352
+ if (existsSync(nsFile)) {
353
+ nsFileMap.set(ns, `./${ns}.ts`);
354
+ }
355
+ }
356
+ const hasNamespaceFiles = nsFileMap.size > 0;
357
+
358
+ if (hasNamespaceFiles) {
359
+ for (const [ns, path] of nsFileMap) {
360
+ out += `import type * as ${ns} from "${path}";\n`;
361
+ }
362
+ // 네임스페이스 파일이 없는 namespace는 타입 생략 (typeof 참조 제거)
363
+ } else {
364
+ // 단일 파일 프로젝트 — index.ts에서 모든 export를 import
365
+ out += `import type * as _all from "./index.ts";\n`;
349
366
  }
350
367
 
351
368
  out += `\nexport const api = {\n`;
@@ -353,11 +370,24 @@ process.exit(0);
353
370
  out += ` ${ns}: {\n`;
354
371
  for (const q of fns.queries) {
355
372
  const tsExport = q === "delete" ? "_delete" : q;
356
- out += ` ${q}: defineQuery<typeof ${ns}.${tsExport}>("${ns}.${q}"),\n`;
373
+ if (hasNamespaceFiles && nsFileMap.has(ns)) {
374
+ out += ` ${q}: defineQuery<typeof ${ns}.${tsExport}>("${ns}.${q}"),\n`;
375
+ } else if (!hasNamespaceFiles) {
376
+ out += ` ${q}: defineQuery<typeof _all.${tsExport}>("${ns}.${q}"),\n`;
377
+ } else {
378
+ // 네임스페이스 파일이 일부만 있고 이 ns에는 없는 경우
379
+ out += ` ${q}: defineQuery("${ns}.${q}"),\n`;
380
+ }
357
381
  }
358
382
  for (const m of fns.mutations) {
359
383
  const tsExport = m === "delete" ? "_delete" : m;
360
- out += ` ${m}: defineMutation<typeof ${ns}.${tsExport}>("${ns}.${m}"),\n`;
384
+ if (hasNamespaceFiles && nsFileMap.has(ns)) {
385
+ out += ` ${m}: defineMutation<typeof ${ns}.${tsExport}>("${ns}.${m}"),\n`;
386
+ } else if (!hasNamespaceFiles) {
387
+ out += ` ${m}: defineMutation<typeof _all.${tsExport}>("${ns}.${m}"),\n`;
388
+ } else {
389
+ out += ` ${m}: defineMutation("${ns}.${m}"),\n`;
390
+ }
361
391
  }
362
392
  out += ` },\n`;
363
393
  }
@@ -552,12 +582,13 @@ function generateReadmeMd(config, apiObj) {
552
582
  md += `### 백엔드에서 AI 호출\n\n`;
553
583
  md += `\`\`\`typescript\n`;
554
584
  md += `// gencow/ai.ts\n`;
555
- md += `import { mutation } from "@gencow/core";\n\n`;
556
- md += `export const chat = mutation({\n`;
557
- md += ` handler: async (ctx, { message }) => {\n`;
585
+ md += `import { mutation, v } from "@gencow/core";\n\n`;
586
+ md += `export const chat = mutation("ai.chat", {\n`;
587
+ md += ` args: { message: v.string() },\n`;
588
+ md += ` handler: async (ctx, args) => {\n`;
558
589
  md += ` const result = await ctx.ai.chat({\n`;
559
590
  md += ` model: "gpt-4o-mini", // 또는 "gpt-4o"\n`;
560
- md += ` messages: [{ role: "user", content: message }],\n`;
591
+ md += ` messages: [{ role: "user", content: args.message }],\n`;
561
592
  md += ` });\n`;
562
593
  md += ` return result.text;\n`;
563
594
  md += ` },\n`;
@@ -880,7 +911,7 @@ const commands = {
880
911
  `);
881
912
  success("Created gencow.config.ts");
882
913
 
883
- // 4.5. drizzle.config.ts (standalone db:generate에 필요)
914
+ // 4.5. drizzle.config.ts (Cloud-first: DATABASE_URL 있으면 Cloud DB, 없으면 --local PGlite)
884
915
  writeFileSync(resolve(projectDir, "drizzle.config.ts"),
885
916
  `import { defineConfig } from "drizzle-kit";
886
917
 
@@ -888,8 +919,9 @@ export default defineConfig({
888
919
  dialect: "postgresql",
889
920
  schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
890
921
  out: "./migrations",
891
- driver: "pglite",
892
- dbCredentials: { url: "./.gencow/data" },
922
+ ...(process.env.DATABASE_URL
923
+ ? { dbCredentials: { url: process.env.DATABASE_URL } }
924
+ : { driver: "pglite", dbCredentials: { url: "./.gencow/data" } }),
893
925
  });
894
926
  `);
895
927
  success("Created drizzle.config.ts");
@@ -3246,10 +3278,11 @@ process.exit(0);
3246
3278
  log(`
3247
3279
  ${BOLD}${CYAN}🚀 Gencow Cloud Dev${RESET}
3248
3280
 
3249
- ${GREEN}▸${RESET} App: ${BOLD}${appName}${RESET}
3250
- ${GREEN}▸${RESET} URL: ${DIM}${appUrl}${RESET}
3251
- ${GREEN}▸${RESET} Watching: ${DIM}${functionsDir}/ (${watchedFiles.join(", ") || "*.ts"})${RESET}
3252
- ${GREEN}▸${RESET} Mode: ${DIM}Cloud (PostgreSQL)${RESET}
3281
+ ${GREEN}▸${RESET} App: ${BOLD}${appName}${RESET}
3282
+ ${GREEN}▸${RESET} URL: ${DIM}${appUrl}${RESET}
3283
+ ${GREEN}▸${RESET} Dashboard: ${DIM}https://gencow.app/_cloud/apps/${appName}${RESET}
3284
+ ${GREEN}▸${RESET} Watching: ${DIM}${functionsDir}/ (${watchedFiles.join(", ") || "*.ts"})${RESET}
3285
+ ${GREEN}▸${RESET} Mode: ${DIM}Cloud (PostgreSQL)${RESET}
3253
3286
 
3254
3287
  ${DIM}Ctrl+C to stop${RESET}
3255
3288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -72569,6 +72569,7 @@ async function main() {
72569
72569
  }
72570
72570
  const mutations = getRegisteredMutations();
72571
72571
  const mutationMap = new Map(mutations.map((m) => [m.name, m]));
72572
+ const publicQueryWarned = /* @__PURE__ */ new Set();
72572
72573
  const apiRouter = new Hono2().post("/query", async (c) => {
72573
72574
  const body = await c.req.json().catch(() => ({}));
72574
72575
  const { name: name21, args } = body;
@@ -72585,6 +72586,13 @@ async function main() {
72585
72586
  try {
72586
72587
  const validatedArgs = parseArgs(queryDef.argsSchema, args);
72587
72588
  const result = await queryDef.handler(ctx, validatedArgs);
72589
+ if (queryDef.isPublic && !IS_BAAS && Array.isArray(result) && result.length >= 100) {
72590
+ if (!publicQueryWarned.has(name21)) {
72591
+ publicQueryWarned.add(name21);
72592
+ console.warn(`[gencow] \u26A0\uFE0F "${name21}" (public) returned ${result.length} rows without pagination.`);
72593
+ console.warn(`[gencow] \u{1F4A1} Add .limit(100) to prevent full table scans. See SECURITY.md`);
72594
+ }
72595
+ }
72588
72596
  return c.json(result);
72589
72597
  } catch (err) {
72590
72598
  const status = err instanceof GencowValidationError ? 400 : 500;
@@ -72641,11 +72649,15 @@ async function main() {
72641
72649
  console.log(`[gencow] \u26A0\uFE0F PUBLIC FUNCTIONS (no auth required):`);
72642
72650
  if (publicQueries.length > 0) {
72643
72651
  console.log(`[gencow] queries: ${publicQueries.join(", ")}`);
72652
+ console.log(`[gencow] \u{1F513} These queries return data WITHOUT authentication.`);
72653
+ console.log(`[gencow] Ensure no PII (personal data) is exposed.`);
72654
+ console.log(`[gencow] Add .limit() to prevent full table scans on large datasets.`);
72644
72655
  }
72645
72656
  if (publicMutations.length > 0) {
72646
72657
  console.log(`[gencow] mutations: ${publicMutations.join(", ")}`);
72647
72658
  }
72648
- console.log(`[gencow] \u2192 Use { public: true } only for intentionally open endpoints.`);
72659
+ console.log(`[gencow] \u2192 Remove { public: true } if auth is intended.`);
72660
+ console.log(`[gencow] \u2192 \u{1F4C4} gencow/SECURITY.md \uCC38\uACE0`);
72649
72661
  }
72650
72662
  console.log(`[gencow] \u{1F512} Secure by Default: ${protectedQueryCount} queries + ${protectedMutationCount} mutations require auth`);
72651
72663
  const _aiKey = process.env.OPENAI_API_KEY;