@gencow/core 0.1.27 → 0.1.29
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 +92 -5
- package/dist/config.d.ts +107 -0
- package/dist/config.js +12 -0
- package/dist/context.d.ts +139 -0
- package/dist/context.js +3 -0
- package/dist/crud.d.ts +5 -5
- package/dist/crud.js +19 -35
- package/dist/document-types.d.ts +65 -0
- package/dist/document-types.js +15 -0
- package/dist/grounded-answer-types.d.ts +62 -0
- package/dist/grounded-answer-types.js +6 -0
- package/dist/http-action.d.ts +77 -0
- package/dist/http-action.js +41 -0
- package/dist/index.d.ts +30 -5
- package/dist/index.js +15 -2
- package/dist/platform-capacity-profile.d.ts +19 -0
- package/dist/platform-capacity-profile.js +94 -0
- package/dist/procedure.d.ts +58 -0
- package/dist/procedure.js +115 -0
- package/dist/rag-ingest-types.d.ts +39 -0
- package/dist/rag-ingest-types.js +1 -0
- package/dist/rag-operations-types.d.ts +81 -0
- package/dist/rag-operations-types.js +1 -0
- package/dist/rag-schema.d.ts +1466 -0
- package/dist/rag-schema.js +87 -0
- package/dist/reactive-mutation-types.d.ts +11 -0
- package/dist/reactive-mutation-types.js +1 -0
- package/dist/reactive-mutation.d.ts +51 -0
- package/dist/reactive-mutation.js +75 -0
- package/dist/reactive-query-types.d.ts +12 -0
- package/dist/reactive-query-types.js +1 -0
- package/dist/reactive-query.d.ts +14 -0
- package/dist/reactive-query.js +28 -0
- package/dist/reactive-realtime.d.ts +48 -0
- package/dist/reactive-realtime.js +236 -0
- package/dist/reactive.d.ts +29 -5
- package/dist/reactive.js +65 -0
- package/dist/rls-db.d.ts +9 -2
- package/dist/runtime-env-policy.d.ts +5 -0
- package/dist/runtime-env-policy.js +56 -0
- package/dist/search-types.d.ts +83 -0
- package/dist/search-types.js +1 -0
- package/dist/server.d.ts +1 -2
- package/dist/server.js +0 -1
- package/dist/storage-metering.d.ts +13 -0
- package/dist/storage-metering.js +18 -0
- package/dist/storage-shared.d.ts +36 -0
- package/dist/storage-shared.js +39 -0
- package/dist/storage.d.ts +5 -27
- package/dist/storage.js +30 -22
- package/dist/wake-app-result.d.ts +22 -0
- package/dist/wake-app-result.js +11 -0
- package/dist/workflow-types.d.ts +16 -2
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.js +136 -11
- package/dist/workflows-api.js +71 -3
- package/package.json +11 -7
- package/src/auth-config.ts +104 -3
- package/src/config.ts +119 -0
- package/src/context.ts +152 -0
- package/src/crud.ts +18 -35
- package/src/document-types.ts +102 -0
- package/src/grounded-answer-types.ts +78 -0
- package/src/http-action.ts +101 -0
- package/src/index.ts +142 -19
- package/src/platform-capacity-profile.ts +114 -0
- package/src/procedure.ts +283 -0
- package/src/rag-ingest-types.ts +52 -0
- package/src/rag-operations-types.ts +90 -0
- package/src/rag-schema.ts +94 -0
- package/src/reactive-mutation-types.ts +13 -0
- package/src/reactive-mutation.ts +115 -0
- package/src/reactive-query-types.ts +14 -0
- package/src/reactive-query.ts +48 -0
- package/src/reactive-realtime.ts +267 -0
- package/src/rls-db.ts +9 -4
- package/src/runtime-env-policy.ts +66 -0
- package/src/search-types.ts +91 -0
- package/src/server.ts +6 -2
- package/src/storage-metering.ts +35 -0
- package/src/storage-shared.ts +74 -0
- package/src/storage.ts +44 -53
- package/src/wake-app-result.ts +37 -0
- package/src/workflow-types.ts +16 -2
- package/src/workflow.ts +166 -12
- package/src/workflows-api.ts +82 -3
- package/src/__tests__/auth.test.ts +0 -118
- package/src/__tests__/crons.test.ts +0 -83
- package/src/__tests__/crud-codegen-integration.test.ts +0 -246
- package/src/__tests__/crud-owner-rls.test.ts +0 -387
- package/src/__tests__/crud.test.ts +0 -930
- package/src/__tests__/dist-exports.test.ts +0 -176
- package/src/__tests__/fixtures/basic/auth.ts +0 -32
- package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
- package/src/__tests__/fixtures/basic/index.ts +0 -6
- package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
- package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
- package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
- package/src/__tests__/fixtures/basic/schema.ts +0 -51
- package/src/__tests__/fixtures/basic/tasks.ts +0 -15
- package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
- package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
- package/src/__tests__/helpers/pglite-migrations.ts +0 -32
- package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
- package/src/__tests__/helpers/seed-like-fill.ts +0 -202
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
- package/src/__tests__/httpaction.test.ts +0 -122
- package/src/__tests__/image-optimization.test.ts +0 -648
- package/src/__tests__/load.test.ts +0 -389
- package/src/__tests__/network-sim.test.ts +0 -319
- package/src/__tests__/reactive.test.ts +0 -479
- package/src/__tests__/retry.test.ts +0 -113
- package/src/__tests__/rls-crud-basic.test.ts +0 -317
- package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
- package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
- package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
- package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
- package/src/__tests__/rls-session-and-policies.test.ts +0 -228
- package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
- package/src/__tests__/scheduler-durable.test.ts +0 -173
- package/src/__tests__/scheduler-exec.test.ts +0 -328
- package/src/__tests__/scheduler.test.ts +0 -187
- package/src/__tests__/storage.test.ts +0 -334
- package/src/__tests__/tsconfig.json +0 -8
- package/src/__tests__/validator.test.ts +0 -323
- package/src/__tests__/workflow.test.ts +0 -606
- package/src/__tests__/ws-integration.test.ts +0 -309
- package/src/__tests__/ws-scale.test.ts +0 -241
- package/src/auth.ts +0 -155
- package/src/reactive.ts +0 -580
package/dist/auth-config.d.ts
CHANGED
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
* shadcn 패턴: auth.ts는 사용자가 소유하고 직접 수정할 수 있는 파일.
|
|
8
8
|
* defineAuth()는 타입 안전한 설정 헬퍼일 뿐, 런타임 로직은 server에서 처리.
|
|
9
9
|
*/
|
|
10
|
+
export interface AuthHookContext {
|
|
11
|
+
db?: unknown;
|
|
12
|
+
request?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface AuthUserLike {
|
|
15
|
+
id?: string;
|
|
16
|
+
email: string;
|
|
17
|
+
name?: string | null;
|
|
18
|
+
emailVerified?: boolean;
|
|
19
|
+
createdAt?: Date | string;
|
|
20
|
+
updatedAt?: Date | string;
|
|
21
|
+
}
|
|
10
22
|
export interface AuthEmailVerification {
|
|
11
23
|
/** 가입 시 인증 메일 자동 발송 (default: true) */
|
|
12
24
|
sendOnSignUp?: boolean;
|
|
@@ -16,16 +28,91 @@ export interface AuthEmailVerification {
|
|
|
16
28
|
autoSignInAfterVerification?: boolean;
|
|
17
29
|
/** 인증 메일 발송 함수 — 사용자가 직접 구현 */
|
|
18
30
|
sendVerificationEmail: (data: {
|
|
19
|
-
user:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
user: AuthUserLike;
|
|
32
|
+
url: string;
|
|
33
|
+
token: string;
|
|
34
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
35
|
+
/** 인증 직전 훅 — better-auth의 emailVerification.beforeEmailVerification으로 전달 */
|
|
36
|
+
beforeEmailVerification?: (user: AuthUserLike, context?: AuthHookContext) => Promise<void>;
|
|
37
|
+
/** 인증 완료 훅 — 환영 메일 등 idempotent 후처리에 사용 */
|
|
38
|
+
afterEmailVerification?: (user: AuthUserLike, context?: AuthHookContext) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export interface AuthPasswordReset {
|
|
41
|
+
/** 비밀번호 재설정 메일 발송 함수 — reset URL은 반드시 사용자에게 전달되어야 한다. */
|
|
42
|
+
sendResetPassword: (data: {
|
|
43
|
+
user: AuthUserLike;
|
|
23
44
|
url: string;
|
|
24
45
|
token: string;
|
|
25
|
-
}) => Promise<void>;
|
|
46
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
47
|
+
/** 비밀번호 재설정 완료 후 훅 */
|
|
48
|
+
onPasswordReset?: (data: {
|
|
49
|
+
user: AuthUserLike;
|
|
50
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
51
|
+
/** reset token 유효 시간(초). default: better-auth 기본값 3600초 */
|
|
52
|
+
resetPasswordTokenExpiresIn?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface AuthEvents {
|
|
55
|
+
/** 회원가입 성공 후 실행. 운영 알림처럼 실패해도 가입을 막지 않아야 하는 작업에 사용. */
|
|
56
|
+
afterSignUp?: (data: {
|
|
57
|
+
user: AuthUserLike;
|
|
58
|
+
inviteCode?: string | null;
|
|
59
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface SocialProviderConfig {
|
|
62
|
+
clientId: string;
|
|
63
|
+
clientSecret: string;
|
|
64
|
+
/** Provider-specific callback override. Most apps should leave this unset. */
|
|
65
|
+
redirectURI?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface OAuthUserInfo {
|
|
68
|
+
id: string;
|
|
69
|
+
email: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
image?: string;
|
|
72
|
+
emailVerified?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface CustomOAuthProvider {
|
|
75
|
+
clientId: string;
|
|
76
|
+
clientSecret?: string;
|
|
77
|
+
authorizationUrl: string;
|
|
78
|
+
tokenUrl: string;
|
|
79
|
+
userInfoUrl: string;
|
|
80
|
+
scopes?: string[];
|
|
81
|
+
pkce?: boolean;
|
|
82
|
+
/** Provider-specific callback override. Most apps should leave this unset. */
|
|
83
|
+
redirectURI?: string;
|
|
84
|
+
/** Map provider profile responses such as Naver/Kakao into better-auth user fields. */
|
|
85
|
+
mapProfileToUser?: (profile: Record<string, unknown>) => Partial<OAuthUserInfo> | Promise<Partial<OAuthUserInfo>>;
|
|
86
|
+
}
|
|
87
|
+
export type KakaoConfig = SocialProviderConfig & Partial<CustomOAuthProvider>;
|
|
88
|
+
export type NaverConfig = SocialProviderConfig & Partial<CustomOAuthProvider>;
|
|
89
|
+
export type SocialProvidersConfig = {
|
|
90
|
+
google?: SocialProviderConfig;
|
|
91
|
+
apple?: SocialProviderConfig;
|
|
92
|
+
/**
|
|
93
|
+
* Kakao/Naver use Gencow's built-in OAuth endpoint defaults.
|
|
94
|
+
* Override any CustomOAuthProvider field when a provider dashboard requires it.
|
|
95
|
+
*/
|
|
96
|
+
kakao?: KakaoConfig;
|
|
97
|
+
naver?: NaverConfig;
|
|
98
|
+
/** Additional OAuth providers can be configured with explicit OAuth endpoints. */
|
|
99
|
+
[providerId: string]: SocialProviderConfig | KakaoConfig | NaverConfig | CustomOAuthProvider | undefined;
|
|
100
|
+
};
|
|
101
|
+
export interface AuthOAuthConfig {
|
|
102
|
+
/**
|
|
103
|
+
* Frontend route that receives the short-lived OAuth handoff code.
|
|
104
|
+
* If omitted, Gencow uses APP_PUBLIC_DOMAIN + /auth/callback, then falls back to the auth server origin.
|
|
105
|
+
*/
|
|
106
|
+
callbackURL?: string;
|
|
107
|
+
/** Extra allowed frontend callback URLs for local development or multi-frontend apps. */
|
|
108
|
+
allowedCallbackURLs?: string[];
|
|
26
109
|
}
|
|
27
110
|
export interface GencowAuthConfig {
|
|
28
111
|
emailVerification?: AuthEmailVerification;
|
|
112
|
+
socialProviders?: SocialProvidersConfig;
|
|
113
|
+
oauth?: AuthOAuthConfig;
|
|
114
|
+
passwordReset?: AuthPasswordReset;
|
|
115
|
+
events?: AuthEvents;
|
|
29
116
|
}
|
|
30
117
|
/**
|
|
31
118
|
* Auth 설정 정의 헬퍼.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing project configuration for `gencow.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* The config file is intentionally small today, but we expose a stable typed
|
|
5
|
+
* contract so editors can offer autocomplete and inline explanations.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Cloud deployment settings written to `gencow.config.ts`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* deploy: {
|
|
13
|
+
* app: "my-todo",
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface GencowDeployConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Default cloud app name to target for CLI flows that attach project metadata.
|
|
20
|
+
*
|
|
21
|
+
* This is commonly added automatically after `gencow app create`.
|
|
22
|
+
*/
|
|
23
|
+
app?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Local database settings for the Gencow dev server.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* db: {
|
|
31
|
+
* url: "./.gencow/data",
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export interface GencowDbConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Local database path used by the default PGlite setup.
|
|
38
|
+
*
|
|
39
|
+
* Default: `"./.gencow/data"`
|
|
40
|
+
*/
|
|
41
|
+
url?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Project configuration for `gencow.config.ts`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { defineConfig } from "@gencow/core";
|
|
49
|
+
*
|
|
50
|
+
* export default defineConfig({
|
|
51
|
+
* functionsDir: "./gencow",
|
|
52
|
+
* schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
|
|
53
|
+
* codegenOutDir: "./src/gencow",
|
|
54
|
+
* storage: "./.gencow/uploads",
|
|
55
|
+
* db: { url: "./.gencow/data" },
|
|
56
|
+
* port: 5456,
|
|
57
|
+
* deploy: { app: "my-app" },
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export interface GencowConfig {
|
|
62
|
+
/**
|
|
63
|
+
* Directory containing your backend modules such as `index.ts`, `schema.ts`,
|
|
64
|
+
* and optional files like `crons.ts` or `seed.ts`.
|
|
65
|
+
*
|
|
66
|
+
* Default: `"./gencow"`
|
|
67
|
+
*/
|
|
68
|
+
functionsDir?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Path to the Drizzle schema file, or multiple schema files when your project
|
|
71
|
+
* splits auth and application tables.
|
|
72
|
+
*
|
|
73
|
+
* Default: `"./gencow/schema.ts"`
|
|
74
|
+
*/
|
|
75
|
+
schema?: string | string[];
|
|
76
|
+
/**
|
|
77
|
+
* Output directory for generated frontend codegen artifacts such as `api.ts`
|
|
78
|
+
* and declaration files.
|
|
79
|
+
*
|
|
80
|
+
* Default: `"./src/gencow"`
|
|
81
|
+
*/
|
|
82
|
+
codegenOutDir?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Directory used for uploaded file storage in local development.
|
|
85
|
+
*
|
|
86
|
+
* Default: `"./.gencow/uploads"`
|
|
87
|
+
*/
|
|
88
|
+
storage?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Local database configuration.
|
|
91
|
+
*/
|
|
92
|
+
db?: GencowDbConfig;
|
|
93
|
+
/**
|
|
94
|
+
* Port used by the local dev server.
|
|
95
|
+
*
|
|
96
|
+
* Default: `5456`
|
|
97
|
+
*/
|
|
98
|
+
port?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Optional cloud deployment metadata.
|
|
101
|
+
*/
|
|
102
|
+
deploy?: GencowDeployConfig;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Helper for authoring typed `gencow.config.ts` files with editor autocomplete.
|
|
106
|
+
*/
|
|
107
|
+
export declare function defineConfig(config: GencowConfig): GencowConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing project configuration for `gencow.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* The config file is intentionally small today, but we expose a stable typed
|
|
5
|
+
* contract so editors can offer autocomplete and inline explanations.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Helper for authoring typed `gencow.config.ts` files with editor autocomplete.
|
|
9
|
+
*/
|
|
10
|
+
export function defineConfig(config) {
|
|
11
|
+
return config;
|
|
12
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Storage } from "./storage.js";
|
|
2
|
+
import type { Scheduler } from "./scheduler.js";
|
|
3
|
+
import type { HybridSearchOptions, SearchOptions, SearchResponse, VectorSearchOptions } from "./search-types.js";
|
|
4
|
+
import type { GencowServicesCtx } from "./document-types.js";
|
|
5
|
+
import type { GroundingRuntime } from "./grounded-answer-types.js";
|
|
6
|
+
import type { RetryOptions } from "./retry.js";
|
|
7
|
+
import type { GencowProcedureDef } from "./procedure.js";
|
|
8
|
+
export interface UserIdentity {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthCtx {
|
|
14
|
+
/** 현재 유저 반환 (비로그인 시 null) — Convex의 ctx.auth.getUserIdentity() */
|
|
15
|
+
getUserIdentity(): UserIdentity | null;
|
|
16
|
+
/** 현재 유저 반환 (비로그인 시 401 throw) */
|
|
17
|
+
requireAuth(): UserIdentity;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* mutation handler 내에서 구독자들에게 데이터를 즉시 push합니다.
|
|
21
|
+
* 클라이언트는 이 데이터를 받아 re-fetch 없이 state를 업데이트합니다.
|
|
22
|
+
*/
|
|
23
|
+
export interface RealtimeCtx {
|
|
24
|
+
/**
|
|
25
|
+
* 특정 queryKey를 구독 중인 클라이언트에 데이터를 즉시 push합니다.
|
|
26
|
+
* 초고빈도 mutation (채팅 등)에서 query re-run 비용을 회피할 때 사용.
|
|
27
|
+
*
|
|
28
|
+
* @param queryKey - 업데이트할 쿼리 키 (예: "tasks.list")
|
|
29
|
+
* @param data - push할 데이터 (해당 query 결과와 동일한 타입)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const freshList = await ctx.db.select().from(tasks);
|
|
33
|
+
* ctx.realtime.emit("tasks.list", freshList);
|
|
34
|
+
*/
|
|
35
|
+
emit(queryKey: string, data: unknown): void;
|
|
36
|
+
/**
|
|
37
|
+
* 특정 queryKey 구독자에게 재조회 신호만 보냅니다.
|
|
38
|
+
* private/RLS query처럼 서버가 안전하게 full payload를 만들 수 없는 경우 기본값입니다.
|
|
39
|
+
*/
|
|
40
|
+
invalidate(queryKey: string | string[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* 수동 mutation에서 리얼타임 업데이트의 기본 권장 방식.
|
|
43
|
+
* mutation handler 완료 후 서버가 해당 query를 re-run하여 결과를 push합니다.
|
|
44
|
+
* 복잡한 JOIN query도 queryKey만 지정하면 됩니다.
|
|
45
|
+
*
|
|
46
|
+
* @param queryKey - re-run할 쿼리 키 (예: "dashboard.revenue")
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* mutation("orders.place", {
|
|
50
|
+
* handler: async (ctx, args) => {
|
|
51
|
+
* await ctx.db.insert(orders).values(args);
|
|
52
|
+
* ctx.realtime.refresh("orders.list");
|
|
53
|
+
* ctx.realtime.refresh("dashboard.revenue"); // JOIN query도 OK
|
|
54
|
+
* }
|
|
55
|
+
* });
|
|
56
|
+
*/
|
|
57
|
+
refresh(queryKey: string): void;
|
|
58
|
+
}
|
|
59
|
+
export type RealtimeNotifyEvent = {
|
|
60
|
+
type: "emit";
|
|
61
|
+
queryKey: string;
|
|
62
|
+
data: unknown;
|
|
63
|
+
} | {
|
|
64
|
+
type: "invalidate";
|
|
65
|
+
queryKeys: string[];
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 사용자 함수(query/mutation)에 주입되는 컨텍스트.
|
|
69
|
+
* Convex의 ctx 패턴과 동일하게, 이 객체를 통해서만 DB/Storage/Auth에 접근 가능.
|
|
70
|
+
* fs, child_process 등 원시 Node.js API는 노출되지 않음.
|
|
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
|
+
export interface GencowCtx {
|
|
106
|
+
/** Drizzle DB 인스턴스 (scoped) — 스키마 filter 자동 적용, execute 차단 */
|
|
107
|
+
db: any;
|
|
108
|
+
/** Raw Drizzle DB — 필터 없음, execute 허용. ⚠️ 이름이 곧 경고. */
|
|
109
|
+
unsafeDb: any;
|
|
110
|
+
/** 인증 컨텍스트 — ctx.auth.getUserIdentity() */
|
|
111
|
+
auth: AuthCtx;
|
|
112
|
+
/** 파일 스토리지 — ctx.storage.store(), ctx.storage.getUrl() */
|
|
113
|
+
storage: Storage;
|
|
114
|
+
/** 스케줄러 — ctx.scheduler.runAfter(), ctx.scheduler.cron() */
|
|
115
|
+
scheduler: Scheduler;
|
|
116
|
+
/** 실시간 push — ctx.realtime.emit(queryKey, data) */
|
|
117
|
+
realtime: RealtimeCtx;
|
|
118
|
+
/** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
|
|
119
|
+
retry: <T>(fn: () => Promise<T>, options?: RetryOptions) => Promise<T>;
|
|
120
|
+
/** 프레임워크 서비스 헬퍼 — workflow 전용 service는 별도 ctx에서 노출 */
|
|
121
|
+
services: GencowServicesCtx;
|
|
122
|
+
/** AI 헬퍼 */
|
|
123
|
+
ai?: AIContext;
|
|
124
|
+
/** Full-text / hybrid search helper */
|
|
125
|
+
search: (table: string, query: string, options: SearchOptions) => Promise<SearchResponse>;
|
|
126
|
+
/** Vector / semantic search helper */
|
|
127
|
+
vectorSearch: (table: string, options: VectorSearchOptions) => Promise<SearchResponse>;
|
|
128
|
+
/** Hybrid search helper (lexical + vector) */
|
|
129
|
+
hybridSearch: (table: string, query: string, options: HybridSearchOptions) => Promise<SearchResponse>;
|
|
130
|
+
/** Grounded answer helper over canonical rag_* tables */
|
|
131
|
+
grounding?: GroundingRuntime;
|
|
132
|
+
}
|
|
133
|
+
export interface GencowDefinition {
|
|
134
|
+
[procedureName: string]: GencowProcedureDef<any, any, any, any>;
|
|
135
|
+
}
|
|
136
|
+
export interface GencowApiDefinition {
|
|
137
|
+
procedures: GencowDefinition;
|
|
138
|
+
}
|
|
139
|
+
export declare function defineApi<const TApi extends GencowApiDefinition>(api: TApi): TApi;
|
package/dist/context.js
ADDED
package/dist/crud.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export declare function parseFilterNode(node: Record<string, unknown>, table: an
|
|
|
130
130
|
* ```
|
|
131
131
|
*/
|
|
132
132
|
export declare function crud<T extends PgTable>(table: T, options?: CrudOptions<T>): {
|
|
133
|
-
list: import("./reactive.js").QueryDef<{
|
|
133
|
+
list: import("./reactive-query-types.js").QueryDef<{
|
|
134
134
|
page: import("./v.js").Validator<number | undefined>;
|
|
135
135
|
limit: import("./v.js").Validator<number | undefined>;
|
|
136
136
|
search: import("./v.js").Validator<string | undefined>;
|
|
@@ -141,12 +141,12 @@ export declare function crud<T extends PgTable>(table: T, options?: CrudOptions<
|
|
|
141
141
|
data: any;
|
|
142
142
|
total: number;
|
|
143
143
|
}> | undefined;
|
|
144
|
-
get: import("./reactive.js").QueryDef<{
|
|
144
|
+
get: import("./reactive-query-types.js").QueryDef<{
|
|
145
145
|
id: import("./v.js").Validator<string> | import("./v.js").Validator<number>;
|
|
146
146
|
}, any> | undefined;
|
|
147
|
-
create: import("./reactive.js").MutationDef<any, any> | undefined;
|
|
148
|
-
update: import("./reactive.js").MutationDef<any, any> | undefined;
|
|
149
|
-
remove: import("./reactive.js").MutationDef<any, {
|
|
147
|
+
create: import("./reactive-mutation-types.js").MutationDef<any, any> | undefined;
|
|
148
|
+
update: import("./reactive-mutation-types.js").MutationDef<any, any> | undefined;
|
|
149
|
+
remove: import("./reactive-mutation-types.js").MutationDef<any, {
|
|
150
150
|
success: boolean;
|
|
151
151
|
}> | undefined;
|
|
152
152
|
};
|
package/dist/crud.js
CHANGED
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { eq, ne, gt, gte, lt, lte, desc, asc, like, ilike, inArray, notInArray, or, and, count as drizzleCount, getTableName, getTableColumns, } from "drizzle-orm";
|
|
38
38
|
import { getTableConfig } from "drizzle-orm/pg-core";
|
|
39
|
-
import {
|
|
39
|
+
import { buildQuerySubscriptionKey } from "./reactive-realtime.js";
|
|
40
|
+
import { query } from "./reactive-query.js";
|
|
41
|
+
import { mutation } from "./reactive-mutation.js";
|
|
40
42
|
import { v } from "./v.js";
|
|
41
43
|
import { getOwnerRlsMeta, registerOwnerRls } from "./rls.js";
|
|
42
44
|
// ─── ownerRls 감지 추적 (부트 로그용) ──────────────────
|
|
@@ -339,23 +341,11 @@ export function crud(table, options) {
|
|
|
339
341
|
}
|
|
340
342
|
return await fn(db);
|
|
341
343
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// ownerRls 적용 시 realtime emit에도 userId 필터 추가
|
|
348
|
-
// → 다른 사용자에게 타인 데이터가 push되지 않도록
|
|
349
|
-
let effectiveWhere = whereClause;
|
|
350
|
-
if (ownerMeta && userId && !ownerMeta.readPublic) {
|
|
351
|
-
const ownerFilter = eq(ownerMeta.column, userId);
|
|
352
|
-
effectiveWhere = effectiveWhere ? and(effectiveWhere, ownerFilter) : ownerFilter;
|
|
353
|
-
}
|
|
354
|
-
return await inRlsOrPlainTx(db, async (tx) => {
|
|
355
|
-
const data = await tx.select().from(anyTable).where(effectiveWhere).orderBy(desc(defaultOrderCol));
|
|
356
|
-
const countResult = await tx.select({ count: drizzleCount() }).from(anyTable).where(effectiveWhere);
|
|
357
|
-
return { data, total: Number(countResult[0]?.count ?? 0) };
|
|
358
|
-
});
|
|
344
|
+
function invalidateListRealtime(ctx) {
|
|
345
|
+
ctx.realtime.invalidate(`${prefix}.list`);
|
|
346
|
+
}
|
|
347
|
+
function invalidateGetRealtime(ctx, id) {
|
|
348
|
+
ctx.realtime.invalidate(buildQuerySubscriptionKey(`${prefix}.get`, { id }));
|
|
359
349
|
}
|
|
360
350
|
// ── methods 필터링: 지정된 메서드만 레지스트리 등록 ──
|
|
361
351
|
// methods 옵션 미지정 시 전체 5개 등록 (하위호환)
|
|
@@ -477,13 +467,9 @@ export function crud(table, options) {
|
|
|
477
467
|
insertData = await options.hooks.beforeCreate(insertData);
|
|
478
468
|
}
|
|
479
469
|
const [result] = await inRlsOrPlainTx(ctx.db, async (tx) => tx.insert(anyTable).values(insertData).returning());
|
|
480
|
-
// Realtime push
|
|
481
|
-
// ownerRls 시 해당 사용자 데이터만 push (타 사용자에게 누출 방지)
|
|
482
|
-
// S5: requireAuth로 확실한 userId 획득 (getUserIdentity null 방지)
|
|
470
|
+
// Realtime push: list는 payload 없이 invalidation으로 갱신
|
|
483
471
|
if (useRealtime && enabledMethods.has("list")) {
|
|
484
|
-
|
|
485
|
-
const listResult = await fetchListWithTotal(ctx.db, undefined, currentUserId);
|
|
486
|
-
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
472
|
+
invalidateListRealtime(ctx);
|
|
487
473
|
}
|
|
488
474
|
return result;
|
|
489
475
|
},
|
|
@@ -519,16 +505,17 @@ export function crud(table, options) {
|
|
|
519
505
|
updateData = await options.hooks.beforeUpdate(updateData);
|
|
520
506
|
}
|
|
521
507
|
const [result] = await inRlsOrPlainTx(ctx.db, async (tx) => tx.update(anyTable).set(updateData).where(updateWhere).returning());
|
|
522
|
-
// Realtime push
|
|
523
|
-
// S5: ownerRls 시 user.id 사용 (getUserIdentity null 방지)
|
|
508
|
+
// Realtime push: list는 invalidation, public exact-id get만 full payload 유지
|
|
524
509
|
if (useRealtime) {
|
|
525
|
-
const currentUserId = ownerMeta ? user?.id : undefined;
|
|
526
510
|
if (enabledMethods.has("list")) {
|
|
527
|
-
|
|
528
|
-
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
511
|
+
invalidateListRealtime(ctx);
|
|
529
512
|
}
|
|
530
513
|
if (enabledMethods.has("get")) {
|
|
531
|
-
|
|
514
|
+
const getKey = buildQuerySubscriptionKey(`${prefix}.get`, { id });
|
|
515
|
+
if (isPublic && !ownerMeta)
|
|
516
|
+
ctx.realtime.emit(getKey, result);
|
|
517
|
+
else
|
|
518
|
+
invalidateGetRealtime(ctx, id);
|
|
532
519
|
}
|
|
533
520
|
}
|
|
534
521
|
return result;
|
|
@@ -560,12 +547,9 @@ export function crud(table, options) {
|
|
|
560
547
|
await tx.delete(anyTable).where(deleteWhere);
|
|
561
548
|
}
|
|
562
549
|
});
|
|
563
|
-
// Realtime push
|
|
564
|
-
// S5: ownerRls 시 user.id 사용
|
|
550
|
+
// Realtime push: list는 payload 없이 invalidation으로 갱신
|
|
565
551
|
if (useRealtime && enabledMethods.has("list")) {
|
|
566
|
-
|
|
567
|
-
const listResult = await fetchListWithTotal(ctx.db, undefined, currentUserId);
|
|
568
|
-
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
552
|
+
invalidateListRealtime(ctx);
|
|
569
553
|
}
|
|
570
554
|
return { success: true };
|
|
571
555
|
},
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type DocumentVisibility = "private" | "shared" | "public";
|
|
2
|
+
export type DocumentConvertMode = "auto" | "text-only" | "no-external-provider" | "prefer-external" | "force-external" | "force-ocr";
|
|
3
|
+
export type DocumentConvertProvider = "auto" | "local_text" | "opendataloader" | "gemini" | "openai" | "ocr" | "custom_vlm";
|
|
4
|
+
export type DocumentResolvedProvider = Exclude<DocumentConvertProvider, "auto">;
|
|
5
|
+
export type DocumentProviderRoute = "local_text" | "opendataloader" | "vlm" | "ocr_fallback";
|
|
6
|
+
export type DocumentCacheArtifact = "convert" | "embedding" | "summary" | "rerank";
|
|
7
|
+
export type DocumentConvertInput = {
|
|
8
|
+
storageId: string;
|
|
9
|
+
filename?: string;
|
|
10
|
+
mimeType?: string;
|
|
11
|
+
corpus: string;
|
|
12
|
+
visibility: DocumentVisibility;
|
|
13
|
+
ownerUserId?: string;
|
|
14
|
+
mode?: DocumentConvertMode;
|
|
15
|
+
provider?: DocumentConvertProvider;
|
|
16
|
+
};
|
|
17
|
+
export type DocumentPage = {
|
|
18
|
+
page: number;
|
|
19
|
+
charStart: number;
|
|
20
|
+
charEnd: number;
|
|
21
|
+
previewText?: string;
|
|
22
|
+
};
|
|
23
|
+
export type DocumentSection = {
|
|
24
|
+
id: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
depth: number;
|
|
27
|
+
path: string[];
|
|
28
|
+
pageStart?: number;
|
|
29
|
+
pageEnd?: number;
|
|
30
|
+
charStart: number;
|
|
31
|
+
charEnd: number;
|
|
32
|
+
};
|
|
33
|
+
export type DocumentProviderTrace = {
|
|
34
|
+
provider: DocumentResolvedProvider;
|
|
35
|
+
route: DocumentProviderRoute;
|
|
36
|
+
external: boolean;
|
|
37
|
+
requestId?: string;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
bytes: number;
|
|
40
|
+
};
|
|
41
|
+
export type DocumentConvertResult = {
|
|
42
|
+
text: string;
|
|
43
|
+
markdown: string;
|
|
44
|
+
pages: DocumentPage[];
|
|
45
|
+
sections: DocumentSection[];
|
|
46
|
+
warnings: string[];
|
|
47
|
+
providerTrace: DocumentProviderTrace;
|
|
48
|
+
};
|
|
49
|
+
export interface GencowServicesCtx {
|
|
50
|
+
}
|
|
51
|
+
export interface WorkflowDocumentServicesCtx {
|
|
52
|
+
document: {
|
|
53
|
+
convert(input: DocumentConvertInput): Promise<DocumentConvertResult>;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export type DocumentCacheKeyInput = {
|
|
57
|
+
appId: string;
|
|
58
|
+
ownerUserId?: string | null;
|
|
59
|
+
corpus: string;
|
|
60
|
+
sourceKey: string;
|
|
61
|
+
checksum: string;
|
|
62
|
+
provider: DocumentResolvedProvider;
|
|
63
|
+
artifact: DocumentCacheArtifact;
|
|
64
|
+
};
|
|
65
|
+
export declare function buildDocumentCacheKey(input: DocumentCacheKeyInput): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function normalizeCachePart(value) {
|
|
2
|
+
return value.trim() || "unknown";
|
|
3
|
+
}
|
|
4
|
+
export function buildDocumentCacheKey(input) {
|
|
5
|
+
const owner = input.ownerUserId?.trim() ? input.ownerUserId.trim() : "shared";
|
|
6
|
+
return [
|
|
7
|
+
`app:${normalizeCachePart(input.appId)}`,
|
|
8
|
+
`user:${normalizeCachePart(owner)}`,
|
|
9
|
+
`corpus:${normalizeCachePart(input.corpus)}`,
|
|
10
|
+
`source:${normalizeCachePart(input.sourceKey)}`,
|
|
11
|
+
`checksum:${normalizeCachePart(input.checksum)}`,
|
|
12
|
+
`provider:${normalizeCachePart(input.provider)}`,
|
|
13
|
+
`artifact:${normalizeCachePart(input.artifact)}`,
|
|
14
|
+
].join(":");
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SearchFilter, SearchScope } from "./search-types.js";
|
|
2
|
+
export type CitationCoverage = "direct" | "partial" | "context";
|
|
3
|
+
export type ClaimSupportStatus = "supported" | "partially_supported" | "insufficient_evidence";
|
|
4
|
+
export type GroundedAnswerMode = "qa" | "compare" | "topic";
|
|
5
|
+
export type Citation = {
|
|
6
|
+
sourceId: string;
|
|
7
|
+
sourceTitle: string;
|
|
8
|
+
sectionId?: string;
|
|
9
|
+
sectionPath?: string[];
|
|
10
|
+
pageStart?: number;
|
|
11
|
+
pageEnd?: number;
|
|
12
|
+
chunkIds: string[];
|
|
13
|
+
snippet: string;
|
|
14
|
+
coverage: CitationCoverage;
|
|
15
|
+
confidence: number;
|
|
16
|
+
};
|
|
17
|
+
export type ClaimEvidenceMap = {
|
|
18
|
+
claimId: string;
|
|
19
|
+
claimText: string;
|
|
20
|
+
status: ClaimSupportStatus;
|
|
21
|
+
citations: Citation[];
|
|
22
|
+
missingEvidenceReasons?: string[];
|
|
23
|
+
};
|
|
24
|
+
export type GroundedAnswer = {
|
|
25
|
+
answer: string;
|
|
26
|
+
claims: ClaimEvidenceMap[];
|
|
27
|
+
citations: Citation[];
|
|
28
|
+
warnings: string[];
|
|
29
|
+
grounded: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type GroundingBudget = {
|
|
32
|
+
maxVerifyLoops: number;
|
|
33
|
+
maxResearchQueriesPerLoop: number;
|
|
34
|
+
maxCitationsPerClaim: number;
|
|
35
|
+
maxClaimsPerAnswer: number;
|
|
36
|
+
};
|
|
37
|
+
export declare const DEFAULT_GROUNDING_BUDGET: GroundingBudget;
|
|
38
|
+
export type GroundedClaimInput = {
|
|
39
|
+
claimId?: string;
|
|
40
|
+
claimText: string;
|
|
41
|
+
requiredTerms?: string[];
|
|
42
|
+
};
|
|
43
|
+
export type GroundedCompareInput = {
|
|
44
|
+
left: string;
|
|
45
|
+
right: string;
|
|
46
|
+
};
|
|
47
|
+
export type GroundedTopicInput = {
|
|
48
|
+
minDistinctSections?: number;
|
|
49
|
+
};
|
|
50
|
+
export type GroundedAnswerInput = {
|
|
51
|
+
question: string;
|
|
52
|
+
scope: SearchScope;
|
|
53
|
+
filters?: SearchFilter;
|
|
54
|
+
claims?: GroundedClaimInput[];
|
|
55
|
+
mode?: GroundedAnswerMode;
|
|
56
|
+
compare?: GroundedCompareInput;
|
|
57
|
+
topic?: GroundedTopicInput;
|
|
58
|
+
budget?: Partial<GroundingBudget>;
|
|
59
|
+
};
|
|
60
|
+
export type GroundingRuntime = {
|
|
61
|
+
answer(input: GroundedAnswerInput): Promise<GroundedAnswer>;
|
|
62
|
+
};
|