@gencow/core 0.1.27 → 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.
- 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/index.d.ts +10 -1
- package/dist/index.js +4 -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 +1557 -0
- package/dist/rag-schema.js +87 -0
- package/dist/reactive.d.ts +13 -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-shared.d.ts +36 -0
- package/dist/storage-shared.js +39 -0
- package/dist/storage.d.ts +2 -26
- package/dist/storage.js +19 -15
- package/dist/workflow-types.d.ts +3 -1
- package/package.json +8 -7
- package/src/document-types.ts +95 -0
- package/src/grounded-answer-types.ts +78 -0
- package/src/index.ts +66 -1
- 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.ts +13 -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 +1 -2
- package/src/storage-shared.ts +74 -0
- package/src/storage.ts +29 -46
- package/src/workflow-types.ts +3 -1
- 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
|
@@ -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
|
+
});
|
package/dist/reactive.d.ts
CHANGED
|
@@ -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:
|
|
53
|
+
export declare function createRlsDb<TDb extends RlsDrizzleDatabaseLike>(db: TDb, rls: RlsSessionContext): TDb;
|
|
54
|
+
export {};
|
|
@@ -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
|
@@ -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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
174
|
+
return loadStorageMeta({ storageId, dir, metaStore });
|
|
171
175
|
},
|
|
172
176
|
async delete(storageId) {
|
|
173
177
|
const meta = metaStore.get(storageId);
|
package/dist/workflow-types.d.ts
CHANGED
|
@@ -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.
|
|
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",
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
"dist/",
|
|
22
22
|
"src/"
|
|
23
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"db:generate:fixture-basic": "drizzle-kit generate --config ./src/__tests__/fixtures/basic/drizzle.config.ts",
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
24
30
|
"dependencies": {
|
|
25
31
|
"node-cron": "^4.2.1"
|
|
26
32
|
},
|
|
@@ -39,10 +45,5 @@
|
|
|
39
45
|
"hono": "^4.12.0",
|
|
40
46
|
"typescript": "^5.9.3",
|
|
41
47
|
"uuid": "^13.0.0"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"db:generate:fixture-basic": "drizzle-kit generate --config ./src/__tests__/fixtures/basic/drizzle.config.ts",
|
|
45
|
-
"build": "tsc",
|
|
46
|
-
"typecheck": "tsc --noEmit"
|
|
47
48
|
}
|
|
48
|
-
}
|
|
49
|
+
}
|
|
@@ -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
|
+
}
|