@gencow/core 0.1.29 → 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-json.d.ts +2 -0
- package/dist/workflow-json.js +5 -0
- package/dist/workflow.js +54 -54
- package/dist/workflows-api.js +3 -2
- 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-json.ts +6 -0
- package/src/workflow.ts +54 -56
- package/src/workflows-api.ts +3 -2
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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
2
|
import { mutation } from "./reactive-mutation.js";
|
|
3
|
+
import { workflowJsonb } from "./workflow-json.js";
|
|
3
4
|
import { registerWorkflowsApi } from "./workflows-api.js";
|
|
4
5
|
const workflowRegistry = (globalThis.__gencow_workflowRegistry ??= new Map());
|
|
5
6
|
export const DEFAULT_WORKFLOW_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
@@ -90,63 +91,60 @@ function isMissingWorkflowV2SchemaError(error) {
|
|
|
90
91
|
message.includes('column "retry_count"') ||
|
|
91
92
|
message.includes('column "user_id"'));
|
|
92
93
|
}
|
|
93
|
-
async function tryInsertWorkflowV2WakeOutbox(db, workflowId) {
|
|
94
|
-
try {
|
|
95
|
-
await db.execute(sql `
|
|
96
|
-
INSERT INTO _gencow_workflow_outbox_v2 (
|
|
97
|
-
id,
|
|
98
|
-
run_id,
|
|
99
|
-
kind,
|
|
100
|
-
available_at,
|
|
101
|
-
status
|
|
102
|
-
)
|
|
103
|
-
VALUES (
|
|
104
|
-
${`start:${workflowId}`},
|
|
105
|
-
${workflowId},
|
|
106
|
-
'wake_run',
|
|
107
|
-
NOW(),
|
|
108
|
-
'pending'
|
|
109
|
-
)
|
|
110
|
-
ON CONFLICT (id) DO NOTHING
|
|
111
|
-
`);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (!isMissingWorkflowV2SchemaError(error))
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
94
|
async function tryInsertWorkflowV2Run(options) {
|
|
119
95
|
try {
|
|
120
96
|
await options.db.execute(sql `
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
97
|
+
WITH inserted_run AS (
|
|
98
|
+
INSERT INTO _gencow_workflow_runs_v2 (
|
|
99
|
+
id,
|
|
100
|
+
workflow_name,
|
|
101
|
+
workflow_version,
|
|
102
|
+
args_json,
|
|
103
|
+
user_id,
|
|
104
|
+
max_active_duration_ms,
|
|
105
|
+
lifecycle_deadline_at,
|
|
106
|
+
retry_count,
|
|
107
|
+
max_retries,
|
|
108
|
+
max_attempts
|
|
109
|
+
)
|
|
110
|
+
VALUES (
|
|
111
|
+
${options.workflowId},
|
|
112
|
+
${options.workflowName},
|
|
113
|
+
${options.workflowVersion ?? null},
|
|
114
|
+
${workflowJsonb(options.args)},
|
|
115
|
+
${options.userId},
|
|
116
|
+
${options.maxActiveDurationMs},
|
|
117
|
+
CASE
|
|
118
|
+
WHEN ${options.lifecycleTimeoutMs}::bigint IS NULL THEN NULL
|
|
119
|
+
ELSE NOW() + (${options.lifecycleTimeoutMs}::bigint * INTERVAL '1 millisecond')
|
|
120
|
+
END,
|
|
121
|
+
0,
|
|
122
|
+
${options.maxRetries},
|
|
123
|
+
${options.maxRetries + 1}
|
|
124
|
+
)
|
|
125
|
+
RETURNING id
|
|
126
|
+
),
|
|
127
|
+
inserted_outbox AS (
|
|
128
|
+
INSERT INTO _gencow_workflow_outbox_v2 (
|
|
129
|
+
id,
|
|
130
|
+
run_id,
|
|
131
|
+
kind,
|
|
132
|
+
available_at,
|
|
133
|
+
status
|
|
134
|
+
)
|
|
135
|
+
SELECT
|
|
136
|
+
'start:' || inserted_run.id,
|
|
137
|
+
inserted_run.id,
|
|
138
|
+
'wake_run',
|
|
139
|
+
NOW(),
|
|
140
|
+
'pending'
|
|
141
|
+
FROM inserted_run
|
|
142
|
+
ON CONFLICT (id) DO NOTHING
|
|
143
|
+
RETURNING id
|
|
147
144
|
)
|
|
145
|
+
SELECT id
|
|
146
|
+
FROM inserted_run
|
|
148
147
|
`);
|
|
149
|
-
await tryInsertWorkflowV2WakeOutbox(options.db, options.workflowId);
|
|
150
148
|
return true;
|
|
151
149
|
}
|
|
152
150
|
catch (error) {
|
|
@@ -230,7 +228,7 @@ export function workflow(name, options) {
|
|
|
230
228
|
VALUES (
|
|
231
229
|
${workflowId},
|
|
232
230
|
${name},
|
|
233
|
-
${
|
|
231
|
+
${workflowJsonb(persistedArgs)},
|
|
234
232
|
${realtimeToken},
|
|
235
233
|
'pending',
|
|
236
234
|
0,
|
|
@@ -252,7 +250,9 @@ export function workflow(name, options) {
|
|
|
252
250
|
lifecycleTimeoutMs,
|
|
253
251
|
maxRetries,
|
|
254
252
|
});
|
|
255
|
-
const scheduledJobId =
|
|
253
|
+
const scheduledJobId = insertedWorkflowV2
|
|
254
|
+
? `start:${workflowId}`
|
|
255
|
+
: ctx.scheduler.runAfter(0, resumeAction, { workflowId });
|
|
256
256
|
return {
|
|
257
257
|
id: workflowId,
|
|
258
258
|
name,
|
package/dist/workflows-api.js
CHANGED
|
@@ -2,6 +2,7 @@ import { sql } from "drizzle-orm";
|
|
|
2
2
|
import { query } from "./reactive-query.js";
|
|
3
3
|
import { mutation } from "./reactive-mutation.js";
|
|
4
4
|
import { createWorkflowRealtimeToken, deserializeWorkflowValue, getWorkflowResumeActionName, getWorkflowRealtimeKey, serializeWorkflowValue, } from "./workflow.js";
|
|
5
|
+
import { workflowJsonb } from "./workflow-json.js";
|
|
5
6
|
import { GencowValidationError, v } from "./v.js";
|
|
6
7
|
import { deriveWorkflowStatus } from "./workflow-types.js";
|
|
7
8
|
const WORKFLOW_STATUSES = new Set(["pending", "running", "completed", "failed"]);
|
|
@@ -160,7 +161,7 @@ async function tryRecordWorkflowV2Signal(options) {
|
|
|
160
161
|
${crypto.randomUUID()},
|
|
161
162
|
${options.workflowId},
|
|
162
163
|
${options.event},
|
|
163
|
-
${
|
|
164
|
+
${workflowJsonb(options.payload)},
|
|
164
165
|
${crypto.randomUUID()}
|
|
165
166
|
)
|
|
166
167
|
RETURNING run_id
|
|
@@ -297,7 +298,7 @@ export function registerWorkflowsApi() {
|
|
|
297
298
|
${crypto.randomUUID()},
|
|
298
299
|
${workflow.id},
|
|
299
300
|
${normalizedEvent},
|
|
300
|
-
${
|
|
301
|
+
${workflowJsonb(persistedPayload)}
|
|
301
302
|
)
|
|
302
303
|
`);
|
|
303
304
|
await tryRecordWorkflowV2Signal({
|
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
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
WorkflowOptions,
|
|
8
8
|
WorkflowStartResult,
|
|
9
9
|
} from "./workflow-types.js";
|
|
10
|
+
import { workflowJsonb } from "./workflow-json.js";
|
|
10
11
|
import { registerWorkflowsApi } from "./workflows-api.js";
|
|
11
12
|
|
|
12
13
|
declare global {
|
|
@@ -125,33 +126,6 @@ function isMissingWorkflowV2SchemaError(error: unknown): boolean {
|
|
|
125
126
|
);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
async function tryInsertWorkflowV2WakeOutbox(
|
|
129
|
-
db: { execute: (query: ReturnType<typeof sql>) => Promise<unknown> },
|
|
130
|
-
workflowId: string,
|
|
131
|
-
): Promise<void> {
|
|
132
|
-
try {
|
|
133
|
-
await db.execute(sql`
|
|
134
|
-
INSERT INTO _gencow_workflow_outbox_v2 (
|
|
135
|
-
id,
|
|
136
|
-
run_id,
|
|
137
|
-
kind,
|
|
138
|
-
available_at,
|
|
139
|
-
status
|
|
140
|
-
)
|
|
141
|
-
VALUES (
|
|
142
|
-
${`start:${workflowId}`},
|
|
143
|
-
${workflowId},
|
|
144
|
-
'wake_run',
|
|
145
|
-
NOW(),
|
|
146
|
-
'pending'
|
|
147
|
-
)
|
|
148
|
-
ON CONFLICT (id) DO NOTHING
|
|
149
|
-
`);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
if (!isMissingWorkflowV2SchemaError(error)) throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
129
|
async function tryInsertWorkflowV2Run(options: {
|
|
156
130
|
db: { execute: (query: ReturnType<typeof sql>) => Promise<unknown> };
|
|
157
131
|
workflowId: string;
|
|
@@ -165,35 +139,57 @@ async function tryInsertWorkflowV2Run(options: {
|
|
|
165
139
|
}): Promise<boolean> {
|
|
166
140
|
try {
|
|
167
141
|
await options.db.execute(sql`
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
142
|
+
WITH inserted_run AS (
|
|
143
|
+
INSERT INTO _gencow_workflow_runs_v2 (
|
|
144
|
+
id,
|
|
145
|
+
workflow_name,
|
|
146
|
+
workflow_version,
|
|
147
|
+
args_json,
|
|
148
|
+
user_id,
|
|
149
|
+
max_active_duration_ms,
|
|
150
|
+
lifecycle_deadline_at,
|
|
151
|
+
retry_count,
|
|
152
|
+
max_retries,
|
|
153
|
+
max_attempts
|
|
154
|
+
)
|
|
155
|
+
VALUES (
|
|
156
|
+
${options.workflowId},
|
|
157
|
+
${options.workflowName},
|
|
158
|
+
${options.workflowVersion ?? null},
|
|
159
|
+
${workflowJsonb(options.args)},
|
|
160
|
+
${options.userId},
|
|
161
|
+
${options.maxActiveDurationMs},
|
|
162
|
+
CASE
|
|
163
|
+
WHEN ${options.lifecycleTimeoutMs}::bigint IS NULL THEN NULL
|
|
164
|
+
ELSE NOW() + (${options.lifecycleTimeoutMs}::bigint * INTERVAL '1 millisecond')
|
|
165
|
+
END,
|
|
166
|
+
0,
|
|
167
|
+
${options.maxRetries},
|
|
168
|
+
${options.maxRetries + 1}
|
|
169
|
+
)
|
|
170
|
+
RETURNING id
|
|
171
|
+
),
|
|
172
|
+
inserted_outbox AS (
|
|
173
|
+
INSERT INTO _gencow_workflow_outbox_v2 (
|
|
174
|
+
id,
|
|
175
|
+
run_id,
|
|
176
|
+
kind,
|
|
177
|
+
available_at,
|
|
178
|
+
status
|
|
179
|
+
)
|
|
180
|
+
SELECT
|
|
181
|
+
'start:' || inserted_run.id,
|
|
182
|
+
inserted_run.id,
|
|
183
|
+
'wake_run',
|
|
184
|
+
NOW(),
|
|
185
|
+
'pending'
|
|
186
|
+
FROM inserted_run
|
|
187
|
+
ON CONFLICT (id) DO NOTHING
|
|
188
|
+
RETURNING id
|
|
194
189
|
)
|
|
190
|
+
SELECT id
|
|
191
|
+
FROM inserted_run
|
|
195
192
|
`);
|
|
196
|
-
await tryInsertWorkflowV2WakeOutbox(options.db, options.workflowId);
|
|
197
193
|
return true;
|
|
198
194
|
} catch (error) {
|
|
199
195
|
if (isMissingWorkflowV2SchemaError(error)) return false;
|
|
@@ -297,7 +293,7 @@ export function workflow<TSchema = any, TReturn = any>(
|
|
|
297
293
|
VALUES (
|
|
298
294
|
${workflowId},
|
|
299
295
|
${name},
|
|
300
|
-
${
|
|
296
|
+
${workflowJsonb(persistedArgs)},
|
|
301
297
|
${realtimeToken},
|
|
302
298
|
'pending',
|
|
303
299
|
0,
|
|
@@ -320,7 +316,9 @@ export function workflow<TSchema = any, TReturn = any>(
|
|
|
320
316
|
lifecycleTimeoutMs,
|
|
321
317
|
maxRetries,
|
|
322
318
|
});
|
|
323
|
-
const scheduledJobId =
|
|
319
|
+
const scheduledJobId = insertedWorkflowV2
|
|
320
|
+
? `start:${workflowId}`
|
|
321
|
+
: ctx.scheduler.runAfter(0, resumeAction, { workflowId });
|
|
324
322
|
return {
|
|
325
323
|
id: workflowId,
|
|
326
324
|
name,
|
package/src/workflows-api.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getWorkflowRealtimeKey,
|
|
9
9
|
serializeWorkflowValue,
|
|
10
10
|
} from "./workflow.js";
|
|
11
|
+
import { workflowJsonb } from "./workflow-json.js";
|
|
11
12
|
import { GencowValidationError, v } from "./v.js";
|
|
12
13
|
import type {
|
|
13
14
|
WorkflowDerivedStatus,
|
|
@@ -246,7 +247,7 @@ async function tryRecordWorkflowV2Signal(options: {
|
|
|
246
247
|
${crypto.randomUUID()},
|
|
247
248
|
${options.workflowId},
|
|
248
249
|
${options.event},
|
|
249
|
-
${
|
|
250
|
+
${workflowJsonb(options.payload)},
|
|
250
251
|
${crypto.randomUUID()}
|
|
251
252
|
)
|
|
252
253
|
RETURNING run_id
|
|
@@ -397,7 +398,7 @@ export function registerWorkflowsApi(): void {
|
|
|
397
398
|
${crypto.randomUUID()},
|
|
398
399
|
${workflow.id},
|
|
399
400
|
${normalizedEvent},
|
|
400
|
-
${
|
|
401
|
+
${workflowJsonb(persistedPayload)}
|
|
401
402
|
)
|
|
402
403
|
`);
|
|
403
404
|
await tryRecordWorkflowV2Signal({
|