@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.
Files changed (130) hide show
  1. package/dist/auth-config.d.ts +92 -5
  2. package/dist/config.d.ts +107 -0
  3. package/dist/config.js +12 -0
  4. package/dist/context.d.ts +139 -0
  5. package/dist/context.js +3 -0
  6. package/dist/crud.d.ts +5 -5
  7. package/dist/crud.js +19 -35
  8. package/dist/document-types.d.ts +65 -0
  9. package/dist/document-types.js +15 -0
  10. package/dist/grounded-answer-types.d.ts +62 -0
  11. package/dist/grounded-answer-types.js +6 -0
  12. package/dist/http-action.d.ts +77 -0
  13. package/dist/http-action.js +41 -0
  14. package/dist/index.d.ts +30 -5
  15. package/dist/index.js +15 -2
  16. package/dist/platform-capacity-profile.d.ts +19 -0
  17. package/dist/platform-capacity-profile.js +94 -0
  18. package/dist/procedure.d.ts +58 -0
  19. package/dist/procedure.js +115 -0
  20. package/dist/rag-ingest-types.d.ts +39 -0
  21. package/dist/rag-ingest-types.js +1 -0
  22. package/dist/rag-operations-types.d.ts +81 -0
  23. package/dist/rag-operations-types.js +1 -0
  24. package/dist/rag-schema.d.ts +1466 -0
  25. package/dist/rag-schema.js +87 -0
  26. package/dist/reactive-mutation-types.d.ts +11 -0
  27. package/dist/reactive-mutation-types.js +1 -0
  28. package/dist/reactive-mutation.d.ts +51 -0
  29. package/dist/reactive-mutation.js +75 -0
  30. package/dist/reactive-query-types.d.ts +12 -0
  31. package/dist/reactive-query-types.js +1 -0
  32. package/dist/reactive-query.d.ts +14 -0
  33. package/dist/reactive-query.js +28 -0
  34. package/dist/reactive-realtime.d.ts +48 -0
  35. package/dist/reactive-realtime.js +236 -0
  36. package/dist/reactive.d.ts +29 -5
  37. package/dist/reactive.js +65 -0
  38. package/dist/rls-db.d.ts +9 -2
  39. package/dist/runtime-env-policy.d.ts +5 -0
  40. package/dist/runtime-env-policy.js +56 -0
  41. package/dist/search-types.d.ts +83 -0
  42. package/dist/search-types.js +1 -0
  43. package/dist/server.d.ts +1 -2
  44. package/dist/server.js +0 -1
  45. package/dist/storage-metering.d.ts +13 -0
  46. package/dist/storage-metering.js +18 -0
  47. package/dist/storage-shared.d.ts +36 -0
  48. package/dist/storage-shared.js +39 -0
  49. package/dist/storage.d.ts +5 -27
  50. package/dist/storage.js +30 -22
  51. package/dist/wake-app-result.d.ts +22 -0
  52. package/dist/wake-app-result.js +11 -0
  53. package/dist/workflow-types.d.ts +16 -2
  54. package/dist/workflow.d.ts +1 -1
  55. package/dist/workflow.js +136 -11
  56. package/dist/workflows-api.js +71 -3
  57. package/package.json +11 -7
  58. package/src/auth-config.ts +104 -3
  59. package/src/config.ts +119 -0
  60. package/src/context.ts +152 -0
  61. package/src/crud.ts +18 -35
  62. package/src/document-types.ts +102 -0
  63. package/src/grounded-answer-types.ts +78 -0
  64. package/src/http-action.ts +101 -0
  65. package/src/index.ts +142 -19
  66. package/src/platform-capacity-profile.ts +114 -0
  67. package/src/procedure.ts +283 -0
  68. package/src/rag-ingest-types.ts +52 -0
  69. package/src/rag-operations-types.ts +90 -0
  70. package/src/rag-schema.ts +94 -0
  71. package/src/reactive-mutation-types.ts +13 -0
  72. package/src/reactive-mutation.ts +115 -0
  73. package/src/reactive-query-types.ts +14 -0
  74. package/src/reactive-query.ts +48 -0
  75. package/src/reactive-realtime.ts +267 -0
  76. package/src/rls-db.ts +9 -4
  77. package/src/runtime-env-policy.ts +66 -0
  78. package/src/search-types.ts +91 -0
  79. package/src/server.ts +6 -2
  80. package/src/storage-metering.ts +35 -0
  81. package/src/storage-shared.ts +74 -0
  82. package/src/storage.ts +44 -53
  83. package/src/wake-app-result.ts +37 -0
  84. package/src/workflow-types.ts +16 -2
  85. package/src/workflow.ts +166 -12
  86. package/src/workflows-api.ts +82 -3
  87. package/src/__tests__/auth.test.ts +0 -118
  88. package/src/__tests__/crons.test.ts +0 -83
  89. package/src/__tests__/crud-codegen-integration.test.ts +0 -246
  90. package/src/__tests__/crud-owner-rls.test.ts +0 -387
  91. package/src/__tests__/crud.test.ts +0 -930
  92. package/src/__tests__/dist-exports.test.ts +0 -176
  93. package/src/__tests__/fixtures/basic/auth.ts +0 -32
  94. package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
  95. package/src/__tests__/fixtures/basic/index.ts +0 -6
  96. package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
  97. package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
  98. package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
  99. package/src/__tests__/fixtures/basic/schema.ts +0 -51
  100. package/src/__tests__/fixtures/basic/tasks.ts +0 -15
  101. package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
  102. package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
  103. package/src/__tests__/helpers/pglite-migrations.ts +0 -32
  104. package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
  105. package/src/__tests__/helpers/seed-like-fill.ts +0 -202
  106. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
  107. package/src/__tests__/httpaction.test.ts +0 -122
  108. package/src/__tests__/image-optimization.test.ts +0 -648
  109. package/src/__tests__/load.test.ts +0 -389
  110. package/src/__tests__/network-sim.test.ts +0 -319
  111. package/src/__tests__/reactive.test.ts +0 -479
  112. package/src/__tests__/retry.test.ts +0 -113
  113. package/src/__tests__/rls-crud-basic.test.ts +0 -317
  114. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
  115. package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
  116. package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
  117. package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
  118. package/src/__tests__/rls-session-and-policies.test.ts +0 -228
  119. package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
  120. package/src/__tests__/scheduler-durable.test.ts +0 -173
  121. package/src/__tests__/scheduler-exec.test.ts +0 -328
  122. package/src/__tests__/scheduler.test.ts +0 -187
  123. package/src/__tests__/storage.test.ts +0 -334
  124. package/src/__tests__/tsconfig.json +0 -8
  125. package/src/__tests__/validator.test.ts +0 -323
  126. package/src/__tests__/workflow.test.ts +0 -606
  127. package/src/__tests__/ws-integration.test.ts +0 -309
  128. package/src/__tests__/ws-scale.test.ts +0 -241
  129. package/src/auth.ts +0 -155
  130. package/src/reactive.ts +0 -580
@@ -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
- email: string;
21
- name: string;
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 설정 정의 헬퍼.
@@ -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;
@@ -0,0 +1,3 @@
1
+ export function defineApi(api) {
2
+ return api;
3
+ }
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 { query, mutation } from "./reactive.js";
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
- // ── 내부 헬퍼: list+count 데이터 가져오기 (realtime push용 재사용) ──
343
- // Runs inside db.transaction so createRlsDb() RLS session vars apply for RLS.
344
- // ⚠️ limit/offset 없이 전체 SELECT — 대량 데이터 시 성능 저하 주의
345
- // TODO(P2): realtime emit 시 invalidation 메시지만 전송하고 클라이언트가 re-fetch하는 패턴 검토
346
- async function fetchListWithTotal(db, whereClause, userId) {
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 { data, total } 형태로 emit
481
- // ownerRls 시 해당 사용자 데이터만 push (타 사용자에게 누출 방지)
482
- // S5: requireAuth로 확실한 userId 획득 (getUserIdentity null 방지)
470
+ // Realtime push: list는 payload 없이 invalidation으로 갱신
483
471
  if (useRealtime && enabledMethods.has("list")) {
484
- const currentUserId = user?.id;
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 (list + get 양쪽) { data, total } 형태
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
- const listResult = await fetchListWithTotal(ctx.db, undefined, currentUserId);
528
- ctx.realtime.emit(`${prefix}.list`, listResult);
511
+ invalidateListRealtime(ctx);
529
512
  }
530
513
  if (enabledMethods.has("get")) {
531
- ctx.realtime.emit(`${prefix}.get`, result);
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 { data, total } 형태
564
- // S5: ownerRls 시 user.id 사용
550
+ // Realtime push: list는 payload 없이 invalidation으로 갱신
565
551
  if (useRealtime && enabledMethods.has("list")) {
566
- const currentUserId = ownerMeta ? user?.id : undefined;
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
+ };
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_GROUNDING_BUDGET = {
2
+ maxVerifyLoops: 2,
3
+ maxResearchQueriesPerLoop: 3,
4
+ maxCitationsPerClaim: 3,
5
+ maxClaimsPerAnswer: 12,
6
+ };