@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.
- package/dist/auth-config.d.ts +30 -2
- package/dist/auth-config.js +1 -1
- package/dist/config.d.ts +27 -5
- package/dist/context.d.ts +0 -35
- package/dist/index.d.ts +3 -3
- package/dist/procedure-route-maps.d.ts +5 -0
- package/dist/procedure-route-maps.js +37 -0
- package/dist/rag-schema.js +1 -1
- package/dist/reactive-realtime.js +1 -1
- package/dist/runtime-env-policy.js +8 -1
- package/dist/scheduler.d.ts +2 -0
- package/dist/scheduler.js +1 -1
- package/dist/storage.js +1 -1
- package/dist/workflow.js +3 -1
- package/package.json +1 -1
- package/src/auth-config.ts +59 -21
- package/src/config.ts +30 -5
- package/src/context.ts +0 -34
- package/src/index.ts +10 -8
- package/src/platform-capacity-profile.ts +42 -41
- package/src/procedure.ts +9 -5
- package/src/rag-ingest-types.ts +6 -1
- package/src/rag-schema.ts +11 -1
- package/src/reactive-realtime.ts +3 -1
- package/src/rls-db.ts +5 -5
- package/src/runtime-env-policy.ts +8 -1
- package/src/scheduler.ts +3 -1
- package/src/storage.ts +1 -6
- package/src/workflow.ts +3 -1
package/dist/auth-config.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
162
|
+
export declare function defineAuth(config: GencowAuthConfigInput): GencowAuthConfig;
|
package/dist/auth-config.js
CHANGED
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
|
-
*
|
|
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
|
-
*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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,
|
|
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 {
|
|
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
|
+
}
|
package/dist/rag-schema.js
CHANGED
|
@@ -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 = [
|
|
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) ||
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -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
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
|
|
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 =
|
|
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
package/src/auth-config.ts
CHANGED
|
@@ -32,11 +32,14 @@ export interface AuthEmailVerification {
|
|
|
32
32
|
/** 인증 완료 후 자동 로그인 (default: true) */
|
|
33
33
|
autoSignInAfterVerification?: boolean;
|
|
34
34
|
/** 인증 메일 발송 함수 — 사용자가 직접 구현 */
|
|
35
|
-
sendVerificationEmail: (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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: (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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?: (
|
|
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?: (
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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> =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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");
|
package/src/rag-ingest-types.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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 {
|
|
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 {
|
package/src/reactive-realtime.ts
CHANGED
|
@@ -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
|
|
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: (
|
|
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 = [
|
|
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 =
|
|
319
|
+
const scheduledJobId = insertedWorkflowV2
|
|
320
|
+
? `start:${workflowId}`
|
|
321
|
+
: ctx.scheduler.runAfter(0, resumeAction, { workflowId });
|
|
320
322
|
return {
|
|
321
323
|
id: workflowId,
|
|
322
324
|
name,
|