@gencow/core 0.1.26 → 0.1.28

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 (88) hide show
  1. package/dist/crud.d.ts +12 -0
  2. package/dist/crud.js +16 -0
  3. package/dist/db.d.ts +13 -0
  4. package/dist/db.js +16 -0
  5. package/dist/document-types.d.ts +65 -0
  6. package/dist/document-types.js +15 -0
  7. package/dist/grounded-answer-types.d.ts +62 -0
  8. package/dist/grounded-answer-types.js +6 -0
  9. package/dist/index.d.ts +12 -2
  10. package/dist/index.js +5 -1
  11. package/dist/rag-ingest-types.d.ts +39 -0
  12. package/dist/rag-ingest-types.js +1 -0
  13. package/dist/rag-operations-types.d.ts +81 -0
  14. package/dist/rag-operations-types.js +1 -0
  15. package/dist/rag-schema.d.ts +1557 -0
  16. package/dist/rag-schema.js +87 -0
  17. package/dist/reactive.d.ts +13 -0
  18. package/dist/rls-db.d.ts +9 -2
  19. package/dist/runtime-env-policy.d.ts +5 -0
  20. package/dist/runtime-env-policy.js +56 -0
  21. package/dist/search-types.d.ts +83 -0
  22. package/dist/search-types.js +1 -0
  23. package/dist/server.d.ts +1 -2
  24. package/dist/server.js +0 -1
  25. package/dist/storage-shared.d.ts +36 -0
  26. package/dist/storage-shared.js +39 -0
  27. package/dist/storage.d.ts +2 -26
  28. package/dist/storage.js +19 -15
  29. package/dist/workflow-types.d.ts +3 -1
  30. package/package.json +1 -1
  31. package/src/crud.ts +33 -0
  32. package/src/document-types.ts +95 -0
  33. package/src/grounded-answer-types.ts +78 -0
  34. package/src/index.ts +68 -2
  35. package/src/rag-ingest-types.ts +52 -0
  36. package/src/rag-operations-types.ts +90 -0
  37. package/src/rag-schema.ts +94 -0
  38. package/src/reactive.ts +13 -0
  39. package/src/rls-db.ts +9 -4
  40. package/src/runtime-env-policy.ts +66 -0
  41. package/src/search-types.ts +91 -0
  42. package/src/server.ts +1 -2
  43. package/src/storage-shared.ts +74 -0
  44. package/src/storage.ts +29 -46
  45. package/src/workflow-types.ts +3 -1
  46. package/src/__tests__/auth.test.ts +0 -118
  47. package/src/__tests__/crons.test.ts +0 -83
  48. package/src/__tests__/crud-codegen-integration.test.ts +0 -246
  49. package/src/__tests__/crud-owner-rls.test.ts +0 -387
  50. package/src/__tests__/crud.test.ts +0 -930
  51. package/src/__tests__/dist-exports.test.ts +0 -176
  52. package/src/__tests__/fixtures/basic/auth.ts +0 -32
  53. package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
  54. package/src/__tests__/fixtures/basic/index.ts +0 -6
  55. package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
  56. package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
  57. package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
  58. package/src/__tests__/fixtures/basic/schema.ts +0 -51
  59. package/src/__tests__/fixtures/basic/tasks.ts +0 -15
  60. package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
  61. package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
  62. package/src/__tests__/helpers/pglite-migrations.ts +0 -32
  63. package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
  64. package/src/__tests__/helpers/seed-like-fill.ts +0 -202
  65. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
  66. package/src/__tests__/httpaction.test.ts +0 -122
  67. package/src/__tests__/image-optimization.test.ts +0 -648
  68. package/src/__tests__/load.test.ts +0 -389
  69. package/src/__tests__/network-sim.test.ts +0 -319
  70. package/src/__tests__/reactive.test.ts +0 -479
  71. package/src/__tests__/retry.test.ts +0 -113
  72. package/src/__tests__/rls-crud-basic.test.ts +0 -317
  73. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
  74. package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
  75. package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
  76. package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
  77. package/src/__tests__/rls-session-and-policies.test.ts +0 -228
  78. package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
  79. package/src/__tests__/scheduler-durable.test.ts +0 -173
  80. package/src/__tests__/scheduler-exec.test.ts +0 -328
  81. package/src/__tests__/scheduler.test.ts +0 -187
  82. package/src/__tests__/storage.test.ts +0 -334
  83. package/src/__tests__/tsconfig.json +0 -8
  84. package/src/__tests__/validator.test.ts +0 -323
  85. package/src/__tests__/workflow.test.ts +0 -606
  86. package/src/__tests__/ws-integration.test.ts +0 -309
  87. package/src/__tests__/ws-scale.test.ts +0 -241
  88. package/src/auth.ts +0 -155
@@ -0,0 +1,87 @@
1
+ import { bigint, boolean, doublePrecision, integer, jsonb, pgTable, text, timestamp, vector } from "drizzle-orm/pg-core";
2
+ function buildScopeColumns() {
3
+ return {
4
+ id: text("id").primaryKey(),
5
+ corpus: text("corpus").notNull(),
6
+ visibilityScope: text("visibility_scope").notNull(),
7
+ ownerUserId: text("owner_user_id"),
8
+ createdAt: timestamp("created_at").defaultNow().notNull(),
9
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
10
+ };
11
+ }
12
+ export const ragCorpora = pgTable("rag_corpora", {
13
+ ...buildScopeColumns(),
14
+ title: text("title").notNull(),
15
+ description: text("description"),
16
+ allowExternalProviders: boolean("allow_external_providers").default(false).notNull(),
17
+ retentionDays: integer("retention_days"),
18
+ metadata: jsonb("metadata").default({}).notNull(),
19
+ });
20
+ export const ragSources = pgTable("rag_sources", {
21
+ ...buildScopeColumns(),
22
+ storageId: text("storage_id").notNull(),
23
+ sourceKey: text("source_key").notNull(),
24
+ sourceTitle: text("source_title").notNull(),
25
+ mimeType: text("mime_type").notNull(),
26
+ byteSize: bigint("byte_size", { mode: "number" }).notNull(),
27
+ sourceChecksum: text("source_checksum").notNull(),
28
+ convertProvider: text("convert_provider").notNull(),
29
+ convertStatus: text("convert_status").notNull(),
30
+ markdown: text("markdown").notNull(),
31
+ text: text("text").notNull(),
32
+ warnings: jsonb("warnings").default([]).notNull(),
33
+ metadata: jsonb("metadata").default({}).notNull(),
34
+ });
35
+ export const ragSections = pgTable("rag_sections", {
36
+ ...buildScopeColumns(),
37
+ sourceId: text("source_id").notNull(),
38
+ sectionIndex: integer("section_index").notNull(),
39
+ sectionPath: jsonb("section_path").default([]).notNull(),
40
+ title: text("title"),
41
+ depth: integer("depth").notNull(),
42
+ pageStart: integer("page_start"),
43
+ pageEnd: integer("page_end"),
44
+ charStart: integer("char_start").notNull(),
45
+ charEnd: integer("char_end").notNull(),
46
+ });
47
+ export const ragChunks = pgTable("rag_chunks", {
48
+ ...buildScopeColumns(),
49
+ sourceId: text("source_id").notNull(),
50
+ sectionId: text("section_id"),
51
+ chunkIndex: integer("chunk_index").notNull(),
52
+ chunkText: text("chunk_text").notNull(),
53
+ lexicalText: text("lexical_text").notNull(),
54
+ embedding: vector("embedding", { dimensions: 1536 }),
55
+ pageStart: integer("page_start"),
56
+ pageEnd: integer("page_end"),
57
+ chunkChecksum: text("chunk_checksum").notNull(),
58
+ metadata: jsonb("metadata").default({}).notNull(),
59
+ });
60
+ export const ragIngestJobs = pgTable("rag_ingest_jobs", {
61
+ ...buildScopeColumns(),
62
+ workflowId: text("workflow_id").notNull(),
63
+ sourceId: text("source_id").notNull(),
64
+ status: text("status").notNull(),
65
+ stage: text("stage").notNull(),
66
+ providerTrace: jsonb("provider_trace").default({}).notNull(),
67
+ metrics: jsonb("metrics").default({}).notNull(),
68
+ error: text("error"),
69
+ startedAt: timestamp("started_at").defaultNow().notNull(),
70
+ completedAt: timestamp("completed_at"),
71
+ });
72
+ export const ragOperationMetrics = pgTable("rag_operation_metrics", {
73
+ id: text("id").primaryKey(),
74
+ appId: text("app_id").notNull(),
75
+ corpus: text("corpus").notNull(),
76
+ visibilityScope: text("visibility_scope").notNull(),
77
+ ownerUserId: text("owner_user_id"),
78
+ operation: text("operation").notNull(),
79
+ jobId: text("job_id"),
80
+ workflowId: text("workflow_id"),
81
+ sourceId: text("source_id"),
82
+ metricName: text("metric_name").notNull(),
83
+ metricValue: doublePrecision("metric_value").notNull(),
84
+ unit: text("unit"),
85
+ metadata: jsonb("metadata").default({}).notNull(),
86
+ recordedAt: timestamp("recorded_at").defaultNow().notNull(),
87
+ });
@@ -2,6 +2,9 @@ import type { WSContext } from "hono/ws";
2
2
  import type { Storage } from "./storage.js";
3
3
  import type { Scheduler } from "./scheduler.js";
4
4
  import { type InferArgs } from "./v.js";
5
+ import type { HybridSearchOptions, SearchOptions, SearchResponse, VectorSearchOptions } from "./search-types.js";
6
+ import type { GencowServicesCtx } from "./document-types.js";
7
+ import type { GroundingRuntime } from "./grounded-answer-types.js";
5
8
  export interface UserIdentity {
6
9
  id: string;
7
10
  email: string;
@@ -101,8 +104,18 @@ export interface GencowCtx {
101
104
  realtime: RealtimeCtx;
102
105
  /** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
103
106
  retry: <T>(fn: () => Promise<T>, options?: import("./retry.js").RetryOptions) => Promise<T>;
107
+ /** 프레임워크 서비스 헬퍼 — workflow 전용 service는 별도 ctx에서 노출 */
108
+ services: GencowServicesCtx;
104
109
  /** AI 헬퍼 */
105
110
  ai?: AIContext;
111
+ /** Full-text / hybrid search helper */
112
+ search: (table: string, query: string, options: SearchOptions) => Promise<SearchResponse>;
113
+ /** Vector / semantic search helper */
114
+ vectorSearch: (table: string, options: VectorSearchOptions) => Promise<SearchResponse>;
115
+ /** Hybrid search helper (lexical + vector) */
116
+ hybridSearch: (table: string, query: string, options: HybridSearchOptions) => Promise<SearchResponse>;
117
+ /** Grounded answer helper over canonical rag_* tables */
118
+ grounding?: GroundingRuntime;
106
119
  }
107
120
  type QueryHandler<TArgs = any, TReturn = any> = (ctx: GencowCtx, args: TArgs) => Promise<TReturn>;
108
121
  type MutationHandler<TArgs = any, TReturn = any> = (ctx: GencowCtx, args: TArgs) => Promise<TReturn>;
package/dist/rls-db.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { PgAsyncDatabase } from "drizzle-orm/pg-core";
2
1
  /**
3
2
  * RLS DB wrapper — execution paths for `withRlsConnection`:
4
3
  * 1. **Reuse outer Drizzle transaction** (`reuseOuterConnection`): same connection, apply GUCs then run `fn`.
@@ -24,6 +23,13 @@ export type RlsSessionContext = {
24
23
  */
25
24
  vars?: Record<string, string>;
26
25
  };
26
+ type RlsDrizzleDatabaseLike = {
27
+ session: unknown;
28
+ _: {
29
+ session?: unknown;
30
+ };
31
+ transaction: (callback: (tx: unknown) => unknown | Promise<unknown>, ...rest: unknown[]) => Promise<unknown>;
32
+ };
27
33
  /**
28
34
  * pg `Pool.connect()`-style client: BEGIN → apply RLS GUCs → `fn` → COMMIT on success,
29
35
  * or ROLLBACK if anything fails (including failed COMMIT). Always `release()` in `finally`.
@@ -44,4 +50,5 @@ export declare function withRlsLeasedConnection<T>(leased: {
44
50
  *
45
51
  * `db.transaction()` still injects the same variables at the start of the callback transaction.
46
52
  */
47
- export declare function createRlsDb(db: PgAsyncDatabase<any, any, any, any>, rls: RlsSessionContext): PgAsyncDatabase<any, any, any, any>;
53
+ export declare function createRlsDb<TDb extends RlsDrizzleDatabaseLike>(db: TDb, rls: RlsSessionContext): TDb;
54
+ export {};
@@ -0,0 +1,5 @@
1
+ export declare function isReservedTenantRuntimeEnvKey(key: string): boolean;
2
+ export declare function filterTenantRuntimeEnvVars(vars: Record<string, string>): {
3
+ allowed: Record<string, string>;
4
+ rejectedKeys: string[];
5
+ };
@@ -0,0 +1,56 @@
1
+ const RESERVED_TENANT_RUNTIME_ENV_KEYS = new Set([
2
+ "PORT",
3
+ "DATABASE_URL",
4
+ "GENCOW_DB_URL",
5
+ "BETTER_AUTH_SECRET",
6
+ "BETTER_AUTH_URL",
7
+ "IS_PLATFORM",
8
+ "GENCOW_PLATFORM_CONFIG_FILE",
9
+ "GENCOW_PLATFORM_URL",
10
+ "GENCOW_PLATFORM_DB",
11
+ "PLATFORM_OPENAI_KEY",
12
+ "PLATFORM_GOOGLE_KEY",
13
+ "PLATFORM_INTERNAL_SECRET",
14
+ "INVITE_ONLY",
15
+ "RUNNER_TYPE",
16
+ "PGBOUNCER_PORT",
17
+ "GENCOW_FUNCTIONS",
18
+ "GENCOW_STORAGE",
19
+ "GENCOW_MIGRATIONS",
20
+ "GENCOW_APP_NAME",
21
+ "GENCOW_APP_DATA_DIR",
22
+ "GENCOW_INTERNAL_TOKEN",
23
+ "GENCOW_CRON_TOKEN",
24
+ "GENCOW_AI_PROXY_URL",
25
+ "GENCOW_AI_PROXY_URL_ALT",
26
+ "GENCOW_AI_PROXY_TOKEN",
27
+ "GENCOW_METERING_URL",
28
+ "GENCOW_METERING_URL_ALT",
29
+ "GENCOW_START_REASON",
30
+ "GENCOW_RESTART_ID",
31
+ "GENCOW_SHUTDOWN_MARKER_PATH",
32
+ "GENCOW_SKIP_MIGRATION",
33
+ "GENCOW_DB_MAX_CONNECTIONS",
34
+ "GENCOW_MEMORY_MB",
35
+ "BUN_JSC_forceRAMSize",
36
+ "MIMALLOC_PURGE_DELAY",
37
+ "NODE_PATH",
38
+ ]);
39
+ const RESERVED_TENANT_RUNTIME_ENV_PREFIXES = ["__GENCOW_", "GENCOW_DOCUMENT_", "GENCOW_WARM_"];
40
+ export function isReservedTenantRuntimeEnvKey(key) {
41
+ const normalized = key.trim();
42
+ return (RESERVED_TENANT_RUNTIME_ENV_KEYS.has(normalized) ||
43
+ RESERVED_TENANT_RUNTIME_ENV_PREFIXES.some((prefix) => normalized.startsWith(prefix)));
44
+ }
45
+ export function filterTenantRuntimeEnvVars(vars) {
46
+ const allowed = {};
47
+ const rejectedKeys = [];
48
+ for (const [key, value] of Object.entries(vars)) {
49
+ if (isReservedTenantRuntimeEnvKey(key)) {
50
+ rejectedKeys.push(key);
51
+ continue;
52
+ }
53
+ allowed[key] = value;
54
+ }
55
+ return { allowed, rejectedKeys };
56
+ }
@@ -0,0 +1,83 @@
1
+ export type SearchPrimitive = string | number | boolean;
2
+ export type SearchScope = {
3
+ corpus: string;
4
+ visibility: "private" | "shared" | "public";
5
+ ownerUserId?: string;
6
+ };
7
+ export type SearchFilter = {
8
+ eq?: Record<string, SearchPrimitive>;
9
+ in?: Record<string, SearchPrimitive[]>;
10
+ range?: Record<string, {
11
+ gte?: string | number;
12
+ lte?: string | number;
13
+ }>;
14
+ };
15
+ export type SearchOptions = {
16
+ fields: [string, ...string[]];
17
+ limit?: number;
18
+ offset?: number;
19
+ filters?: SearchFilter;
20
+ scope: SearchScope;
21
+ };
22
+ export type VectorSearchTuning = {
23
+ minScore?: number;
24
+ };
25
+ export type VectorSearchOptions = Omit<SearchOptions, "fields"> & {
26
+ vector: number[];
27
+ vectorField?: string;
28
+ tuning?: VectorSearchTuning;
29
+ };
30
+ export type HybridSearchFusionTuning = {
31
+ mode?: "rrf";
32
+ rrfK?: number;
33
+ keywordWeight?: number;
34
+ vectorWeight?: number;
35
+ };
36
+ export type HybridSearchTuning = {
37
+ keywordCandidateLimit?: number;
38
+ vectorCandidateLimit?: number;
39
+ minFusedScore?: number;
40
+ fusion?: HybridSearchFusionTuning;
41
+ };
42
+ export type HybridSearchOptions = SearchOptions & {
43
+ vector: number[];
44
+ vectorField?: string;
45
+ fusion?: "rrf";
46
+ keywordCandidateLimit?: number;
47
+ vectorCandidateLimit?: number;
48
+ tuning?: HybridSearchTuning;
49
+ };
50
+ export type SearchHit = {
51
+ id: string | number;
52
+ row: Record<string, unknown>;
53
+ score: number;
54
+ scores?: {
55
+ keyword?: number;
56
+ vector?: number;
57
+ fused?: number;
58
+ };
59
+ matchedBy: Array<"keyword" | "vector">;
60
+ };
61
+ export type SearchResponse = {
62
+ items: SearchHit[];
63
+ meta: {
64
+ engine: "tsvector" | "pgroonga";
65
+ tier: "free" | "pro" | "scale";
66
+ limit: number;
67
+ offset: number;
68
+ nextOffset?: number;
69
+ fusion?: "rrf";
70
+ mode?: "keyword" | "vector" | "hybrid";
71
+ };
72
+ };
73
+ export type SearchTierConfig = {
74
+ plan: "free" | "pro" | "scale";
75
+ engine: "tsvector" | "pgroonga";
76
+ hybridSearch: boolean;
77
+ locale: "english" | "multilingual";
78
+ extensions: {
79
+ vector: boolean;
80
+ pgroonga: boolean;
81
+ };
82
+ degradedReason?: string;
83
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/server.d.ts CHANGED
@@ -6,6 +6,5 @@
6
6
  * bundled into user functions which run in Firecracker.
7
7
  */
8
8
  export { createStorage, storageRoutes } from "./storage.js";
9
- export type { StorageImageTierConfig } from "./storage.js";
9
+ export type { StorageImageTierConfig, StoredFile } from "./storage.js";
10
10
  export { createScheduler, getSchedulerInfo } from "./scheduler.js";
11
- export { authMiddleware, authRoutes, getUsers } from "./auth.js";
package/dist/server.js CHANGED
@@ -7,4 +7,3 @@
7
7
  */
8
8
  export { createStorage, storageRoutes } from "./storage.js";
9
9
  export { createScheduler, getSchedulerInfo } from "./scheduler.js";
10
- export { authMiddleware, authRoutes, getUsers } from "./auth.js";
@@ -0,0 +1,36 @@
1
+ /** 파일 업로드 최대 크기: 50MB (하드코딩 — 사용자가 오버라이드 불가) */
2
+ export declare const MAX_FILE_SIZE: number;
3
+ /** 기본 스토리지 쿼터: 1GB */
4
+ export declare const DEFAULT_STORAGE_QUOTA: number;
5
+ export interface StorageFile {
6
+ id: string;
7
+ name: string;
8
+ size: number;
9
+ type: string;
10
+ path: string;
11
+ }
12
+ export interface StoredFile {
13
+ id: string;
14
+ name: string;
15
+ size: number;
16
+ type: string;
17
+ buffer: Buffer;
18
+ }
19
+ export interface StorageOptions {
20
+ rawSql?: (sql: string, params?: unknown[]) => Promise<unknown[]>;
21
+ storageQuota?: number;
22
+ }
23
+ export interface Storage {
24
+ store(file: File | Blob, filename?: string): Promise<string>;
25
+ storeBuffer(buffer: Buffer, filename: string, type?: string): Promise<string>;
26
+ get(storageId: string): Promise<StoredFile | null>;
27
+ getUrl(storageId: string): string;
28
+ getMeta(storageId: string): Promise<StorageFile | null>;
29
+ delete(storageId: string): Promise<void>;
30
+ }
31
+ export declare function formatBytes(bytes: number): string;
32
+ export declare function loadStorageMeta(params: {
33
+ storageId: string;
34
+ dir: string;
35
+ metaStore: Map<string, StorageFile>;
36
+ }): Promise<StorageFile | null>;
@@ -0,0 +1,39 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ /** 파일 업로드 최대 크기: 50MB (하드코딩 — 사용자가 오버라이드 불가) */
4
+ export const MAX_FILE_SIZE = 50 * 1024 * 1024;
5
+ /** 기본 스토리지 쿼터: 1GB */
6
+ export const DEFAULT_STORAGE_QUOTA = 1024 * 1024 * 1024;
7
+ export function formatBytes(bytes) {
8
+ if (bytes < 1024)
9
+ return `${bytes}B`;
10
+ if (bytes < 1024 * 1024)
11
+ return `${(bytes / 1024).toFixed(1)}KB`;
12
+ if (bytes < 1024 * 1024 * 1024)
13
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
14
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
15
+ }
16
+ export async function loadStorageMeta(params) {
17
+ const cached = params.metaStore.get(params.storageId);
18
+ if (cached) {
19
+ return cached;
20
+ }
21
+ const filePath = path.join(params.dir, params.storageId);
22
+ try {
23
+ const raw = await fs.readFile(`${filePath}.meta`, "utf-8");
24
+ const parsed = JSON.parse(raw);
25
+ const stat = await fs.stat(filePath);
26
+ const meta = {
27
+ id: params.storageId,
28
+ name: typeof parsed.name === "string" ? parsed.name : params.storageId,
29
+ size: typeof parsed.size === "number" ? parsed.size : stat.size,
30
+ type: typeof parsed.type === "string" ? parsed.type : "application/octet-stream",
31
+ path: filePath,
32
+ };
33
+ params.metaStore.set(params.storageId, meta);
34
+ return meta;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
package/dist/storage.d.ts CHANGED
@@ -1,28 +1,5 @@
1
- interface StorageFile {
2
- id: string;
3
- name: string;
4
- size: number;
5
- type: string;
6
- path: string;
7
- }
8
- export interface StorageOptions {
9
- /** Raw SQL 실행 함수 — DB 자동 기록 + 쿼터 검증에 필요 */
10
- rawSql?: (sql: string, params?: unknown[]) => Promise<unknown[]>;
11
- /** 앱별 스토리지 쿼터 (bytes). 0 = 무제한. 기본: 1GB */
12
- storageQuota?: number;
13
- }
14
- export interface Storage {
15
- /** Store a file and return a storageId — Convex의 ctx.storage.store() */
16
- store(file: File | Blob, filename?: string): Promise<string>;
17
- /** Store from raw buffer */
18
- storeBuffer(buffer: Buffer, filename: string, type?: string): Promise<string>;
19
- /** Get a serving URL for the file — Convex의 ctx.storage.getUrl() */
20
- getUrl(storageId: string): string;
21
- /** Get file metadata */
22
- getMeta(storageId: string): Promise<StorageFile | null>;
23
- /** Delete a stored file — Convex의 ctx.storage.delete() */
24
- delete(storageId: string): Promise<void>;
25
- }
1
+ import type { Storage, StorageOptions } from "./storage-shared.js";
2
+ export type { Storage, StorageFile, StorageOptions, StoredFile } from "./storage-shared.js";
26
3
  /**
27
4
  * Create a storage instance — Convex storage 패턴 재현
28
5
  *
@@ -75,4 +52,3 @@ export declare function storageRoutes(storage: ReturnType<typeof createStorage>,
75
52
  json: (data: unknown, status?: number) => Response;
76
53
  body: (data: unknown, status: number, headers: Record<string, string>) => Response;
77
54
  }) => Promise<Response>;
78
- export {};
package/dist/storage.js CHANGED
@@ -2,20 +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
- // ─── Constants ──────────────────────────────────────────
6
- /** 파일 업로드 최대 크기: 50MB (하드코딩 — 사용자가 오버라이드 불가) */
7
- const MAX_FILE_SIZE = 50 * 1024 * 1024;
8
- /** 기본 스토리지 쿼터: 1GB */
9
- const DEFAULT_STORAGE_QUOTA = 1024 * 1024 * 1024;
10
- function formatBytes(bytes) {
11
- if (bytes < 1024)
12
- return `${bytes}B`;
13
- if (bytes < 1024 * 1024)
14
- return `${(bytes / 1024).toFixed(1)}KB`;
15
- if (bytes < 1024 * 1024 * 1024)
16
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
17
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
18
- }
5
+ import { DEFAULT_STORAGE_QUOTA, MAX_FILE_SIZE, formatBytes, loadStorageMeta, } from "./storage-shared.js";
19
6
  // ─── Implementation ─────────────────────────────────────
20
7
  const metaStore = new Map();
21
8
  /**
@@ -166,8 +153,25 @@ export function createStorage(dir = "./uploads", options) {
166
153
  getUrl(storageId) {
167
154
  return `/api/storage/${storageId}`;
168
155
  },
156
+ async get(storageId) {
157
+ const meta = await loadStorageMeta({ storageId, dir, metaStore });
158
+ if (!meta) {
159
+ return null;
160
+ }
161
+ const buffer = await fs.readFile(meta.path).catch(() => null);
162
+ if (!buffer) {
163
+ return null;
164
+ }
165
+ return {
166
+ id: meta.id,
167
+ name: meta.name,
168
+ size: meta.size,
169
+ type: meta.type,
170
+ buffer,
171
+ };
172
+ },
169
173
  async getMeta(storageId) {
170
- return metaStore.get(storageId) || null;
174
+ return loadStorageMeta({ storageId, dir, metaStore });
171
175
  },
172
176
  async delete(storageId) {
173
177
  const meta = metaStore.get(storageId);
@@ -1,4 +1,5 @@
1
1
  import type { GencowCtx } from "./reactive.js";
2
+ import type { WorkflowDocumentServicesCtx } from "./document-types.js";
2
3
  import type { InferArgs } from "./v.js";
3
4
  export type WorkflowStatus = "pending" | "running" | "completed" | "failed";
4
5
  export type WorkflowDuration = number | string;
@@ -53,9 +54,10 @@ export interface WorkflowSignalResult {
53
54
  export interface WorkflowResumePayload {
54
55
  workflowId: string;
55
56
  }
56
- export interface WorkflowCtx extends GencowCtx {
57
+ export interface WorkflowCtx extends Omit<GencowCtx, "services"> {
57
58
  workflowId: string;
58
59
  workflowName: string;
60
+ services: GencowCtx["services"] & WorkflowDocumentServicesCtx;
59
61
  step<TResult>(name: string, run: () => Promise<TResult>): Promise<TResult>;
60
62
  sleep(duration: WorkflowDuration): Promise<void>;
61
63
  waitForEvent<TPayload = unknown>(name: string, timeout?: WorkflowDuration): Promise<TPayload>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gencow/core",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Gencow core library — defineQuery, defineMutation, reactive subscriptions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/crud.ts CHANGED
@@ -108,6 +108,29 @@ type CrudOptions<T extends PgTable> = {
108
108
  methods?: ("list" | "get" | "create" | "update" | "remove")[];
109
109
  };
110
110
 
111
+ export interface CrudCodegenMeta {
112
+ tableName: string;
113
+ prefix: string;
114
+ methods: ("list" | "get" | "create" | "update" | "remove")[];
115
+ allowedFilters: string[];
116
+ searchFields: string[];
117
+ isPublic: boolean;
118
+ }
119
+
120
+ declare global {
121
+ var __gencow_crudCodegenRegistry: Map<string, CrudCodegenMeta>;
122
+ }
123
+
124
+ if (!globalThis.__gencow_crudCodegenRegistry) {
125
+ globalThis.__gencow_crudCodegenRegistry = new Map();
126
+ }
127
+
128
+ const crudCodegenRegistry = globalThis.__gencow_crudCodegenRegistry;
129
+
130
+ export function getRegisteredCrudCodegenMeta(): CrudCodegenMeta[] {
131
+ return Array.from(crudCodegenRegistry.values());
132
+ }
133
+
111
134
  // ─── Helpers ────────────────────────────────────────────
112
135
 
113
136
  /**
@@ -472,6 +495,16 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
472
495
  // ── methods 필터링: 지정된 메서드만 레지스트리 등록 ──
473
496
  // methods 옵션 미지정 시 전체 5개 등록 (하위호환)
474
497
  const enabledMethods = new Set(options?.methods ?? ["list", "get", "create", "update", "remove"]);
498
+ const enabledMethodsList = Array.from(enabledMethods) as CrudCodegenMeta["methods"];
499
+
500
+ crudCodegenRegistry.set(prefix, {
501
+ tableName,
502
+ prefix,
503
+ methods: enabledMethodsList,
504
+ allowedFilters: (options?.allowedFilters ?? []).map((field) => String(field)),
505
+ searchFields: (options?.searchFields ?? []).map((field) => String(field)),
506
+ isPublic,
507
+ });
475
508
 
476
509
  // ── list ──────────────────────────────────────
477
510
 
@@ -0,0 +1,95 @@
1
+ export type DocumentVisibility = "private" | "shared" | "public";
2
+ export type DocumentConvertMode =
3
+ | "auto"
4
+ | "text-only"
5
+ | "no-external-provider"
6
+ | "prefer-external"
7
+ | "force-external"
8
+ | "force-ocr";
9
+ export type DocumentConvertProvider = "auto" | "local_text" | "gemini" | "openai" | "ocr" | "custom_vlm";
10
+ export type DocumentResolvedProvider = Exclude<DocumentConvertProvider, "auto">;
11
+ export type DocumentProviderRoute = "local_text" | "vlm" | "ocr_fallback";
12
+ export type DocumentCacheArtifact = "convert" | "embedding" | "summary" | "rerank";
13
+
14
+ export type DocumentConvertInput = {
15
+ storageId: string;
16
+ filename?: string;
17
+ mimeType?: string;
18
+ corpus: string;
19
+ visibility: DocumentVisibility;
20
+ ownerUserId?: string;
21
+ mode?: DocumentConvertMode;
22
+ provider?: DocumentConvertProvider;
23
+ };
24
+
25
+ export type DocumentPage = {
26
+ page: number;
27
+ charStart: number;
28
+ charEnd: number;
29
+ previewText?: string;
30
+ };
31
+
32
+ export type DocumentSection = {
33
+ id: string;
34
+ title?: string;
35
+ depth: number;
36
+ path: string[];
37
+ pageStart?: number;
38
+ pageEnd?: number;
39
+ charStart: number;
40
+ charEnd: number;
41
+ };
42
+
43
+ export type DocumentProviderTrace = {
44
+ provider: DocumentResolvedProvider;
45
+ route: DocumentProviderRoute;
46
+ external: boolean;
47
+ requestId?: string;
48
+ durationMs: number;
49
+ bytes: number;
50
+ };
51
+
52
+ export type DocumentConvertResult = {
53
+ text: string;
54
+ markdown: string;
55
+ pages: DocumentPage[];
56
+ sections: DocumentSection[];
57
+ warnings: string[];
58
+ providerTrace: DocumentProviderTrace;
59
+ };
60
+
61
+ export interface GencowServicesCtx {}
62
+
63
+ export interface WorkflowDocumentServicesCtx {
64
+ document: {
65
+ convert(input: DocumentConvertInput): Promise<DocumentConvertResult>;
66
+ };
67
+ }
68
+
69
+ export type DocumentCacheKeyInput = {
70
+ appId: string;
71
+ ownerUserId?: string | null;
72
+ corpus: string;
73
+ sourceKey: string;
74
+ checksum: string;
75
+ provider: DocumentResolvedProvider;
76
+ artifact: DocumentCacheArtifact;
77
+ };
78
+
79
+ function normalizeCachePart(value: string): string {
80
+ return value.trim() || "unknown";
81
+ }
82
+
83
+ export function buildDocumentCacheKey(input: DocumentCacheKeyInput): string {
84
+ const owner = input.ownerUserId?.trim() ? input.ownerUserId.trim() : "shared";
85
+
86
+ return [
87
+ `app:${normalizeCachePart(input.appId)}`,
88
+ `user:${normalizeCachePart(owner)}`,
89
+ `corpus:${normalizeCachePart(input.corpus)}`,
90
+ `source:${normalizeCachePart(input.sourceKey)}`,
91
+ `checksum:${normalizeCachePart(input.checksum)}`,
92
+ `provider:${normalizeCachePart(input.provider)}`,
93
+ `artifact:${normalizeCachePart(input.artifact)}`,
94
+ ].join(":");
95
+ }