@gencow/core 0.1.30 → 0.1.31

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.
@@ -58,6 +58,19 @@ export interface AuthEvents {
58
58
  inviteCode?: string | null;
59
59
  }, context?: AuthHookContext) => Promise<void>;
60
60
  }
61
+ export type AuthUserFieldType = "text" | "string" | "boolean" | "integer" | "number" | "timestamp";
62
+ export interface AuthUserField {
63
+ /** Scalar field type. `text` is emitted as Drizzle `text()` and better-auth `string`. */
64
+ type: AuthUserFieldType;
65
+ /** Optional database column name. Defaults to snake_case(fieldName). */
66
+ columnName?: string;
67
+ /** Whether the DB column may be null. Defaults to false when no default is provided. */
68
+ nullable?: boolean;
69
+ /** Default value for DB schema and better-auth runtime metadata. */
70
+ default?: string | number | boolean;
71
+ /** Whether signup/update input may set this field. Defaults to false for safety. */
72
+ input?: boolean;
73
+ }
61
74
  export interface SocialProviderConfig {
62
75
  clientId: string;
63
76
  clientSecret: string;
@@ -107,13 +120,28 @@ export interface AuthOAuthConfig {
107
120
  /** Extra allowed frontend callback URLs for local development or multi-frontend apps. */
108
121
  allowedCallbackURLs?: string[];
109
122
  }
110
- export interface GencowAuthConfig {
123
+ export interface GencowAuthBaseConfig {
111
124
  emailVerification?: AuthEmailVerification;
112
125
  socialProviders?: SocialProvidersConfig;
113
126
  oauth?: AuthOAuthConfig;
114
127
  passwordReset?: AuthPasswordReset;
115
128
  events?: AuthEvents;
116
129
  }
130
+ export interface GencowBetterAuthConfig extends GencowAuthBaseConfig {
131
+ provider: "better-auth";
132
+ user?: {
133
+ /** App-specific scalar fields stored on the better-auth user table. */
134
+ additionalFields?: Record<string, AuthUserField>;
135
+ };
136
+ /**
137
+ * Escape hatch for provider-specific better-auth options/plugins.
138
+ */
139
+ betterAuth?: (defaults: Record<string, unknown>) => Record<string, unknown>;
140
+ }
141
+ export type GencowAuthConfig = GencowBetterAuthConfig;
142
+ export type GencowAuthConfigInput = Omit<GencowBetterAuthConfig, "provider"> & {
143
+ provider?: "better-auth";
144
+ };
117
145
  /**
118
146
  * Auth 설정 정의 헬퍼.
119
147
  *
@@ -131,4 +159,4 @@ export interface GencowAuthConfig {
131
159
  * });
132
160
  * ```
133
161
  */
134
- export declare function defineAuth(config: GencowAuthConfig): GencowAuthConfig;
162
+ export declare function defineAuth(config: GencowAuthConfigInput): GencowAuthConfig;
@@ -26,5 +26,5 @@
26
26
  * ```
27
27
  */
28
28
  export function defineAuth(config) {
29
- return config;
29
+ return { ...config, provider: config.provider ?? "better-auth" };
30
30
  }
package/dist/config.d.ts CHANGED
@@ -40,6 +40,24 @@ export interface GencowDbConfig {
40
40
  */
41
41
  url?: string;
42
42
  }
43
+ /**
44
+ * Code generation settings for `gencow codegen` and `gencow dev`.
45
+ */
46
+ export interface GencowCodegenConfig {
47
+ /**
48
+ * Output directory for generated frontend codegen artifacts such as `api.ts`
49
+ * and declaration files.
50
+ *
51
+ * Default: `"./src/gencow"`
52
+ */
53
+ outDir?: string;
54
+ /**
55
+ * Whether codegen should regenerate `gencow/auth-schema.ts` from `gencow/auth.ts`.
56
+ *
57
+ * Default: `true`
58
+ */
59
+ authSchema?: boolean;
60
+ }
43
61
  /**
44
62
  * Project configuration for `gencow.config.ts`.
45
63
  *
@@ -50,7 +68,10 @@ export interface GencowDbConfig {
50
68
  * export default defineConfig({
51
69
  * functionsDir: "./gencow",
52
70
  * schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
53
- * codegenOutDir: "./src/gencow",
71
+ * codegen: {
72
+ * outDir: "./src/gencow",
73
+ * authSchema: true,
74
+ * },
54
75
  * storage: "./.gencow/uploads",
55
76
  * db: { url: "./.gencow/data" },
56
77
  * port: 5456,
@@ -74,10 +95,11 @@ export interface GencowConfig {
74
95
  */
75
96
  schema?: string | string[];
76
97
  /**
77
- * Output directory for generated frontend codegen artifacts such as `api.ts`
78
- * and declaration files.
79
- *
80
- * Default: `"./src/gencow"`
98
+ * Code generation settings.
99
+ */
100
+ codegen?: GencowCodegenConfig;
101
+ /**
102
+ * @deprecated Use `codegen.outDir` instead.
81
103
  */
82
104
  codegenOutDir?: string;
83
105
  /**
package/dist/context.d.ts CHANGED
@@ -69,39 +69,6 @@ export type RealtimeNotifyEvent = {
69
69
  * Convex의 ctx 패턴과 동일하게, 이 객체를 통해서만 DB/Storage/Auth에 접근 가능.
70
70
  * fs, child_process 등 원시 Node.js API는 노출되지 않음.
71
71
  */
72
- export interface AIMessage {
73
- role: "user" | "system" | "assistant";
74
- content: string;
75
- }
76
- export interface AIResult {
77
- text: string;
78
- usage: {
79
- promptTokens: number;
80
- completionTokens: number;
81
- totalTokens: number;
82
- };
83
- creditsCharged: number;
84
- model: string;
85
- }
86
- export interface AIContext {
87
- /** AI 텍스트 생성 */
88
- chat: (opts: {
89
- model?: string;
90
- messages: AIMessage[];
91
- /** System prompt — shorthand for adding a system message */
92
- system?: string;
93
- temperature?: number;
94
- maxTokens?: number;
95
- /** Response format — e.g. { type: "json_object" } for JSON mode */
96
- responseFormat?: {
97
- type: string;
98
- };
99
- }) => Promise<AIResult>;
100
- /** 텍스트 임베딩 (단일) */
101
- embed: (text: string) => Promise<number[]>;
102
- /** 배치 임베딩 */
103
- embedMany: (texts: string[]) => Promise<number[][]>;
104
- }
105
72
  export interface GencowCtx {
106
73
  /** Drizzle DB 인스턴스 (scoped) — 스키마 filter 자동 적용, execute 차단 */
107
74
  db: any;
@@ -119,8 +86,6 @@ export interface GencowCtx {
119
86
  retry: <T>(fn: () => Promise<T>, options?: RetryOptions) => Promise<T>;
120
87
  /** 프레임워크 서비스 헬퍼 — workflow 전용 service는 별도 ctx에서 노출 */
121
88
  services: GencowServicesCtx;
122
- /** AI 헬퍼 */
123
- ai?: AIContext;
124
89
  /** Full-text / hybrid search helper */
125
90
  search: (table: string, query: string, options: SearchOptions) => Promise<SearchResponse>;
126
91
  /** Vector / semantic search helper */
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Provides: query, mutation, storage, scheduler, auth
5
5
  * All with Convex-compatible DX patterns.
6
6
  */
7
- export type { GencowCtx, AuthCtx, UserIdentity, RealtimeCtx, RealtimeNotifyEvent, AIContext, AIMessage, AIResult, } from "./context.js";
7
+ export type { GencowCtx, AuthCtx, UserIdentity, RealtimeCtx, RealtimeNotifyEvent, } from "./context.js";
8
8
  export { defineApi } from "./context.js";
9
9
  export type { QueryDef } from "./reactive-query-types.js";
10
10
  export type { MutationDef } from "./reactive-mutation-types.js";
@@ -43,9 +43,9 @@ export { filterTenantRuntimeEnvVars, isReservedTenantRuntimeEnvKey } from "./run
43
43
  export { cronJobs } from "./crons.js";
44
44
  export type { CronJobsBuilder, CronJobDef, IntervalOptions, DailyOptions, WeeklyOptions } from "./crons.js";
45
45
  export { defineAuth } from "./auth-config.js";
46
- export type { GencowAuthConfig, AuthEmailVerification, AuthPasswordReset, AuthEvents, AuthHookContext, AuthUserLike, AuthOAuthConfig, SocialProviderConfig, SocialProvidersConfig, KakaoConfig, NaverConfig, CustomOAuthProvider, OAuthUserInfo, } from "./auth-config.js";
46
+ export type { AuthEmailVerification, AuthPasswordReset, AuthEvents, AuthHookContext, AuthUserLike, AuthOAuthConfig, AuthUserField, AuthUserFieldType, CustomOAuthProvider, GencowAuthConfig, GencowAuthConfigInput, GencowAuthBaseConfig, GencowBetterAuthConfig, KakaoConfig, NaverConfig, OAuthUserInfo, SocialProviderConfig, SocialProvidersConfig, } from "./auth-config.js";
47
47
  export { defineConfig } from "./config.js";
48
- export type { GencowConfig, GencowDbConfig, GencowDeployConfig } from "./config.js";
48
+ export type { GencowCodegenConfig, GencowConfig, GencowDbConfig, GencowDeployConfig } from "./config.js";
49
49
  export { ownerRls, getOwnerRlsMeta, registerOwnerRls } from "./rls.js";
50
50
  export type { OwnerRlsMeta } from "./rls.js";
51
51
  export { createRlsDb } from "./rls-db.js";
@@ -0,0 +1,5 @@
1
+ import type { GencowProcedureDef } from "./procedure.js";
2
+ export declare function collectProcedureRouteMaps(appModule: unknown): {
3
+ queryProcedureMap: Map<string, GencowProcedureDef<"query", any, any, any>>;
4
+ mutationProcedureMap: Map<string, GencowProcedureDef<"mutation", any, any, any>>;
5
+ };
@@ -0,0 +1,37 @@
1
+ function asProcedureDef(value) {
2
+ if (!value || typeof value !== "object")
3
+ return undefined;
4
+ const d = value;
5
+ if (d.kind !== "query" && d.kind !== "mutation")
6
+ return undefined;
7
+ if (typeof d.name !== "string" || typeof d.handler !== "function")
8
+ return undefined;
9
+ return { ...d, isPublic: d.isPublic === true };
10
+ }
11
+ export function collectProcedureRouteMaps(appModule) {
12
+ const queryProcedureMap = new Map();
13
+ const mutationProcedureMap = new Map();
14
+ if (!appModule || typeof appModule !== "object") {
15
+ return { queryProcedureMap, mutationProcedureMap };
16
+ }
17
+ const mod = appModule;
18
+ const apiCandidate = mod.default !== undefined && mod.default !== null && typeof mod.default === "object"
19
+ ? mod.default
20
+ : appModule;
21
+ const procedures = apiCandidate.procedures;
22
+ if (!procedures || typeof procedures !== "object") {
23
+ return { queryProcedureMap, mutationProcedureMap };
24
+ }
25
+ for (const def of Object.values(procedures)) {
26
+ const proc = asProcedureDef(def);
27
+ if (!proc)
28
+ continue;
29
+ if (proc.kind === "query") {
30
+ queryProcedureMap.set(proc.name, proc);
31
+ }
32
+ else {
33
+ mutationProcedureMap.set(proc.name, proc);
34
+ }
35
+ }
36
+ return { queryProcedureMap, mutationProcedureMap };
37
+ }
@@ -1,4 +1,4 @@
1
- import { bigint, boolean, doublePrecision, integer, jsonb, pgTable, text, timestamp, vector } from "drizzle-orm/pg-core";
1
+ import { bigint, boolean, doublePrecision, integer, jsonb, pgTable, text, timestamp, vector, } from "drizzle-orm/pg-core";
2
2
  function buildScopeColumns() {
3
3
  return {
4
4
  id: text("id").primaryKey(),
@@ -43,7 +43,7 @@ export function buildQuerySubscriptionKey(queryKey, args) {
43
43
  return `${queryKey}${SUBSCRIPTION_KEY_SEPARATOR}${JSON.stringify(normalized)}`;
44
44
  }
45
45
  export function subscriptionKeyMatchesQueryKey(subscriptionKey, queryKey) {
46
- return subscriptionKey === queryKey || subscriptionKey.startsWith(`${queryKey}${SUBSCRIPTION_KEY_SEPARATOR}`);
46
+ return (subscriptionKey === queryKey || subscriptionKey.startsWith(`${queryKey}${SUBSCRIPTION_KEY_SEPARATOR}`));
47
47
  }
48
48
  export function subscribe(queryKey, ws) {
49
49
  connectedClients.add(ws);
@@ -31,12 +31,19 @@ const RESERVED_TENANT_RUNTIME_ENV_KEYS = new Set([
31
31
  "GENCOW_SHUTDOWN_MARKER_PATH",
32
32
  "GENCOW_SKIP_MIGRATION",
33
33
  "GENCOW_DB_MAX_CONNECTIONS",
34
+ "GENCOW_DB_IDLE_TIMEOUT_SECONDS",
35
+ "GENCOW_DB_CONNECTION_TIMEOUT_SECONDS",
34
36
  "GENCOW_MEMORY_MB",
35
37
  "BUN_JSC_forceRAMSize",
36
38
  "MIMALLOC_PURGE_DELAY",
37
39
  "NODE_PATH",
38
40
  ]);
39
- const RESERVED_TENANT_RUNTIME_ENV_PREFIXES = ["__GENCOW_", "GENCOW_DOCUMENT_", "GENCOW_TEMPLATE_", "GENCOW_WARM_"];
41
+ const RESERVED_TENANT_RUNTIME_ENV_PREFIXES = [
42
+ "__GENCOW_",
43
+ "GENCOW_DOCUMENT_",
44
+ "GENCOW_TEMPLATE_",
45
+ "GENCOW_WARM_",
46
+ ];
40
47
  export function isReservedTenantRuntimeEnvKey(key) {
41
48
  const normalized = key.trim();
42
49
  return (RESERVED_TENANT_RUNTIME_ENV_KEYS.has(normalized) ||
@@ -3,6 +3,8 @@ type ActionHandler = (args: any) => Promise<any>;
3
3
  export interface ScheduleOptions {
4
4
  /** 실패 시 호출할 action 이름 (dead-letter 패턴) */
5
5
  onError?: string;
6
+ /** Optional deterministic id for idempotent durable scheduling. */
7
+ id?: string;
6
8
  }
7
9
  /** scheduled job의 DB 영속화를 위한 콜백 */
8
10
  export interface ScheduledJobRecord {
package/dist/scheduler.js CHANGED
@@ -88,7 +88,7 @@ export function createScheduler(options) {
88
88
  }
89
89
  return {
90
90
  runAfter(ms, action, args, scheduleOpts) {
91
- const id = generateId();
91
+ const id = scheduleOpts?.id ?? generateId();
92
92
  const jobEntry = {
93
93
  id,
94
94
  action,
package/dist/storage.js CHANGED
@@ -2,7 +2,7 @@ import * as fs from "fs/promises";
2
2
  import * as fsSync from "fs";
3
3
  import * as path from "path";
4
4
  import * as crypto from "crypto";
5
- import { DEFAULT_STORAGE_QUOTA, MAX_FILE_SIZE, formatBytes, loadStorageMeta, } from "./storage-shared.js";
5
+ import { DEFAULT_STORAGE_QUOTA, MAX_FILE_SIZE, formatBytes, loadStorageMeta } from "./storage-shared.js";
6
6
  import { recordStorageImageTransform } from "./storage-metering.js";
7
7
  // ─── Implementation ─────────────────────────────────────
8
8
  const metaStore = new Map();
package/dist/workflow.js CHANGED
@@ -250,7 +250,9 @@ export function workflow(name, options) {
250
250
  lifecycleTimeoutMs,
251
251
  maxRetries,
252
252
  });
253
- const scheduledJobId = ctx.scheduler.runAfter(0, resumeAction, { workflowId });
253
+ const scheduledJobId = insertedWorkflowV2
254
+ ? `start:${workflowId}`
255
+ : ctx.scheduler.runAfter(0, resumeAction, { workflowId });
254
256
  return {
255
257
  id: workflowId,
256
258
  name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gencow/core",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "Gencow core library — defineQuery, defineMutation, reactive subscriptions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,11 +32,14 @@ export interface AuthEmailVerification {
32
32
  /** 인증 완료 후 자동 로그인 (default: true) */
33
33
  autoSignInAfterVerification?: boolean;
34
34
  /** 인증 메일 발송 함수 — 사용자가 직접 구현 */
35
- sendVerificationEmail: (data: {
36
- user: AuthUserLike;
37
- url: string;
38
- token: string;
39
- }, context?: AuthHookContext) => Promise<void>;
35
+ sendVerificationEmail: (
36
+ data: {
37
+ user: AuthUserLike;
38
+ url: string;
39
+ token: string;
40
+ },
41
+ context?: AuthHookContext,
42
+ ) => Promise<void>;
40
43
  /** 인증 직전 훅 — better-auth의 emailVerification.beforeEmailVerification으로 전달 */
41
44
  beforeEmailVerification?: (user: AuthUserLike, context?: AuthHookContext) => Promise<void>;
42
45
  /** 인증 완료 훅 — 환영 메일 등 idempotent 후처리에 사용 */
@@ -45,11 +48,14 @@ export interface AuthEmailVerification {
45
48
 
46
49
  export interface AuthPasswordReset {
47
50
  /** 비밀번호 재설정 메일 발송 함수 — reset URL은 반드시 사용자에게 전달되어야 한다. */
48
- sendResetPassword: (data: {
49
- user: AuthUserLike;
50
- url: string;
51
- token: string;
52
- }, context?: AuthHookContext) => Promise<void>;
51
+ sendResetPassword: (
52
+ data: {
53
+ user: AuthUserLike;
54
+ url: string;
55
+ token: string;
56
+ },
57
+ context?: AuthHookContext,
58
+ ) => Promise<void>;
53
59
  /** 비밀번호 재설정 완료 후 훅 */
54
60
  onPasswordReset?: (data: { user: AuthUserLike }, context?: AuthHookContext) => Promise<void>;
55
61
  /** reset token 유효 시간(초). default: better-auth 기본값 3600초 */
@@ -58,7 +64,27 @@ export interface AuthPasswordReset {
58
64
 
59
65
  export interface AuthEvents {
60
66
  /** 회원가입 성공 후 실행. 운영 알림처럼 실패해도 가입을 막지 않아야 하는 작업에 사용. */
61
- afterSignUp?: (data: { user: AuthUserLike; inviteCode?: string | null }, context?: AuthHookContext) => Promise<void>;
67
+ afterSignUp?: (
68
+ data: { user: AuthUserLike; inviteCode?: string | null },
69
+ context?: AuthHookContext,
70
+ ) => Promise<void>;
71
+ }
72
+
73
+ // ─── Custom User Fields ─────────────────────────────────
74
+
75
+ export type AuthUserFieldType = "text" | "string" | "boolean" | "integer" | "number" | "timestamp";
76
+
77
+ export interface AuthUserField {
78
+ /** Scalar field type. `text` is emitted as Drizzle `text()` and better-auth `string`. */
79
+ type: AuthUserFieldType;
80
+ /** Optional database column name. Defaults to snake_case(fieldName). */
81
+ columnName?: string;
82
+ /** Whether the DB column may be null. Defaults to false when no default is provided. */
83
+ nullable?: boolean;
84
+ /** Default value for DB schema and better-auth runtime metadata. */
85
+ default?: string | number | boolean;
86
+ /** Whether signup/update input may set this field. Defaults to false for safety. */
87
+ input?: boolean;
62
88
  }
63
89
 
64
90
  // ─── Social Login / OAuth ───────────────────────────────
@@ -89,7 +115,9 @@ export interface CustomOAuthProvider {
89
115
  /** Provider-specific callback override. Most apps should leave this unset. */
90
116
  redirectURI?: string;
91
117
  /** Map provider profile responses such as Naver/Kakao into better-auth user fields. */
92
- mapProfileToUser?: (profile: Record<string, unknown>) => Partial<OAuthUserInfo> | Promise<Partial<OAuthUserInfo>>;
118
+ mapProfileToUser?: (
119
+ profile: Record<string, unknown>,
120
+ ) => Partial<OAuthUserInfo> | Promise<Partial<OAuthUserInfo>>;
93
121
  }
94
122
 
95
123
  export type KakaoConfig = SocialProviderConfig & Partial<CustomOAuthProvider>;
@@ -105,12 +133,7 @@ export type SocialProvidersConfig = {
105
133
  kakao?: KakaoConfig;
106
134
  naver?: NaverConfig;
107
135
  /** Additional OAuth providers can be configured with explicit OAuth endpoints. */
108
- [providerId: string]:
109
- | SocialProviderConfig
110
- | KakaoConfig
111
- | NaverConfig
112
- | CustomOAuthProvider
113
- | undefined;
136
+ [providerId: string]: SocialProviderConfig | KakaoConfig | NaverConfig | CustomOAuthProvider | undefined;
114
137
  };
115
138
 
116
139
  export interface AuthOAuthConfig {
@@ -125,7 +148,7 @@ export interface AuthOAuthConfig {
125
148
 
126
149
  // ─── Auth Config ─────────────────────────────────────────
127
150
 
128
- export interface GencowAuthConfig {
151
+ export interface GencowAuthBaseConfig {
129
152
  emailVerification?: AuthEmailVerification;
130
153
  socialProviders?: SocialProvidersConfig;
131
154
  oauth?: AuthOAuthConfig;
@@ -136,6 +159,21 @@ export interface GencowAuthConfig {
136
159
  // sessionExpiry?: number
137
160
  }
138
161
 
162
+ export interface GencowBetterAuthConfig extends GencowAuthBaseConfig {
163
+ provider: "better-auth";
164
+ user?: {
165
+ /** App-specific scalar fields stored on the better-auth user table. */
166
+ additionalFields?: Record<string, AuthUserField>;
167
+ };
168
+ /**
169
+ * Escape hatch for provider-specific better-auth options/plugins.
170
+ */
171
+ betterAuth?: (defaults: Record<string, unknown>) => Record<string, unknown>;
172
+ }
173
+
174
+ export type GencowAuthConfig = GencowBetterAuthConfig;
175
+ export type GencowAuthConfigInput = Omit<GencowBetterAuthConfig, "provider"> & { provider?: "better-auth" };
176
+
139
177
  // ─── defineAuth() ────────────────────────────────────────
140
178
 
141
179
  /**
@@ -155,6 +193,6 @@ export interface GencowAuthConfig {
155
193
  * });
156
194
  * ```
157
195
  */
158
- export function defineAuth(config: GencowAuthConfig): GencowAuthConfig {
159
- return config;
196
+ export function defineAuth(config: GencowAuthConfigInput): GencowAuthConfig {
197
+ return { ...config, provider: config.provider ?? "better-auth" };
160
198
  }
package/src/config.ts CHANGED
@@ -43,6 +43,26 @@ export interface GencowDbConfig {
43
43
  url?: string;
44
44
  }
45
45
 
46
+ /**
47
+ * Code generation settings for `gencow codegen` and `gencow dev`.
48
+ */
49
+ export interface GencowCodegenConfig {
50
+ /**
51
+ * Output directory for generated frontend codegen artifacts such as `api.ts`
52
+ * and declaration files.
53
+ *
54
+ * Default: `"./src/gencow"`
55
+ */
56
+ outDir?: string;
57
+
58
+ /**
59
+ * Whether codegen should regenerate `gencow/auth-schema.ts` from `gencow/auth.ts`.
60
+ *
61
+ * Default: `true`
62
+ */
63
+ authSchema?: boolean;
64
+ }
65
+
46
66
  /**
47
67
  * Project configuration for `gencow.config.ts`.
48
68
  *
@@ -53,7 +73,10 @@ export interface GencowDbConfig {
53
73
  * export default defineConfig({
54
74
  * functionsDir: "./gencow",
55
75
  * schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
56
- * codegenOutDir: "./src/gencow",
76
+ * codegen: {
77
+ * outDir: "./src/gencow",
78
+ * authSchema: true,
79
+ * },
57
80
  * storage: "./.gencow/uploads",
58
81
  * db: { url: "./.gencow/data" },
59
82
  * port: 5456,
@@ -79,10 +102,12 @@ export interface GencowConfig {
79
102
  schema?: string | string[];
80
103
 
81
104
  /**
82
- * Output directory for generated frontend codegen artifacts such as `api.ts`
83
- * and declaration files.
84
- *
85
- * Default: `"./src/gencow"`
105
+ * Code generation settings.
106
+ */
107
+ codegen?: GencowCodegenConfig;
108
+
109
+ /**
110
+ * @deprecated Use `codegen.outDir` instead.
86
111
  */
87
112
  codegenOutDir?: string;
88
113
 
package/src/context.ts CHANGED
@@ -78,38 +78,6 @@ export type RealtimeNotifyEvent =
78
78
  * Convex의 ctx 패턴과 동일하게, 이 객체를 통해서만 DB/Storage/Auth에 접근 가능.
79
79
  * fs, child_process 등 원시 Node.js API는 노출되지 않음.
80
80
  */
81
- // ─── AI Context ─────────────────────────────────────────
82
-
83
- export interface AIMessage {
84
- role: "user" | "system" | "assistant";
85
- content: string;
86
- }
87
-
88
- export interface AIResult {
89
- text: string;
90
- usage: { promptTokens: number; completionTokens: number; totalTokens: number };
91
- creditsCharged: number;
92
- model: string;
93
- }
94
-
95
- export interface AIContext {
96
- /** AI 텍스트 생성 */
97
- chat: (opts: {
98
- model?: string;
99
- messages: AIMessage[];
100
- /** System prompt — shorthand for adding a system message */
101
- system?: string;
102
- temperature?: number;
103
- maxTokens?: number;
104
- /** Response format — e.g. { type: "json_object" } for JSON mode */
105
- responseFormat?: { type: string };
106
- }) => Promise<AIResult>;
107
- /** 텍스트 임베딩 (단일) */
108
- embed: (text: string) => Promise<number[]>;
109
- /** 배치 임베딩 */
110
- embedMany: (texts: string[]) => Promise<number[][]>;
111
- }
112
-
113
81
  export interface GencowCtx {
114
82
  /** Drizzle DB 인스턴스 (scoped) — 스키마 filter 자동 적용, execute 차단 */
115
83
  db: any; // typed per-app via generic
@@ -127,8 +95,6 @@ export interface GencowCtx {
127
95
  retry: <T>(fn: () => Promise<T>, options?: RetryOptions) => Promise<T>;
128
96
  /** 프레임워크 서비스 헬퍼 — workflow 전용 service는 별도 ctx에서 노출 */
129
97
  services: GencowServicesCtx;
130
- /** AI 헬퍼 */
131
- ai?: AIContext;
132
98
  /** Full-text / hybrid search helper */
133
99
  search: (table: string, query: string, options: SearchOptions) => Promise<SearchResponse>;
134
100
  /** Vector / semantic search helper */
package/src/index.ts CHANGED
@@ -11,9 +11,6 @@ export type {
11
11
  UserIdentity,
12
12
  RealtimeCtx,
13
13
  RealtimeNotifyEvent,
14
- AIContext,
15
- AIMessage,
16
- AIResult,
17
14
  } from "./context.js";
18
15
  export { defineApi } from "./context.js";
19
16
  export type { QueryDef } from "./reactive-query-types.js";
@@ -187,22 +184,27 @@ export { cronJobs } from "./crons.js";
187
184
  export type { CronJobsBuilder, CronJobDef, IntervalOptions, DailyOptions, WeeklyOptions } from "./crons.js";
188
185
  export { defineAuth } from "./auth-config.js";
189
186
  export type {
190
- GencowAuthConfig,
191
187
  AuthEmailVerification,
192
188
  AuthPasswordReset,
193
189
  AuthEvents,
194
190
  AuthHookContext,
195
191
  AuthUserLike,
196
192
  AuthOAuthConfig,
197
- SocialProviderConfig,
198
- SocialProvidersConfig,
193
+ AuthUserField,
194
+ AuthUserFieldType,
195
+ CustomOAuthProvider,
196
+ GencowAuthConfig,
197
+ GencowAuthConfigInput,
198
+ GencowAuthBaseConfig,
199
+ GencowBetterAuthConfig,
199
200
  KakaoConfig,
200
201
  NaverConfig,
201
- CustomOAuthProvider,
202
202
  OAuthUserInfo,
203
+ SocialProviderConfig,
204
+ SocialProvidersConfig,
203
205
  } from "./auth-config.js";
204
206
  export { defineConfig } from "./config.js";
205
- export type { GencowConfig, GencowDbConfig, GencowDeployConfig } from "./config.js";
207
+ export type { GencowCodegenConfig, GencowConfig, GencowDbConfig, GencowDeployConfig } from "./config.js";
206
208
 
207
209
  // ─── RLS + CRUD Factory ───────────
208
210
  export { ownerRls, getOwnerRlsMeta, registerOwnerRls } from "./rls.js";
@@ -33,47 +33,48 @@ export const PLATFORM_CAPACITY_ENV_KEYS = [
33
33
  "COWBOX_WARM_POOL_PORT_RANGE",
34
34
  ] as const;
35
35
 
36
- export const PLATFORM_CAPACITY_PRESETS: Record<PlatformCapacityProfileName, PlatformCapacityPreset> = Object.freeze({
37
- prod: Object.freeze({
38
- profile: "prod",
39
- maxConcurrentRunning: 600,
40
- maxConcurrentWake: 20,
41
- maxWakeQueueMs: 5000,
42
- minAvailableRamMB: 8192,
43
- evictionThresholdMB: 16384,
44
- sleepTimeoutMinutes: 15,
45
- sleepMaxPerCycle: 50,
46
- warmPoolMinIdle: 10,
47
- warmPoolMax: 650,
48
- deployCandidateReserve: 40,
49
- }),
50
- dev: Object.freeze({
51
- profile: "dev",
52
- maxConcurrentRunning: 60,
53
- maxConcurrentWake: 5,
54
- maxWakeQueueMs: 3000,
55
- minAvailableRamMB: 2048,
56
- evictionThresholdMB: 4096,
57
- sleepTimeoutMinutes: 10,
58
- sleepMaxPerCycle: 20,
59
- warmPoolMinIdle: 3,
60
- warmPoolMax: 70,
61
- deployCandidateReserve: 5,
62
- }),
63
- local: Object.freeze({
64
- profile: "local",
65
- maxConcurrentRunning: null,
66
- maxConcurrentWake: null,
67
- maxWakeQueueMs: 5000,
68
- minAvailableRamMB: null,
69
- evictionThresholdMB: null,
70
- sleepTimeoutMinutes: 30,
71
- sleepMaxPerCycle: 10,
72
- warmPoolMinIdle: 3,
73
- warmPoolMax: 10,
74
- deployCandidateReserve: 0,
75
- }),
76
- });
36
+ export const PLATFORM_CAPACITY_PRESETS: Record<PlatformCapacityProfileName, PlatformCapacityPreset> =
37
+ Object.freeze({
38
+ prod: Object.freeze({
39
+ profile: "prod",
40
+ maxConcurrentRunning: 600,
41
+ maxConcurrentWake: 20,
42
+ maxWakeQueueMs: 5000,
43
+ minAvailableRamMB: 8192,
44
+ evictionThresholdMB: 16384,
45
+ sleepTimeoutMinutes: 15,
46
+ sleepMaxPerCycle: 50,
47
+ warmPoolMinIdle: 10,
48
+ warmPoolMax: 650,
49
+ deployCandidateReserve: 40,
50
+ }),
51
+ dev: Object.freeze({
52
+ profile: "dev",
53
+ maxConcurrentRunning: 60,
54
+ maxConcurrentWake: 5,
55
+ maxWakeQueueMs: 3000,
56
+ minAvailableRamMB: 2048,
57
+ evictionThresholdMB: 4096,
58
+ sleepTimeoutMinutes: 10,
59
+ sleepMaxPerCycle: 20,
60
+ warmPoolMinIdle: 3,
61
+ warmPoolMax: 70,
62
+ deployCandidateReserve: 5,
63
+ }),
64
+ local: Object.freeze({
65
+ profile: "local",
66
+ maxConcurrentRunning: null,
67
+ maxConcurrentWake: null,
68
+ maxWakeQueueMs: 5000,
69
+ minAvailableRamMB: null,
70
+ evictionThresholdMB: null,
71
+ sleepTimeoutMinutes: 30,
72
+ sleepMaxPerCycle: 10,
73
+ warmPoolMinIdle: 3,
74
+ warmPoolMax: 10,
75
+ deployCandidateReserve: 0,
76
+ }),
77
+ });
77
78
 
78
79
  function normalizeDomain(value: string | undefined): string {
79
80
  return (value || "")
package/src/procedure.ts CHANGED
@@ -276,8 +276,12 @@ class GencowProcedureBuilderImpl<
276
276
  }
277
277
  }
278
278
 
279
- export const procQuery: GencowProcedureBuilder<"query", GencowCtx> =
280
- new GencowProcedureBuilderImpl<"query", GencowCtx>("query");
281
-
282
- export const procMutation: GencowProcedureBuilder<"mutation", GencowCtx> =
283
- new GencowProcedureBuilderImpl<"mutation", GencowCtx>("mutation");
279
+ export const procQuery: GencowProcedureBuilder<"query", GencowCtx> = new GencowProcedureBuilderImpl<
280
+ "query",
281
+ GencowCtx
282
+ >("query");
283
+
284
+ export const procMutation: GencowProcedureBuilder<"mutation", GencowCtx> = new GencowProcedureBuilderImpl<
285
+ "mutation",
286
+ GencowCtx
287
+ >("mutation");
@@ -1,4 +1,9 @@
1
- import type { DocumentConvertProvider, DocumentConvertMode, DocumentProviderTrace, DocumentVisibility } from "./document-types.js";
1
+ import type {
2
+ DocumentConvertProvider,
3
+ DocumentConvertMode,
4
+ DocumentProviderTrace,
5
+ DocumentVisibility,
6
+ } from "./document-types.js";
2
7
 
3
8
  export type RagIngestReindexMode = "if_changed" | "force";
4
9
  export type RagIngestJobStatus =
package/src/rag-schema.ts CHANGED
@@ -1,4 +1,14 @@
1
- import { bigint, boolean, doublePrecision, integer, jsonb, pgTable, text, timestamp, vector } from "drizzle-orm/pg-core";
1
+ import {
2
+ bigint,
3
+ boolean,
4
+ doublePrecision,
5
+ integer,
6
+ jsonb,
7
+ pgTable,
8
+ text,
9
+ timestamp,
10
+ vector,
11
+ } from "drizzle-orm/pg-core";
2
12
 
3
13
  function buildScopeColumns() {
4
14
  return {
@@ -57,7 +57,9 @@ export function buildQuerySubscriptionKey(queryKey: string, args?: unknown): str
57
57
  }
58
58
 
59
59
  export function subscriptionKeyMatchesQueryKey(subscriptionKey: string, queryKey: string): boolean {
60
- return subscriptionKey === queryKey || subscriptionKey.startsWith(`${queryKey}${SUBSCRIPTION_KEY_SEPARATOR}`);
60
+ return (
61
+ subscriptionKey === queryKey || subscriptionKey.startsWith(`${queryKey}${SUBSCRIPTION_KEY_SEPARATOR}`)
62
+ );
61
63
  }
62
64
 
63
65
  export function subscribe(queryKey: string, ws: WSContext) {
package/src/rls-db.ts CHANGED
@@ -35,7 +35,10 @@ const RESERVED_VARS_KEYS = new Set(["app.current_user_id", "app.current_user_rol
35
35
  type RlsDrizzleDatabaseLike = {
36
36
  session: unknown;
37
37
  _: { session?: unknown };
38
- transaction: (callback: (tx: unknown) => unknown | Promise<unknown>, ...rest: unknown[]) => Promise<unknown>;
38
+ transaction: (
39
+ callback: (tx: unknown) => unknown | Promise<unknown>,
40
+ ...rest: unknown[]
41
+ ) => Promise<unknown>;
39
42
  };
40
43
 
41
44
  function assertSafeGucName(key: string): void {
@@ -249,10 +252,7 @@ function wrapSession(session: any, rls: RlsSessionContext, reuseOuterConnection:
249
252
  *
250
253
  * `db.transaction()` still injects the same variables at the start of the callback transaction.
251
254
  */
252
- export function createRlsDb<TDb extends RlsDrizzleDatabaseLike>(
253
- db: TDb,
254
- rls: RlsSessionContext,
255
- ): TDb {
255
+ export function createRlsDb<TDb extends RlsDrizzleDatabaseLike>(db: TDb, rls: RlsSessionContext): TDb {
256
256
  const reuseOuterConnection = isDrizzleTransactionDb(db);
257
257
  const baseSession = (db as unknown as { session: any }).session;
258
258
  const wrappedSession = wrapSession(baseSession, rls, reuseOuterConnection);
@@ -31,13 +31,20 @@ const RESERVED_TENANT_RUNTIME_ENV_KEYS = new Set([
31
31
  "GENCOW_SHUTDOWN_MARKER_PATH",
32
32
  "GENCOW_SKIP_MIGRATION",
33
33
  "GENCOW_DB_MAX_CONNECTIONS",
34
+ "GENCOW_DB_IDLE_TIMEOUT_SECONDS",
35
+ "GENCOW_DB_CONNECTION_TIMEOUT_SECONDS",
34
36
  "GENCOW_MEMORY_MB",
35
37
  "BUN_JSC_forceRAMSize",
36
38
  "MIMALLOC_PURGE_DELAY",
37
39
  "NODE_PATH",
38
40
  ]);
39
41
 
40
- const RESERVED_TENANT_RUNTIME_ENV_PREFIXES = ["__GENCOW_", "GENCOW_DOCUMENT_", "GENCOW_TEMPLATE_", "GENCOW_WARM_"];
42
+ const RESERVED_TENANT_RUNTIME_ENV_PREFIXES = [
43
+ "__GENCOW_",
44
+ "GENCOW_DOCUMENT_",
45
+ "GENCOW_TEMPLATE_",
46
+ "GENCOW_WARM_",
47
+ ];
41
48
 
42
49
  export function isReservedTenantRuntimeEnvKey(key: string): boolean {
43
50
  const normalized = key.trim();
package/src/scheduler.ts CHANGED
@@ -8,6 +8,8 @@ type ActionHandler = (args: any) => Promise<any>;
8
8
  export interface ScheduleOptions {
9
9
  /** 실패 시 호출할 action 이름 (dead-letter 패턴) */
10
10
  onError?: string;
11
+ /** Optional deterministic id for idempotent durable scheduling. */
12
+ id?: string;
11
13
  }
12
14
 
13
15
  /** scheduled job의 DB 영속화를 위한 콜백 */
@@ -209,7 +211,7 @@ export function createScheduler(options?: CreateSchedulerOptions): Scheduler {
209
211
 
210
212
  return {
211
213
  runAfter(ms: number, action: string, args?: any, scheduleOpts?: ScheduleOptions): string {
212
- const id = generateId();
214
+ const id = scheduleOpts?.id ?? generateId();
213
215
  const jobEntry: PendingJobEntry = {
214
216
  id,
215
217
  action,
package/src/storage.ts CHANGED
@@ -2,12 +2,7 @@ import * as fs from "fs/promises";
2
2
  import * as fsSync from "fs";
3
3
  import * as path from "path";
4
4
  import * as crypto from "crypto";
5
- import {
6
- DEFAULT_STORAGE_QUOTA,
7
- MAX_FILE_SIZE,
8
- formatBytes,
9
- loadStorageMeta,
10
- } from "./storage-shared.js";
5
+ import { DEFAULT_STORAGE_QUOTA, MAX_FILE_SIZE, formatBytes, loadStorageMeta } from "./storage-shared.js";
11
6
  import type { Storage, StorageFile, StorageOptions, StoredFile } from "./storage-shared.js";
12
7
  import { recordStorageImageTransform } from "./storage-metering.js";
13
8
  import type { StorageMeteringOptions } from "./storage-metering.js";
package/src/workflow.ts CHANGED
@@ -316,7 +316,9 @@ export function workflow<TSchema = any, TReturn = any>(
316
316
  lifecycleTimeoutMs,
317
317
  maxRetries,
318
318
  });
319
- const scheduledJobId = ctx.scheduler.runAfter(0, resumeAction, { workflowId });
319
+ const scheduledJobId = insertedWorkflowV2
320
+ ? `start:${workflowId}`
321
+ : ctx.scheduler.runAfter(0, resumeAction, { workflowId });
320
322
  return {
321
323
  id: workflowId,
322
324
  name,